UNPKG

1.66 MBSource Map (JSON)View Raw
1{"version":3,"file":"CustomVisuals.js","sources":["webpack:///webpack/bootstrap c35d750342349c7c595f?6b86","webpack:///./src/Clients/CustomVisuals/module.ts","webpack:///./src/Clients/CustomVisuals \\.less$","webpack:///./src/Clients/CustomVisuals/styles/styles.less","webpack:///./src/Clients/CustomVisuals/visuals/asterPlot/visual/styles/asterPlot.less","webpack:///./src/Clients/CustomVisuals/visuals/bulletChart/visual/styles/bulletChart.less","webpack:///./src/Clients/CustomVisuals/visuals/chicletSlicer/visual/styles/chicletSlicer.less","webpack:///./src/Clients/CustomVisuals/visuals/chordChart/visual/styles/chordChart.less","webpack:///./src/Clients/CustomVisuals/visuals/dotPlot/visual/styles/dotPlot.less","webpack:///./src/Clients/CustomVisuals/visuals/enhancedScatterChart/visual/styles/enhancedScatterChart.less","webpack:///./src/Clients/CustomVisuals/visuals/forceGraph/visual/styles/forceGraph.less","webpack:///./src/Clients/CustomVisuals/visuals/gantt/visual/styles/gantt.less","webpack:///./src/Clients/CustomVisuals/visuals/globeMap/visual/styles/globeMap.less","webpack:///./src/Clients/CustomVisuals/visuals/histogram/visual/styles/histogram.less","webpack:///./src/Clients/CustomVisuals/visuals/lineDotChart/visual/styles/lineDotChart.less","webpack:///./src/Clients/CustomVisuals/visuals/mekkoChart/visual/styles/mekkoChart.less","webpack:///./src/Clients/CustomVisuals/visuals/pulseChart/visual/styles/pulseChart.less","webpack:///./src/Clients/CustomVisuals/visuals/radarChart/visual/styles/radarChart.less","webpack:///./src/Clients/CustomVisuals/visuals/sankeyDiagram/visual/styles/sankeyDiagram.less","webpack:///./src/Clients/CustomVisuals/visuals/streamGraph/visual/styles/streamGraph.less","webpack:///./src/Clients/CustomVisuals/visuals/sunburst/visual/styles/sunburst.less","webpack:///./src/Clients/CustomVisuals/visuals/timeline/visual/styles/timeline.less","webpack:///./src/Clients/CustomVisuals/visuals/tornadoChart/visual/styles/tornadoChart.less","webpack:///./src/Clients/CustomVisuals/visuals/wordCloud/visual/styles/wordCloud.less","webpack:///./src/Clients/CustomVisuals/_references.ts","webpack:///./src/Clients/CustomVisuals/visuals/asterPlot/visual/asterPlot.ts","webpack:///./src/Clients/CustomVisuals/visuals/tornadoChart/visual/tornadoChart.ts","webpack:///./src/Clients/CustomVisuals/visuals/mekkoChart/visual/mekkoChart.ts","webpack:///./src/Clients/CustomVisuals/visuals/sankeyDiagram/visual/sankeyDiagram.ts","webpack:///./src/Clients/CustomVisuals/visuals/bulletChart/visual/bulletChart.ts","webpack:///./src/Clients/CustomVisuals/visuals/wordCloud/visual/wordCloud.ts","webpack:///./src/Clients/CustomVisuals/visuals/chicletSlicer/visual/chicletSlicer.ts","webpack:///./src/Clients/CustomVisuals/visuals/chordChart/visual/chordChart.ts","webpack:///./src/Clients/CustomVisuals/visuals/enhancedScatterChart/visual/enhancedScatterChart.ts","webpack:///./src/Clients/CustomVisuals/visuals/globeMap/visual/globeMap.ts","webpack:///./src/Clients/CustomVisuals/visuals/radarChart/visual/radarChart.ts","webpack:///./src/Clients/CustomVisuals/visuals/histogram/visual/histogram.ts","webpack:///./src/Clients/CustomVisuals/visuals/dotPlot/visual/dotPlot.ts","webpack:///./src/Clients/CustomVisuals/visuals/forceGraph/visual/forceGraph.ts","webpack:///./src/Clients/CustomVisuals/visuals/gantt/visual/gantt.ts","webpack:///./src/Clients/CustomVisuals/visuals/timeline/visual/timeline.ts","webpack:///./src/Clients/CustomVisuals/visuals/streamGraph/visual/streamGraph.ts","webpack:///./src/Clients/CustomVisuals/visuals/pulseChart/visual/pulseChart.ts","webpack:///./src/Clients/CustomVisuals/visuals/lineDotChart/visual/lineDotChart.ts","webpack:///./src/Clients/CustomVisuals/visuals/sunburst/visual/sunburst.ts","webpack:///./src/Clients/CustomVisuals/plugins.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\n\r\nrequireAll(require.context(\"./\", true, /\\.less$/));\r\n\r\n// Require all files from the `_references.ts`\r\nrequire(\"ReferencesLoader!./_references.ts\");\r\n\r\nfunction requireAll(requireContext) {\r\n return requireContext.keys().map(requireContext);\r\n}\r\n\r\n\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/CustomVisuals/module.ts\n **/","var map = {\n\t\"./styles/styles.less\": 15,\n\t\"./visuals/asterPlot/visual/styles/asterPlot.less\": 17,\n\t\"./visuals/bulletChart/visual/styles/bulletChart.less\": 18,\n\t\"./visuals/chicletSlicer/visual/styles/chicletSlicer.less\": 19,\n\t\"./visuals/chordChart/visual/styles/chordChart.less\": 20,\n\t\"./visuals/dotPlot/visual/styles/dotPlot.less\": 21,\n\t\"./visuals/enhancedScatterChart/visual/styles/enhancedScatterChart.less\": 22,\n\t\"./visuals/forceGraph/visual/styles/forceGraph.less\": 23,\n\t\"./visuals/gantt/visual/styles/gantt.less\": 24,\n\t\"./visuals/globeMap/visual/styles/globeMap.less\": 25,\n\t\"./visuals/histogram/visual/styles/histogram.less\": 26,\n\t\"./visuals/lineDotChart/visual/styles/lineDotChart.less\": 27,\n\t\"./visuals/mekkoChart/visual/styles/mekkoChart.less\": 28,\n\t\"./visuals/pulseChart/visual/styles/pulseChart.less\": 29,\n\t\"./visuals/radarChart/visual/styles/radarChart.less\": 30,\n\t\"./visuals/sankeyDiagram/visual/styles/sankeyDiagram.less\": 31,\n\t\"./visuals/streamGraph/visual/styles/streamGraph.less\": 32,\n\t\"./visuals/sunburst/visual/styles/sunburst.less\": 33,\n\t\"./visuals/timeline/visual/styles/timeline.less\": 34,\n\t\"./visuals/tornadoChart/visual/styles/tornadoChart.less\": 35,\n\t\"./visuals/wordCloud/visual/styles/wordCloud.less\": 36\n};\nfunction webpackContext(req) {\n\treturn __webpack_require__(webpackContextResolve(req));\n};\nfunction webpackContextResolve(req) {\n\treturn map[req] || (function() { throw new Error(\"Cannot find module '\" + req + \"'.\") }());\n};\nwebpackContext.keys = function webpackContextKeys() {\n\treturn Object.keys(map);\n};\nwebpackContext.resolve = webpackContextResolve;\nmodule.exports = webpackContext;\nwebpackContext.id = 14;\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./src/Clients/CustomVisuals \\.less$\n ** module id = 14\n ** module chunks = 0\n **/","// removed by extract-text-webpack-plugin\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./src/Clients/CustomVisuals/styles/styles.less\n ** module id = 15\n ** module chunks = 0\n **/","// removed by extract-text-webpack-plugin\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./src/Clients/CustomVisuals/visuals/asterPlot/visual/styles/asterPlot.less\n ** module id = 17\n ** module chunks = 0\n **/","// removed by extract-text-webpack-plugin\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./src/Clients/CustomVisuals/visuals/bulletChart/visual/styles/bulletChart.less\n ** module id = 18\n ** module chunks = 0\n **/","// removed by extract-text-webpack-plugin\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./src/Clients/CustomVisuals/visuals/chicletSlicer/visual/styles/chicletSlicer.less\n ** module id = 19\n ** module chunks = 0\n **/","// removed by extract-text-webpack-plugin\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./src/Clients/CustomVisuals/visuals/chordChart/visual/styles/chordChart.less\n ** module id = 20\n ** module chunks = 0\n **/","// removed by extract-text-webpack-plugin\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./src/Clients/CustomVisuals/visuals/dotPlot/visual/styles/dotPlot.less\n ** module id = 21\n ** module chunks = 0\n **/","// removed by extract-text-webpack-plugin\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./src/Clients/CustomVisuals/visuals/enhancedScatterChart/visual/styles/enhancedScatterChart.less\n ** module id = 22\n ** module chunks = 0\n **/","// removed by extract-text-webpack-plugin\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./src/Clients/CustomVisuals/visuals/forceGraph/visual/styles/forceGraph.less\n ** module id = 23\n ** module chunks = 0\n **/","// removed by extract-text-webpack-plugin\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./src/Clients/CustomVisuals/visuals/gantt/visual/styles/gantt.less\n ** module id = 24\n ** module chunks = 0\n **/","// removed by extract-text-webpack-plugin\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./src/Clients/CustomVisuals/visuals/globeMap/visual/styles/globeMap.less\n ** module id = 25\n ** module chunks = 0\n **/","// removed by extract-text-webpack-plugin\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./src/Clients/CustomVisuals/visuals/histogram/visual/styles/histogram.less\n ** module id = 26\n ** module chunks = 0\n **/","// removed by extract-text-webpack-plugin\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./src/Clients/CustomVisuals/visuals/lineDotChart/visual/styles/lineDotChart.less\n ** module id = 27\n ** module chunks = 0\n **/","// removed by extract-text-webpack-plugin\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./src/Clients/CustomVisuals/visuals/mekkoChart/visual/styles/mekkoChart.less\n ** module id = 28\n ** module chunks = 0\n **/","// removed by extract-text-webpack-plugin\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./src/Clients/CustomVisuals/visuals/pulseChart/visual/styles/pulseChart.less\n ** module id = 29\n ** module chunks = 0\n **/","// removed by extract-text-webpack-plugin\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./src/Clients/CustomVisuals/visuals/radarChart/visual/styles/radarChart.less\n ** module id = 30\n ** module chunks = 0\n **/","// removed by extract-text-webpack-plugin\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./src/Clients/CustomVisuals/visuals/sankeyDiagram/visual/styles/sankeyDiagram.less\n ** module id = 31\n ** module chunks = 0\n **/","// removed by extract-text-webpack-plugin\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./src/Clients/CustomVisuals/visuals/streamGraph/visual/styles/streamGraph.less\n ** module id = 32\n ** module chunks = 0\n **/","// removed by extract-text-webpack-plugin\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./src/Clients/CustomVisuals/visuals/sunburst/visual/styles/sunburst.less\n ** module id = 33\n ** module chunks = 0\n **/","// removed by extract-text-webpack-plugin\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./src/Clients/CustomVisuals/visuals/timeline/visual/styles/timeline.less\n ** module id = 34\n ** module chunks = 0\n **/","// removed by extract-text-webpack-plugin\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./src/Clients/CustomVisuals/visuals/tornadoChart/visual/styles/tornadoChart.less\n ** module id = 35\n ** module chunks = 0\n **/","// removed by extract-text-webpack-plugin\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./src/Clients/CustomVisuals/visuals/wordCloud/visual/styles/wordCloud.less\n ** module id = 36\n ** module chunks = 0\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\n/// <reference path=\"../Visuals/_references.ts\"/>\r\n/// <reference path=\"../VisualsCommon/_references.ts\"/>\r\n/// <reference path=\"../VisualsContracts/_references.ts\"/>\r\n/// <reference path=\"../VisualsData/_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(\"./visuals/asterPlot/visual/asterPlot.ts\");\r\nrequire(\"./visuals/tornadoChart/visual/tornadoChart.ts\");\r\nrequire(\"./visuals/mekkoChart/visual/mekkoChart.ts\");\r\nrequire(\"./visuals/sankeyDiagram/visual/sankeyDiagram.ts\");\r\nrequire(\"./visuals/bulletChart/visual/bulletChart.ts\");\r\nrequire(\"./visuals/wordCloud/visual/wordCloud.ts\");\r\nrequire(\"./visuals/chicletSlicer/visual/chicletSlicer.ts\");\r\nrequire(\"./visuals/chordChart/visual/chordChart.ts\");\r\nrequire(\"./visuals/enhancedScatterChart/visual/enhancedScatterChart.ts\");\r\nrequire(\"./visuals/globeMap/visual/globeMap.ts\");\r\nrequire(\"./visuals/radarChart/visual/radarChart.ts\");\r\nrequire(\"./visuals/histogram/visual/histogram.ts\");\r\nrequire(\"./visuals/dotPlot/visual/dotPlot.ts\");\r\nrequire(\"./visuals/forceGraph/visual/forceGraph.ts\");\r\nrequire(\"./visuals/gantt/visual/gantt.ts\");\r\nrequire(\"./visuals/timeline/visual/timeline.ts\");\r\nrequire(\"./visuals/streamGraph/visual/streamGraph.ts\");\r\nrequire(\"./visuals/pulseChart/visual/pulseChart.ts\");\r\nrequire(\"./visuals/lineDotChart/visual/lineDotChart.ts\");\r\nrequire(\"./visuals/sunburst/visual/sunburst.ts\");\r\nrequire(\"./plugins.ts\"); \r\n\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./build/webpack/ReferencesLoader.js!./src/Clients/CustomVisuals/_references.ts\n ** module id = 37\n ** module chunks = 0\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 ArcDescriptor = D3.Layout.ArcDescriptor;\r\n import ClassAndSelector = jsCommon.CssConstants.ClassAndSelector;\r\n import createClassAndSelector = jsCommon.CssConstants.createClassAndSelector;\r\n import PixelConverter = jsCommon.PixelConverter;\r\n import ValueFormatter = powerbi.visuals.valueFormatter;\r\n\r\n var AsterPlotVisualClassName: string = \"asterPlot\";\r\n var AsterRadiusRatio: number = 0.9;\r\n var AsterConflictRatio = 0.9;\r\n\r\n export interface AsterPlotData {\r\n dataPoints: AsterDataPoint[];\r\n highlightedDataPoints?: AsterDataPoint[];\r\n settings: AsterPlotSettings;\r\n hasHighlights: boolean;\r\n legendData: LegendData;\r\n labelFormatter: IValueFormatter;\r\n centerText: string;\r\n }\r\n\r\n export interface AsterArcDescriptor extends ArcDescriptor {\r\n isLabelHasConflict?: boolean;\r\n data: AsterDataPoint;\r\n }\r\n\r\n export interface AsterDataPoint extends SelectableDataPoint {\r\n color: string;\r\n sliceHeight: number;\r\n sliceWidth: number;\r\n label: string;\r\n highlight?: boolean;\r\n tooltipInfo: TooltipDataItem[];\r\n labelFontSize: string;\r\n }\r\n\r\n export interface AsterPlotBehaviorOptions {\r\n selection: D3.Selection;\r\n clearCatcher: D3.Selection;\r\n interactivityService: IInteractivityService;\r\n hasHighlights: boolean;\r\n }\r\n\r\n class AsterPlotWebBehavior implements IInteractiveBehavior {\r\n private selection: D3.Selection;\r\n private clearCatcher: D3.Selection;\r\n private interactivityService: IInteractivityService;\r\n private hasHighlights: boolean;\r\n\r\n public bindEvents(options: AsterPlotBehaviorOptions, selectionHandler: ISelectionHandler) {\r\n this.selection = options.selection;\r\n this.clearCatcher = options.clearCatcher;\r\n this.interactivityService = options.interactivityService;\r\n this.hasHighlights = options.hasHighlights;\r\n\r\n this.selection.on(\"click\", (d, i: number) => {\r\n selectionHandler.handleSelection(d.data, d3.event.ctrlKey);\r\n });\r\n\r\n this.clearCatcher.on(\"click\", () => {\r\n selectionHandler.handleClearSelection();\r\n });\r\n\r\n this.renderSelection(this.interactivityService.hasSelection());\r\n }\r\n\r\n public renderSelection(hasSelection: boolean) {\r\n\r\n this.selection.style(\"fill-opacity\", (d) => {\r\n return ColumnUtil.getFillOpacity(d.data.selected, d.data.highlight, hasSelection, this.hasHighlights);\r\n });\r\n }\r\n }\r\n\r\n export class AsterPlotWarning implements IVisualWarning {\r\n private message: string;\r\n constructor(message: string) {\r\n this.message = message;\r\n }\r\n\r\n public get code(): string {\r\n return \"AsterPlotWarning\";\r\n }\r\n\r\n public getMessages(resourceProvider: jsCommon.IStringResourceProvider): IVisualErrorMessage {\r\n return {\r\n message: this.message,\r\n title: resourceProvider.get(\"\"),\r\n detail: resourceProvider.get(\"\")\r\n };\r\n }\r\n }\r\n\r\n class VisualLayout {\r\n private marginValue: IMargin;\r\n private viewportValue: IViewport;\r\n private viewportInValue: IViewport;\r\n private minViewportValue: IViewport;\r\n private originalViewportValue: IViewport;\r\n private previousOriginalViewportValue: IViewport;\r\n\r\n public defaultMargin: IMargin;\r\n public defaultViewport: IViewport;\r\n\r\n constructor(defaultViewport?: IViewport, defaultMargin?: IMargin) {\r\n this.defaultViewport = defaultViewport || { width: 0, height: 0 };\r\n this.defaultMargin = defaultMargin || { top: 0, bottom: 0, right: 0, left: 0 };\r\n }\r\n\r\n public get viewport(): IViewport {\r\n return this.viewportValue || (this.viewportValue = this.defaultViewport);\r\n }\r\n\r\n public get viewportCopy(): IViewport {\r\n return _.clone(this.viewport);\r\n }\r\n\r\n //Returns viewport minus margin\r\n public get viewportIn(): IViewport {\r\n return this.viewportInValue || this.viewport;\r\n }\r\n\r\n public get minViewport(): IViewport {\r\n return this.minViewportValue || { width: 0, height: 0 };\r\n }\r\n\r\n public get margin(): IMargin {\r\n return this.marginValue || (this.marginValue = this.defaultMargin);\r\n }\r\n\r\n public set minViewport(value: IViewport) {\r\n this.setUpdateObject(value, v => this.minViewportValue = v, VisualLayout.restrictToMinMax);\r\n }\r\n\r\n public set viewport(value: IViewport) {\r\n this.previousOriginalViewportValue = _.clone(this.originalViewportValue);\r\n this.originalViewportValue = _.clone(value);\r\n this.setUpdateObject(value,\r\n v => this.viewportValue = v,\r\n o => VisualLayout.restrictToMinMax(o, this.minViewport));\r\n }\r\n\r\n public set margin(value: IMargin) {\r\n this.setUpdateObject(value, v => this.marginValue = v, VisualLayout.restrictToMinMax);\r\n }\r\n\r\n //Returns true if viewport has updated after last change.\r\n public get viewportChanged(): boolean {\r\n return !!this.originalViewportValue && (!this.previousOriginalViewportValue\r\n || this.previousOriginalViewportValue.height !== this.originalViewportValue.height\r\n || this.previousOriginalViewportValue.width !== this.originalViewportValue.width);\r\n }\r\n\r\n public get viewportInIsZero(): boolean {\r\n return this.viewportIn.width === 0 || this.viewportIn.height === 0;\r\n }\r\n\r\n public resetMargin(): void {\r\n this.margin = this.defaultMargin;\r\n }\r\n\r\n private update(): void {\r\n this.viewportInValue = VisualLayout.restrictToMinMax({\r\n width: this.viewport.width - (this.margin.left + this.margin.right),\r\n height: this.viewport.height - (this.margin.top + this.margin.bottom)\r\n }, this.minViewportValue);\r\n }\r\n\r\n private setUpdateObject<T>(object: T, setObjectFn: (T) => void, beforeUpdateFn?: (T) => void): void {\r\n object = _.clone(object);\r\n setObjectFn(VisualLayout.createNotifyChangedObject(object, o => {\r\n if(beforeUpdateFn) beforeUpdateFn(object);\r\n this.update();\r\n }));\r\n\r\n if(beforeUpdateFn) beforeUpdateFn(object);\r\n this.update();\r\n }\r\n\r\n private static createNotifyChangedObject<T>(object: T, objectChanged: (o?: T, key?: string) => void): T {\r\n var result: T = <any>{};\r\n _.keys(object).forEach(key => Object.defineProperty(result, key, {\r\n get: () => object[key],\r\n set: (value) => { object[key] = value; objectChanged(object, key); },\r\n enumerable: true,\r\n configurable: true\r\n }));\r\n return result;\r\n }\r\n\r\n private static restrictToMinMax<T>(value: T, minValue?: T): T {\r\n _.keys(value).forEach(x => value[x] = Math.max(minValue && minValue[x] || 0, value[x]));\r\n return value;\r\n }\r\n }\r\n\r\n class Helpers {\r\n public static setAttrThroughTransitionIfNotResized(\r\n element: D3.Selection,\r\n setTransision: (t: D3.Transition.Transition) => D3.Transition.Transition,\r\n attrName: string,\r\n attrValue: (data: any, index: number) => any | any,\r\n attrTransitionValue: (data: any, index: number) => any | any,\r\n viewportChanged: boolean) {\r\n if(viewportChanged) {\r\n element.attr(attrName, attrValue);\r\n } else {\r\n setTransision(element.transition()).attrTween(attrName, attrTransitionValue);\r\n }\r\n }\r\n\r\n public static interpolateArc(arc: D3.Svg.Arc) {\r\n return function (data) {\r\n if (!this.oldData) {\r\n this.oldData = data;\r\n return () => arc(data);\r\n }\r\n\r\n var interpolation = d3.interpolate(this.oldData, data);\r\n this.oldData = interpolation(0);\r\n return (x) => arc(interpolation(x));\r\n };\r\n }\r\n\r\n public static addContext(context: any, fn: Function): any {\r\n return <any>function() {\r\n return fn.apply(context, [this].concat(_.toArray(arguments)));\r\n };\r\n }\r\n }\r\n\r\n export class AsterPlotSettings {\r\n public static get Default() { \r\n return new this();\r\n }\r\n\r\n public static parse(dataView: DataView, capabilities: VisualCapabilities) {\r\n var settings = new this();\r\n if(!dataView || !dataView.metadata || !dataView.metadata.objects) {\r\n return settings;\r\n }\r\n\r\n var properties = this.getProperties(capabilities);\r\n for(var objectKey in capabilities.objects) {\r\n for(var propKey in capabilities.objects[objectKey].properties) {\r\n if(!settings[objectKey] || !_.has(settings[objectKey], propKey)) {\r\n continue;\r\n }\r\n\r\n var type = capabilities.objects[objectKey].properties[propKey].type;\r\n var getValueFn = this.getValueFnByType(type);\r\n settings[objectKey][propKey] = getValueFn(\r\n dataView.metadata.objects,\r\n properties[objectKey][propKey],\r\n settings[objectKey][propKey]);\r\n }\r\n }\r\n\r\n return settings;\r\n }\r\n\r\n public static getProperties(capabilities: VisualCapabilities)\r\n : { [i: string]: { [i: string]: DataViewObjectPropertyIdentifier } } & { \r\n general: { formatString: DataViewObjectPropertyIdentifier },\r\n dataPoint: { fill: DataViewObjectPropertyIdentifier } } {\r\n var objects = _.merge({ \r\n general: { properties: { formatString: {} } } \r\n }, capabilities.objects);\r\n var properties = <any>{};\r\n for(var objectKey in objects) {\r\n properties[objectKey] = {};\r\n for(var propKey in objects[objectKey].properties) {\r\n properties[objectKey][propKey] = <DataViewObjectPropertyIdentifier> {\r\n objectName: objectKey,\r\n propertyName: propKey\r\n };\r\n }\r\n }\r\n\r\n return properties;\r\n }\r\n\r\n public static createEnumTypeFromEnum(type: any): IEnumType {\r\n var even: any = false;\r\n return createEnumType(Object.keys(type)\r\n .filter((key,i) => ((!!(i % 2)) === even && type[key] === key\r\n && !void(even = !even)) || (!!(i % 2)) !== even)\r\n .map(x => <IEnumMember>{ value: x, displayName: x }));\r\n }\r\n\r\n private static getValueFnByType(type: powerbi.data.DataViewObjectPropertyTypeDescriptor) {\r\n switch(_.keys(type)[0]) {\r\n case \"fill\": \r\n return DataViewObjects.getFillColor;\r\n default:\r\n return DataViewObjects.getValue;\r\n }\r\n }\r\n\r\n public static enumerateObjectInstances(\r\n settings = new this(),\r\n options: EnumerateVisualObjectInstancesOptions,\r\n capabilities: VisualCapabilities): ObjectEnumerationBuilder {\r\n\r\n var enumeration = new ObjectEnumerationBuilder();\r\n var object = settings && settings[options.objectName];\r\n if(!object) {\r\n return enumeration;\r\n }\r\n\r\n var instance = <VisualObjectInstance>{\r\n objectName: options.objectName,\r\n selector: null,\r\n properties: {}\r\n };\r\n\r\n for(var key in object) {\r\n if(_.has(object,key)) {\r\n instance.properties[key] = object[key];\r\n }\r\n }\r\n\r\n enumeration.pushInstance(instance);\r\n return enumeration;\r\n }\r\n\r\n public originalSettings: AsterPlotSettings;\r\n public createOriginalSettings(): void {\r\n this.originalSettings = _.cloneDeep(this);\r\n }\r\n\r\n //Default Settings\r\n public legend = {\r\n show: false,\r\n position: LegendPosition[LegendPosition.Top],\r\n showTitle: true,\r\n titleText: \"\",\r\n labelColor: LegendData.DefaultLegendLabelFillColor,\r\n fontSize: 8,\r\n };\r\n public labels = {\r\n show: false,\r\n color: dataLabelUtils.defaultLabelColor,\r\n displayUnits: 0,\r\n precision: dataLabelUtils.defaultLabelPrecision,\r\n fontSize: dataLabelUtils.DefaultFontSizeInPt,\r\n };\r\n public outerLine = {\r\n show: false,\r\n thickness: 1,\r\n };\r\n }\r\n\r\n export class AsterPlotColumns<T> {\r\n public static Roles = Object.freeze(\r\n _.mapValues(new AsterPlotColumns<string>(), (x, i) => i));\r\n\r\n public static getColumnSources(dataView: DataView) {\r\n return this.getColumnSourcesT<DataViewMetadataColumn>(dataView);\r\n }\r\n\r\n public static getTableValues(dataView: DataView) {\r\n var table = dataView && dataView.table;\r\n var columns = this.getColumnSourcesT<any[]>(dataView);\r\n return columns && table && _.mapValues(\r\n columns, (n: DataViewMetadataColumn, i) => n && table.rows.map(row => row[n.index]));\r\n }\r\n\r\n public static getTableRows(dataView: DataView) {\r\n var table = dataView && dataView.table;\r\n var columns = this.getColumnSourcesT<any[]>(dataView);\r\n return columns && table && table.rows.map(row =>\r\n _.mapValues(columns, (n: DataViewMetadataColumn, i) => n && row[n.index]));\r\n }\r\n\r\n public static getCategoricalValues(dataView: DataView) {\r\n var categorical = dataView && dataView.categorical;\r\n var categories = categorical && categorical.categories || [];\r\n var values = categorical && categorical.values || <DataViewValueColumns>[];\r\n var series: string[] = categorical && values.source && this.getSeriesValues(dataView);\r\n return categorical && _.mapValues(new this<any[]>(), (n, i) =>\r\n (<DataViewCategoricalColumn[]>_.toArray(categories)).concat(_.toArray(values))\r\n .filter(x => x.source.roles && x.source.roles[i]).map(x => x.values)[0]\r\n || values.source && values.source.roles && values.source.roles[i] && series);\r\n }\r\n\r\n public static getSeriesValues(dataView: DataView) {\r\n return dataView && dataView.categorical && dataView.categorical.values\r\n && dataView.categorical.values.map(x => converterHelper.getSeriesName(x.source));\r\n }\r\n\r\n public static getCategoricalColumns(dataView: DataView) {\r\n var categorical = dataView && dataView.categorical;\r\n var categories = categorical && categorical.categories || [];\r\n var values = categorical && categorical.values || <DataViewValueColumns>[];\r\n return categorical && _.mapValues(\r\n new this<DataViewCategoryColumn & DataViewValueColumn[] & DataViewValueColumns>(),\r\n (n, i) => categories.filter(x => x.source.roles && x.source.roles[i])[0]\r\n || values.source && values.source.roles && values.source.roles[i]\r\n || values.filter(x => x.source.roles && x.source.roles[i]));\r\n }\r\n\r\n private static getColumnSourcesT<T>(dataView: DataView) {\r\n var columns = dataView && dataView.metadata && dataView.metadata.columns;\r\n return columns && _.mapValues(\r\n new this<T>(), (n, i) => columns.filter(x => x.roles && x.roles[i])[0]);\r\n }\r\n\r\n //Data Roles\r\n public Category: T = null;\r\n public Y: T = null;\r\n }\r\n\r\n export class AsterPlot implements IVisual {\r\n public static capabilities: VisualCapabilities = {\r\n dataRoles: [\r\n {\r\n displayName: \"Category\",\r\n name: AsterPlotColumns.Roles.Category,\r\n kind: powerbi.VisualDataRoleKind.Grouping,\r\n },\r\n {\r\n displayName: \"Y Axis\",\r\n name: AsterPlotColumns.Roles.Y,\r\n kind: powerbi.VisualDataRoleKind.Measure,\r\n },\r\n ],\r\n dataViewMappings: [{\r\n conditions: [\r\n { \"Category\": { max: 1 }, \"Y\": { max: 2 } }\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 objects: {\r\n general: {\r\n displayName: data.createDisplayNameGetter(\"Visual_General\"),\r\n properties: {\r\n formatString: {\r\n type: { formatting: { formatString: true } },\r\n },\r\n },\r\n },\r\n legend: {\r\n displayName: \"Legend\",\r\n description: \"Display legend options\",\r\n properties: {\r\n show: {\r\n displayName: \"Show\",\r\n type: { bool: true }\r\n },\r\n position: {\r\n displayName: \"Position\",\r\n description: \"Select the location for the legend\",\r\n type: { enumeration: legendPosition.type }\r\n },\r\n showTitle: {\r\n displayName: \"Title\",\r\n description: \"Display a title for legend symbols\",\r\n type: { bool: true }\r\n },\r\n titleText: {\r\n displayName: \"Legend Name\",\r\n description: \"Title text\",\r\n type: { text: true },\r\n suppressFormatPainterCopy: true\r\n },\r\n labelColor: {\r\n displayName: \"Color\",\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n fontSize: {\r\n displayName: \"Text Size\",\r\n type: { formatting: { fontSize: true } }\r\n }\r\n }\r\n },\r\n label: {\r\n displayName: \"Center Label\",\r\n properties: {\r\n fill: {\r\n displayName: \"Fill\",\r\n type: { fill: { solid: { color: true } } }\r\n }\r\n }\r\n },\r\n labels: {\r\n displayName: \"Detail Labels\",\r\n properties: {\r\n show: {\r\n type: { bool: true }\r\n },\r\n color: {\r\n displayName: \"Color\",\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n displayUnits: {\r\n displayName: \"Display Units\",\r\n type: { formatting: { displayUnits: true } },\r\n },\r\n precision: {\r\n displayName: \"Decimal Places\",\r\n placeHolderText: \"Auto\",\r\n type: { numeric: true },\r\n },\r\n fontSize: {\r\n displayName: \"Text Size\",\r\n type: { formatting: { fontSize: true } },\r\n },\r\n },\r\n },\r\n outerLine: {\r\n displayName: \"Outer line\",\r\n properties: {\r\n show: {\r\n displayName: \"Show\",\r\n type: { bool: true }\r\n },\r\n thickness: {\r\n displayName: \"Thickness\",\r\n type: { numeric: true }\r\n }\r\n }\r\n }\r\n },\r\n supportsHighlight: true,\r\n };\r\n\r\n private static AsterSlices: ClassAndSelector = createClassAndSelector(\"asterSlices\");\r\n private static AsterSlice: ClassAndSelector = createClassAndSelector(\"asterSlice\");\r\n private static AsterHighlightedSlice: ClassAndSelector = createClassAndSelector(\"asterHighlightedSlice\");\r\n private static OuterLine: ClassAndSelector = createClassAndSelector(\"outerLine\");\r\n private static labelGraphicsContextClass: ClassAndSelector = createClassAndSelector(\"labels\");\r\n private static linesGraphicsContextClass: ClassAndSelector = createClassAndSelector(\"lines\");\r\n private static CenterLabelClass: ClassAndSelector = createClassAndSelector(\"centerLabel\");\r\n private static CenterTextFontHeightCoefficient = 0.4;\r\n private static CenterTextFontWidthCoefficient = 1.9;\r\n\r\n public static converter(dataView: DataView, colors: IDataColorPalette): AsterPlotData {\r\n var categorical = AsterPlotColumns.getCategoricalColumns(dataView);\r\n var catValues = AsterPlotColumns.getCategoricalValues(dataView);\r\n if(!categorical\r\n || !categorical.Category\r\n || _.isEmpty(categorical.Category.values)\r\n || _.isEmpty(categorical.Y)\r\n || _.isEmpty(categorical.Y[0].values)) {\r\n return;\r\n }\r\n\r\n var settings = AsterPlot.parseSettings(dataView, categorical.Category.source);\r\n var properties = AsterPlotSettings.getProperties(AsterPlot.capabilities);\r\n\r\n var dataPoints: AsterDataPoint[] = [];\r\n var highlightedDataPoints: AsterDataPoint[] = [];\r\n var legendData = <LegendData>{\r\n dataPoints: [],\r\n title: null,\r\n fontSize: AsterPlotSettings.Default.legend.fontSize,\r\n labelColor: LegendData.DefaultLegendLabelFillColor\r\n };\r\n\r\n var colorHelper: ColorHelper = new ColorHelper(colors/*, properties.dataPoint.fill*/);\r\n\r\n var hasHighlights: boolean = !!(categorical.Y[0].highlights);\r\n\r\n var maxValue: number = Math.max(d3.min(categorical.Y[0].values));\r\n var minValue: number = Math.min(0, d3.min(categorical.Y[0].values));\r\n var labelFormatter: IValueFormatter = ValueFormatter.create({\r\n format: ValueFormatter.getFormatString(categorical.Y[0].source, properties.general.formatString),\r\n precision: settings.labels.precision,\r\n value: (settings.labels.displayUnits === 0) && (maxValue != null) ? maxValue : settings.labels.displayUnits,\r\n });\r\n var categorySourceFormatString = valueFormatter.getFormatString(categorical.Category.source, properties.general.formatString);\r\n var fontSizeInPx: string = PixelConverter.fromPoint(settings.labels.fontSize);\r\n\r\n for (var i = 0; i < catValues.Category.length; i++) {\r\n var formattedCategoryValue = valueFormatter.format(catValues.Category[i], categorySourceFormatString);\r\n var currentValue = categorical.Y[0].values[i];\r\n\r\n var tooltipInfo: TooltipDataItem[] = TooltipBuilder.createTooltipInfo(\r\n properties.general.formatString,\r\n dataView.categorical,\r\n formattedCategoryValue,\r\n currentValue,\r\n null,\r\n null,\r\n 0);\r\n\r\n if (categorical.Y.length > 1) {\r\n var toolTip: TooltipDataItem = TooltipBuilder.createTooltipInfo(\r\n properties.general.formatString,\r\n dataView.categorical,\r\n formattedCategoryValue,\r\n categorical.Y[1].values[i],\r\n null,\r\n null,\r\n 1)[1];\r\n if (toolTip)\r\n tooltipInfo.push(toolTip);\r\n\r\n currentValue += categorical.Y[1].values[i];\r\n }\r\n\r\n var identity: DataViewScopeIdentity = categorical.Category.identity[i];\r\n var color: string = colorHelper.getColorForMeasure(categorical.Category.objects && categorical.Category.objects[i], identity.key);\r\n var selector: SelectionId = SelectionId.createWithId(identity);\r\n var sliceWidth: number = Math.max(0, categorical.Y.length > 1 ? categorical.Y[1].values[i] : 1);\r\n\r\n if(sliceWidth > 0) {\r\n dataPoints.push({\r\n sliceHeight: categorical.Y[0].values[i] - minValue,\r\n sliceWidth: sliceWidth,\r\n label: labelFormatter.format(currentValue),\r\n color: color,\r\n identity: selector,\r\n selected: false,\r\n tooltipInfo: tooltipInfo,\r\n labelFontSize: fontSizeInPx,\r\n highlight: false,\r\n });\r\n }\r\n\r\n // Handle legend data\r\n if (settings.legend.show) {\r\n legendData.dataPoints.push({\r\n label: formattedCategoryValue,\r\n color: color,\r\n icon: LegendIcon.Box,\r\n selected: false,\r\n identity: selector\r\n });\r\n }\r\n\r\n // Handle highlights\r\n if (hasHighlights) {\r\n var highlightIdentity: SelectionId = SelectionId.createWithHighlight(selector);\r\n var notNull: boolean = categorical.Y[0].highlights[i] != null;\r\n currentValue = notNull ? categorical.Y[0].highlights[i] : 0;\r\n\r\n tooltipInfo = TooltipBuilder.createTooltipInfo(\r\n properties.general.formatString,\r\n dataView.categorical,\r\n formattedCategoryValue,\r\n currentValue,\r\n null,\r\n null,\r\n 0);\r\n\r\n if (categorical.Y.length > 1) {\r\n var toolTip: TooltipDataItem = TooltipBuilder.createTooltipInfo(\r\n properties.general.formatString,\r\n dataView.categorical,\r\n formattedCategoryValue,\r\n categorical.Y[1].highlights[i],\r\n null,\r\n null,\r\n 1)[1];\r\n if (toolTip)\r\n tooltipInfo.push(toolTip);\r\n\r\n currentValue += categorical.Y[1].highlights[i] !== null ? categorical.Y[1].highlights[i] : 0;\r\n }\r\n\r\n highlightedDataPoints.push({\r\n sliceHeight: notNull ? categorical.Y[0].highlights[i] - minValue : null,\r\n sliceWidth: Math.max(0, (categorical.Y.length > 1 && categorical.Y[1].highlights[i] !== null) ? categorical.Y[1].highlights[i] : sliceWidth),\r\n label: labelFormatter.format(currentValue),\r\n color: color,\r\n identity: highlightIdentity,\r\n selected: false,\r\n tooltipInfo: tooltipInfo,\r\n labelFontSize: fontSizeInPx,\r\n highlight: true,\r\n });\r\n }\r\n }\r\n\r\n return dataPoints.length && <AsterPlotData>{\r\n dataPoints: dataPoints,\r\n settings: settings,\r\n hasHighlights: hasHighlights,\r\n legendData: legendData,\r\n highlightedDataPoints: highlightedDataPoints,\r\n labelFormatter: labelFormatter,\r\n centerText: categorical.Category.source.displayName\r\n };\r\n }\r\n\r\n private static parseSettings(dataView: DataView, categorySource: DataViewMetadataColumn): AsterPlotSettings {\r\n var settings = AsterPlotSettings.parse(dataView, AsterPlot.capabilities);\r\n settings.labels.precision = Math.min(17, Math.max(0, settings.labels.precision));\r\n settings.outerLine.thickness = Math.min(300, Math.max(1, settings.outerLine.thickness));\r\n settings.createOriginalSettings();\r\n if(_.isEmpty(settings.legend.titleText)) {\r\n settings.legend.titleText = categorySource.displayName;\r\n }\r\n\r\n return settings;\r\n }\r\n\r\n private layout: VisualLayout;\r\n private svg: D3.Selection;\r\n private mainGroupElement: D3.Selection;\r\n private mainLabelsElement: D3.Selection;\r\n private slicesElement: D3.Selection;\r\n private centerText: D3.Selection;\r\n private clearCatcher: D3.Selection;\r\n private colors: IDataColorPalette;\r\n private hostServices: IVisualHostServices;\r\n private interactivityService: IInteractivityService;\r\n private legend: ILegend;\r\n private data: AsterPlotData;\r\n private get settings(): AsterPlotSettings {\r\n return this.data && this.data.settings;\r\n }\r\n\r\n private behavior: IInteractiveBehavior;\r\n\r\n public init(options: VisualInitOptions): void {\r\n this.hostServices = options.host;\r\n this.hostServices.canSelect = (args: SelectEventArgs) => {\r\n if (args.data && (args.data.length > 1)) {\r\n if (args.data.some((value: data.Selector) => value && value.data && value.data.length > 1)) {\r\n return false;\r\n }\r\n }\r\n\r\n return true;\r\n };\r\n\r\n this.layout = new VisualLayout(options.viewport, { top: 10, right: 10, bottom: 15, left: 10 });\r\n var element: JQuery = options.element;\r\n var svg: D3.Selection = this.svg = d3.select(element.get(0))\r\n .append(\"svg\")\r\n .classed(AsterPlotVisualClassName, true)\r\n .style(\"position\", \"absolute\");\r\n\r\n this.colors = options.style.colorPalette.dataColors;\r\n this.mainGroupElement = svg.append(\"g\");\r\n this.mainLabelsElement = svg.append(\"g\");\r\n this.behavior = new AsterPlotWebBehavior();\r\n this.clearCatcher = appendClearCatcher(this.mainGroupElement);\r\n this.slicesElement = this.mainGroupElement.append(\"g\").classed(AsterPlot.AsterSlices.class, true);\r\n\r\n var interactivity = options.interactivity;\r\n this.interactivityService = createInteractivityService(this.hostServices);\r\n this.legend = createLegend(element, interactivity && interactivity.isInteractiveLegend, this.interactivityService, true);\r\n }\r\n\r\n public update(options: VisualUpdateOptions) {\r\n if (!options || !options.dataViews || !options.dataViews[0]) {\r\n return; // or clear the view, display an error, etc.\r\n }\r\n\r\n this.layout.viewport = options.viewport;\r\n\r\n var duration = options.suppressAnimations ? 0 : AnimatorCommon.MinervaAnimationDuration;\r\n var data = AsterPlot.converter(options.dataViews[0], this.colors);\r\n\r\n if (!data) {\r\n this.clear();\r\n return;\r\n }\r\n\r\n this.data = data;\r\n\r\n if (this.interactivityService) {\r\n this.interactivityService.applySelectionStateToData(this.data.dataPoints);\r\n this.interactivityService.applySelectionStateToData(this.data.highlightedDataPoints);\r\n }\r\n\r\n this.renderLegend();\r\n this.updateViewPortAccordingToLegend();\r\n\r\n this.svg.attr(this.layout.viewport);\r\n\r\n var transformX: number = (this.layout.viewportIn.width + this.layout.margin.right) / 2;\r\n var transformY: number = (this.layout.viewportIn.height + this.layout.margin.bottom) / 2;\r\n this.mainGroupElement.attr(\"transform\", SVGUtil.translate(transformX, transformY));\r\n this.mainLabelsElement.attr(\"transform\", SVGUtil.translate(transformX, transformY));\r\n\r\n // Move back the clearCatcher\r\n this.clearCatcher.attr(\"transform\", SVGUtil.translate(-transformX, -transformY));\r\n\r\n dataLabelUtils.cleanDataLabels(this.mainLabelsElement, true);\r\n\r\n this.renderArcsAndLabels(duration);\r\n if(this.data.hasHighlights) {\r\n this.renderArcsAndLabels(duration, true);\r\n } else {\r\n this.slicesElement.selectAll(AsterPlot.AsterHighlightedSlice.selector).remove();\r\n }\r\n\r\n if (this.interactivityService) {\r\n var behaviorOptions: AsterPlotBehaviorOptions = {\r\n selection: this.slicesElement.selectAll(AsterPlot.AsterSlice.selector + \", \" + AsterPlot.AsterHighlightedSlice.selector),\r\n clearCatcher: this.clearCatcher,\r\n interactivityService: this.interactivityService,\r\n hasHighlights: this.data.hasHighlights\r\n };\r\n this.interactivityService.bind(this.data.dataPoints.concat(this.data.highlightedDataPoints), this.behavior, behaviorOptions);\r\n }\r\n }\r\n\r\n private renderArcsAndLabels(duration: number, isHighlight: boolean = false): D3.UpdateSelection {\r\n var radius: number = Math.min(this.layout.viewportIn.width, this.layout.viewportIn.height) / 2;\r\n var innerRadius: number = 0.3 * (this.settings.labels.show ? radius * AsterRadiusRatio : radius);\r\n var maxScore: number = d3.max(this.data.dataPoints, d => d.sliceHeight);\r\n var totalWeight: number = d3.sum(this.data.dataPoints, d => d.sliceWidth);\r\n\r\n var pie: D3.Layout.PieLayout = d3.layout.pie()\r\n .sort(null)\r\n .value(d => (d && !isNaN(d.sliceWidth) ? d.sliceWidth : 0) / totalWeight);\r\n\r\n var arc: D3.Svg.Arc = d3.svg.arc()\r\n .innerRadius(innerRadius)\r\n .outerRadius(d => {\r\n var height: number = (radius - innerRadius) * (d && d.data && !isNaN(d.data.sliceHeight) ? d.data.sliceHeight : 1) / maxScore;\r\n //The chart should shrink if data labels are on\r\n var heightIsLabelsOn = innerRadius + (this.settings.labels.show ? height * AsterRadiusRatio : height);\r\n // Prevent from data to be inside the inner radius\r\n return Math.max(heightIsLabelsOn, innerRadius);\r\n });\r\n\r\n var arcDescriptorDataPoints: AsterArcDescriptor[] = pie(isHighlight ? this.data.highlightedDataPoints : this.data.dataPoints);\r\n var classSelector: ClassAndSelector = isHighlight ? AsterPlot.AsterHighlightedSlice : AsterPlot.AsterSlice;\r\n\r\n var selection = this.slicesElement.selectAll(classSelector.selector)\r\n .data(arcDescriptorDataPoints, (d: AsterArcDescriptor, i: number) => d.data ? d.data.identity.getKey() : i);\r\n\r\n selection.enter()\r\n .append(\"path\")\r\n .classed(classSelector.class, true)\r\n .attr(\"stroke\", \"#333\");\r\n\r\n selection\r\n .attr(\"fill\", d => d.data.color)\r\n .call(selection => Helpers.setAttrThroughTransitionIfNotResized(\r\n selection, s => s.duration(duration), 'd', arc, Helpers.interpolateArc(arc), this.layout.viewportChanged));\r\n\r\n selection.exit().remove();\r\n\r\n TooltipManager.addTooltip(selection, (tooltipEvent: TooltipEvent) => tooltipEvent.data.data.tooltipInfo);\r\n\r\n // Draw data labels only if they are on and there are no highlights or there are highlights and this is the highlighted data labels\r\n if (this.settings.labels.show && (!this.data.hasHighlights || (this.data.hasHighlights && isHighlight))) {\r\n var labelRadCalc = (d: AsterDataPoint) => {\r\n var height: number = radius * (d && !isNaN(d.sliceHeight) ? d.sliceHeight : 1) / maxScore + innerRadius;\r\n return Math.max(height, innerRadius);\r\n };\r\n var labelArc = d3.svg.arc()\r\n .innerRadius(d => labelRadCalc(d.data))\r\n .outerRadius(d => labelRadCalc(d.data));\r\n\r\n var lineRadCalc = (d: AsterDataPoint) => {\r\n var height: number = (radius - innerRadius) * (d && !isNaN(d.sliceHeight) ? d.sliceHeight : 1) / maxScore;\r\n height = innerRadius + height * AsterRadiusRatio;\r\n return Math.max(height, innerRadius);\r\n };\r\n var outlineArc = d3.svg.arc()\r\n .innerRadius(d => lineRadCalc(d.data))\r\n .outerRadius(d => lineRadCalc(d.data));\r\n\r\n var labelLayout = this.getLabelLayout(labelArc, this.layout.viewport);\r\n this.drawLabels(\r\n arcDescriptorDataPoints.filter(x => !isHighlight || x.data.sliceHeight !== null),\r\n this.mainLabelsElement,\r\n labelLayout,\r\n this.layout.viewport,\r\n outlineArc,\r\n labelArc);\r\n }\r\n else {\r\n dataLabelUtils.cleanDataLabels(this.mainLabelsElement, true);\r\n }\r\n\r\n // Draw center text and outline once for original data points\r\n if (!isHighlight) {\r\n this.drawCenterText(innerRadius);\r\n this.drawOuterLine(innerRadius, _.max(arcDescriptorDataPoints.map(d => arc.outerRadius()(d))), arcDescriptorDataPoints);\r\n }\r\n\r\n return selection;\r\n }\r\n\r\n private getLabelLayout(arc: D3.Svg.Arc, viewport: IViewport): ILabelLayout {\r\n var midAngle = function (d: ArcDescriptor) { return d.startAngle + (d.endAngle - d.startAngle) / 2; };\r\n var textProperties: TextProperties = {\r\n fontFamily: dataLabelUtils.StandardFontFamily,\r\n fontSize: PixelConverter.fromPoint(this.settings.labels.fontSize),\r\n text: \"\",\r\n };\r\n var isLabelsHasConflict = function (d: AsterArcDescriptor) {\r\n var pos = arc.centroid(d);\r\n textProperties.text = d.data.label;\r\n var textWidth = TextMeasurementService.measureSvgTextWidth(textProperties);\r\n var horizontalSpaceAvaliableForLabels = viewport.width / 2 - Math.abs(pos[0]);\r\n var textHeight = TextMeasurementService.estimateSvgTextHeight(textProperties);\r\n var verticalSpaceAvaliableForLabels = viewport.height / 2 - Math.abs(pos[1]);\r\n d.isLabelHasConflict = textWidth > horizontalSpaceAvaliableForLabels || textHeight > verticalSpaceAvaliableForLabels;\r\n return d.isLabelHasConflict;\r\n };\r\n\r\n return {\r\n labelText: (d: AsterArcDescriptor) => {\r\n textProperties.text = d.data.label;\r\n var pos = arc.centroid(d);\r\n var xPos = isLabelsHasConflict(d) ? pos[0] * AsterConflictRatio : pos[0];\r\n var spaceAvaliableForLabels = viewport.width / 2 - Math.abs(xPos);\r\n return TextMeasurementService.getTailoredTextOrDefault(textProperties, spaceAvaliableForLabels);\r\n },\r\n labelLayout: {\r\n x: (d: AsterArcDescriptor) => {\r\n var pos = arc.centroid(d);\r\n textProperties.text = d.data.label;\r\n var xPos = d.isLabelHasConflict ? pos[0] * AsterConflictRatio : pos[0];\r\n return xPos;\r\n },\r\n y: (d: AsterArcDescriptor) => {\r\n var pos = arc.centroid(d);\r\n var yPos = d.isLabelHasConflict ? pos[1] * AsterConflictRatio : pos[1];\r\n return yPos;\r\n },\r\n },\r\n filter: (d: AsterArcDescriptor) => (d != null && !_.isEmpty(d.data.label)),\r\n style: {\r\n \"fill\": this.settings.labels.color,\r\n \"font-size\": textProperties.fontSize,\r\n \"text-anchor\": (d: AsterArcDescriptor) => midAngle(d) < Math.PI ? \"start\" : \"end\",\r\n },\r\n };\r\n }\r\n\r\n private drawLabels(data: ArcDescriptor[],\r\n context: D3.Selection,\r\n layout: ILabelLayout,\r\n viewport: IViewport,\r\n outlineArc: D3.Svg.Arc,\r\n labelArc: D3.Svg.Arc): void {\r\n\r\n // Hide and reposition labels that overlap\r\n var dataLabelManager = new DataLabelManager();\r\n var filteredData = dataLabelManager.hideCollidedLabels(viewport, data, layout, true /* addTransform */);\r\n\r\n if (filteredData.length === 0) {\r\n dataLabelUtils.cleanDataLabels(context, true);\r\n return;\r\n }\r\n\r\n // Draw labels\r\n if (context.select(AsterPlot.labelGraphicsContextClass.selector).empty())\r\n context.append(\"g\").classed(AsterPlot.labelGraphicsContextClass.class, true);\r\n\r\n var labels = context\r\n .select(AsterPlot.labelGraphicsContextClass.selector)\r\n .selectAll(\".data-labels\").data(filteredData, (d: ArcDescriptor) => d.data.identity.getKey());\r\n\r\n labels.enter().append(\"text\").classed(\"data-labels\", true);\r\n\r\n if (!labels)\r\n return;\r\n\r\n labels\r\n .attr({ x: (d: LabelEnabledDataPoint) => d.labelX, y: (d: LabelEnabledDataPoint) => d.labelY, dy: \".35em\" })\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 // Draw lines\r\n if (context.select(AsterPlot.linesGraphicsContextClass.selector).empty())\r\n context.append(\"g\").classed(AsterPlot.linesGraphicsContextClass.class, true);\r\n\r\n // Remove lines for null and zero values\r\n filteredData = _.filter(filteredData, (d: ArcDescriptor) => d.data.sliceHeight !== null && d.data.sliceHeight !== 0);\r\n\r\n var lines = context.select(AsterPlot.linesGraphicsContextClass.selector).selectAll(\"polyline\")\r\n .data(filteredData, (d: ArcDescriptor) => d.data.identity.getKey());\r\n\r\n var labelLinePadding = 4;\r\n var chartLinePadding = 1.02;\r\n\r\n var midAngle = function (d: ArcDescriptor) { return d.startAngle + (d.endAngle - d.startAngle) / 2; };\r\n\r\n lines.enter()\r\n .append(\"polyline\")\r\n .classed(\"line-label\", true);\r\n\r\n lines\r\n .attr(\"points\", function (d) {\r\n var textPoint = [d.labelX, d.labelY];\r\n textPoint[0] = textPoint[0] + ((midAngle(d) < Math.PI ? -1 : 1) * labelLinePadding);\r\n var chartPoint = outlineArc.centroid(d);\r\n chartPoint[0] *= chartLinePadding;\r\n chartPoint[1] *= chartLinePadding;\r\n return [chartPoint, textPoint];\r\n }).\r\n style({\r\n \"opacity\": 0.5,\r\n \"fill-opacity\": 0,\r\n \"stroke\": (d: ArcDescriptor) => this.settings.labels.color,\r\n });\r\n\r\n lines\r\n .exit()\r\n .remove();\r\n\r\n }\r\n\r\n private renderLegend(): void {\r\n if (this.settings.legend.show) {\r\n\r\n // Force update for title text\r\n var legendObject = _.clone(this.settings.legend);\r\n legendObject.labelColor = <any>{ solid: { color: legendObject.labelColor } };\r\n LegendData.update(this.data.legendData, <any>legendObject);\r\n this.legend.changeOrientation(LegendPosition[this.settings.legend.position]);\r\n }\r\n\r\n this.legend.drawLegend(this.data.legendData, this.layout.viewportCopy);\r\n Legend.positionChartArea(this.svg, this.legend);\r\n }\r\n\r\n private updateViewPortAccordingToLegend(): void {\r\n if (!this.settings.legend.show)\r\n return;\r\n\r\n var legendMargins: IViewport = this.legend.getMargins();\r\n var legendPosition: LegendPosition = LegendPosition[this.settings.legend.position];\r\n\r\n switch (legendPosition) {\r\n case LegendPosition.Top:\r\n case LegendPosition.TopCenter:\r\n case LegendPosition.Bottom:\r\n case LegendPosition.BottomCenter: {\r\n this.layout.viewport.height -= legendMargins.height;\r\n break;\r\n }\r\n case LegendPosition.Left:\r\n case LegendPosition.LeftCenter:\r\n case LegendPosition.Right:\r\n case LegendPosition.RightCenter: {\r\n this.layout.viewport.width -= legendMargins.width;\r\n break;\r\n }\r\n default:\r\n break;\r\n }\r\n }\r\n\r\n private drawOuterLine(innerRadius: number, radius: number, data: ArcDescriptor[]): void {\r\n var mainGroup = this.mainGroupElement;\r\n var outlineArc = d3.svg.arc()\r\n .innerRadius(innerRadius)\r\n .outerRadius(radius);\r\n if (this.settings.outerLine.show) {\r\n var OuterThickness: string = this.settings.outerLine.thickness + \"px\";\r\n var outerLine = mainGroup.selectAll(AsterPlot.OuterLine.selector).data(data);\r\n outerLine.enter().append(\"path\");\r\n outerLine.attr(\"fill\", \"none\")\r\n .attr({\r\n \"stroke\": \"#333\",\r\n \"stroke-width\": OuterThickness,\r\n \"d\": outlineArc\r\n })\r\n .style(\"opacity\", 1)\r\n .classed(AsterPlot.OuterLine.class, true);\r\n outerLine.exit().remove();\r\n }\r\n else\r\n mainGroup.selectAll(AsterPlot.OuterLine.selector).remove();\r\n }\r\n\r\n private drawCenterText(innerRadius: number): void {\r\n if (_.isEmpty(this.data.centerText)) {\r\n this.mainGroupElement.select(AsterPlot.CenterLabelClass.selector).remove();\r\n return;\r\n }\r\n\r\n var centerTextProperties: TextProperties = {\r\n fontFamily: dataLabelUtils.StandardFontFamily,\r\n fontWeight: \"bold\",\r\n fontSize: PixelConverter.toString(innerRadius * AsterPlot.CenterTextFontHeightCoefficient),\r\n text: this.data.centerText\r\n };\r\n\r\n if (this.mainGroupElement.select(AsterPlot.CenterLabelClass.selector).empty())\r\n this.centerText = this.mainGroupElement.append(\"text\").classed(AsterPlot.CenterLabelClass.class, true);\r\n\r\n this.centerText\r\n .style({\r\n \"line-height\": 1,\r\n \"font-weight\": centerTextProperties.fontWeight,\r\n \"font-size\": centerTextProperties.fontSize,\r\n \"fill\": this.settings.labels.color\r\n })\r\n .attr({\r\n \"dy\": \"0.35em\",\r\n \"text-anchor\": \"middle\"\r\n })\r\n .text(TextMeasurementService.getTailoredTextOrDefault(centerTextProperties, innerRadius * AsterPlot.CenterTextFontWidthCoefficient));\r\n }\r\n\r\n private clear(): void {\r\n this.mainGroupElement.selectAll(\"path\").remove();\r\n this.mainGroupElement.select(AsterPlot.CenterLabelClass.selector).remove();\r\n dataLabelUtils.cleanDataLabels(this.mainLabelsElement, true);\r\n this.legend.drawLegend({ dataPoints: [] }, this.layout.viewportCopy);\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 // This function retruns the values to be displayed in the property pane for each object.\r\n // Usually it is a bind pass of what the property pane gave you, but sometimes you may want to do\r\n // validation and return other values/defaults\r\n public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstanceEnumerationObject {\r\n var instances = AsterPlotSettings.enumerateObjectInstances(\r\n this.settings && this.settings.originalSettings,\r\n options,\r\n AsterPlot.capabilities);\r\n\r\n return instances.complete();\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/CustomVisuals/visuals/asterPlot/visual/asterPlot.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 IStringResourceProvider = jsCommon.IStringResourceProvider;\r\n import ClassAndSelector = jsCommon.CssConstants.ClassAndSelector;\r\n import PixelConverter = jsCommon.PixelConverter;\r\n\r\n export interface TornadoChartTextOptions {\r\n fontFamily?: string;\r\n fontSize?: number;\r\n sizeUnit?: string;\r\n }\r\n\r\n export interface TornadoChartConstructorOptions {\r\n svg?: D3.Selection;\r\n animator?: IGenericAnimator;\r\n margin?: IMargin;\r\n columnPadding?: number;\r\n }\r\n\r\n export interface TornadoChartSeries {\r\n fill: string;\r\n name: string;\r\n selectionId: SelectionId;\r\n categoryAxisEnd: number;\r\n }\r\n\r\n export interface TornadoChartSettings {\r\n labelOutsideFillColor: string;\r\n categoriesFillColor: string;\r\n labelSettings: VisualDataLabelsSettings;\r\n showLegend?: boolean;\r\n showCategories?: boolean;\r\n legendFontSize?: number;\r\n legendColor?: string;\r\n getLabelValueFormatter?: (formatString: string) => IValueFormatter;\r\n }\r\n\r\n export interface TornadoChartDataView {\r\n categories: TextData[];\r\n series: TornadoChartSeries[];\r\n settings: TornadoChartSettings;\r\n legend: LegendData;\r\n dataPoints: TornadoChartPoint[];\r\n highlightedDataPoints?: TornadoChartPoint[];\r\n hasDynamicSeries: boolean;\r\n hasHighlights: boolean;\r\n labelHeight: number;\r\n maxLabelsWidth: number;\r\n legendObjectProperties: DataViewObject;\r\n }\r\n\r\n export interface TornadoChartPoint extends SelectableDataPoint {\r\n dx?: number;\r\n dy?: number;\r\n px?: number;\r\n py?: number;\r\n angle?: number;\r\n width?: number;\r\n height?: number;\r\n label?: LabelData;\r\n color: string;\r\n tooltipData: TooltipDataItem[];\r\n categoryIndex: number;\r\n highlight?: boolean;\r\n value: number;\r\n minValue: number;\r\n maxValue: number;\r\n formatString: string;\r\n }\r\n\r\n export interface LabelData {\r\n dx: number;\r\n value: number | string;\r\n source: number | string;\r\n color: string;\r\n }\r\n\r\n export interface LineData {\r\n x1: number;\r\n y1: number;\r\n x2: number;\r\n y2: number;\r\n }\r\n\r\n export interface TextData {\r\n text: string;\r\n height: number;\r\n width: number;\r\n textProperties: TextProperties;\r\n }\r\n\r\n export interface TornadoBehaviorOptions {\r\n columns: D3.Selection;\r\n clearCatcher: D3.Selection;\r\n interactivityService: IInteractivityService;\r\n }\r\n\r\n class TornadoWebBehavior implements IInteractiveBehavior {\r\n private columns: D3.Selection;\r\n private clearCatcher: D3.Selection;\r\n private interactivityService: IInteractivityService;\r\n\r\n public bindEvents(options: TornadoBehaviorOptions, selectionHandler: ISelectionHandler) {\r\n this.columns = options.columns;\r\n this.clearCatcher = options.clearCatcher;\r\n this.interactivityService = options.interactivityService;\r\n\r\n this.columns.on('click', (d: SelectableDataPoint, i: number) => {\r\n selectionHandler.handleSelection(d, d3.event.ctrlKey);\r\n });\r\n\r\n this.clearCatcher.on('click', () => {\r\n selectionHandler.handleClearSelection();\r\n });\r\n }\r\n\r\n public renderSelection(hasSelection: boolean) {\r\n var hasHighlights = this.interactivityService.hasSelection();\r\n this.columns.style(\"fill-opacity\", (d: TornadoChartPoint) => ColumnUtil.getFillOpacity(d.selected,\r\n d.highlight,\r\n !d.highlight && hasSelection,\r\n !d.selected && hasHighlights));\r\n }\r\n }\r\n\r\n class TornadoChartScrolling {\r\n public isScrollable: boolean;\r\n public get scrollViewport(): IViewport {\r\n return { \r\n height: this.viewport.height,\r\n width: this.viewport.width\r\n - ((this.isYScrollBarVisible && this.isScrollable) ? TornadoChart.ScrollBarWidth : 0)\r\n };\r\n }\r\n\r\n private static ScrollBarMinLength = 15;\r\n private isYScrollBarVisible: boolean;\r\n private brushGraphicsContextY: D3.Selection;\r\n private scrollYBrush: D3.Svg.Brush = d3.svg.brush();\r\n\r\n private getRoot: () => D3.Selection;\r\n private getViewport: () => IViewport;\r\n private getPrefferedHeight: () => number;\r\n\r\n private get root(): D3.Selection {\r\n return this.getRoot();\r\n }\r\n\r\n private get viewport(): IViewport {\r\n return this.getViewport();\r\n }\r\n\r\n constructor(\r\n getRoot: () => D3.Selection,\r\n getViewport: () => IViewport,\r\n getMargin: () => IMargin,\r\n getPrefferedHeight: () => number,\r\n isScrollable: boolean) {\r\n\r\n this.getRoot = getRoot;\r\n this.getViewport = getViewport;\r\n this.isScrollable = isScrollable;\r\n this.getPrefferedHeight = getPrefferedHeight;\r\n }\r\n\r\n public renderY(data: TornadoChartDataView, onScroll: () => {}): void {\r\n this.isYScrollBarVisible = this.isScrollable &&\r\n this.getPrefferedHeight() > this.viewport.height\r\n && this.viewport.height > 0\r\n && this.viewport.width > 0;\r\n\r\n this.brushGraphicsContextY = this.createOrRemoveScrollbar(this.isYScrollBarVisible, this.brushGraphicsContextY, 'y brush');\r\n\r\n if (!this.isYScrollBarVisible) {\r\n onScroll.call(this, jQuery.extend(true, {}, data), 0, 1);\r\n return;\r\n }\r\n\r\n var scrollSpaceLength: number = this.viewport.height;\r\n var extentData: any = this.getExtentData(this.getPrefferedHeight(), scrollSpaceLength);\r\n\r\n var onRender = (wheelDelta: number = 0) => {\r\n var position: number[] = this.scrollYBrush.extent();\r\n if (wheelDelta !== 0) {\r\n\r\n // Handle mouse wheel manually by moving the scrollbar half of its size\r\n var halfScrollsize: number = (position[1] - position[0]) / 2;\r\n position[0] += (wheelDelta > 0) ? halfScrollsize : -halfScrollsize;\r\n position[1] += (wheelDelta > 0) ? halfScrollsize : -halfScrollsize;\r\n\r\n if (position[0] < 0) {\r\n var offset: number = 0 - position[0];\r\n position[0] += offset;\r\n position[1] += offset;\r\n }\r\n if (position[1] > scrollSpaceLength) {\r\n var offset: number = position[1] - scrollSpaceLength;\r\n position[0] -= offset;\r\n position[1] -= offset;\r\n }\r\n\r\n // Update the scroll bar accordingly and redraw\r\n this.scrollYBrush.extent(position);\r\n this.brushGraphicsContextY.select('.extent').attr('y', position[0]);\r\n }\r\n var scrollPosition = extentData.toScrollPosition(position, scrollSpaceLength);\r\n onScroll.call(this, jQuery.extend(true, {}, data), scrollPosition[0], scrollPosition[1]);\r\n this.setScrollBarSize(this.brushGraphicsContextY, extentData.value[1], true);\r\n };\r\n\r\n var scrollYScale: D3.Scale.OrdinalScale = d3.scale.ordinal().rangeBands([0, scrollSpaceLength]);\r\n this.scrollYBrush.y(scrollYScale).extent(extentData.value);\r\n\r\n this.renderScrollbar(\r\n this.scrollYBrush,\r\n this.brushGraphicsContextY,\r\n this.viewport.width,\r\n onRender);\r\n\r\n onRender();\r\n }\r\n\r\n private createOrRemoveScrollbar(isVisible, brushGraphicsContext, brushClass) {\r\n if (isVisible && this.isScrollable) {\r\n return brushGraphicsContext || this.root.append(\"g\").classed(brushClass, true);\r\n }\r\n\r\n return brushGraphicsContext ? void brushGraphicsContext.remove() : undefined;\r\n }\r\n\r\n private renderScrollbar(brush: D3.Svg.Brush,\r\n brushGraphicsContext: D3.Selection,\r\n brushX: number,\r\n onRender: (number) => void): void {\r\n\r\n brush.on(\"brush\", () => window.requestAnimationFrame(() => onRender(0)));\r\n this.root.on('wheel', () => {\r\n if (!this.isYScrollBarVisible) return;\r\n var wheelEvent: any = d3.event; // Casting to any to avoid compilation errors\r\n onRender(wheelEvent.deltaY);\r\n });\r\n\r\n brushGraphicsContext.attr({\r\n \"transform\": visuals.SVGUtil.translate(brushX, 0),\r\n \"drag-resize-disabled\": \"true\" /*disables resizing of the visual when dragging the scrollbar in edit mode*/\r\n });\r\n\r\n brushGraphicsContext.call(brush); /*call the brush function, causing it to create the rectangles */\r\n /* Disabling the zooming feature */\r\n brushGraphicsContext.selectAll(\".resize\").remove();\r\n brushGraphicsContext.select(\".background\").remove();\r\n brushGraphicsContext.selectAll(\".extent\").style({\r\n \"fill-opacity\": 0.125,\r\n \"cursor\": \"default\",\r\n });\r\n }\r\n\r\n private setScrollBarSize(brushGraphicsContext: D3.Selection, minExtent: number, isVertical: boolean): void {\r\n brushGraphicsContext.selectAll(\"rect\").attr(isVertical ? \"width\" : \"height\", TornadoChart.ScrollBarWidth);\r\n brushGraphicsContext.selectAll(\"rect\").attr(isVertical ? \"height\" : \"width\", minExtent);\r\n }\r\n\r\n private getExtentData(svgLength: number, scrollSpaceLength: number): any {\r\n var value: number = scrollSpaceLength * scrollSpaceLength / svgLength;\r\n\r\n var scaleMultipler: number = TornadoChartScrolling.ScrollBarMinLength <= value\r\n ? 1\r\n : value / TornadoChartScrolling.ScrollBarMinLength;\r\n\r\n value = Math.max(value, TornadoChartScrolling.ScrollBarMinLength);\r\n\r\n var toScrollPosition = (extent: number[], scrollSpaceLength: number) => {\r\n var scrollSize: number = extent[1] - extent[0];\r\n var scrollPosition: number = extent[0] / (scrollSpaceLength - scrollSize);\r\n\r\n scrollSize *= scaleMultipler;\r\n\r\n var start: number = (scrollPosition * (scrollSpaceLength - scrollSize));\r\n var end: number = (start + scrollSize);\r\n\r\n return [start / scrollSpaceLength, end / scrollSpaceLength];\r\n };\r\n\r\n return { value: [0, value], toScrollPosition: toScrollPosition };\r\n }\r\n\r\n public clearData(): void {\r\n if (this.brushGraphicsContextY)\r\n this.brushGraphicsContextY.selectAll(\"*\").remove();\r\n }\r\n }\r\n\r\n export class TornadoChartWarning implements IVisualWarning {\r\n public get code(): string {\r\n return \"TornadoChartWarning\";\r\n }\r\n\r\n public getMessages(resourceProvider: IStringResourceProvider): IVisualErrorMessage {\r\n var message: string = \"This visual requires two distinct values to be returned for the Legend field.\",\r\n titleKey: string = \"\",\r\n detailKey: string = \"\",\r\n visualMessage: IVisualErrorMessage;\r\n\r\n visualMessage = {\r\n message: message,\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 function getTornadoChartWarning(): IVisualWarning {\r\n return new TornadoChartWarning();\r\n }\r\n\r\n export class TornadoChart implements IVisual {\r\n private static ClassName: string = \"tornado-chart\";\r\n\r\n public static capabilities: VisualCapabilities = {\r\n dataRoles: [{\r\n name: \"Category\",\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: data.createDisplayNameGetter(\"Role_DisplayName_Group\")\r\n }, {\r\n name: \"Series\",\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Legend')\r\n }, {\r\n name: \"Values\",\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: data.createDisplayNameGetter(\"Role_DisplayName_Values\")\r\n }],\r\n dataViewMappings: [{\r\n conditions: [\r\n { \"Category\": { max: 1 }, \"Values\": { min: 0, max: 1 }, \"Series\": { min: 0, max: 1 } },\r\n { \"Category\": { max: 1 }, \"Values\": { min: 2, max: 2 }, \"Series\": { max: 0 } }\r\n ],\r\n categorical: {\r\n categories: {\r\n for: {\r\n in: \"Category\"\r\n }\r\n },\r\n values: {\r\n group: {\r\n by: \"Series\",\r\n select: [{ for: { in: \"Values\" } }],\r\n dataReductionAlgorithm: { top: {} }\r\n }\r\n }\r\n }\r\n }],\r\n objects: {\r\n general: {\r\n displayName: 'General',\r\n properties: {\r\n formatString: {\r\n type: {\r\n formatting: {\r\n formatString: true\r\n }\r\n },\r\n }\r\n }\r\n },\r\n dataPoint: {\r\n displayName: 'Data Colors',\r\n properties: {\r\n fill: {\r\n displayName: data.createDisplayNameGetter('Visual_Fill'),\r\n type: { fill: { solid: { color: true } } }\r\n }\r\n }\r\n },\r\n categoryAxis: {\r\n displayName: 'X-Axis',\r\n properties: {\r\n end: {\r\n displayName: 'End',\r\n type: { numeric: true },\r\n suppressFormatPainterCopy: true,\r\n },\r\n }\r\n },\r\n labels: {\r\n displayName: 'Data Labels',\r\n properties: {\r\n show: {\r\n displayName: 'Show',\r\n type: { bool: true }\r\n },\r\n labelPrecision: {\r\n displayName: 'Decimal Places',\r\n placeHolderText: 'Auto',\r\n type: { numeric: true }\r\n },\r\n fontSize: {\r\n displayName: data.createDisplayNameGetter('Visual_TextSize'),\r\n type: { formatting: { fontSize: true } }\r\n },\r\n labelDisplayUnits: {\r\n displayName: 'Display Units',\r\n type: { formatting: { labelDisplayUnits: true } },\r\n },\r\n insideFill: {\r\n displayName: 'Inside fill',\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n outsideFill: {\r\n displayName: 'Outside fill',\r\n type: { fill: { solid: { color: true } } }\r\n }\r\n }\r\n },\r\n legend: {\r\n displayName: 'Legend',\r\n properties: {\r\n show: {\r\n displayName: 'Show',\r\n type: { bool: true }\r\n },\r\n position: {\r\n displayName: 'Position',\r\n description: data.createDisplayNameGetter('Visual_LegendPositionDescription'),\r\n type: { enumeration: legendPosition.type }\r\n },\r\n showTitle: {\r\n displayName: 'Title',\r\n description: data.createDisplayNameGetter('Visual_LegendShowTitleDescription'),\r\n type: { bool: true }\r\n },\r\n titleText: {\r\n displayName: 'Legend Name',\r\n description: data.createDisplayNameGetter('Visual_LegendNameDescription'),\r\n type: { text: true }\r\n },\r\n labelColor: {\r\n displayName: 'Color',\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n fontSize: {\r\n displayName: 'TextSize',\r\n type: { formatting: { fontSize: true } }\r\n },\r\n }\r\n },\r\n categories: {\r\n displayName: 'Group',\r\n properties: {\r\n show: {\r\n displayName: 'Show',\r\n type: { bool: true }\r\n },\r\n fill: {\r\n displayName: 'Color',\r\n type: { fill: { solid: { color: true } } }\r\n }\r\n }\r\n }\r\n },\r\n supportsHighlight: true,\r\n };\r\n\r\n private static Properties: any = TornadoChart.getProperties(TornadoChart.capabilities);\r\n public static getProperties(capabilities: VisualCapabilities): any {\r\n var result = {};\r\n for(var objectKey in capabilities.objects) {\r\n result[objectKey] = {};\r\n for(var propKey in capabilities.objects[objectKey].properties) {\r\n result[objectKey][propKey] = <DataViewObjectPropertyIdentifier> {\r\n objectName: objectKey,\r\n propertyName: propKey\r\n };\r\n }\r\n }\r\n\r\n return result;\r\n }\r\n\r\n private static Columns: ClassAndSelector = {\r\n \"class\": \"columns\",\r\n selector: \".columns\"\r\n };\r\n\r\n private static Column: ClassAndSelector = {\r\n \"class\": \"column\",\r\n selector: \".column\"\r\n };\r\n\r\n private static Axes: ClassAndSelector = {\r\n \"class\": \"axes\",\r\n selector: \".axes\"\r\n };\r\n\r\n private static Axis: ClassAndSelector = {\r\n \"class\": \"axis\",\r\n selector: \".axis\"\r\n };\r\n\r\n private static Labels: ClassAndSelector = {\r\n \"class\": \"labels\",\r\n selector: \".labels\"\r\n };\r\n\r\n private static Label: ClassAndSelector = {\r\n \"class\": \"label\",\r\n selector: \".label\"\r\n };\r\n\r\n private static LabelTitle: ClassAndSelector = {\r\n \"class\": \"label-title\",\r\n selector: \".label-title\"\r\n };\r\n\r\n private static LabelText: ClassAndSelector = {\r\n \"class\": \"label-text\",\r\n selector: \".label-text\"\r\n };\r\n\r\n private static Categories: ClassAndSelector = {\r\n \"class\": \"categories\",\r\n selector: \".categories\"\r\n };\r\n\r\n private static Category: ClassAndSelector = {\r\n \"class\": \"category\",\r\n selector: \".category\"\r\n };\r\n\r\n private static CategoryTitle: ClassAndSelector = {\r\n \"class\": \"category-title\",\r\n selector: \".category-title\"\r\n };\r\n\r\n private static CategoryText: ClassAndSelector = {\r\n \"class\": \"category-text\",\r\n selector: \".category-text\"\r\n };\r\n\r\n private static MaxSeries: number = 2;\r\n private static MaxPrecision: number = 17; // max number of decimals in float\r\n private static LabelPadding: number = 2.5;\r\n private static CategoryMinHeight: number = 25;\r\n private static DefaultFontSize: number = 9;\r\n private static DefaultLegendFontSize: number = 8;\r\n private static HighlightedShapeFactor: number = 0.5;\r\n private static CategoryLabelMargin: number = 10;\r\n\r\n public static ScrollBarWidth = 22;\r\n\r\n private static DefaultTornadoChartSettings: TornadoChartSettings = {\r\n labelOutsideFillColor: dataLabelUtils.defaultLabelColor,\r\n labelSettings: {\r\n show: true,\r\n precision: null,\r\n fontSize: TornadoChart.DefaultFontSize,\r\n displayUnits: 0,\r\n labelColor: dataLabelUtils.defaultInsideLabelColor,\r\n },\r\n showCategories: true,\r\n showLegend: true,\r\n legendFontSize: TornadoChart.DefaultLegendFontSize,\r\n legendColor: LegendData.DefaultLegendLabelFillColor,\r\n categoriesFillColor: \"#777\"\r\n };\r\n\r\n public static converter(dataView: DataView, textOptions: TornadoChartTextOptions, colors: IDataColorPalette): TornadoChartDataView {\r\n if (!dataView ||\r\n !dataView.categorical ||\r\n !dataView.categorical.categories ||\r\n !dataView.categorical.categories[0] ||\r\n !dataView.categorical.categories[0].source ||\r\n !dataView.categorical.values ||\r\n !dataView.categorical.values[0]) {\r\n return null;\r\n }\r\n\r\n var categorical: DataViewCategorical = dataView.categorical;\r\n var categories: DataViewCategoryColumn[] = categorical.categories || [];\r\n var values: DataViewValueColumns = categorical.values;\r\n\r\n var category: DataViewCategoricalColumn = categories[0];\r\n var formatStringProp: DataViewObjectPropertyIdentifier = TornadoChart.Properties.general.formatString;\r\n var maxValue: number = d3.max(values[0].values);\r\n var settings: TornadoChartSettings = TornadoChart.parseSettings(dataView.metadata.objects, maxValue, colors);\r\n var hasDynamicSeries = !!values.source;\r\n var hasHighlights: boolean = !!(values.length > 0 && values[0].highlights);\r\n var labelHeight = TextMeasurementService.estimateSvgTextHeight({\r\n fontFamily: dataLabelUtils.StandardFontFamily,\r\n fontSize: PixelConverter.fromPoint(settings.labelSettings.fontSize),\r\n });\r\n\r\n var series: TornadoChartSeries[] = [];\r\n var dataPoints: TornadoChartPoint[] = [];\r\n var highlightedDataPoints: TornadoChartPoint[] = [];\r\n\r\n var categorySourceFormatString: string = valueFormatter.getFormatString(category.source, formatStringProp);\r\n var categoriesLabels: TextData[] = category.values.map(value => {\r\n var formattedCategoryValue = valueFormatter.format(value, categorySourceFormatString);\r\n return TornadoChart.getTextData(formattedCategoryValue, textOptions, true);\r\n });\r\n\r\n var groupedValues: DataViewValueColumnGroup[] = values.grouped ? values.grouped() : null;\r\n\r\n var minValue: number = Math.min(d3.min(values[0].values), 0);\r\n if (values.length === TornadoChart.MaxSeries) {\r\n minValue = d3.min([minValue, d3.min(values[1].values)]);\r\n maxValue = d3.max([maxValue, d3.max(values[1].values)]);\r\n }\r\n\r\n for (var seriesIndex = 0; seriesIndex < values.length; seriesIndex++) {\r\n var columnGroup: DataViewValueColumnGroup = groupedValues && groupedValues.length > seriesIndex \r\n && groupedValues[seriesIndex].values ? groupedValues[seriesIndex] : null;\r\n\r\n var parsedSeries: TornadoChartSeries = TornadoChart.parseSeries(values, seriesIndex, hasDynamicSeries, columnGroup, colors);\r\n\r\n series.push(parsedSeries);\r\n\r\n var currentSeries = values[seriesIndex];\r\n var measureName = currentSeries.source.queryName;\r\n\r\n for (var i = 0; i < category.values.length; i++) {\r\n var value = currentSeries.values[i] == null || isNaN(currentSeries.values[i]) ? 0 : currentSeries.values[i];\r\n\r\n var identity = SelectionIdBuilder.builder()\r\n .withCategory(category, i)\r\n .withSeries(values, columnGroup)\r\n .withMeasure(measureName)\r\n .createSelectionId();\r\n\r\n var formattedCategoryValue = categoriesLabels[i].text;\r\n var tooltipInfo: TooltipDataItem[];\r\n tooltipInfo = TooltipBuilder.createTooltipInfo(formatStringProp, categorical, formattedCategoryValue, value, null, null, seriesIndex, i, null);\r\n\r\n // Limit maximum value with what the user choose\r\n var currentMaxValue = parsedSeries.categoryAxisEnd ? Math.min(parsedSeries.categoryAxisEnd, maxValue) : maxValue;\r\n var formatString = dataView.categorical.values[seriesIndex].source.format;\r\n\r\n dataPoints.push({\r\n value: value,\r\n minValue: minValue,\r\n maxValue: currentMaxValue,\r\n formatString: formatString,\r\n color: parsedSeries.fill,\r\n selected: false,\r\n identity: identity,\r\n tooltipData: tooltipInfo,\r\n categoryIndex: i,\r\n });\r\n\r\n if (hasHighlights) {\r\n var highlightIdentity = SelectionId.createWithHighlight(identity);\r\n var highlight = currentSeries.highlights[i];\r\n var highlightedValue = highlight != null ? highlight : 0;\r\n tooltipInfo = TooltipBuilder.createTooltipInfo(formatStringProp, categorical, formattedCategoryValue, value, null, null, seriesIndex, i, highlightedValue);\r\n\r\n highlightedDataPoints.push({\r\n value: highlightedValue,\r\n minValue: minValue,\r\n maxValue: currentMaxValue,\r\n formatString: formatString,\r\n color: parsedSeries.fill,\r\n selected: false,\r\n identity: highlightIdentity,\r\n tooltipData: tooltipInfo,\r\n categoryIndex: i,\r\n highlight: true,\r\n });\r\n }\r\n }\r\n }\r\n\r\n return {\r\n categories: categoriesLabels,\r\n series: series,\r\n settings: settings,\r\n legend: TornadoChart.getLegendData(series, hasDynamicSeries),\r\n dataPoints: dataPoints,\r\n highlightedDataPoints: highlightedDataPoints,\r\n maxLabelsWidth: _.max(categoriesLabels.map(x => x.width)),\r\n hasDynamicSeries: hasDynamicSeries,\r\n hasHighlights: hasHighlights,\r\n labelHeight: labelHeight,\r\n legendObjectProperties: DataViewObjects.getObject(dataView.metadata.objects, \"legend\", {}),\r\n };\r\n }\r\n\r\n public static parseSeries(\r\n dataViewValueColumns: DataViewValueColumns,\r\n index: number,\r\n isGrouped: boolean,\r\n columnGroup: DataViewValueColumnGroup,\r\n colors: IDataColorPalette): TornadoChartSeries {\r\n\r\n var dataViewValueColumn: DataViewValueColumn = dataViewValueColumns ? dataViewValueColumns[index] : null,\r\n source: DataViewMetadataColumn = dataViewValueColumn ? dataViewValueColumn.source : null,\r\n identity: DataViewScopeIdentity = columnGroup ? columnGroup.identity : null,\r\n queryName: string = source ? source.queryName : null;\r\n\r\n var selectionId: SelectionId = identity\r\n ? SelectionId.createWithId(identity)\r\n : SelectionIdBuilder.builder()\r\n .withSeries(dataViewValueColumns, columnGroup)\r\n .withMeasure(queryName)\r\n .createSelectionId();\r\n\r\n var objects: DataViewObjects,\r\n categoryAxisObject: DataViewObject | DataViewObjectWithId[],\r\n displayName: string = source ? source.groupName\r\n ? source.groupName : source.displayName\r\n : null;\r\n\r\n if (isGrouped && columnGroup) {\r\n categoryAxisObject = columnGroup.objects ? columnGroup.objects['categoryAxis'] : null;\r\n objects = columnGroup.objects;\r\n }\r\n else if (source) {\r\n objects = source.objects;\r\n categoryAxisObject = objects ? objects['categoryAxis'] : null;\r\n }\r\n\r\n var color: string = TornadoChart.getColor(\r\n TornadoChart.Properties.dataPoint.fill,\r\n [\"purple\", \"teal\"][index],\r\n objects, colors);\r\n\r\n var categoryAxisEnd: number = categoryAxisObject ? categoryAxisObject['end'] : null;\r\n\r\n return <TornadoChartSeries>{\r\n fill: color,\r\n name: displayName,\r\n selectionId: selectionId,\r\n categoryAxisEnd: categoryAxisEnd,\r\n };\r\n }\r\n\r\n private static getColor(properties: any, defaultColor: string, objects: DataViewObjects, colors: IDataColorPalette): string {\r\n var colorHelper: ColorHelper = new ColorHelper(colors, properties, defaultColor);\r\n return colorHelper.getColorForMeasure(objects, \"\");\r\n }\r\n\r\n private static getTextData(\r\n text: string,\r\n textOptions: TornadoChartTextOptions,\r\n measureWidth: boolean = false,\r\n measureHeight: boolean = false,\r\n overrideFontSize?: number): TextData {\r\n\r\n var width: number = 0,\r\n height: number = 0,\r\n fontSize: string,\r\n textProperties: TextProperties;\r\n\r\n text = text || \"\";\r\n\r\n fontSize = overrideFontSize\r\n ? PixelConverter.fromPoint(overrideFontSize)\r\n : `${textOptions.fontSize}${textOptions.sizeUnit}`;\r\n\r\n textProperties = {\r\n text: text,\r\n fontFamily: textOptions.fontFamily,\r\n fontSize: fontSize\r\n };\r\n\r\n if (measureWidth) {\r\n width = TextMeasurementService.measureSvgTextWidth(textProperties);\r\n }\r\n\r\n if (measureHeight) {\r\n height = TextMeasurementService.estimateSvgTextHeight(textProperties);\r\n }\r\n\r\n return {\r\n text: text,\r\n width: width,\r\n height: height,\r\n textProperties: textProperties\r\n };\r\n }\r\n\r\n public colors: IDataColorPalette;\r\n public textOptions: TornadoChartTextOptions = {};\r\n\r\n private columnPadding: number = 5;\r\n private leftLabelMargin: number = 4;\r\n private durationAnimations: number;\r\n private InnerTextHeightDelta: number = 2;\r\n \r\n private margin: IMargin = {\r\n top: 10,\r\n right: 5,\r\n bottom: 10,\r\n left: 10\r\n };\r\n\r\n private root: D3.Selection;\r\n private svg: D3.Selection;\r\n private main: D3.Selection;\r\n private columns: D3.Selection;\r\n private axes: D3.Selection;\r\n private labels: D3.Selection;\r\n private categories: D3.Selection;\r\n private clearCatcher: D3.Selection;\r\n\r\n private legend: ILegend;\r\n private behavior: IInteractiveBehavior;\r\n private interactivityService: IInteractivityService;\r\n private animator: IGenericAnimator;\r\n private hostService: IVisualHostServices;\r\n private scrolling: TornadoChartScrolling;\r\n\r\n private viewport: IViewport;\r\n private dataView: TornadoChartDataView;\r\n private heightColumn: number = 0;\r\n\r\n private get allLabelsWidth(): number {\r\n return (this.dataView.settings.showCategories\r\n ? Math.min(this.dataView.maxLabelsWidth, this.scrolling.scrollViewport.width/2)\r\n : 3) + TornadoChart.CategoryLabelMargin;\r\n }\r\n\r\n private get allColumnsWidth(): number {\r\n return this.scrolling.scrollViewport.width - this.allLabelsWidth;\r\n }\r\n \r\n private get columnWidth(): number {\r\n return this.dataView.series.length === TornadoChart.MaxSeries\r\n ? this.allColumnsWidth/2\r\n : this.allColumnsWidth;\r\n }\r\n\r\n constructor(tornadoChartConstructorOptions?: TornadoChartConstructorOptions) {\r\n if (tornadoChartConstructorOptions) {\r\n this.svg = tornadoChartConstructorOptions.svg || this.svg;\r\n this.margin = tornadoChartConstructorOptions.margin || this.margin;\r\n this.columnPadding = tornadoChartConstructorOptions.columnPadding || this.columnPadding;\r\n this.animator = tornadoChartConstructorOptions.animator;\r\n }\r\n }\r\n\r\n public init(visualInitOptions: VisualInitOptions): void {\r\n var style: IVisualStyle = visualInitOptions.style,\r\n fontSize: string;\r\n\r\n this.hostService = visualInitOptions.host;\r\n var element: JQuery = visualInitOptions.element;\r\n this.colors = style.colorPalette.dataColors;\r\n var interactivity = visualInitOptions.interactivity;\r\n this.interactivityService = createInteractivityService(this.hostService);\r\n\r\n var root: D3.Selection;\r\n if (this.svg)\r\n this.root = root = this.svg;\r\n else\r\n this.root = root = d3.select(element.get(0))\r\n .append(\"svg\");\r\n\r\n root\r\n .classed(TornadoChart.ClassName, true)\r\n .style('position', 'absolute');\r\n\r\n fontSize = root.style(\"font-size\");\r\n\r\n this.textOptions.sizeUnit = fontSize.slice(fontSize.length - 2);\r\n this.textOptions.fontSize = Number(fontSize.slice(0, fontSize.length - 2));\r\n this.textOptions.fontFamily = root.style(\"font-family\");\r\n this.viewport = visualInitOptions.viewport;\r\n this.scrolling = new TornadoChartScrolling(\r\n () => root,\r\n () => this.viewport,\r\n () => this.margin,\r\n () => this.dataView.categories.length * TornadoChart.CategoryMinHeight,\r\n true);\r\n\r\n var main: D3.Selection = this.main = root.append(\"g\");\r\n this.clearCatcher = appendClearCatcher(main);\r\n this.columns = main\r\n .append(\"g\")\r\n .classed(TornadoChart.Columns.class, true);\r\n\r\n this.axes = main\r\n .append(\"g\")\r\n .classed(TornadoChart.Axes.class, true);\r\n\r\n this.labels = main\r\n .append(\"g\")\r\n .classed(TornadoChart.Labels.class, true);\r\n\r\n this.categories = main\r\n .append(\"g\")\r\n .classed(TornadoChart.Categories.class, true);\r\n\r\n this.behavior = new TornadoWebBehavior();\r\n this.legend = createLegend(element, interactivity && interactivity.isInteractiveLegend, this.interactivityService, true);\r\n }\r\n\r\n public update(visualUpdateOptions: VisualUpdateOptions): void {\r\n if (!visualUpdateOptions ||\r\n !visualUpdateOptions.dataViews ||\r\n !visualUpdateOptions.dataViews[0]) {\r\n return;\r\n }\r\n\r\n this.viewport = {\r\n height: Math.max(0, visualUpdateOptions.viewport.height - this.margin.top - this.margin.bottom),\r\n width: Math.max(0, visualUpdateOptions.viewport.width - this.margin.left - this.margin.right)\r\n };\r\n\r\n if (this.animator)\r\n this.durationAnimations = AnimatorCommon.GetAnimationDuration(this.animator, visualUpdateOptions.suppressAnimations);\r\n else\r\n this.durationAnimations = visualUpdateOptions.suppressAnimations ? 0 : 250;\r\n\r\n this.dataView = TornadoChart.converter(this.validateDataView(visualUpdateOptions.dataViews[0]), this.textOptions, this.colors);\r\n if (!this.dataView || this.scrolling.scrollViewport.height < TornadoChart.CategoryMinHeight) {\r\n this.clearData();\r\n return;\r\n }\r\n\r\n if (this.dataView && this.interactivityService) {\r\n this.interactivityService.applySelectionStateToData(this.dataView.dataPoints);\r\n this.interactivityService.applySelectionStateToData(this.dataView.highlightedDataPoints);\r\n }\r\n\r\n this.render();\r\n }\r\n\r\n private validateDataView(dataView: DataView): DataView {\r\n if(!dataView || !dataView.categorical || !dataView.categorical.values) {\r\n return null;\r\n }\r\n\r\n if (dataView.categorical.values.length > TornadoChart.MaxSeries) {\r\n this.hostService.setWarnings([getTornadoChartWarning()]);\r\n return null;\r\n }\r\n\r\n return dataView;\r\n }\r\n\r\n private updateElements(): void {\r\n var elementsTranslate: string = SVGUtil.translate(this.allLabelsWidth, 0);\r\n\r\n this.root.attr({\r\n \"height\": this.viewport.height + this.margin.top + this.margin.bottom,\r\n \"width\": this.viewport.width + this.margin.left + this.margin.right\r\n });\r\n\r\n this.columns\r\n .attr(\"transform\", elementsTranslate);\r\n\r\n this.labels\r\n .attr(\"transform\", elementsTranslate);\r\n\r\n this.axes\r\n .attr(\"transform\", elementsTranslate);\r\n }\r\n\r\n private static parseSettings(objects: DataViewObjects, value: number, colors: IDataColorPalette): TornadoChartSettings {\r\n var precision: number = TornadoChart.getPrecision(objects);\r\n\r\n var displayUnits: number = DataViewObjects.getValue<number>(\r\n objects,\r\n TornadoChart.Properties.labels.labelDisplayUnits,\r\n TornadoChart.DefaultTornadoChartSettings.labelSettings.displayUnits);\r\n\r\n var labelSettings = TornadoChart.DefaultTornadoChartSettings.labelSettings;\r\n\r\n var getLabelValueFormatter = (formatString: string) => valueFormatter.create({\r\n format: formatString,\r\n precision: precision,\r\n value: (displayUnits === 0) && (value != null) ? value : displayUnits,\r\n });\r\n\r\n return {\r\n labelOutsideFillColor: TornadoChart.getColor(\r\n TornadoChart.Properties.labels.outsideFill,\r\n TornadoChart.DefaultTornadoChartSettings.labelOutsideFillColor,\r\n objects,\r\n colors),\r\n\r\n labelSettings: {\r\n show: DataViewObjects.getValue<boolean>(objects, TornadoChart.Properties.labels.show, labelSettings.show),\r\n precision: precision,\r\n fontSize: DataViewObjects.getValue<number>(objects, TornadoChart.Properties.labels.fontSize, labelSettings.fontSize),\r\n displayUnits: displayUnits,\r\n labelColor: TornadoChart.getColor(TornadoChart.Properties.labels.insideFill, labelSettings.labelColor, objects, colors),\r\n },\r\n showCategories: DataViewObjects.getValue<boolean>(objects, TornadoChart.Properties.categories.show, TornadoChart.DefaultTornadoChartSettings.showCategories),\r\n showLegend: DataViewObjects.getValue<boolean>(objects, TornadoChart.Properties.legend.show, TornadoChart.DefaultTornadoChartSettings.showLegend),\r\n legendFontSize: DataViewObjects.getValue<number>(objects, TornadoChart.Properties.legend.fontSize, TornadoChart.DefaultTornadoChartSettings.legendFontSize),\r\n legendColor: TornadoChart.getColor(TornadoChart.Properties.legend.labelColor, TornadoChart.DefaultTornadoChartSettings.legendColor, objects, colors),\r\n categoriesFillColor: TornadoChart.getColor(TornadoChart.Properties.categories.fill, TornadoChart.DefaultTornadoChartSettings.categoriesFillColor, objects, colors),\r\n getLabelValueFormatter: getLabelValueFormatter\r\n };\r\n }\r\n\r\n private static getPrecision(objects: DataViewObjects): number {\r\n var precision: number = DataViewObjects.getValue<number>(\r\n objects,\r\n TornadoChart.Properties.labels.labelPrecision,\r\n TornadoChart.DefaultTornadoChartSettings.labelSettings.precision);\r\n\r\n return Math.min(Math.max(0, precision), TornadoChart.MaxPrecision);\r\n }\r\n\r\n private static getLegendData(series: TornadoChartSeries[], hasDynamicSeries: boolean): LegendData {\r\n var legendDataPoints: LegendDataPoint[] = [];\r\n\r\n if (hasDynamicSeries)\r\n legendDataPoints = series.map((series: TornadoChartSeries) => {\r\n return <LegendDataPoint>{\r\n label: series.name,\r\n color: series.fill,\r\n icon: LegendIcon.Box,\r\n selected: false,\r\n identity: series.selectionId\r\n };\r\n });\r\n\r\n return {\r\n dataPoints: legendDataPoints\r\n };\r\n }\r\n\r\n private render(): void {\r\n this.updateElements();\r\n this.renderLegend();\r\n this.scrolling.renderY(this.dataView, this.renderWithScrolling.bind(this));\r\n }\r\n\r\n private clearData(): void {\r\n this.columns.selectAll(\"*\").remove();\r\n this.axes.selectAll(\"*\").remove();\r\n this.labels.selectAll(\"*\").remove();\r\n this.categories.selectAll(\"*\").remove();\r\n this.legend.drawLegend({ dataPoints: [] }, this.viewport);\r\n this.scrolling.clearData();\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 private renderWithScrolling(tornadoChartDataView: TornadoChartDataView, scrollStart: number, scrollEnd: number): void {\r\n if (!this.dataView || !this.dataView.settings)\r\n return;\r\n var categoriesLength = tornadoChartDataView.categories.length;\r\n var startIndex: number = scrollStart * categoriesLength;\r\n var endIndex: number = scrollEnd * categoriesLength;\r\n\r\n var startIndexRound: number = Math.floor(startIndex);\r\n var endIndexRound: number = Math.floor(endIndex);\r\n\r\n var maxValues: number = Math.floor(this.scrolling.scrollViewport.height / TornadoChart.CategoryMinHeight);\r\n\r\n if (scrollEnd - scrollStart < 1 && maxValues < endIndexRound - startIndexRound) {\r\n if (startIndex - startIndexRound > endIndex - endIndexRound) {\r\n startIndexRound++;\r\n }\r\n else {\r\n endIndex--;\r\n }\r\n }\r\n\r\n if (this.interactivityService) {\r\n this.interactivityService.applySelectionStateToData(tornadoChartDataView.dataPoints);\r\n this.interactivityService.applySelectionStateToData(tornadoChartDataView.highlightedDataPoints);\r\n }\r\n\r\n // Filter data according to the visible visual area\r\n tornadoChartDataView.categories = tornadoChartDataView.categories.slice(startIndexRound, endIndexRound);\r\n tornadoChartDataView.dataPoints = _.filter(tornadoChartDataView.dataPoints, (d: TornadoChartPoint) => d.categoryIndex >= startIndexRound && d.categoryIndex < endIndexRound);\r\n tornadoChartDataView.highlightedDataPoints = _.filter(tornadoChartDataView.highlightedDataPoints, (d: TornadoChartPoint) => d.categoryIndex >= startIndexRound && d.categoryIndex < endIndexRound);\r\n\r\n this.dataView = tornadoChartDataView;\r\n this.computeHeightColumn();\r\n this.renderMiddleSection();\r\n this.renderAxes();\r\n this.renderCategories();\r\n }\r\n\r\n private updateViewport(): void {\r\n var legendMargins: IViewport = this.legend.getMargins(),\r\n legendPosition: LegendPosition;\r\n\r\n legendPosition = LegendPosition[<string>this.dataView.legendObjectProperties[legendProps.position]];\r\n\r\n switch (legendPosition) {\r\n case LegendPosition.Top:\r\n case LegendPosition.TopCenter:\r\n case LegendPosition.Bottom:\r\n case LegendPosition.BottomCenter: {\r\n this.viewport.height -= legendMargins.height;\r\n\r\n break;\r\n }\r\n case LegendPosition.Left:\r\n case LegendPosition.LeftCenter:\r\n case LegendPosition.Right:\r\n case LegendPosition.RightCenter: {\r\n this.viewport.width -= legendMargins.width;\r\n\r\n break;\r\n }\r\n }\r\n }\r\n\r\n private computeHeightColumn(): void {\r\n var length: number = this.dataView.categories.length;\r\n this.heightColumn = (this.scrolling.scrollViewport.height - ((length - 1) * this.columnPadding)) / length;\r\n }\r\n\r\n private renderMiddleSection(): void {\r\n var tornadoChartDataView: TornadoChartDataView = this.dataView;\r\n this.calculateDataPoints(tornadoChartDataView.dataPoints);\r\n this.calculateDataPoints(tornadoChartDataView.highlightedDataPoints);\r\n var dataPointsWithHighlights: TornadoChartPoint[] = tornadoChartDataView.dataPoints.concat(tornadoChartDataView.highlightedDataPoints);\r\n this.renderColumns(dataPointsWithHighlights, tornadoChartDataView.series.length === 2);\r\n this.renderLabels(this.dataView.hasHighlights ? tornadoChartDataView.highlightedDataPoints : tornadoChartDataView.dataPoints, tornadoChartDataView.settings.labelSettings);\r\n }\r\n\r\n /**\r\n * Calculate the width, dx value and label info for every data point\r\n */\r\n private calculateDataPoints(dataPoints: TornadoChartPoint[]): void {\r\n var categoriesLength: number = this.dataView.categories.length;\r\n var settings: TornadoChartSettings = this.dataView.settings;\r\n var heightColumn = Math.max(this.heightColumn, 0);\r\n var py = heightColumn / 2;\r\n var pyHighlighted = heightColumn * TornadoChart.HighlightedShapeFactor / 2;\r\n var maxSeries: boolean = this.dataView.series.length === TornadoChart.MaxSeries;\r\n\r\n for (var i = 0; i < dataPoints.length; i++) {\r\n var dataPoint = dataPoints[i];\r\n\r\n var shiftToMiddle = i < categoriesLength && maxSeries;\r\n var shiftToRight: boolean = i > categoriesLength - 1;\r\n var widthOfColumn: number = this.getColumnWidth(dataPoint.value, dataPoint.minValue, dataPoint.maxValue, this.columnWidth);\r\n var dx: number = (this.columnWidth - widthOfColumn) * Number(shiftToMiddle) + this.columnWidth * Number(shiftToRight)/* - scrollBarWidth*/;\r\n dx = Math.max(dx, 0);\r\n\r\n var highlighted: boolean = this.dataView.hasHighlights && dataPoint.highlight;\r\n var highlightOffset: number = highlighted ? heightColumn * (1 - TornadoChart.HighlightedShapeFactor) / 2 : 0;\r\n var dy: number = (heightColumn + this.columnPadding) * (i % categoriesLength) + highlightOffset;\r\n\r\n var label: LabelData = this.getLabelData(\r\n dataPoint.value,\r\n dx,\r\n widthOfColumn,\r\n shiftToMiddle,\r\n dataPoint.formatString,\r\n settings);\r\n\r\n dataPoint.dx = dx;\r\n dataPoint.dy = dy;\r\n dataPoint.px = widthOfColumn / 2;\r\n dataPoint.py = highlighted ? pyHighlighted : py;\r\n dataPoint.angle = shiftToMiddle ? 180 : 0;\r\n dataPoint.width = widthOfColumn;\r\n dataPoint.height = highlighted ? heightColumn * TornadoChart.HighlightedShapeFactor : heightColumn;\r\n dataPoint.label = label;\r\n }\r\n }\r\n\r\n private renderColumns(columnsData: TornadoChartPoint[], selectSecondSeries: boolean = false): void {\r\n var hasSelection: boolean = this.interactivityService && this.interactivityService.hasSelection();\r\n\r\n var columnsSelection: D3.UpdateSelection = this.columns\r\n .selectAll(TornadoChart.Column.selector)\r\n .data(columnsData);\r\n\r\n columnsSelection\r\n .enter()\r\n .append(\"svg:rect\")\r\n .classed(TornadoChart.Column.class, true);\r\n\r\n columnsSelection\r\n .style(\"fill\", (p: TornadoChartPoint) => p.color)\r\n .style(\"fill-opacity\", (p: TornadoChartPoint) => ColumnUtil.getFillOpacity(\r\n p.selected,\r\n p.highlight,\r\n hasSelection,\r\n this.dataView.hasHighlights))\r\n .attr(\"transform\", (p: TornadoChartPoint) => SVGUtil.translateAndRotate(p.dx, p.dy, p.px, p.py, p.angle))\r\n .attr(\"height\", (p: TornadoChartPoint) => p.height)\r\n .attr(\"width\", (p: TornadoChartPoint) => p.width);\r\n\r\n columnsSelection\r\n .exit()\r\n .remove();\r\n\r\n var interactivityService = this.interactivityService;\r\n\r\n if (interactivityService) {\r\n interactivityService.applySelectionStateToData(columnsData);\r\n var behaviorOptions: TornadoBehaviorOptions = {\r\n columns: columnsSelection,\r\n clearCatcher: this.clearCatcher,\r\n interactivityService: this.interactivityService,\r\n };\r\n interactivityService.bind(columnsData, this.behavior, behaviorOptions);\r\n }\r\n\r\n this.renderTooltip(columnsSelection);\r\n }\r\n\r\n private renderTooltip(selection: D3.UpdateSelection): void {\r\n TooltipManager.addTooltip(selection, (tooltipEvent: TooltipEvent) => {\r\n return (<TornadoChartPoint>tooltipEvent.data).tooltipData;\r\n });\r\n }\r\n\r\n private getColumnWidth(value: number, minValue: number, maxValue: number, width: number): number {\r\n if (minValue === maxValue) {\r\n return width;\r\n }\r\n var columnWidth = width * (value - minValue) / (maxValue - minValue);\r\n\r\n // In case the user specifies a custom category axis end we limit the\r\n // column width to the maximum available width\r\n return Math.max(0, Math.min(width, columnWidth));\r\n }\r\n\r\n private getLabelData(\r\n value: number,\r\n dxColumn: number,\r\n columnWidth: number,\r\n isColumnPositionLeft: boolean,\r\n formatStringProp: string,\r\n settings?: TornadoChartSettings): LabelData {\r\n\r\n var dx: number,\r\n tornadoChartSettings: TornadoChartSettings = settings ? settings : this.dataView.settings,\r\n labelSettings: VisualDataLabelsSettings = tornadoChartSettings.labelSettings,\r\n fontSize: number = labelSettings.fontSize,\r\n color: string = labelSettings.labelColor;\r\n\r\n var maxOutsideLabelWidth = isColumnPositionLeft\r\n ? dxColumn - this.leftLabelMargin\r\n : this.allColumnsWidth - (dxColumn + columnWidth + this.leftLabelMargin);\r\n var maxLabelWidth = Math.max(maxOutsideLabelWidth, columnWidth - this.leftLabelMargin);\r\n\r\n var textProperties: TextProperties = {\r\n fontFamily: dataLabelUtils.StandardFontFamily,\r\n fontSize: PixelConverter.fromPoint(fontSize),\r\n text: tornadoChartSettings.getLabelValueFormatter(formatStringProp).format(value)\r\n };\r\n var valueAfterValueFormatter: string = TextMeasurementService.getTailoredTextOrDefault(textProperties, maxLabelWidth);\r\n var textDataAfterValueFormatter: TextData = TornadoChart.getTextData(valueAfterValueFormatter, this.textOptions, true, false, fontSize);\r\n\r\n if (columnWidth > textDataAfterValueFormatter.width + TornadoChart.LabelPadding) {\r\n dx = dxColumn + columnWidth / 2 - textDataAfterValueFormatter.width / 2;\r\n } else {\r\n if (isColumnPositionLeft) {\r\n dx = dxColumn - this.leftLabelMargin - textDataAfterValueFormatter.width;\r\n } else {\r\n dx = dxColumn + columnWidth + this.leftLabelMargin;\r\n }\r\n color = tornadoChartSettings.labelOutsideFillColor;\r\n }\r\n\r\n return {\r\n dx: dx,\r\n source: value,\r\n value: valueAfterValueFormatter,\r\n color: color\r\n };\r\n }\r\n\r\n private renderAxes(): void {\r\n var linesData: LineData[],\r\n axesSelection: D3.UpdateSelection,\r\n axesElements: D3.Selection = this.main\r\n .select(TornadoChart.Axes.selector)\r\n .selectAll(TornadoChart.Axis.selector);\r\n\r\n if (this.dataView.series.length !== TornadoChart.MaxSeries) {\r\n axesElements.remove();\r\n return;\r\n }\r\n\r\n linesData = this.generateAxesData();\r\n\r\n axesSelection = axesElements.data(linesData);\r\n\r\n axesSelection\r\n .enter()\r\n .append(\"svg:line\")\r\n .classed(TornadoChart.Axis.class, true);\r\n\r\n axesSelection\r\n .attr(\"x1\", (data: LineData) => data.x1)\r\n .attr(\"y1\", (data: LineData) => data.y1)\r\n .attr(\"x2\", (data: LineData) => data.x2)\r\n .attr(\"y2\", (data: LineData) => data.y2);\r\n\r\n axesSelection\r\n .exit()\r\n .remove();\r\n }\r\n\r\n private generateAxesData(): LineData[] {\r\n var x: number,\r\n y1: number,\r\n y2: number;\r\n\r\n x = this.allColumnsWidth / 2;\r\n y1 = 0;\r\n y2 = this.scrolling.scrollViewport.height;\r\n\r\n return [{\r\n x1: x,\r\n y1: y1,\r\n x2: x,\r\n y2: y2\r\n }];\r\n }\r\n\r\n private renderLabels(dataPoints: TornadoChartPoint[], labelsSettings: VisualDataLabelsSettings): void {\r\n var labelEnterSelection: D3.Selection,\r\n labelSelection: D3.UpdateSelection = this.main\r\n .select(TornadoChart.Labels.selector)\r\n .selectAll(TornadoChart.Label.selector)\r\n .data(_.filter(dataPoints, (p: TornadoChartPoint) => p.label.dx >= 0));\r\n\r\n // Check if labels can be displayed\r\n if (!labelsSettings.show || this.dataView.labelHeight >= this.heightColumn) {\r\n this.labels.selectAll(\"*\").remove();\r\n return;\r\n }\r\n\r\n var fontSizeInPx: string = PixelConverter.fromPoint(labelsSettings.fontSize);\r\n var labelYOffset: number = this.heightColumn / 2 + this.dataView.labelHeight / 2 - this.InnerTextHeightDelta;\r\n var categoriesLength: number = this.dataView.categories.length;\r\n\r\n labelEnterSelection = labelSelection\r\n .enter()\r\n .append(\"g\");\r\n\r\n labelEnterSelection\r\n .append(\"svg:title\")\r\n .classed(TornadoChart.LabelTitle.class, true);\r\n\r\n labelEnterSelection\r\n .append(\"svg:text\")\r\n .attr(\"dy\", dataLabelUtils.DefaultDy)\r\n .classed(TornadoChart.LabelText.class, true);\r\n\r\n labelSelection\r\n .attr(\"pointer-events\", \"none\")\r\n .classed(TornadoChart.Label.class, true);\r\n\r\n labelSelection\r\n .select(TornadoChart.LabelTitle.selector)\r\n .text((p: TornadoChartPoint) => p.label.source);\r\n\r\n labelSelection\r\n .attr(\"transform\", (p: TornadoChartPoint, index: number) => {\r\n var dy = (this.heightColumn + this.columnPadding) * (index % categoriesLength);\r\n return SVGUtil.translate(p.label.dx, dy + labelYOffset);\r\n });\r\n\r\n labelSelection\r\n .select(TornadoChart.LabelText.selector)\r\n .attr(\"fill\", (p: TornadoChartPoint) => p.label.color)\r\n .attr(\"font-size\", (p: TornadoChartPoint) => fontSizeInPx)\r\n .text((p: TornadoChartPoint) => p.label.value);\r\n\r\n labelSelection\r\n .exit()\r\n .remove();\r\n }\r\n\r\n private renderCategories(): void {\r\n var settings: TornadoChartSettings = this.dataView.settings,\r\n color: string = settings.categoriesFillColor,\r\n categoriesEnterSelection: D3.Selection,\r\n categoriesSelection: D3.UpdateSelection,\r\n categoryElements: D3.Selection = this.main\r\n .select(TornadoChart.Categories.selector)\r\n .selectAll(TornadoChart.Category.selector);\r\n\r\n if (!settings.showCategories) {\r\n categoryElements.remove();\r\n return;\r\n }\r\n\r\n categoriesSelection = categoryElements.data(this.dataView.categories);\r\n\r\n categoriesEnterSelection = categoriesSelection\r\n .enter()\r\n .append(\"g\");\r\n\r\n categoriesEnterSelection\r\n .append(\"svg:title\")\r\n .classed(TornadoChart.CategoryTitle.class, true);\r\n\r\n categoriesEnterSelection\r\n .append(\"svg:text\")\r\n .classed(TornadoChart.CategoryText.class, true);\r\n\r\n categoriesSelection\r\n .attr(\"transform\", (text: string, index: number) => {\r\n var shift: number = (this.heightColumn + this.columnPadding) * index + this.heightColumn / 2,\r\n textData: TextData = TornadoChart.getTextData(text, this.textOptions, false, true);\r\n\r\n shift = shift + textData.height / 2 - this.InnerTextHeightDelta;\r\n\r\n return SVGUtil.translate(0, shift);\r\n })\r\n .classed(TornadoChart.Category.class, true);\r\n\r\n categoriesSelection\r\n .select(TornadoChart.CategoryTitle.selector)\r\n .text((text: TextData) => text.text);\r\n\r\n categoriesSelection\r\n .select(TornadoChart.CategoryText.selector)\r\n .attr(\"fill\", color)\r\n .text((data: TextData) => this.dataView.settings.showCategories\r\n ? TextMeasurementService.getTailoredTextOrDefault(\r\n TornadoChart.getTextData(data.text, this.textOptions).textProperties, this.allLabelsWidth)\r\n : \"\");\r\n\r\n categoriesSelection\r\n .exit()\r\n .remove();\r\n }\r\n\r\n private renderLegend(): void {\r\n var legend = this.dataView.legend;\r\n if (!legend) {\r\n return;\r\n }\r\n var settings: TornadoChartSettings = this.dataView.settings;\r\n\r\n var legendData: LegendData = {\r\n title: legend.title,\r\n dataPoints: legend.dataPoints,\r\n fontSize: settings.legendFontSize,\r\n labelColor: settings.legendColor,\r\n };\r\n\r\n if (this.dataView.legendObjectProperties) {\r\n var position: string;\r\n\r\n LegendData.update(legendData, this.dataView.legendObjectProperties);\r\n\r\n position = <string>this.dataView.legendObjectProperties[legendProps.position];\r\n\r\n if (position) {\r\n this.legend.changeOrientation(LegendPosition[position]);\r\n }\r\n }\r\n\r\n // Draw the legend on a viewport with the original height and width\r\n var viewport: IViewport = {\r\n height: this.viewport.height + this.margin.top + this.margin.bottom,\r\n width: this.viewport.width + this.margin.left + this.margin.right,\r\n };\r\n this.legend.drawLegend(legendData, viewport);\r\n Legend.positionChartArea(this.root, this.legend);\r\n\r\n if (legendData.dataPoints.length > 0 && settings.showLegend)\r\n this.updateViewport();\r\n }\r\n\r\n public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstanceEnumeration {\r\n var enumeration = new ObjectEnumerationBuilder(),\r\n settings: TornadoChartSettings;\r\n\r\n if (!this.dataView ||\r\n !this.dataView.settings) {\r\n return [];\r\n }\r\n\r\n settings = this.dataView.settings;\r\n\r\n switch (options.objectName) {\r\n case \"dataPoint\": {\r\n this.enumerateDataPoint(enumeration);\r\n break;\r\n }\r\n case \"categoryAxis\": {\r\n this.enumerateCategoryAxis(enumeration);\r\n break;\r\n }\r\n case \"labels\": {\r\n var labelSettings = settings.labelSettings;\r\n var labels: VisualObjectInstance = {\r\n objectName: \"labels\",\r\n displayName: \"Labels\",\r\n selector: null,\r\n properties: {\r\n show: labelSettings.show,\r\n fontSize: labelSettings.fontSize,\r\n labelPrecision: labelSettings.precision,\r\n labelDisplayUnits: labelSettings.displayUnits,\r\n insideFill: labelSettings.labelColor,\r\n outsideFill: settings.labelOutsideFillColor\r\n }\r\n };\r\n\r\n enumeration.pushInstance(labels);\r\n break;\r\n }\r\n case \"legend\": {\r\n if (!this.dataView.hasDynamicSeries)\r\n return;\r\n\r\n var showTitle: boolean = true,\r\n titleText: string = \"\",\r\n legend: VisualObjectInstance;\r\n\r\n showTitle = DataViewObject.getValue<boolean>(\r\n this.dataView.legendObjectProperties,\r\n legendProps.showTitle,\r\n showTitle);\r\n\r\n titleText = DataViewObject.getValue<string>(\r\n this.dataView.legendObjectProperties,\r\n legendProps.titleText,\r\n titleText);\r\n\r\n legend = {\r\n objectName: \"legend\",\r\n displayName: \"Legend\",\r\n selector: null,\r\n properties: {\r\n show: settings.showLegend,\r\n position: LegendPosition[this.legend.getOrientation()],\r\n showTitle: showTitle,\r\n titleText: titleText,\r\n fontSize: settings.legendFontSize,\r\n labelColor: settings.legendColor,\r\n }\r\n };\r\n\r\n enumeration.pushInstance(legend);\r\n break;\r\n }\r\n case \"categories\": {\r\n var categories: VisualObjectInstance = {\r\n objectName: \"categories\",\r\n displayName: \"Categories\",\r\n selector: null,\r\n properties: {\r\n show: settings.showCategories,\r\n fill: settings.categoriesFillColor\r\n }\r\n };\r\n\r\n enumeration.pushInstance(categories);\r\n break;\r\n }\r\n }\r\n\r\n return enumeration.complete();\r\n }\r\n\r\n private enumerateDataPoint(enumeration: ObjectEnumerationBuilder): void {\r\n if (!this.dataView ||\r\n !this.dataView.series) {\r\n return;\r\n }\r\n\r\n var series: TornadoChartSeries[] = this.dataView.series;\r\n\r\n for (var i = 0, length = series.length; i < length; i++) {\r\n enumeration.pushInstance({\r\n objectName: \"dataPoint\",\r\n displayName: series[i].name,\r\n selector: ColorHelper.normalizeSelector(series[i].selectionId.getSelector(), false),\r\n properties: {\r\n fill: { solid: { color: series[i].fill } }\r\n }\r\n });\r\n }\r\n }\r\n\r\n private enumerateCategoryAxis(enumeration: ObjectEnumerationBuilder): void {\r\n if (!this.dataView || !this.dataView.series)\r\n return;\r\n\r\n var series: TornadoChartSeries[] = this.dataView.series;\r\n\r\n for (var i = 0, length = series.length; i < length; i++) {\r\n enumeration.pushInstance({\r\n objectName: \"categoryAxis\",\r\n displayName: series[i].name,\r\n selector: series[i].selectionId ? series[i].selectionId.getSelector() : null,\r\n properties: {\r\n end: series[i].categoryAxisEnd,\r\n }\r\n });\r\n }\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/CustomVisuals/visuals/tornadoChart/visual/tornadoChart.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 ClassAndSelector = jsCommon.CssConstants.ClassAndSelector;\r\n import createClassAndSelector = jsCommon.CssConstants.createClassAndSelector;\r\n import NewDataLabelUtils = powerbi.visuals.NewDataLabelUtils;\r\n import ISize = shapes.ISize;\r\n\r\n export const enum MekkoChartType {\r\n HundredPercentStackedColumn,\r\n }\r\n\r\n export interface MekkoColumnChartDrawInfo /*extends ColumnChartDrawInfo*/ {\r\n shapesSelection: D3.Selection;\r\n viewport: IViewport;\r\n axisOptions: MekkoColumnAxisOptions;\r\n\r\n labelDataPoints: MekkoLabelDataPoint[];\r\n }\r\n\r\n export interface MekkoLabelDataPointsGroup/* extends LabelDataPointsGroup */ {\r\n labelDataPoints: MekkoLabelDataPoint[];\r\n maxNumberOfLabels: number;\r\n }\r\n\r\n export interface MekkoLabelParentRect {\r\n /** The rectangle this data label belongs to */\r\n rect: IRect;\r\n /** The orientation of the parent rectangle */\r\n orientation: NewRectOrientation;\r\n /** Valid positions to place the label ordered by preference */\r\n validPositions: RectLabelPosition[];\r\n }\r\n\r\n export interface MekkoLabelDataPoint/* extends LabelDataPoint*/ {\r\n isParentRect?: boolean;\r\n /** Text to be displayed in the label */\r\n text: string;\r\n /** The measured size of the text */\r\n textSize: ISize;\r\n /** Is data label preferred? Preferred labels will be rendered first */\r\n isPreferred: boolean;\r\n /** Color to use for the data label if drawn inside */\r\n insideFill: string;\r\n /** Color to use for the data label if drawn outside */\r\n outsideFill: string;\r\n /** Whether or not the data label has been rendered */\r\n hasBeenRendered?: boolean;\r\n /** Whether the parent type is a rectangle, point or polygon */\r\n parentType: LabelDataPointParentType;\r\n /** The parent geometry for the data label */\r\n parentShape: MekkoLabelParentRect;//LabelParentRect | LabelParentPoint | LabelParentPolygon;\r\n /** The identity of the data point associated with the data label */\r\n identity: powerbi.visuals.SelectionId;\r\n /** The font size of the data point associated with the data label */\r\n fontSize?: number;\r\n /** Second row of text to be displayed in the label, for additional information */\r\n secondRowText?: string;\r\n /** The calculated weight of the data point associated with the data label */\r\n weight?: number;\r\n }\r\n\r\n export interface MekkoVisualRenderResult {\r\n dataPoints: SelectableDataPoint[];\r\n behaviorOptions: any;\r\n labelDataPoints: MekkoLabelDataPoint[];\r\n labelsAreNumeric: boolean;\r\n labelDataPointGroups?: MekkoLabelDataPointsGroup[];\r\n }\r\n\r\n export interface MekkoCalculateScaleAndDomainOptions extends CalculateScaleAndDomainOptions {\r\n }\r\n\r\n export interface MekkoConstructorOptions {\r\n chartType: MekkoChartType;\r\n isScrollable?: boolean;\r\n animator?: IGenericAnimator;\r\n cartesianSmallViewPortProperties?: CartesianSmallViewPortProperties;\r\n behavior?: IInteractiveBehavior;\r\n }\r\n\r\n export interface MekkoColumnChartData extends ColumnChartData {\r\n borderSettings: MekkoBorderSettings;\r\n categoriesWidth: number[];\r\n }\r\n\r\n export interface MekkoBorderSettings {\r\n show: boolean;\r\n color: any;\r\n width: number;\r\n maxWidth?: number;\r\n }\r\n\r\n export interface MekkoLabelSettings {\r\n maxPrecision: number;\r\n minPrecision: number;\r\n }\r\n\r\n export interface MekkoColumnAxisOptions extends ColumnAxisOptions {\r\n }\r\n\r\n export interface IMekkoColumnLayout extends IColumnLayout {\r\n shapeBorder?: {\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 shapeXAxis?: {\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 MekkoAxisRenderingOptions {\r\n axisLabels: ChartAxesLabels;\r\n legendMargin: number;\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 }\r\n\r\n export interface MekkoDataPoints {\r\n categoriesWidth: number[];\r\n series: ColumnChartSeries[];\r\n hasHighlights: boolean;\r\n hasDynamicSeries: boolean;\r\n }\r\n\r\n export interface MekkoLegendDataPoint extends LegendDataPoint {\r\n fontSize?: number;\r\n }\r\n\r\n export interface MekkoCreateAxisOptions extends CreateAxisOptions {\r\n formatString: string;\r\n is100Pct?: boolean;\r\n shouldClamp?: boolean;\r\n formatStringProp?: DataViewObjectPropertyIdentifier;\r\n }\r\n\r\n export interface MekkoColumnChartContext extends ColumnChartContext {\r\n height: number;\r\n width: number;\r\n duration: number;\r\n margin: IMargin;\r\n mainGraphicsContext: D3.Selection;\r\n labelGraphicsContext: 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 hostService: IVisualHostServices;\r\n isComboChart: boolean;\r\n }\r\n\r\n export class MekkoDataWrapper {\r\n private data: CartesianData;\r\n private isScalar: boolean;\r\n\r\n public constructor(columnChartData: CartesianData, isScalar: boolean) {\r\n this.data = columnChartData;\r\n this.isScalar = isScalar;\r\n }\r\n\r\n public lookupXValue(index: number, type: ValueType): any {\r\n debug.assertValue(this.data, 'this.data');\r\n\r\n var isDateTime: boolean = AxisHelper.isDateTime(type);\r\n if (isDateTime && this.isScalar) {\r\n return new Date(index);\r\n }\r\n\r\n var data = this.data;\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 else {\r\n var firstSeries = data.series[0];\r\n if (firstSeries) {\r\n var seriesValues = firstSeries.data;\r\n if (seriesValues) {\r\n if (this.data.hasHighlights) {\r\n index = index * 2;\r\n }\r\n var dataPoint = seriesValues[index];\r\n if (dataPoint) {\r\n if (isDateTime) {\r\n return new Date(dataPoint.categoryValue);\r\n }\r\n return dataPoint.categoryValue;\r\n }\r\n }\r\n }\r\n }\r\n\r\n return index;\r\n }\r\n }\r\n\r\n export class MekkoColumnChartStrategy implements IMekkoColumnChartStrategy {\r\n private static classes = {\r\n item: <ClassAndSelector>createClassAndSelector('column'),\r\n highlightItem: <ClassAndSelector>createClassAndSelector('highlightColumn')\r\n };\r\n\r\n private layout: IMekkoColumnLayout;\r\n private data: MekkoColumnChartData;\r\n private graphicsContext: MekkoColumnChartContext;\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\r\n private static validLabelPositions = [1];\r\n\r\n public setupVisualProps(columnChartProps: MekkoColumnChartContext): 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 }\r\n\r\n public setData(data: MekkoColumnChartData) {\r\n this.data = data;\r\n }\r\n\r\n private static 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): IValueFormatter {\r\n\r\n var formatter: IValueFormatter;\r\n if (dataType.dateTime) {\r\n if (isScalar) {\r\n var value = new Date(scaleDomain[0]);\r\n var 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 formatter = valueFormatter.create({ format: formatString, value: value, value2: value2, tickCount: bestTickCount });\r\n }\r\n else {\r\n if (getValueFn == null) {\r\n debug.assertFail('getValueFn must be supplied for ordinal datetime tickValues');\r\n }\r\n var minDate: Date = getValueFn(0, dataType);\r\n var maxDate: Date = getValueFn(scaleDomain.length - 1, dataType);\r\n formatter = valueFormatter.create({ format: formatString, value: minDate, value2: maxDate, tickCount: bestTickCount });\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 var domainMin = tickValues[1] - tickValues[0];\r\n var domainMax = 0; //force tickInterval to be used with display units\r\n formatter = valueFormatter.create({ format: formatString, value: domainMin, value2: domainMax, allowFormatBeautification: true });\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 private static formatAxisTickValues(\r\n axis: D3.Svg.Axis,\r\n tickValues: any[],\r\n formatter: IValueFormatter,\r\n dataType: ValueType,\r\n isScalar: boolean,\r\n getValueFn?: (index: number, type: ValueType) => any) {\r\n\r\n var formattedTickValues = [];\r\n if (formatter) {\r\n // getValueFn takes an ordinal axis index or builds DateTime from milliseconds, do not pass a numeric scalar value.\r\n if (getValueFn && !(dataType.numeric && isScalar)) {\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 axis.tickFormat(d => formatter.format(d));\r\n formattedTickValues = tickValues.map((d) => formatter.format(d));\r\n }\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 /**\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 private createAxis(options): IAxisProperties {\r\n var pixelSpan = options.pixelSpan,\r\n dataDomain = options.dataDomain,\r\n metaDataColumn = options.metaDataColumn,\r\n formatStringProp = options.formatStringProp,\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\r\n var formatString = valueFormatter.getFormatString(metaDataColumn, formatStringProp);\r\n var dataType: ValueType = AxisHelper.getCategoryValueType(metaDataColumn, isScalar);\r\n var isLogScaleAllowed = AxisHelper.isLogScalePossible(dataDomain, dataType);\r\n\r\n var scale = d3.scale.linear();\r\n var scaleDomain = [0, 1];\r\n var bestTickCount = dataDomain.length || 1;\r\n\r\n var borderWidth: number = MekkoColumnChart.getBorderWidth(options.borderSettings);\r\n var chartWidth = pixelSpan - borderWidth * (bestTickCount - 1);\r\n\r\n if (chartWidth < MekkoChart.MinOrdinalRectThickness) {\r\n chartWidth = MekkoChart.MinOrdinalRectThickness;\r\n }\r\n\r\n scale.domain(scaleDomain)\r\n .range([0, chartWidth]);\r\n var tickValues = dataDomain;\r\n\r\n var formatter = MekkoColumnChartStrategy.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\r\n // sets default orientation only, cartesianChart will fix y2 for comboChart\r\n // tickSize(pixelSpan) is used to create gridLines\r\n var 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(dataDomain);\r\n\r\n var formattedTickValues = [];\r\n if (metaDataColumn) {\r\n formattedTickValues = MekkoColumnChartStrategy.formatAxisTickValues(axis, tickValues, formatter, dataType, isScalar, getValueFn);\r\n }\r\n\r\n var 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 // When there is > 1 ticks then we need to +1 so that their widths don't overlap\r\n // Example: 2 ticks are drawn at 33.33% and 66.66%, their width needs to be 33.33% so they don't overlap.\r\n var labelAreaCount = tickValues.length > 1 ? tickValues.length + 1 : tickValues.length;\r\n xLabelMaxWidth = labelAreaCount > 1 ? pixelSpan / labelAreaCount : pixelSpan;\r\n xLabelMaxWidth = Math.max(1, xLabelMaxWidth - CartesianChart.TickLabelPadding * 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: false,//scaleResult.usingDefaultDomain,\r\n isLogScaleAllowed: isLogScaleAllowed\r\n };\r\n }\r\n\r\n private getCategoryAxis(\r\n data: MekkoColumnChartData,\r\n size: number,\r\n layout: CategoryLayout,\r\n isVertical: boolean,\r\n forcedXMin?: DataViewPropertyValue,\r\n forcedXMax?: DataViewPropertyValue,\r\n axisScaleType?: string): IAxisProperties {\r\n\r\n var categoryThickness = layout.categoryThickness;\r\n var isScalar: boolean = layout.isScalar;\r\n var outerPaddingRatio = layout.outerPaddingRatio;\r\n var dw = new MekkoDataWrapper(data, isScalar);\r\n var domain: number[] = [];\r\n\r\n if (data.series &&\r\n (data.series.length > 0) &&\r\n data.series[0].data &&\r\n (data.series[0].data.length > 0)\r\n ) {\r\n var domainDoubles = data.series[0].data.map((item: ColumnChartDataPoint) => {\r\n return item.originalPosition + (item.value / 2);\r\n });\r\n\r\n domain = domainDoubles.filter(function(item, pos) {\r\n return domainDoubles.indexOf(item) === pos;\r\n });\r\n }\r\n\r\n var axisProperties: IAxisProperties = this.createAxis({\r\n pixelSpan: size,\r\n dataDomain: domain,\r\n metaDataColumn: data.categoryMetadata,\r\n formatStringProp: 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) => {\r\n var domainIndex = domain.indexOf(index);\r\n var value = dw.lookupXValue(domainIndex, type);\r\n return value;\r\n },\r\n scaleType: axisScaleType,\r\n borderSettings: data.borderSettings\r\n });\r\n // intentionally updating the input layout by ref\r\n layout.categoryThickness = axisProperties.categoryThickness;\r\n return axisProperties;\r\n }\r\n\r\n public setXScale(is100Pct: boolean, forcedTickCount?: number, forcedXDomain?: any[], axisScaleType?: string): IAxisProperties {\r\n var width = this.width;\r\n var 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 var props = this.xProps = this.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\r\n return props;\r\n }\r\n\r\n public setYScale(is100Pct: boolean, forcedTickCount?: number, forcedYDomain?: any[], axisScaleType?: string): IAxisProperties {\r\n var height = this.viewportHeight;\r\n var valueDomain = StackedUtil.calcValueDomain(this.data.series, is100Pct);\r\n var valueDomainArr = [valueDomain.min, valueDomain.max];\r\n var combinedDomain = AxisHelper.combineDomain(forcedYDomain, valueDomainArr);\r\n var shouldClamp = AxisHelper.scaleShouldClamp(combinedDomain, valueDomainArr);\r\n var metadataColumn = this.data.valuesMetadata[0];\r\n var formatString = is100Pct ?\r\n this.graphicsContext.hostService.getLocalizedString('Percentage')\r\n : valueFormatter.getFormatString(metadataColumn, columnChartProps.general.formatString);\r\n\r\n var mekkoMekkoCreateAxisOptions: MekkoCreateAxisOptions = {\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: 0,\r\n axisPrecision: 0,\r\n is100Pct: is100Pct,\r\n shouldClamp: shouldClamp,\r\n formatStringProp: undefined,\r\n };\r\n\r\n this.yProps = AxisHelper.createAxis(mekkoMekkoCreateAxisOptions);\r\n return this.yProps;\r\n }\r\n\r\n public drawColumns(useAnimation: boolean): MekkoColumnChartDrawInfo {\r\n var data = this.data;\r\n debug.assertValue(data, 'data should not be null or undefined');\r\n this.columnsCenters = null; // invalidate the columnsCenters so that will be calculated again\r\n\r\n var axisOptions: MekkoColumnAxisOptions = {\r\n columnWidth: 0,\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 var stackedColumnLayout = this.layout = MekkoColumnChartStrategy.getLayout(data, axisOptions);\r\n //var dataLabelSettings = data.labelSettings;\r\n var labelDataPoints: MekkoLabelDataPoint[] = this.createMekkoLabelDataPoints();\r\n var result: ColumnChartAnimationResult;\r\n var shapes: D3.UpdateSelection;\r\n var 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: MekkoColumnChartStrategy.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 = MekkoColumnChartStrategy.drawDefaultShapes(data,\r\n series,\r\n stackedColumnLayout,\r\n MekkoColumnChartStrategy.classes.item,\r\n !this.animator,\r\n this.interactivityService && this.interactivityService.hasSelection());\r\n }\r\n\r\n ColumnUtil.applyInteractivity(shapes, this.graphicsContext.onDragStart);\r\n\r\n return {\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 private static drawDefaultShapes(data: MekkoColumnChartData,\r\n series: D3.UpdateSelection,\r\n layout: IMekkoColumnLayout,\r\n itemCS: ClassAndSelector,\r\n filterZeros: boolean,\r\n 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\r\n var rectName: string = 'rect';\r\n filterZeros = false;\r\n\r\n var dataSelector: (d: ColumnChartSeries) => any[];\r\n if (filterZeros) {\r\n dataSelector = (d: ColumnChartSeries) => {\r\n var 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 var shapeSelection = series.selectAll(itemCS.selector);\r\n var 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\", (d: ColumnChartDataPoint) => d.color)\r\n .style(\"fill-opacity\", (d: ColumnChartDataPoint) => ColumnUtil.getFillOpacity(d.selected, d.highlight, hasSelection, data.hasHighlights))\r\n .attr(layout.shapeLayout);\r\n\r\n shapes\r\n .exit()\r\n .remove();\r\n\r\n var borderSelection = series.selectAll(MekkoColumnChart.BorderClass.selector);\r\n var borders = borderSelection.data(dataSelector, (d: ColumnChartDataPoint) => d.key);\r\n\r\n var borderColor = MekkoColumnChart.getBorderColor(data.borderSettings);\r\n\r\n borders.enter()\r\n .append(rectName)\r\n .classed(MekkoColumnChart.BorderClass.class, true);\r\n\r\n borders\r\n .style(\"fill\", (d: ColumnChartDataPoint) => borderColor)\r\n .style(\"fill-opacity\", (d: ColumnChartDataPoint) => {\r\n return data.hasHighlights ? ColumnUtil.DimmedOpacity : ColumnUtil.DefaultOpacity;\r\n })\r\n .attr(layout.shapeBorder);\r\n\r\n borders\r\n .exit()\r\n .remove();\r\n\r\n return shapes;\r\n }\r\n\r\n public selectColumn(selectedColumnIndex: number, lastSelectedColumnIndex: number): void {\r\n ColumnUtil.setChosenColumnOpacity(this.graphicsContext.mainGraphicsContext, MekkoColumnChartStrategy.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 var 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 var xScaleOffset = 0;\r\n if (!this.categoryLayout.isScalar) {\r\n xScaleOffset = categoryWidth / 2;\r\n }\r\n var firstSeries = this.data.series[0];\r\n if (firstSeries &&\r\n firstSeries.data) {\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 }\r\n return this.columnsCenters;\r\n }\r\n\r\n private moveHandle(selectedColumnIndex: number) {\r\n var columnCenters = this.getColumnsCenters();\r\n var x = columnCenters[selectedColumnIndex];\r\n\r\n if (!this.columnSelectionLineHandle) {\r\n var handle = this.columnSelectionLineHandle = this.graphicsContext.mainGraphicsContext.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 var 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: MekkoColumnChartData, axisOptions: MekkoColumnAxisOptions): IMekkoColumnLayout {\r\n var xScale = axisOptions.xScale;\r\n var yScale = axisOptions.yScale;\r\n var scaledY0 = yScale(0);\r\n var scaledX0 = xScale(0);\r\n\r\n var borderWidth: number = MekkoColumnChart.getBorderWidth(data.borderSettings);\r\n\r\n var columnWidthScale = (d: ColumnChartDataPoint) => {\r\n var value: number = AxisHelper.diffScaled(xScale, d.value, 0);\r\n return value;\r\n };\r\n\r\n var columnStart = (d: ColumnChartDataPoint) => {\r\n var value: number = scaledX0 +\r\n AxisHelper.diffScaled(xScale, d.originalPosition, 0) +\r\n borderWidth * d.categoryIndex;\r\n return value;\r\n };\r\n\r\n var borderStart = (d: ColumnChartDataPoint) => {\r\n var value: number = scaledX0 +\r\n AxisHelper.diffScaled(xScale, d.originalPosition, 0) +\r\n AxisHelper.diffScaled(xScale, d.value, 0) +\r\n borderWidth * d.categoryIndex;\r\n\r\n return value;\r\n };\r\n\r\n return {\r\n shapeLayout: {\r\n width: columnWidthScale,\r\n x: columnStart,\r\n y: (d: ColumnChartDataPoint) => scaledY0 + AxisHelper.diffScaled(yScale, d.position, 0),\r\n height: (d: ColumnChartDataPoint) => StackedUtil.getSize(yScale, d.valueAbsolute)\r\n },\r\n shapeBorder: {\r\n width: (d: ColumnChartDataPoint) => borderWidth,\r\n x: borderStart,\r\n y: (d: ColumnChartDataPoint) => scaledY0 + AxisHelper.diffScaled(yScale, d.position, 0),\r\n height: (d: ColumnChartDataPoint) => StackedUtil.getSize(yScale, d.valueAbsolute)\r\n },\r\n shapeLayoutWithoutHighlights: {\r\n width: columnWidthScale,\r\n x: columnStart,\r\n y: (d: ColumnChartDataPoint) => scaledY0 + AxisHelper.diffScaled(yScale, d.position, 0),\r\n height: (d: ColumnChartDataPoint) => StackedUtil.getSize(yScale, d.originalValueAbsolute)\r\n },\r\n zeroShapeLayout: {\r\n width: columnWidthScale,\r\n x: columnStart,\r\n y: (d: ColumnChartDataPoint) => scaledY0 + AxisHelper.diffScaled(yScale, d.position, 0) + StackedUtil.getSize(yScale, d.valueAbsolute),\r\n height: (d: ColumnChartDataPoint) => 0\r\n },\r\n shapeXAxis: {\r\n width: columnWidthScale,\r\n x: columnStart,\r\n y: (d: ColumnChartDataPoint) => scaledY0 + AxisHelper.diffScaled(yScale, d.position, 0),\r\n height: (d: ColumnChartDataPoint) => StackedUtil.getSize(yScale, d.valueAbsolute)\r\n },\r\n };\r\n }\r\n\r\n private createMekkoLabelDataPoints(): MekkoLabelDataPoint[] {\r\n var labelDataPoints: MekkoLabelDataPoint[] = [];\r\n var data = this.data;\r\n var series = data.series;\r\n var formattersCache = NewDataLabelUtils.createColumnFormatterCacheManager();\r\n var shapeLayout = this.layout.shapeLayout;\r\n\r\n for (var i: number = 0, ilen = series.length; i < ilen; i++) {\r\n var currentSeries = series[i];\r\n var labelSettings = currentSeries.labelSettings ? currentSeries.labelSettings : data.labelSettings;\r\n\r\n if (!labelSettings.show) {\r\n continue;\r\n }\r\n\r\n if (!currentSeries.data) {\r\n continue;\r\n }\r\n\r\n var axisFormatter: number = NewDataLabelUtils.getDisplayUnitValueFromAxisFormatter(this.yProps.formatter, labelSettings);\r\n\r\n for (var j: number = 0; j < currentSeries.data.length; j++) {\r\n var dataPoint: ColumnChartDataPoint = currentSeries.data[j];\r\n if ((data.hasHighlights && !dataPoint.highlight) || dataPoint.value == null) {\r\n continue;\r\n }\r\n\r\n // Calculate parent rectangle\r\n var 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 var formatString = null;\r\n var value: number = dataPoint.valueOriginal;\r\n\r\n if (!labelSettings.displayUnits) {\r\n formatString = NewDataLabelUtils.hundredPercentFormat;\r\n value = dataPoint.valueAbsolute;\r\n }\r\n\r\n var formatter = formattersCache.getOrCreate(formatString, labelSettings, axisFormatter);\r\n var text = NewDataLabelUtils.getLabelFormattedText(formatter.format(value));\r\n\r\n // Calculate text size\r\n var properties: 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 var textWidth = TextMeasurementService.measureSvgTextWidth(properties);\r\n var 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.defaultLabelColor,\r\n insideFill: labelSettings.labelColor ? labelSettings.labelColor : NewDataLabelUtils.defaultInsideLabelColor,\r\n isParentRect: true,\r\n parentShape: {\r\n rect: parentRect,\r\n orientation: 1,\r\n validPositions: MekkoColumnChartStrategy.validLabelPositions,\r\n },\r\n identity: dataPoint.identity,\r\n parentType: 1,//LabelDataPointParentType.Rectangle,\r\n });\r\n }\r\n }\r\n\r\n return labelDataPoints;\r\n }\r\n }\r\n\r\n export interface MekkoChartSettings {\r\n columnBorder: MekkoBorderSettings;\r\n labelSettings: MekkoLabelSettings;\r\n }\r\n\r\n /**\r\n * Renders a data series as a cartestian visual.\r\n */\r\n export class MekkoChart implements IVisual {\r\n public static capabilities: VisualCapabilities = {\r\n dataRoles: [\r\n {\r\n name: 'Category',\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: 'Category',\r\n }, {\r\n name: 'Series',\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: 'Series',\r\n }, {\r\n name: 'Y',\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: 'Y Axis',\r\n }, {\r\n name: 'Width',\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: 'Axis width',\r\n }\r\n ],\r\n objects: {\r\n columnBorder: {\r\n displayName: 'Column Border',\r\n properties: {\r\n show: {\r\n displayName: data.createDisplayNameGetter('Visual_Show'),\r\n type: { bool: true }\r\n },\r\n color: {\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 width: {\r\n displayName: 'Width',\r\n type: { numeric: 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: {\r\n displayName: data.createDisplayNameGetter('Visual_Show'),\r\n type: { bool: true }\r\n },\r\n showSeries: {\r\n displayName: data.createDisplayNameGetter('Visual_Show'),\r\n type: { bool: true }\r\n },\r\n color: {\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 labelDisplayUnits: {\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 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 suppressFormatPainterCopy: true\r\n },\r\n showAll: {\r\n displayName: data.createDisplayNameGetter('Visual_ShowAll'),\r\n type: { bool: true }\r\n },\r\n fontSize: {\r\n displayName: data.createDisplayNameGetter('Visual_TextSize'),\r\n type: { formatting: { fontSize: true } }\r\n },\r\n },\r\n },\r\n legend: {\r\n displayName: data.createDisplayNameGetter('Visual_Legend'),\r\n properties: {\r\n show: {\r\n displayName: data.createDisplayNameGetter('Visual_Show'),\r\n type: { bool: true }\r\n },\r\n position: {\r\n displayName: data.createDisplayNameGetter('Visual_LegendPosition'),\r\n type: { formatting: { legendPosition: true } }\r\n },\r\n showTitle: {\r\n displayName: data.createDisplayNameGetter('Visual_LegendShowTitle'),\r\n type: { bool: true }\r\n },\r\n titleText: {\r\n displayName: 'Title text',\r\n type: { text: true }\r\n },\r\n fontSize: {\r\n displayName: 'Text size',\r\n type: { formatting: { fontSize: true } }\r\n },\r\n }\r\n },\r\n categoryAxis: {\r\n displayName: data.createDisplayNameGetter('Visual_XAxis'),\r\n properties: {\r\n show: {\r\n displayName: data.createDisplayNameGetter('Visual_Show'),\r\n type: { bool: true }\r\n },\r\n position: {\r\n displayName: data.createDisplayNameGetter('Visual_YAxis_Position'),\r\n type: { formatting: { yAxisPosition: true } }\r\n },\r\n axisScale: {\r\n displayName: data.createDisplayNameGetter('Visual_Axis_Scale'),\r\n type: { formatting: { axisScale: true } }\r\n },\r\n /*start: {\r\n displayName: data.createDisplayNameGetter('Visual_Axis_Start'),\r\n type: { numeric: true }\r\n },\r\n end: {\r\n displayName: data.createDisplayNameGetter('Visual_Axis_End'),\r\n type: { numeric: true }\r\n },*/\r\n axisType: {\r\n displayName: data.createDisplayNameGetter('Visual_Axis_Type'),\r\n type: { formatting: { axisType: true } }\r\n },\r\n showAxisTitle: {\r\n displayName: data.createDisplayNameGetter('Visual_Axis_Title'),\r\n type: { bool: true }\r\n },\r\n axisStyle: {\r\n displayName: data.createDisplayNameGetter('Visual_Axis_Style'),\r\n type: { formatting: { axisStyle: true } }\r\n },\r\n labelColor: {\r\n displayName: data.createDisplayNameGetter('Visual_Axis_LabelColor'),\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 }\r\n },\r\n valueAxis: {\r\n displayName: data.createDisplayNameGetter('Visual_YAxis'),\r\n properties: {\r\n show: {\r\n displayName: data.createDisplayNameGetter('Visual_Show'),\r\n type: { bool: true }\r\n },\r\n position: {\r\n displayName: data.createDisplayNameGetter('Visual_YAxis_Position'),\r\n type: { formatting: { yAxisPosition: true } }\r\n },\r\n axisScale: {\r\n displayName: data.createDisplayNameGetter('Visual_Axis_Scale'),\r\n type: { formatting: { axisScale: true } }\r\n },\r\n /*start: {\r\n displayName: data.createDisplayNameGetter('Visual_Axis_Start'),\r\n type: { numeric: true }\r\n },\r\n end: {\r\n displayName: data.createDisplayNameGetter('Visual_Axis_End'),\r\n type: { numeric: true }\r\n },*/\r\n intersection: {\r\n displayName: data.createDisplayNameGetter('Visual_Axis_Intersection'),\r\n type: { numeric: true }\r\n },\r\n showAxisTitle: {\r\n displayName: data.createDisplayNameGetter('Visual_Axis_Title'),\r\n type: { bool: true }\r\n },\r\n axisStyle: {\r\n displayName: data.createDisplayNameGetter('Visual_Axis_Style'),\r\n type: { formatting: { axisStyle: true } }\r\n },\r\n labelColor: {\r\n displayName: data.createDisplayNameGetter('Visual_Axis_LabelColor'),\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\r\n }\r\n },\r\n dataPoint: {\r\n displayName: data.createDisplayNameGetter('Visual_DataPoint'),\r\n properties: {\r\n defaultColor: {\r\n displayName: data.createDisplayNameGetter('Visual_DefaultColor'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n showAllDataPoints: {\r\n displayName: data.createDisplayNameGetter('Visual_DataPoint_Show_All'),\r\n type: { bool: true }\r\n },\r\n fill: {\r\n displayName: data.createDisplayNameGetter('Visual_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: ['Category'],\r\n },\r\n },\r\n }\r\n }\r\n },\r\n\r\n },\r\n dataViewMappings: [{\r\n conditions: [\r\n { 'Category': { min: 0, max: 1 }, 'Series': { min: 0, max: 1 }, 'Y': { min: 0, max: 1 }, 'Width': { min: 0, 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' } }, { for: { in: 'Width' } }],\r\n dataReductionAlgorithm: { top: {} }\r\n }\r\n },\r\n rowCount: { preferred: { min: 1, max: 1 }, supported: { min: 0 } }\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 private static properties = {\r\n general: {\r\n formatString: <DataViewObjectPropertyIdentifier>{\r\n objectName: \"general\",\r\n propertyName: \"formatString\"\r\n }\r\n },\r\n columnBorder: {\r\n show: <DataViewObjectPropertyIdentifier>{ objectName: 'columnBorder', propertyName: 'show', },\r\n color: <DataViewObjectPropertyIdentifier>{ objectName: 'columnBorder', propertyName: 'color' },\r\n width: <DataViewObjectPropertyIdentifier>{ objectName: 'columnBorder', propertyName: 'width' },\r\n },\r\n };\r\n\r\n public static DefaultSettings: MekkoChartSettings = {\r\n columnBorder: {\r\n show: true,\r\n color: '#fff',\r\n width: 2,\r\n maxWidth: 5,\r\n },\r\n labelSettings: {\r\n maxPrecision: 4,\r\n minPrecision: 0,\r\n }\r\n };\r\n\r\n private static getTextProperties(fontSize: number = MekkoChart.FontSize): TextProperties {\r\n return {\r\n fontFamily: 'wf_segoe-ui_normal',\r\n fontSize: jsCommon.PixelConverter.toString(fontSize),\r\n };\r\n }\r\n\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;\r\n\r\n private static ClassName = 'cartesianChart';\r\n private static AxisGraphicsContextClassName = 'axisGraphicsContext';\r\n private static MaxMarginFactor = 0.25;\r\n private static MinBottomMargin = 50;\r\n private static LeftPadding = 10;\r\n private static RightPadding = 10;\r\n private static BottomPadding = 16;\r\n private static YAxisLabelPadding = 20;\r\n private static XAxisLabelPadding = 20;\r\n private static TickPaddingY = 10;\r\n private static TickPaddingRotatedX = 5;\r\n private static FontSize = 11;\r\n\r\n public static MaxNumberOfLabels = 100;\r\n\r\n private static MinWidth: number = 100;\r\n private static MinHeight: number = 100;\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 element: JQuery;\r\n private svg: D3.Selection;\r\n private clearCatcher: D3.Selection;\r\n private margin: IMargin;\r\n private type: MekkoChartType;\r\n private hostServices: IVisualHostServices;\r\n private layers: IMekkoColumnChartVisual[];\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\r\n private borderObjectProperties: DataViewObject;\r\n private legendObjectProperties: DataViewObject;\r\n private categoryAxisProperties: DataViewObject;\r\n\r\n private valueAxisProperties: DataViewObject;\r\n private cartesianSmallViewPortProperties: CartesianSmallViewPortProperties;\r\n private interactivityService: IInteractivityService;\r\n private behavior: IInteractiveBehavior;\r\n private y2AxisExists: boolean;\r\n private categoryAxisHasUnitType: boolean;\r\n private valueAxisHasUnitType: boolean;\r\n private hasCategoryAxis: boolean;\r\n private yAxisIsCategorical: boolean;\r\n private secValueAxisHasUnitType: boolean;\r\n private axes: CartesianAxisProperties;\r\n private yAxisOrientation: string;\r\n private bottomMarginLimit: number;\r\n private leftRightMarginLimit: number;\r\n private sharedColorPalette: SharedColorPalette;\r\n\r\n public animator: IGenericAnimator;\r\n\r\n // Scrollbar related\r\n private isScrollable: boolean;\r\n private scrollY: boolean;\r\n private scrollX: boolean;\r\n private isXScrollBarVisible: boolean;\r\n private isYScrollBarVisible: boolean;\r\n private svgScrollable: D3.Selection;\r\n private axisGraphicsContextScrollable: D3.Selection;\r\n private labelGraphicsContextScrollable: D3.Selection;\r\n private brushGraphicsContext: D3.Selection;\r\n private brush: D3.Svg.Brush;\r\n private static ScrollBarWidth = 10;\r\n // TODO: Remove onDataChanged & onResizing once all visuals have implemented update.\r\n private dataViews: DataView[];\r\n private currentViewport: IViewport;\r\n\r\n constructor(options: MekkoConstructorOptions) {\r\n this.isScrollable = false;\r\n if (options) {\r\n this.type = options.chartType;\r\n if (options.isScrollable)\r\n this.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 } else {\r\n this.behavior = new MekkoChartBehavior([new ColumnChartWebBehavior()]);\r\n }\r\n }\r\n\r\n public init(options: VisualInitOptions) {\r\n this.visualInitOptions = options;\r\n this.layers = [];\r\n\r\n var element = this.element = options.element;\r\n var viewport = this.currentViewport = options.viewport;\r\n this.hostServices = options.host;\r\n this.brush = d3.svg.brush();\r\n element.addClass(MekkoChart.ClassName);\r\n this.margin = {\r\n top: 1,\r\n right: 1,\r\n bottom: 1,\r\n left: 1\r\n };\r\n this.yAxisOrientation = yAxisPosition.left;\r\n this.adjustMargins(viewport);\r\n\r\n this.sharedColorPalette = new SharedColorPalette(options.style.colorPalette.dataColors);\r\n\r\n var showLinesOnX = true;\r\n var showLinesOnY = true;\r\n\r\n var svg = this.svg = d3.select(element.get(0)).append('svg');\r\n svg.style('position', 'absolute');\r\n\r\n var axisGraphicsContext = this.axisGraphicsContext = svg.append('g')\r\n .classed(MekkoChart.AxisGraphicsContextClassName, true);\r\n\r\n this.svgScrollable = svg.append('svg')\r\n .classed('svgScrollable', true)\r\n .style('overflow', 'hidden');\r\n\r\n var axisGraphicsContextScrollable = this.axisGraphicsContextScrollable = this.svgScrollable.append('g')\r\n .classed(MekkoChart.AxisGraphicsContextClassName, true);\r\n\r\n this.labelGraphicsContextScrollable = this.svgScrollable.append('g')\r\n .classed(NewDataLabelUtils.labelGraphicsContextClass.class, true);\r\n\r\n if (this.behavior) {\r\n this.clearCatcher = appendClearCatcher(this.axisGraphicsContextScrollable);\r\n }\r\n\r\n var axisGroup = showLinesOnX ? axisGraphicsContextScrollable : axisGraphicsContext;\r\n\r\n this.xAxisGraphicsContext = showLinesOnX ? axisGraphicsContext.append('g').attr('class', 'x axis') : axisGraphicsContextScrollable.append('g').attr('class', 'x axis');\r\n this.y1AxisGraphicsContext = axisGroup.append('g').attr('class', 'y axis');\r\n this.y2AxisGraphicsContext = axisGroup.append('g').attr('class', 'y axis');\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 if (this.behavior) {\r\n this.interactivityService = createInteractivityService(this.hostServices);\r\n }\r\n this.legend = createLegend(\r\n element,\r\n options.interactivity && options.interactivity.isInteractiveLegend,\r\n this.interactivityService,\r\n true);\r\n }\r\n\r\n private renderAxesLabels(options: MekkoAxisRenderingOptions): 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 var margin = this.margin;\r\n var width = options.viewport.width - (margin.left + margin.right);\r\n var height = options.viewport.height;\r\n var fontSize = MekkoChart.FontSize;\r\n var heightOffset = fontSize;\r\n\r\n var showOnRight = this.yAxisOrientation === yAxisPosition.right;\r\n\r\n if (!options.hideXAxisTitle) {\r\n var 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 var 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 }\r\n\r\n if (!options.hideYAxisTitle) {\r\n var 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 var text = d3.select(this);\r\n text.attr({\r\n \"class\": \"yAxisLabel\",\r\n \"transform\": \"rotate(-90)\",\r\n \"y\": showOnRight ? width + margin.right - fontSize : -margin.left,\r\n \"x\": -((height - margin.top - options.legendMargin) / 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 }\r\n\r\n if (!options.hideY2AxisTitle && options.axisLabels.y2) {\r\n var 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 var 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 - fontSize,\r\n \"x\": -((height - margin.top - options.legendMargin) / 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 }\r\n }\r\n\r\n private adjustMargins(viewport: IViewport): void {\r\n var margin = this.margin;\r\n\r\n var width = viewport.width - (margin.left + margin.right);\r\n var height = viewport.height - (margin.top + margin.bottom);\r\n\r\n // Adjust margins if ticks are not going to be shown on either axis\r\n var xAxis = this.element.find('.x.axis');\r\n\r\n if (AxisHelper.getRecommendedNumberOfTicksForXAxis(width) === 0\r\n && AxisHelper.getRecommendedNumberOfTicksForYAxis(height) === 0) {\r\n this.margin = {\r\n top: 0,\r\n right: 0,\r\n bottom: 0,\r\n left: 0\r\n };\r\n xAxis.hide();\r\n } else {\r\n xAxis.show();\r\n }\r\n }\r\n\r\n private translateAxes(viewport: IViewport): void {\r\n this.adjustMargins(viewport);\r\n var margin = this.margin;\r\n\r\n var width = viewport.width - (margin.left + margin.right);\r\n var height = viewport.height - (margin.top + margin.bottom);\r\n\r\n var showY1OnRight = this.yAxisOrientation === yAxisPosition.right;\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.svg.attr({\r\n 'width': viewport.width,\r\n 'height': viewport.height\r\n });\r\n\r\n this.svg.style('top', this.legend.isVisible() ? this.legend.getMargins().height + 'px' : 0);\r\n\r\n this.svgScrollable.attr({\r\n 'width': viewport.width,\r\n 'height': viewport.height\r\n });\r\n\r\n this.svgScrollable.attr({\r\n 'x': 0\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.labelGraphicsContextScrollable.attr('transform', SVGUtil.translate(margin.left, margin.top));\r\n\r\n if (this.isXScrollBarVisible) {\r\n this.svgScrollable.attr({\r\n 'x': this.margin.left\r\n });\r\n this.axisGraphicsContextScrollable.attr('transform', SVGUtil.translate(0, margin.top));\r\n this.labelGraphicsContextScrollable.attr('transform', SVGUtil.translate(0, margin.top));\r\n this.svgScrollable.attr('width', width);\r\n this.svg.attr('width', viewport.width)\r\n .attr('height', viewport.height + MekkoChart.ScrollBarWidth);\r\n }\r\n else if (this.isYScrollBarVisible) {\r\n this.svgScrollable.attr('height', height + margin.top);\r\n this.svg.attr('width', viewport.width + MekkoChart.ScrollBarWidth)\r\n .attr('height', viewport.height);\r\n }\r\n }\r\n\r\n public static getIsScalar(objects: DataViewObjects, propertyId: DataViewObjectPropertyIdentifier, type: ValueType): boolean {\r\n var axisTypeValue = DataViewObjects.getValue(objects, propertyId);\r\n\r\n if (!objects || axisTypeValue === undefined) {\r\n // If we don't have anything set (Auto), show charts as Scalar if the category type is numeric or time.\r\n // If we have the property, it will override the type.\r\n return !AxisHelper.isOrdinal(type);\r\n }\r\n\r\n // also checking type here to be in sync with AxisHelper, which ignores scalar if the type is non-numeric.\r\n return (axisTypeValue === axisType.scalar) && !AxisHelper.isOrdinal(type);\r\n }\r\n\r\n private populateObjectProperties(dataViews: DataView[]) {\r\n if (dataViews && dataViews.length > 0) {\r\n var dataViewMetadata = dataViews[0].metadata;\r\n\r\n if (dataViewMetadata) {\r\n this.legendObjectProperties = DataViewObjects.getObject(dataViewMetadata.objects, 'legend', {});\r\n this.borderObjectProperties = DataViewObjects.getObject(dataViewMetadata.objects, 'columnBorder', {});\r\n }\r\n else {\r\n this.legendObjectProperties = {};\r\n this.borderObjectProperties = {};\r\n }\r\n\r\n this.categoryAxisProperties = CartesianHelper.getCategoryAxisProperties(dataViewMetadata);\r\n this.valueAxisProperties = CartesianHelper.getValueAxisProperties(dataViewMetadata);\r\n\r\n if (dataViewMetadata &&\r\n dataViewMetadata.objects) {\r\n var categoryAxis = dataViewMetadata.objects['categoryAxis'];\r\n var valueAxis = dataViewMetadata.objects['valueAxis'];\r\n\r\n if (categoryAxis) {\r\n this.categoryAxisProperties['showBorder'] = categoryAxis['showBorder'];\r\n this.categoryAxisProperties['fontSize'] = categoryAxis['fontSize'];\r\n }\r\n\r\n if (valueAxis) {\r\n this.valueAxisProperties['fontSize'] = valueAxis['fontSize'];\r\n }\r\n }\r\n var axisPosition = this.valueAxisProperties['position'];\r\n this.yAxisOrientation = axisPosition ? axisPosition.toString() : yAxisPosition.left;\r\n }\r\n }\r\n\r\n public update(options: VisualUpdateOptions) {\r\n debug.assertValue(options, 'options');\r\n\r\n var dataViews = this.dataViews = options.dataViews;\r\n this.currentViewport = options.viewport;\r\n\r\n if (!dataViews) {\r\n this.clearViewport();\r\n return;\r\n }\r\n\r\n if ((this.currentViewport.width < MekkoChart.MinWidth) ||\r\n (this.currentViewport.height < MekkoChart.MinHeight)) {\r\n this.clearViewport();\r\n return;\r\n }\r\n\r\n if (this.layers.length === 0) {\r\n // Lazily instantiate the chart layers on the first data load.\r\n this.layers = this.createAndInitLayers(dataViews);\r\n\r\n debug.assert(this.layers.length > 0, 'createAndInitLayers should update the layers.');\r\n }\r\n var layers = this.layers;\r\n\r\n if (dataViews && dataViews.length > 0) {\r\n var warnings = getInvalidValueWarnings(\r\n dataViews,\r\n false /*supportsNaN*/,\r\n false /*supportsNegativeInfinity*/,\r\n false /*supportsPositiveInfinity*/);\r\n\r\n if (warnings && warnings.length > 0) {\r\n this.hostServices.setWarnings(warnings);\r\n }\r\n this.populateObjectProperties(dataViews);\r\n }\r\n\r\n this.sharedColorPalette.clearPreferredScale();\r\n for (var i: number = 0, len: number = layers.length; i < len; i++) {\r\n layers[i].setData(getLayerData(dataViews, i, len));\r\n\r\n if (len > 1) {\r\n this.sharedColorPalette.rotateScale();\r\n\t\t\t\t}\r\n }\r\n\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 this.render(!this.hasSetData || options.suppressAnimations);\r\n this.hasSetData = this.hasSetData || (dataViews && dataViews.length > 0);\r\n }\r\n\r\n /**\r\n * Clear the viewport area\r\n */\r\n private clearViewport(): void {\r\n this.legend.reset();\r\n this.setVisibility(false);\r\n }\r\n\r\n private setVisibility(status: boolean = true): void {\r\n this.svg.style('display', status ? 'block' : 'none');\r\n this.element.find('.legend').toggle(status);\r\n }\r\n\r\n public static parseLabelSettings(objects: DataViewObjects): VisualDataLabelsSettings {\r\n var labelSettings: VisualDataLabelsSettings = dataLabelUtils.getDefaultColumnLabelSettings(true);\r\n var labelsObj: DataLabelObject = <DataLabelObject>objects['labels'];\r\n var minPrecision = MekkoChart.DefaultSettings.labelSettings.minPrecision,\r\n maxPrecision = MekkoChart.DefaultSettings.labelSettings.maxPrecision;\r\n\r\n dataLabelUtils.updateLabelSettingsFromLabelsObject(labelsObj, labelSettings);\r\n\r\n if (labelSettings.precision < minPrecision) {\r\n labelSettings.precision = minPrecision;\r\n }\r\n\r\n if (labelSettings.precision > maxPrecision) {\r\n labelSettings.precision = maxPrecision;\r\n }\r\n\r\n return labelSettings;\r\n }\r\n\r\n public static parseBorderSettings(objects: DataViewObjects): MekkoBorderSettings {\r\n var show: boolean = DataViewObjects.getValue(objects, MekkoChart.properties.columnBorder.show, MekkoChart.DefaultSettings.columnBorder.show);\r\n var color = DataViewObjects.getFillColor(objects, MekkoChart.properties.columnBorder.color, MekkoChart.DefaultSettings.columnBorder.color);\r\n var width: number = DataViewObjects.getValue(objects, MekkoChart.properties.columnBorder.width, MekkoChart.DefaultSettings.columnBorder.width);\r\n var maxWidth: number = MekkoChart.DefaultSettings.columnBorder.maxWidth;\r\n\r\n if (width > maxWidth) {\r\n width = maxWidth;\r\n } else if (width < 0) {\r\n width = 0;\r\n }\r\n\r\n if (!show) {\r\n width = 0;\r\n }\r\n\r\n return {\r\n show: show,\r\n color: color,\r\n width: width,\r\n };\r\n }\r\n\r\n private enumerateBorder(enumeration: ObjectEnumerationBuilder): void {\r\n var objects: DataViewObjects = {\r\n columnBorder: this.borderObjectProperties\r\n };\r\n\r\n var show = DataViewObjects.getValue(objects, MekkoChart.properties.columnBorder.show, MekkoChart.DefaultSettings.columnBorder.show);\r\n var color = DataViewObjects.getFillColor(objects, MekkoChart.properties.columnBorder.color, MekkoChart.DefaultSettings.columnBorder.color);\r\n var width = DataViewObjects.getValue(objects, MekkoChart.properties.columnBorder.width, MekkoChart.DefaultSettings.columnBorder.width);\r\n\r\n var maxWidth: number = MekkoChart.DefaultSettings.columnBorder.maxWidth;\r\n\r\n if (width > maxWidth) {\r\n width = maxWidth;\r\n } else if (width < 0) {\r\n width = 0;\r\n }\r\n\r\n var instance: VisualObjectInstance = {\r\n objectName: 'columnBorder',\r\n selector: null,\r\n properties: {\r\n show: show,\r\n color: color,\r\n width: width,\r\n },\r\n };\r\n enumeration\r\n .pushInstance(instance);\r\n }\r\n\r\n public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstanceEnumeration {\r\n var enumeration = new ObjectEnumerationBuilder();\r\n var layersLength = this.layers ? this.layers.length : 0;\r\n\r\n if (options.objectName === 'columnBorder') {\r\n this.enumerateBorder(enumeration);\r\n }\r\n else if (options.objectName === 'legend') {\r\n if (!this.shouldShowLegendCard()) {\r\n return;\r\n }\r\n\r\n var show = DataViewObject.getValue(this.legendObjectProperties, legendProps.show, this.legend.isVisible());\r\n var showTitle = DataViewObject.getValue(this.legendObjectProperties, legendProps.showTitle, true);\r\n var titleText = DataViewObject.getValue(this.legendObjectProperties, legendProps.titleText, this.layerLegendData && this.layerLegendData.title ? this.layerLegendData.title : '');\r\n var fontSize = DataViewObject.getValue(this.legendObjectProperties, legendProps.fontSize, this.layerLegendData && this.layerLegendData.fontSize ? this.layerLegendData.fontSize : NewDataLabelUtils.DefaultLabelFontSizeInPt);\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 fontSize: fontSize\r\n },\r\n objectName: options.objectName\r\n });\r\n }\r\n else if (options.objectName === 'categoryAxis' && this.hasCategoryAxis) {\r\n this.getCategoryAxisValues(enumeration);\r\n }\r\n else if (options.objectName === 'valueAxis') {\r\n this.getValueAxisValues(enumeration);\r\n }\r\n\r\n for (var i: number = 0, len: number = layersLength; i < len; i++) {\r\n var layer = this.layers[i];\r\n if (layer.enumerateObjectInstances) {\r\n layer.enumerateObjectInstances(enumeration, options);\r\n }\r\n }\r\n\r\n return enumeration.complete();\r\n }\r\n\r\n private shouldShowLegendCard(): boolean {\r\n var layers = this.layers;\r\n var dataViews = this.dataViews;\r\n\r\n if (layers && dataViews) {\r\n var layersLength = layers.length;\r\n var layersWithValuesCtr = 0;\r\n\r\n for (var i: number = 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 var 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 getCategoryAxisValues(enumeration: ObjectEnumerationBuilder): void {\r\n var supportedType: string = axisType.both;\r\n var isScalar: boolean = false;\r\n var logPossible: boolean = !!this.axes.x.isLogScaleAllowed;\r\n var scaleOptions: string[] = [axisScale.log, axisScale.linear];//until options can be update in propPane, show all options\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 if (this.categoryAxisProperties) {\r\n this.categoryAxisProperties['start'] = null;\r\n this.categoryAxisProperties['end'] = null;\r\n }\r\n }\r\n\r\n var instance: VisualObjectInstance = {\r\n selector: null,\r\n properties: {},\r\n objectName: 'categoryAxis',\r\n validValues: {\r\n axisScale: scaleOptions\r\n }\r\n };\r\n\r\n instance.properties['show'] = this.categoryAxisProperties && this.categoryAxisProperties['show'] != null ? this.categoryAxisProperties['show'] : true;\r\n if (this.yAxisIsCategorical)//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 && this.categoryAxisProperties['axisScale'] != null && logPossible) ? this.categoryAxisProperties['axisScale'] : axisScale.linear;\r\n instance.properties['start'] = this.categoryAxisProperties ? this.categoryAxisProperties['start'] : null;\r\n instance.properties['end'] = this.categoryAxisProperties ? this.categoryAxisProperties['end'] : null;\r\n }\r\n instance.properties['showAxisTitle'] = this.categoryAxisProperties && this.categoryAxisProperties['showAxisTitle'] != null ? this.categoryAxisProperties['showAxisTitle'] : false;\r\n instance.properties['showBorder'] = this.categoryAxisProperties && this.categoryAxisProperties['showBorder'] != null ? this.categoryAxisProperties['showAxisTitle'] : false;\r\n\r\n instance.properties['fontSize'] = this.categoryAxisProperties && this.categoryAxisProperties['fontSize'] != null ? this.categoryAxisProperties['fontSize'] : NewDataLabelUtils.DefaultLabelFontSizeInPt;\r\n\r\n enumeration\r\n .pushInstance(instance)\r\n .pushInstance({\r\n selector: null,\r\n properties: {\r\n axisStyle: this.categoryAxisProperties && this.categoryAxisProperties['axisStyle'] ? this.categoryAxisProperties['axisStyle'] : axisStyle.showTitleOnly,\r\n labelColor: this.categoryAxisProperties ? this.categoryAxisProperties['labelColor'] : null,\r\n fontSize: this.categoryAxisProperties && this.categoryAxisProperties['fontSize'] != null ? this.categoryAxisProperties['fontSize'] : NewDataLabelUtils.DefaultLabelFontSizeInPt\r\n },\r\n objectName: 'categoryAxis',\r\n validValues: {\r\n axisStyle: this.categoryAxisHasUnitType ? [axisStyle.showTitleOnly, axisStyle.showUnitOnly, axisStyle.showBoth] : [axisStyle.showTitleOnly],\r\n }\r\n });\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 var scaleOptions: string[] = [axisScale.log, axisScale.linear]; //until options can be update in propPane, show all options\r\n var logPossible: boolean = !!this.axes.y1.isLogScaleAllowed;\r\n //var secLogPossible = this.axes.y2 != null && this.axes.y2.isLogScaleAllowed;\r\n\r\n var instance: VisualObjectInstance = {\r\n selector: null,\r\n properties: {},\r\n objectName: 'valueAxis',\r\n validValues: {\r\n axisScale: scaleOptions,\r\n secAxisScale: scaleOptions\r\n }\r\n };\r\n\r\n instance.properties['show'] = this.valueAxisProperties && this.valueAxisProperties['show'] != null ? this.valueAxisProperties['show'] : true;\r\n\r\n if (!this.yAxisIsCategorical) {\r\n instance.properties['position'] = this.valueAxisProperties && this.valueAxisProperties['position'] != null ? this.valueAxisProperties['position'] : yAxisPosition.left;\r\n }\r\n instance.properties['axisScale'] = (this.valueAxisProperties && this.valueAxisProperties['axisScale'] != null && logPossible) ? this.valueAxisProperties['axisScale'] : axisScale.linear;\r\n instance.properties['start'] = this.valueAxisProperties ? this.valueAxisProperties['start'] : null;\r\n instance.properties['end'] = this.valueAxisProperties ? this.valueAxisProperties['end'] : null;\r\n instance.properties['showAxisTitle'] = this.valueAxisProperties && this.valueAxisProperties['showAxisTitle'] != null ? this.valueAxisProperties['showAxisTitle'] : false;\r\n\r\n instance.properties['fontSize'] = this.valueAxisProperties && this.valueAxisProperties['fontSize'] != null ? this.valueAxisProperties['fontSize'] : NewDataLabelUtils.DefaultLabelFontSizeInPt;\r\n\r\n enumeration\r\n .pushInstance(instance)\r\n .pushInstance({\r\n selector: null,\r\n properties: {\r\n axisStyle: this.valueAxisProperties && this.valueAxisProperties['axisStyle'] != null ? this.valueAxisProperties['axisStyle'] : axisStyle.showTitleOnly,\r\n labelColor: this.valueAxisProperties ? this.valueAxisProperties['labelColor'] : null,\r\n fontSize: this.valueAxisProperties && this.valueAxisProperties['fontSize'] != null ? this.valueAxisProperties['fontSize'] : NewDataLabelUtils.DefaultLabelFontSizeInPt\r\n },\r\n objectName: 'valueAxis',\r\n validValues: {\r\n axisStyle: this.valueAxisHasUnitType ? [axisStyle.showTitleOnly, axisStyle.showUnitOnly, axisStyle.showBoth] : [axisStyle.showTitleOnly]\r\n },\r\n });\r\n\r\n if (this.layers.length === 2) {\r\n instance.properties['secShow'] = this.valueAxisProperties && this.valueAxisProperties['secShow'] != null ? this.valueAxisProperties['secShow'] : this.y2AxisExists;\r\n if (instance.properties['secShow']) {\r\n instance.properties['axisLabel'] = '';//this.layers[0].getVisualType();//I will keep or remove this, depending on the decision made\r\n }\r\n }\r\n }\r\n\r\n public onClearSelection(): void {\r\n if (this.hasSetData) {\r\n for (var i: number = 0, len: number = this.layers.length; i < len; i++) {\r\n var 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 createAndInitLayers(dataViews: DataView[]): IMekkoColumnChartVisual[] {\r\n var objects: DataViewObjects;\r\n if (dataViews && dataViews.length > 0) {\r\n var dataViewMetadata = dataViews[0].metadata;\r\n if (dataViewMetadata)\r\n objects = dataViewMetadata.objects;\r\n }\r\n\r\n // Create the layers\r\n var layers: IMekkoColumnChartVisual[] = createLayers(this.type, objects, this.interactivityService, this.animator, this.isScrollable);\r\n\r\n // Initialize the layers\r\n var cartesianOptions = <CartesianVisualInitOptions>Prototype.inherit(this.visualInitOptions);\r\n cartesianOptions.svg = this.axisGraphicsContextScrollable;\r\n cartesianOptions.cartesianHost = {\r\n updateLegend: data => this.legend.drawLegend(data, this.currentViewport),\r\n getSharedColors: () => this.sharedColorPalette,\r\n triggerRender: undefined,\r\n };\r\n\r\n for (var i: number = 0, len: number = layers.length; i < len; i++) {\r\n layers[i].init(cartesianOptions);\r\n }\r\n\r\n return layers;\r\n }\r\n\r\n private renderLegend(): void {\r\n var layers: IMekkoColumnChartVisual[] = this.layers;\r\n var legendData: LegendData = { title: \"\", dataPoints: [] };\r\n\r\n for (var i: number = 0, len: number = 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.dataPoints = legendData.dataPoints.concat(this.layerLegendData.dataPoints || []);\r\n if (this.layerLegendData.grouped) {\r\n legendData.grouped = true;\r\n }\r\n }\r\n }\r\n\r\n var legendProperties: DataViewObject = this.legendObjectProperties;\r\n if (legendProperties) {\r\n if (!legendProperties['fontSize']) {\r\n legendProperties['fontSize'] = NewDataLabelUtils.DefaultLabelFontSizeInPt;\r\n }\r\n\r\n LegendData.update(legendData, legendProperties);\r\n var position = <string>legendProperties[legendProps.position];\r\n\r\n if (position) {\r\n this.legend.changeOrientation(LegendPosition[position]);\r\n }\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 addUnitTypeToAxisLabel(axes: CartesianAxisProperties): void {\r\n var unitType = MekkoChart.getUnitType(axes, (axis: CartesianAxisProperties): IAxisProperties => axis.x);\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 = MekkoChart.getUnitType(axes, (axis: CartesianAxisProperties): IAxisProperties => axis.y1);\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 var unitType = MekkoChart.getUnitType(axes, (axis: CartesianAxisProperties): IAxisProperties => axis.y2);\r\n this.secValueAxisHasUnitType = unitType !== null;\r\n if (axes.y2.axisLabel && unitType) {\r\n if (this.valueAxisProperties && this.valueAxisProperties['secAxisStyle']) {\r\n if (this.valueAxisProperties['secAxisStyle'] === axisStyle.showBoth) {\r\n axes.y2.axisLabel = axes.y2.axisLabel + ' (' + unitType + ')';\r\n }\r\n else if (this.valueAxisProperties['secAxisStyle'] === axisStyle.showUnitOnly) {\r\n axes.y2.axisLabel = unitType;\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n private shouldRenderSecondaryAxis(axisProperties: IAxisProperties): boolean {\r\n if (!axisProperties) {\r\n return false;\r\n }\r\n if (!this.valueAxisProperties || this.valueAxisProperties[\"secShow\"] == null || this.valueAxisProperties[\"secShow\"]) {\r\n return axisProperties.values && axisProperties.values.length > 0;\r\n }\r\n return false;\r\n }\r\n\r\n private shouldRenderAxis(axisProperties: IAxisProperties, propertyName: string = \"show\"): boolean {\r\n if (!axisProperties) {\r\n return false;\r\n }\r\n else if (axisProperties.isCategoryAxis && (!this.categoryAxisProperties || this.categoryAxisProperties[propertyName] == null || this.categoryAxisProperties[propertyName])) {\r\n return axisProperties.values && axisProperties.values.length > 0;\r\n }\r\n else if (!axisProperties.isCategoryAxis && (!this.valueAxisProperties || this.valueAxisProperties[propertyName] == null || this.valueAxisProperties[propertyName])) {\r\n return axisProperties.values && axisProperties.values.length > 0;\r\n }\r\n return false;\r\n }\r\n\r\n private render(suppressAnimations: boolean): void {\r\n this.setVisibility(true);\r\n\r\n var legendMargins: IViewport = this.legendMargins = this.legend.getMargins();\r\n var viewport: IViewport = {\r\n height: this.currentViewport.height - legendMargins.height,\r\n width: this.currentViewport.width - legendMargins.width\r\n };\r\n\r\n var maxMarginFactor = this.getMaxMarginFactor();\r\n var leftRightMarginLimit = this.leftRightMarginLimit = viewport.width * maxMarginFactor;\r\n this.bottomMarginLimit = Math.max(MekkoChart.MinBottomMargin, Math.ceil(viewport.height * maxMarginFactor));\r\n\r\n var xAxisTextProperties = MekkoChart.getTextProperties(this.categoryAxisProperties && parseFloat(<any>this.categoryAxisProperties['fontSize']) || undefined);\r\n var y1AxisTextProperties = MekkoChart.getTextProperties(this.valueAxisProperties && parseFloat(<any>this.valueAxisProperties['fontSize']) || undefined);\r\n\r\n var margin = this.margin;\r\n // reset defaults\r\n margin.top = parseFloat(y1AxisTextProperties.fontSize) / 2;\r\n margin.bottom = MekkoChart.MinBottomMargin;\r\n margin.right = 0;\r\n\r\n var axes: CartesianAxisProperties = this.axes = calculateAxes(\r\n this.layers,\r\n viewport,\r\n margin,\r\n this.categoryAxisProperties,\r\n this.valueAxisProperties,\r\n this.isXScrollBarVisible || this.isYScrollBarVisible,\r\n null);\r\n\r\n this.yAxisIsCategorical = axes.y1.isCategoryAxis;\r\n this.hasCategoryAxis = this.yAxisIsCategorical ? axes.y1 && axes.y1.values.length > 0 : axes.x && axes.x.values.length > 0;\r\n\r\n var renderXAxis = this.shouldRenderAxis(axes.x);\r\n var renderY1Axis = this.shouldRenderAxis(axes.y1);\r\n var renderY2Axis = this.shouldRenderSecondaryAxis(axes.y2);\r\n\r\n var width: number = viewport.width - (margin.left + margin.right);\r\n var isScalar: boolean = false;\r\n var mainAxisScale;\r\n var preferredViewport: IViewport;\r\n this.isXScrollBarVisible = false;\r\n this.isYScrollBarVisible = false;\r\n\r\n var yAxisOrientation = this.yAxisOrientation;\r\n var showY1OnRight = yAxisOrientation === yAxisPosition.right;\r\n\r\n if (this.layers) {\r\n if (this.layers[0].getVisualCategoryAxisIsScalar) {\r\n isScalar = this.layers[0].getVisualCategoryAxisIsScalar();\r\n }\r\n\r\n if (!isScalar && this.isScrollable && this.layers[0].getPreferredPlotArea) {\r\n var categoryThickness = this.scrollX ? axes.x.categoryThickness : axes.y1.categoryThickness;\r\n var categoryCount = this.scrollX ? axes.x.values.length : axes.y1.values.length;\r\n preferredViewport = this.layers[0].getPreferredPlotArea(isScalar, categoryCount, categoryThickness);\r\n if (this.scrollX && preferredViewport && preferredViewport.width > viewport.width) {\r\n this.isXScrollBarVisible = true;\r\n viewport.height -= MekkoChart.ScrollBarWidth;\r\n }\r\n\r\n if (this.scrollY && preferredViewport && preferredViewport.height > viewport.height) {\r\n this.isYScrollBarVisible = true;\r\n viewport.width -= MekkoChart.ScrollBarWidth;\r\n width = viewport.width - (margin.left + margin.right);\r\n }\r\n }\r\n }\r\n\r\n // Only create the g tag where there is a scrollbar\r\n if (this.isXScrollBarVisible || this.isYScrollBarVisible) {\r\n if (!this.brushGraphicsContext) {\r\n this.brushGraphicsContext = this.svg.append(\"g\")\r\n .classed('x brush', true);\r\n }\r\n }\r\n else {\r\n // clear any existing brush if no scrollbar is shown\r\n this.svg.selectAll('.brush').remove();\r\n this.brushGraphicsContext = undefined;\r\n }\r\n\r\n // Recalculate axes now that scrollbar visible variables have been set\r\n axes = calculateAxes(\r\n this.layers,\r\n viewport,\r\n margin,\r\n this.categoryAxisProperties,\r\n this.valueAxisProperties,\r\n this.isXScrollBarVisible || this.isYScrollBarVisible,\r\n null);\r\n\r\n // we need to make two passes because the margin changes affect the chosen tick values, which then affect the margins again.\r\n // after the second pass the margins are correct.\r\n var doneWithMargins: boolean = false,\r\n maxIterations: number = 2,\r\n numIterations: number = 0;\r\n var tickLabelMargins = undefined;\r\n var chartHasAxisLabels = undefined;\r\n var axisLabels: ChartAxesLabels = undefined;\r\n while (!doneWithMargins && numIterations < maxIterations) {\r\n numIterations++;\r\n tickLabelMargins = getTickLabelMargins(\r\n { width: width, height: viewport.height },\r\n leftRightMarginLimit,\r\n TextMeasurementService.measureSvgTextWidth,\r\n TextMeasurementService.estimateSvgTextHeight,\r\n axes,\r\n this.bottomMarginLimit,\r\n xAxisTextProperties,\r\n y1AxisTextProperties,\r\n null,\r\n false,\r\n this.isXScrollBarVisible || this.isYScrollBarVisible,\r\n showY1OnRight,\r\n renderXAxis,\r\n renderY1Axis,\r\n renderY2Axis);\r\n\r\n // We look at the y axes as main and second sides, if the y axis orientation is right so the main side represents the right side\r\n var maxMainYaxisSide = showY1OnRight ? tickLabelMargins.yRight : tickLabelMargins.yLeft,\r\n maxSecondYaxisSide = showY1OnRight ? tickLabelMargins.yLeft : tickLabelMargins.yRight,\r\n xMax = renderXAxis ? (tickLabelMargins.xMax/1.8) : 0;\r\n\r\n maxMainYaxisSide += MekkoChart.LeftPadding;\r\n maxSecondYaxisSide += MekkoChart.RightPadding;\r\n xMax += MekkoChart.BottomPadding;\r\n\r\n if (this.hideAxisLabels(legendMargins)) {\r\n axes.x.axisLabel = null;\r\n axes.y1.axisLabel = null;\r\n if (axes.y2) {\r\n axes.y2.axisLabel = null;\r\n }\r\n }\r\n\r\n this.addUnitTypeToAxisLabel(axes);\r\n\r\n axisLabels = { x: axes.x.axisLabel, y: axes.y1.axisLabel, y2: axes.y2 ? axes.y2.axisLabel : null };\r\n chartHasAxisLabels = (axisLabels.x != null) || (axisLabels.y != null || axisLabels.y2 != null);\r\n\r\n if (axisLabels.x != null) {\r\n xMax += MekkoChart.XAxisLabelPadding;\r\n }\r\n if (axisLabels.y != null) {\r\n maxMainYaxisSide += MekkoChart.YAxisLabelPadding;\r\n }\r\n if (axisLabels.y2 != null) {\r\n maxSecondYaxisSide += MekkoChart.YAxisLabelPadding;\r\n }\r\n\r\n margin.left = showY1OnRight ? maxSecondYaxisSide : maxMainYaxisSide;\r\n margin.right = showY1OnRight ? maxMainYaxisSide : maxSecondYaxisSide;\r\n margin.bottom = xMax;\r\n this.margin = margin;\r\n\r\n width = viewport.width - (margin.left + margin.right);\r\n\r\n // re-calculate the axes with the new margins\r\n var previousTickCountY1 = axes.y1.values.length;\r\n var previousTickCountY2 = axes.y2 && axes.y2.values.length;\r\n axes = calculateAxes(\r\n this.layers,\r\n viewport,\r\n margin,\r\n this.categoryAxisProperties,\r\n this.valueAxisProperties,\r\n this.isXScrollBarVisible || this.isYScrollBarVisible,\r\n axes);\r\n\r\n // the minor padding adjustments could have affected the chosen tick values, which would then need to calculate margins again\r\n // e.g. [0,2,4,6,8] vs. [0,5,10] the 10 is wider and needs more margin.\r\n if (axes.y1.values.length === previousTickCountY1 && (!axes.y2 || axes.y2.values.length === previousTickCountY2))\r\n doneWithMargins = true;\r\n }\r\n\r\n this.renderChart(mainAxisScale, axes, width, tickLabelMargins, chartHasAxisLabels, axisLabels, viewport, suppressAnimations);\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 + legendMargins.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 static getUnitType(axis: CartesianAxisProperties, axisPropertiesLookup: (axis: CartesianAxisProperties) => IAxisProperties) {\r\n if (axisPropertiesLookup(axis).formatter &&\r\n axisPropertiesLookup(axis).formatter.displayUnit &&\r\n axisPropertiesLookup(axis).formatter.displayUnit.value > 1) {\r\n return axisPropertiesLookup(axis).formatter.displayUnit.title;\r\n }\r\n return null;\r\n }\r\n\r\n private getMaxMarginFactor(): number {\r\n return this.visualInitOptions.style.maxMarginFactor || MekkoChart.MaxMarginFactor;\r\n }\r\n\r\n private static getChartViewport(viewport: IViewport, margin: IMargin): IViewport {\r\n return {\r\n width: viewport.width - margin.left - margin.right,\r\n height: viewport.height - margin.top - margin.bottom,\r\n };\r\n }\r\n\r\n private static wordBreak(\r\n text: D3.Selection,\r\n axisProperties: IAxisProperties,\r\n columnsWidth: number[],\r\n maxHeight: number,\r\n borderWidth: number): void {\r\n\r\n //var allowedLength = axisProperties.xLabelMaxWidth;\r\n text.each(function(data: any, index: number) {\r\n var width: number, allowedLength: number;\r\n var node = d3.select(this);\r\n if (columnsWidth.length >= index) {\r\n width = columnsWidth[index];\r\n allowedLength = axisProperties.scale(width);\r\n } else {\r\n allowedLength = axisProperties.xLabelMaxWidth;\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, axisProperties.willLabelsWordBreak ? maxHeight : 0);\r\n });\r\n }\r\n\r\n private renderChart(\r\n mainAxisScale: any,\r\n axes: CartesianAxisProperties,\r\n width: number,\r\n tickLabelMargins: any,\r\n chartHasAxisLabels: boolean,\r\n axisLabels: ChartAxesLabels,\r\n viewport: IViewport,\r\n suppressAnimations: boolean,\r\n scrollScale?: any,\r\n extent?: number[]) {\r\n\r\n var bottomMarginLimit: number = this.bottomMarginLimit;\r\n var leftRightMarginLimit: number = this.leftRightMarginLimit;\r\n var layers: IMekkoColumnChartVisual[] = this.layers;\r\n var duration: number = AnimatorCommon.GetAnimationDuration(this.animator, suppressAnimations);\r\n var chartViewport: IViewport = MekkoChart.getChartViewport(viewport, this.margin);\r\n\r\n debug.assertValue(layers, 'layers');\r\n\r\n var xLabelColor: Fill;\r\n var yLabelColor: Fill;\r\n var y2LabelColor: Fill;\r\n\r\n var xFontSize: any;\r\n var yFontSize: any;\r\n //hide show x-axis here\r\n if (this.shouldRenderAxis(axes.x)) {\r\n if (axes.x.isCategoryAxis) {\r\n xLabelColor = this.categoryAxisProperties && this.categoryAxisProperties['labelColor'] ? this.categoryAxisProperties['labelColor'] : null;\r\n xFontSize = this.categoryAxisProperties && this.categoryAxisProperties['fontSize'] != null ? this.categoryAxisProperties['fontSize'] : NewDataLabelUtils.DefaultLabelFontSizeInPt;\r\n } else {\r\n xLabelColor = this.valueAxisProperties && this.valueAxisProperties['labelColor'] ? this.valueAxisProperties['labelColor'] : null;\r\n xFontSize = this.valueAxisProperties && this.valueAxisProperties['fontSize'] ? this.valueAxisProperties['fontSize'] : NewDataLabelUtils.DefaultLabelFontSizeInPt;\r\n }\r\n axes.x.axis.orient(\"bottom\");\r\n if (!axes.x.willLabelsFit) {\r\n axes.x.axis.tickPadding(MekkoChart.TickPaddingRotatedX);\r\n }\r\n\r\n var xAxisGraphicsElement: D3.Selection = this.xAxisGraphicsContext;\r\n if (duration) {\r\n xAxisGraphicsElement\r\n .transition()\r\n .duration(duration)\r\n .call(axes.x.axis);\r\n }\r\n else {\r\n xAxisGraphicsElement\r\n .call(axes.x.axis);\r\n }\r\n\r\n xAxisGraphicsElement\r\n .call(MekkoChart.darkenZeroLine)\r\n .call(MekkoChart.setAxisLabelColor, xLabelColor)\r\n .call(MekkoChart.setAxisLabelFontSize, xFontSize);\r\n\r\n var xAxisTextNodes = xAxisGraphicsElement.selectAll('text');\r\n\r\n var columnWidth: number[] = [];\r\n var borderWidth: number = 0;\r\n if (this.layers && this.layers.length) {\r\n columnWidth = this.layers[0].getColumnsWidth();\r\n borderWidth = this.layers[0].getBorderWidth();\r\n }\r\n\r\n xAxisGraphicsElement\r\n .call(MekkoChart.moveBorder, axes.x.scale, borderWidth, xFontSize / 2 - 8);\r\n\r\n xAxisTextNodes\r\n .call(MekkoChart.wordBreak, axes.x, columnWidth, bottomMarginLimit, borderWidth);\r\n }\r\n else {\r\n this.xAxisGraphicsContext.selectAll('*').remove();\r\n }\r\n\r\n if (this.shouldRenderAxis(axes.y1)) {\r\n if (axes.y1.isCategoryAxis) {\r\n yLabelColor = this.categoryAxisProperties && this.categoryAxisProperties['labelColor'] ? this.categoryAxisProperties['labelColor'] : null;\r\n yFontSize = this.categoryAxisProperties && this.categoryAxisProperties['fontSize'] != null ? this.categoryAxisProperties['fontSize'] : NewDataLabelUtils.DefaultLabelFontSizeInPt;\r\n } else {\r\n yLabelColor = this.valueAxisProperties && this.valueAxisProperties['labelColor'] ? this.valueAxisProperties['labelColor'] : null;\r\n yFontSize = this.valueAxisProperties && this.valueAxisProperties['fontSize'] != null ? this.valueAxisProperties['fontSize'] : NewDataLabelUtils.DefaultLabelFontSizeInPt;\r\n }\r\n var yAxisOrientation = this.yAxisOrientation;\r\n var showY1OnRight = yAxisOrientation === yAxisPosition.right;\r\n axes.y1.axis\r\n .tickSize(-width)\r\n .tickPadding(MekkoChart.TickPaddingY)\r\n .orient(yAxisOrientation.toLowerCase());\r\n\r\n var y1AxisGraphicsElement: D3.Selection = this.y1AxisGraphicsContext;\r\n if (duration) {\r\n y1AxisGraphicsElement\r\n .transition()\r\n .duration(duration)\r\n .call(axes.y1.axis);\r\n }\r\n else {\r\n y1AxisGraphicsElement\r\n .call(axes.y1.axis);\r\n }\r\n\r\n y1AxisGraphicsElement\r\n .call(MekkoChart.darkenZeroLine)\r\n .call(MekkoChart.setAxisLabelColor, yLabelColor)\r\n .call(MekkoChart.setAxisLabelFontSize, yFontSize);\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 - MekkoChart.LeftPadding,\r\n TextMeasurementService.svgEllipsis);\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 axes.y2.axis\r\n .tickPadding(MekkoChart.TickPaddingY)\r\n .orient(showY1OnRight ? yAxisPosition.left.toLowerCase() : yAxisPosition.right.toLowerCase());\r\n\r\n if (duration) {\r\n this.y2AxisGraphicsContext\r\n .transition()\r\n .duration(duration)\r\n .call(axes.y2.axis);\r\n }\r\n else {\r\n this.y2AxisGraphicsContext\r\n .call(axes.y2.axis);\r\n }\r\n\r\n this.y2AxisGraphicsContext\r\n .call(MekkoChart.darkenZeroLine)\r\n .call(MekkoChart.setAxisLabelColor, y2LabelColor);\r\n\r\n if (tickLabelMargins.yRight >= leftRightMarginLimit) {\r\n this.y2AxisGraphicsContext.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 - MekkoChart.RightPadding,\r\n TextMeasurementService.svgEllipsis);\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 if (chartHasAxisLabels) {\r\n var hideXAxisTitle: boolean = !this.shouldRenderAxis(axes.x, \"showAxisTitle\");\r\n var hideYAxisTitle: boolean = !this.shouldRenderAxis(axes.y1, \"showAxisTitle\");\r\n var hideY2AxisTitle: boolean = this.valueAxisProperties && this.valueAxisProperties[\"secShowAxisTitle\"] != null && this.valueAxisProperties[\"secShowAxisTitle\"] === false;\r\n\r\n var renderAxisOptions: MekkoAxisRenderingOptions = {\r\n axisLabels: axisLabels,\r\n legendMargin: this.legendMargins.height,\r\n viewport: viewport,\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 margin: undefined,\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);\r\n\r\n var dataPoints: SelectableDataPoint[] = [];\r\n var layerBehaviorOptions: any[] = [];\r\n var labelDataPointsGroup: MekkoLabelDataPointsGroup[] = [];\r\n\r\n //Render chart columns\r\n if (this.behavior) {\r\n for (var i: number = 0, len: number = layers.length; i < len; i++) {\r\n var result: MekkoVisualRenderResult = layers[i].render(suppressAnimations);\r\n if (result) {\r\n dataPoints = dataPoints.concat(result.dataPoints);\r\n layerBehaviorOptions.push(result.behaviorOptions);\r\n\r\n if (result.labelDataPointGroups) {\r\n var resultLabelDataPointsGroups = result.labelDataPointGroups;\r\n for (var j: number = 0, jlen = resultLabelDataPointsGroups.length; j < jlen; j++) {\r\n var resultLabelDataPointsGroup = resultLabelDataPointsGroups[j];\r\n labelDataPointsGroup.push({\r\n labelDataPoints: resultLabelDataPointsGroup.labelDataPoints,\r\n maxNumberOfLabels: resultLabelDataPointsGroup.maxNumberOfLabels,\r\n });\r\n }\r\n }\r\n else {\r\n var resultsLabelDataPoints: MekkoLabelDataPoint[] = result.labelDataPoints;\r\n var reducedDataPoints: MekkoLabelDataPoint[] = resultsLabelDataPoints;\r\n labelDataPointsGroup.push({\r\n labelDataPoints: reducedDataPoints,\r\n maxNumberOfLabels: reducedDataPoints.length,\r\n });\r\n }\r\n }\r\n }\r\n\r\n var labelLayoutOptions: DataLabelLayoutOptions = {\r\n maximumOffset: NewDataLabelUtils.maxLabelOffset,\r\n startingOffset: NewDataLabelUtils.startingLabelOffset\r\n };\r\n\r\n var labelLayout: LabelLayout = new LabelLayout(labelLayoutOptions);\r\n var dataLabels: Label[] = labelLayout.layout(labelDataPointsGroup, chartViewport);\r\n\r\n if (layers.length > 1) {\r\n NewDataLabelUtils.drawLabelBackground(this.labelGraphicsContextScrollable, dataLabels, \"#FFFFFF\", 0.7);\r\n }\r\n if (this.animator && !suppressAnimations) {\r\n NewDataLabelUtils.animateDefaultLabels(this.labelGraphicsContextScrollable, dataLabels, this.animator.getDuration());\r\n }\r\n else {\r\n NewDataLabelUtils.drawDefaultLabels(this.labelGraphicsContextScrollable, dataLabels);\r\n }\r\n this.labelGraphicsContextScrollable.selectAll(\"text.label\").style(\"pointer-events\", \"none\");\r\n if (this.interactivityService) {\r\n var behaviorOptions: MekkoBehaviorOptions = {\r\n layerOptions: layerBehaviorOptions,\r\n clearCatcher: this.clearCatcher,\r\n };\r\n this.interactivityService.bind(dataPoints, this.behavior, behaviorOptions);\r\n }\r\n }\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 var 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 private static setAxisLabelFontSize(g: D3.Selection, fontSize: number): void {\r\n var value = jsCommon.PixelConverter.toString(fontSize);\r\n g.selectAll('g.tick text').attr('font-size', value);\r\n }\r\n\r\n private static moveBorder(g: D3.Selection, scale: D3.Scale.LinearScale, borderWidth: number, yOffset: number = 0): void {\r\n g.selectAll('g.tick')\r\n .attr(\"transform\", function(value: number, index: number) {\r\n return SVGUtil.translate(scale(value) + (borderWidth * index), yOffset);\r\n });\r\n }\r\n }\r\n\r\n 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 xAxisTextProperties: TextProperties,\r\n y1AxisTextProperties: TextProperties,\r\n y2AxisTextProperties: TextProperties,\r\n enableOverflowCheck: boolean,\r\n scrollbarVisible?: boolean,\r\n showOnRight?: boolean,\r\n renderXAxis?: boolean,\r\n renderY1Axis?: boolean,\r\n renderY2Axis?: boolean): TickLabelMargins {\r\n\r\n var XLabelMaxAllowedOverflow = 35;\r\n\r\n debug.assertValue(axes, 'axes');\r\n var xAxisProperties: IAxisProperties = axes.x;\r\n var y1AxisProperties: IAxisProperties = axes.y1;\r\n var 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 var xLabels = xAxisProperties.values;\r\n var y1Labels = y1AxisProperties.values;\r\n\r\n var leftOverflow = 0;\r\n var rightOverflow = 0;\r\n var maxWidthY1 = 0;\r\n var maxWidthY2 = 0;\r\n var xMax = 0; // bottom margin\r\n var ordinalLabelOffset = xAxisProperties.categoryThickness ? xAxisProperties.categoryThickness / 2 : 0;\r\n var scaleIsOrdinal = AxisHelper.isOrdinalScale(xAxisProperties.scale);\r\n\r\n var 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 (AxisHelper.getRecommendedNumberOfTicksForXAxis(viewport.width) !== 0\r\n ||AxisHelper. getRecommendedNumberOfTicksForYAxis(viewport.height) !== 0) {\r\n var rotation;\r\n if (scrollbarVisible)\r\n rotation = AxisHelper.LabelLayoutStrategy.DefaultRotationWithScrollbar;\r\n else\r\n rotation = AxisHelper.LabelLayoutStrategy.DefaultRotation;\r\n\r\n if (renderY1Axis) {\r\n for (var i = 0, len = y1Labels.length; i < len; i++) {\r\n y1AxisTextProperties.text = y1Labels[i];\r\n maxWidthY1 = Math.max(maxWidthY1, textWidthMeasurer(y1AxisTextProperties));\r\n }\r\n }\r\n\r\n if (y2AxisProperties && renderY2Axis) {\r\n var y2Labels = y2AxisProperties.values;\r\n for (var i = 0, len = y2Labels.length; i < len; i++) {\r\n y2AxisTextProperties.text = y2Labels[i];\r\n maxWidthY2 = Math.max(maxWidthY2, textWidthMeasurer(y2AxisTextProperties));\r\n }\r\n }\r\n\r\n var textHeight = textHeightMeasurer(xAxisTextProperties);\r\n var maxNumLines = Math.floor(bottomMarginLimit / textHeight);\r\n var xScale = xAxisProperties.scale;\r\n var xDomain = xScale.domain();\r\n if (renderXAxis && xLabels.length > 0) {\r\n for (var i = 0, len = xLabels.length; i < len; i++) {\r\n // find the max height of the x-labels, perhaps rotated or wrapped\r\n var height: number;\r\n xAxisTextProperties.text = xLabels[i];\r\n var width = textWidthMeasurer(xAxisTextProperties);\r\n if (xAxisProperties.willLabelsWordBreak) {\r\n // Split label and count rows\r\n var wordBreaks = jsCommon.WordBreaker.splitByWidth(xAxisTextProperties.text, xAxisTextProperties, 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 = textHeight;\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 var 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 var 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 = enableOverflowCheck ? Math.min(leftOverflow, XLabelMaxAllowedOverflow) : 0;\r\n rightOverflow = enableOverflowCheck ? Math.min(rightOverflow, XLabelMaxAllowedOverflow) : 0;\r\n }\r\n }\r\n\r\n var 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 function getLayerData(dataViews: DataView[], currentIdx: number, totalLayers: number): DataView[] {\r\n if (totalLayers > 1) {\r\n if (dataViews && dataViews.length > currentIdx)\r\n return [dataViews[currentIdx]];\r\n return [];\r\n }\r\n\r\n return dataViews;\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 /**\r\n * Computes the Cartesian Chart axes from the set of layers.\r\n */\r\n function calculateAxes(\r\n layers: IMekkoColumnChartVisual[],\r\n viewport: IViewport,\r\n margin: IMargin,\r\n categoryAxisProperties: DataViewObject,\r\n valueAxisProperties: DataViewObject,\r\n scrollbarVisible: boolean,\r\n existingAxisProperties: CartesianAxisProperties): CartesianAxisProperties {\r\n debug.assertValue(layers, 'layers');\r\n\r\n var visualOptions: MekkoCalculateScaleAndDomainOptions = {\r\n viewport: viewport,\r\n margin: margin,\r\n forcedXDomain: [categoryAxisProperties ? categoryAxisProperties['start'] : null, categoryAxisProperties ? categoryAxisProperties['end'] : null],\r\n forceMerge: valueAxisProperties && valueAxisProperties['secShow'] === false,\r\n showCategoryAxisLabel: false,\r\n showValueAxisLabel: false,\r\n categoryAxisScaleType: categoryAxisProperties && categoryAxisProperties['axisScale'] != null ? <string>categoryAxisProperties['axisScale'] : axisScale.linear,\r\n valueAxisScaleType: valueAxisProperties && valueAxisProperties['axisScale'] != null ? <string>valueAxisProperties['axisScale'] : axisScale.linear,\r\n trimOrdinalDataOnOverflow: false\r\n };\r\n\r\n var yAxisWillMerge = false;\r\n\r\n if (valueAxisProperties) {\r\n visualOptions.forcedYDomain = AxisHelper.applyCustomizedDomain([valueAxisProperties['start'], valueAxisProperties['end']], visualOptions.forcedYDomain);\r\n }\r\n\r\n var result: CartesianAxisProperties;\r\n for (var layerNumber: number = 0, len: number = layers.length; layerNumber < len; layerNumber++) {\r\n var currentlayer = layers[layerNumber];\r\n visualOptions.showCategoryAxisLabel = (!!categoryAxisProperties && !!categoryAxisProperties['showAxisTitle']);//here\r\n //visualOptions.showBorder = (!!categoryAxisProperties && !!categoryAxisProperties['showBorder']);//here\r\n visualOptions.showValueAxisLabel = shouldShowYAxisLabel(layerNumber, valueAxisProperties, yAxisWillMerge);\r\n\r\n var 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\r\n result.x.willLabelsFit = false;\r\n result.x.willLabelsWordBreak = false;\r\n }\r\n\r\n return result;\r\n }\r\n\r\n export function createLayers(\r\n type: MekkoChartType,\r\n objects: DataViewObjects,\r\n interactivityService: IInteractivityService,\r\n animator?: any,\r\n isScrollable: boolean = true): IMekkoColumnChartVisual[] {\r\n\r\n var layers: IMekkoColumnChartVisual[] = [];\r\n\r\n var cartesianOptions: CartesianVisualConstructorOptions = {\r\n isScrollable: isScrollable,\r\n animator: animator,\r\n interactivityService: interactivityService\r\n };\r\n\r\n layers.push(createMekkoChartLayer(ColumnChartType.hundredPercentStackedColumn, cartesianOptions));\r\n\r\n return layers;\r\n }\r\n\r\n function createMekkoChartLayer(type: ColumnChartType, defaultOptions: CartesianVisualConstructorOptions): MekkoColumnChart {\r\n var options: ColumnChartConstructorOptions = {\r\n animator: <IColumnChartAnimator>defaultOptions.animator,\r\n interactivityService: defaultOptions.interactivityService,\r\n isScrollable: defaultOptions.isScrollable,\r\n chartType: type\r\n };\r\n return new MekkoColumnChart(options);\r\n }\r\n\r\n import EnumExtensions = jsCommon.EnumExtensions;\r\n import ArrayExtensions = jsCommon.ArrayExtensions;\r\n\r\n var flagBar: number = 1 << 1;\r\n //var flagColumn: number = 1 << 2;\r\n var flagStacked: number = 1 << 4;\r\n\r\n var RoleNames = {\r\n category: 'Category',\r\n series: 'Series',\r\n y: 'Y',\r\n width: 'Width'\r\n };\r\n\r\n /**\r\n * Renders a stacked and clustered column chart.\r\n */\r\n export interface IMekkoColumnChartVisual /*extends ICartesianVisual*/ {\r\n getColumnsWidth(): number[];\r\n getBorderWidth(): number;\r\n\r\n\t\tinit(options: CartesianVisualInitOptions): void;\r\n setData(dataViews: DataView[], resized?: boolean): void;\r\n calculateAxesProperties(options: CalculateScaleAndDomainOptions): IAxisProperties[];\r\n overrideXScale(xProperties: IAxisProperties): void;\r\n render(suppressAnimations: boolean): MekkoVisualRenderResult;\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 }\r\n\r\n export interface IMekkoColumnChartStrategy /*extends IColumnChartStrategy*/ {\r\n\t\tdrawColumns(useAnimation: boolean): MekkoColumnChartDrawInfo;\r\n\r\n\t\tsetData(data: ColumnChartData): void;\r\n setupVisualProps(columnChartProps: ColumnChartContext): void;\r\n setXScale(is100Pct: boolean, forcedTickCount?: number, forcedXDomain?: any[], axisScaleType?: string, axisDisplayUnits?: number, axisPrecision?: number): IAxisProperties;\r\n setYScale(is100Pct: boolean, forcedTickCount?: number, forcedYDomain?: any[], axisScaleType?: string, axisDisplayUnits?: number, axisPrecision?: number): IAxisProperties;\r\n\r\n selectColumn(selectedColumnIndex: number, lastSelectedColumnIndex: number): void;\r\n getClosestColumnIndex(x: number, y: number): number;\r\n }\r\n\r\n export class MekkoColumnChart implements IMekkoColumnChartVisual {\r\n private static ColumnChartClassName = 'columnChart';\r\n\r\n public static SeriesClasses: ClassAndSelector = createClassAndSelector(\"series\");\r\n public static BorderClass: ClassAndSelector = createClassAndSelector(\"mekkoborder\");\r\n\r\n private svg: D3.Selection;\r\n private unclippedGraphicsContext: D3.Selection;\r\n private mainGraphicsContext: D3.Selection;\r\n private labelGraphicsContext: D3.Selection;\r\n private xAxisProperties: IAxisProperties;\r\n private yAxisProperties: IAxisProperties;\r\n private currentViewport: IViewport;\r\n private data: MekkoColumnChartData;\r\n private style: IVisualStyle;\r\n private colors: IDataColorPalette;\r\n private chartType: ColumnChartType;\r\n private columnChart: IMekkoColumnChartStrategy;\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 supportsOverflow: boolean;\r\n private interactivityService: IInteractivityService;\r\n private dataViewCat: DataViewCategorical;\r\n private categoryAxisType: string;\r\n private animator: IColumnChartAnimator;\r\n private isScrollable: boolean;\r\n private element: JQuery;\r\n\r\n constructor(options: ColumnChartConstructorOptions) {\r\n debug.assertValue(options, 'options');\r\n\r\n var chartType: ColumnChartType = 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.interactivityService = options.interactivityService;\r\n }\r\n\r\n public init(options: CartesianVisualInitOptions) {\r\n this.svg = options.svg;\r\n this.unclippedGraphicsContext = this.svg.append('g').classed('columnChartUnclippedGraphicsContext', true);\r\n this.mainGraphicsContext = this.unclippedGraphicsContext.append('svg').classed('columnChartMainGraphicsContext', true);\r\n this.labelGraphicsContext = this.svg.append('g').classed(NewDataLabelUtils.labelGraphicsContextClass.class, true);\r\n\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.supportsOverflow = !EnumExtensions.hasFlag(this.chartType, flagStacked);\r\n var element = this.element = options.element;\r\n element.addClass(MekkoColumnChart.ColumnChartClassName);\r\n\r\n this.columnChart = new MekkoColumnChartStrategy();\r\n }\r\n\r\n private getCategoryLayout(numCategoryValues: number, options: MekkoCalculateScaleAndDomainOptions): CategoryLayout {\r\n var availableWidth: number = this.currentViewport.width - (this.margin.left + this.margin.right);\r\n var metaDataColumn = this.data ? this.data.categoryMetadata : undefined;\r\n var categoryDataType: ValueType = AxisHelper.getCategoryValueType(metaDataColumn);\r\n var isScalar = this.data ? this.data.scalarCategoryAxis : false;\r\n var domain = AxisHelper.createDomain(this.data.series, categoryDataType, isScalar, options.forcedXDomain);\r\n\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: false\r\n });\r\n }\r\n\r\n public static getBorderWidth(border: MekkoBorderSettings) {\r\n if (!border ||\r\n !border.show ||\r\n !border.width) {\r\n return 0;\r\n }\r\n\r\n var width: number = border.width;\r\n\r\n if (width < 0) {\r\n return 0;\r\n }\r\n if (width > border.maxWidth) {\r\n return border.maxWidth;\r\n }\r\n\r\n return width;\r\n }\r\n\r\n public static getBorderColor(border: MekkoBorderSettings) {\r\n if (!border) {\r\n return MekkoChart.DefaultSettings.columnBorder.color;\r\n }\r\n return border.color;\r\n }\r\n\r\n public static converter(dataView: DataViewCategorical,\r\n colors: IDataColorPalette,\r\n is100PercentStacked: boolean = false,\r\n isScalar: boolean = false,\r\n supportsOverflow: boolean = false,\r\n dataViewMetadata: DataViewMetadata = null,\r\n chartType?: ColumnChartType): MekkoColumnChartData {\r\n debug.assertValue(dataView, 'dataView');\r\n debug.assertValue(colors, 'colors');\r\n\r\n var xAxisCardProperties = CartesianHelper.getCategoryAxisProperties(dataViewMetadata);\r\n var valueAxisProperties = CartesianHelper.getValueAxisProperties(dataViewMetadata);\r\n isScalar = CartesianHelper.isScalar(isScalar, xAxisCardProperties);\r\n dataView = ColumnUtil.applyUserMinMax(isScalar, dataView, xAxisCardProperties);\r\n\r\n var converterStrategy = new ColumnChartConverterHelper(dataView);\r\n\r\n var categoryInfo = converterHelper.getPivotedCategories(dataView, columnChartProps.general.formatString);\r\n var categories = categoryInfo.categories,\r\n categoryFormatter: IValueFormatter = categoryInfo.categoryFormatter,\r\n categoryIdentities: DataViewScopeIdentity[] = categoryInfo.categoryIdentities,\r\n categoryMetadata: DataViewMetadataColumn = dataView.categories && dataView.categories.length > 0 ? dataView.categories[0].source : undefined;\r\n //labelFormatString: string = dataView.values && dataView.values[0] ? valueFormatter.getFormatString(dataView.values[0].source, columnChartProps.general.formatString) : undefined;\r\n\r\n var borderSettings: MekkoBorderSettings = MekkoChart.DefaultSettings.columnBorder;\r\n var labelSettings: VisualDataLabelsSettings = dataLabelUtils.getDefaultColumnLabelSettings(true);\r\n\r\n var defaultDataPointColor = undefined;\r\n var showAllDataPoints = undefined;\r\n if (dataViewMetadata && dataViewMetadata.objects) {\r\n var 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\r\n labelSettings = MekkoChart.parseLabelSettings(objects);\r\n borderSettings = MekkoChart.parseBorderSettings(objects);\r\n }\r\n\r\n // Allocate colors\r\n var legendAndSeriesInfo = converterStrategy.getLegend(colors, defaultDataPointColor);\r\n var legend: MekkoLegendDataPoint[] = legendAndSeriesInfo.legend.dataPoints;\r\n var seriesSources: DataViewMetadataColumn[] = legendAndSeriesInfo.seriesSources;\r\n\r\n // Determine data points\r\n var result: MekkoDataPoints = MekkoColumnChart.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 supportsOverflow,\r\n converterHelper.categoryIsAlsoSeriesRole(dataView, RoleNames.series, RoleNames.category),\r\n categoryInfo.categoryObjects,\r\n defaultDataPointColor,\r\n chartType,\r\n categoryMetadata);\r\n var columnSeries: ColumnChartSeries[] = result.series;\r\n\r\n var valuesMetadata: DataViewMetadataColumn[] = [];\r\n for (var j = 0, jlen = legend.length; j < jlen; j++) {\r\n valuesMetadata.push(seriesSources[j]);\r\n }\r\n\r\n var labels = converterHelper.createAxesLabels(xAxisCardProperties, valueAxisProperties, categoryMetadata, valuesMetadata);\r\n\r\n return {\r\n categories: categories,\r\n categoriesWidth: result.categoriesWidth,\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 borderSettings: borderSettings,\r\n labelSettings: labelSettings,\r\n axesLabels: { x: labels.xAxisLabel, y: labels.yAxisLabel },\r\n hasDynamicSeries: result.hasDynamicSeries,\r\n defaultDataPointColor: defaultDataPointColor,\r\n showAllDataPoints: showAllDataPoints,\r\n isMultiMeasure: false,\r\n };\r\n }\r\n\r\n private static getStackedMultiplier(\r\n rawValues: number[][],\r\n rowIdx: number,\r\n seriesCount: number,\r\n categoryCount: number): ValueMultiplers {\r\n\r\n var pos: number = 0,\r\n neg: number = 0;\r\n\r\n for (var i = 0; i < seriesCount; i++) {\r\n var value: number = rawValues[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\r\n var absTotal: number = 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 private static createDataPoints(\r\n dataViewCat: DataViewCategorical,\r\n categories: any[],\r\n categoryIdentities: DataViewScopeIdentity[],\r\n legend: MekkoLegendDataPoint[],\r\n seriesObjectsList: DataViewObjects[][],\r\n converterStrategy: ColumnChartConverterHelper,\r\n\r\n defaultLabelSettings: VisualDataLabelsSettings,\r\n is100PercentStacked: boolean = false,\r\n isScalar: boolean = false,\r\n supportsOverflow: boolean = false,\r\n isCategoryAlsoSeries?: boolean,\r\n categoryObjectsList?: DataViewObjects[],\r\n defaultDataPointColor?: string,\r\n chartType?: ColumnChartType,\r\n categoryMetadata?: DataViewMetadataColumn): MekkoDataPoints {\r\n\r\n var grouped = dataViewCat && dataViewCat.values ? dataViewCat.values.grouped() : undefined;\r\n\r\n var categoryCount = categories.length;\r\n var seriesCount = legend.length;\r\n var columnSeries: ColumnChartSeries[] = [];\r\n\r\n if (seriesCount < 1 || categoryCount < 1 || categories[0] === null) {\r\n return { series: columnSeries,\r\n hasHighlights: false,\r\n hasDynamicSeries: false,\r\n categoriesWidth: [],\r\n };\r\n\t\t\t}\r\n\r\n var dvCategories = dataViewCat.categories;\r\n categoryMetadata = (dvCategories && dvCategories.length > 0)\r\n ? dvCategories[0].source\r\n : null;\r\n var categoryType = AxisHelper.getCategoryValueType(categoryMetadata);\r\n var isDateTime = AxisHelper.isDateTime(categoryType);\r\n var baseValuesPos = [], baseValuesNeg = [];\r\n\r\n var rawValues: number[][] = [];\r\n var rawHighlightValues: number[][] = [];\r\n\r\n var hasDynamicSeries = !!(dataViewCat.values && dataViewCat.values.source);\r\n var widthColumns: number[] = [];\r\n var widthIndex = -1;\r\n\r\n\t\t\tvar seriesIndex: number = 0;\r\n var highlightsOverflow = false; // Overflow means the highlight larger than value or the signs being different\r\n var hasHighlights = converterStrategy.hasHighlightValues(0);\r\n for (seriesIndex = 0; seriesIndex < dataViewCat.values.length; seriesIndex++) {\r\n if (dataViewCat.values[seriesIndex].source.roles &&\r\n dataViewCat.values[seriesIndex].source.roles[RoleNames.width] &&\r\n !dataViewCat.values[seriesIndex].source.roles[RoleNames.y]) {\r\n\r\n widthIndex = seriesIndex;\r\n var widthValues: number[] = dataViewCat.values[seriesIndex].values;\r\n for (var i: number = 0, valuesLen = widthValues.length; i < valuesLen; i++) {\r\n widthColumns[i] = d3.sum([0, widthColumns[i], widthValues[i]]);\r\n }\r\n continue;\r\n }\r\n var seriesValues = [];\r\n var seriesHighlightValues = [];\r\n for (var categoryIndex: number = 0; categoryIndex < categoryCount; categoryIndex++) {\r\n var value = converterStrategy.getValueBySeriesAndCategory(seriesIndex, categoryIndex);\r\n seriesValues[categoryIndex] = value;\r\n if (hasHighlights) {\r\n var 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\t\t\t//console.log(dataViewCat);\r\n\r\n if (highlightsOverflow && !supportsOverflow) {\r\n highlightsOverflow = false;\r\n hasHighlights = false;\r\n rawValues = rawHighlightValues;\r\n }\r\n\r\n if (widthColumns.length < 1) {\r\n for (seriesIndex = 0; seriesIndex < dataViewCat.values.length; seriesIndex++) {\r\n if (dataViewCat.values[seriesIndex].source.roles &&\r\n dataViewCat.values[seriesIndex].source.roles[RoleNames.width]) {\r\n\r\n widthIndex = seriesIndex;\r\n var widthValues: number[] = dataViewCat.values[seriesIndex].values;\r\n for (var i: number = 0, valuesLen: number = widthValues.length; i < valuesLen; i++) {\r\n widthColumns[i] = d3.sum([0, widthColumns[i], widthValues[i]]);\r\n }\r\n continue;\r\n }\r\n }\r\n }\r\n\r\n if (widthColumns.length < 1) {\r\n for (seriesIndex = 0; seriesIndex < categoryCount; seriesIndex++) {\r\n widthColumns.push(1);\r\n }\r\n }\r\n\r\n var totalSum: number = d3.sum(widthColumns);\r\n var linearScale = d3.scale.linear()\r\n .domain([0, totalSum])\r\n .range([0, 1]);\r\n\r\n var columnStartX: number[] = [0];\r\n var columnWidth: number[] = [];\r\n for (seriesIndex = 0; seriesIndex < (categoryCount - 1); seriesIndex++) {\r\n var stepWidth: number = columnStartX[columnStartX.length - 1] + (widthColumns[seriesIndex] || 0);\r\n columnStartX.push(stepWidth);\r\n }\r\n\r\n for (seriesIndex = 0; seriesIndex < categoryCount; seriesIndex++) {\r\n columnStartX[seriesIndex] = linearScale(columnStartX[seriesIndex]);\r\n columnWidth[seriesIndex] = linearScale(widthColumns[seriesIndex]);\r\n }\r\n\r\n var dataPointObjects: DataViewObjects[] = categoryObjectsList,\r\n formatStringProp = columnChartProps.general.formatString;\r\n for (seriesIndex = 0; seriesIndex < seriesCount; seriesIndex++) {\r\n var seriesDataPoints: ColumnChartDataPoint[] = [],\r\n legendItem = legend[seriesIndex],\r\n seriesLabelSettings: VisualDataLabelsSettings;\r\n\r\n if (!hasDynamicSeries) {\r\n var labelsSeriesGroup = grouped && grouped.length > 0 && grouped[0].values ? grouped[0].values[seriesIndex] : null;\r\n var 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 var series: ColumnChartSeries = {\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 }\r\n var metadata = dataViewCat.values[seriesIndex].source;\r\n\r\n for (var 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 var 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\r\n var originalValue: number = value;\r\n var categoryValue = categories[categoryIndex];\r\n if (isDateTime && categoryValue) {\r\n categoryValue = categoryValue.getTime();\r\n }\r\n if (isScalar && (categoryValue == null || isNaN(categoryValue))) {\r\n continue;\r\n }\r\n\r\n var multipliers: ValueMultiplers;\r\n if (is100PercentStacked) {\r\n //multipliers = StackedUtil.getStackedMultiplier(dataViewCat, categoryIndex, seriesCount, categoryCount, converterStrategy);\r\n multipliers = MekkoColumnChart.getStackedMultiplier(rawValues, categoryIndex, seriesCount, categoryCount);\r\n }\r\n var 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\r\n var valueAbsolute = Math.abs(value);\r\n var 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 }\r\n else {\r\n if (!isNaN(valueAbsolute)) {\r\n baseValuesPos[categoryIndex] += valueAbsolute;\r\n }\r\n\r\n position = baseValuesPos[categoryIndex];\r\n }\r\n\r\n var columnGroup: DataViewValueColumnGroup = grouped && grouped.length > seriesIndex && grouped[seriesIndex].values ? grouped[seriesIndex] : null;\r\n var category: DataViewCategoryColumn = dataViewCat.categories && dataViewCat.categories.length > 0 ? dataViewCat.categories[0] : null;\r\n var identity = SelectionIdBuilder.builder()\r\n .withCategory(category, categoryIndex)\r\n .withSeries(dataViewCat.values, columnGroup)\r\n .withMeasure(converterStrategy.getMeasureNameByIndex(seriesIndex))\r\n .createSelectionId();\r\n\r\n var rawCategoryValue = categories[categoryIndex];\r\n var color = MekkoColumnChart.getDataPointColor(legendItem, categoryIndex, dataPointObjects);\r\n\r\n var seriesData: TooltipSeriesDataItem[] = [];\r\n\r\n if (columnGroup) {\r\n\r\n var seriesValueColumn: DataViewValueColumn = {\r\n values: [],\r\n source: dataViewCat.values.source,\r\n };\r\n seriesData.push({\r\n value: columnGroup.name,\r\n metadata: seriesValueColumn,\r\n });\r\n\r\n for (var columnIndex: number = 0; columnIndex < columnGroup.values.length; columnIndex++) {\r\n var columnValues: DataViewValueColumn = columnGroup.values[columnIndex];\r\n seriesData.push({\r\n value: columnValues.values[categoryIndex],\r\n metadata: columnValues,\r\n });\r\n }\r\n }\r\n\r\n var tooltipInfo: TooltipDataItem[] = TooltipBuilder.createTooltipInfo(formatStringProp, null/*dataViewCat*/, rawCategoryValue, originalValue, [category], seriesData, null/*seriesIndex*/, categoryIndex);\r\n\r\n var dataPointLabelSettings = (series && series.labelSettings) ? series.labelSettings : defaultLabelSettings;\r\n var labelColor = dataPointLabelSettings.labelColor;\r\n var lastValue = undefined;\r\n //Stacked column/bar label color is white by default (except last series)\r\n if ((EnumExtensions.hasFlag(chartType, flagStacked))) {\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 value = columnWidth[categoryIndex];\r\n var originalPosition: number = columnStartX[categoryIndex];\r\n\r\n var 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: 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: metadata.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 var valueHighlight = rawHighlightValues[seriesIndex][categoryIndex];\r\n var unadjustedValueHighlight = valueHighlight;\r\n\r\n var 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 var absoluteValueHighlight = Math.abs(valueHighlight);\r\n var 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 var highlightIdentity = SelectionId.createWithHighlight(identity);\r\n var rawCategoryValue = categories[categoryIndex];\r\n //var highlightedValue: number = highlightedTooltip ? valueHighlight : undefined;\r\n //var tooltipInfo: TooltipDataItem[] = TooltipBuilder.createTooltipInfo(formatStringProp, dataViewCat, rawCategoryValue, originalValue, null, null, seriesIndex, categoryIndex, highlightedValue);\r\n\r\n if (highlightedTooltip) {\r\n // Override non highlighted data point\r\n dataPoint.tooltipInfo = tooltipInfo;\r\n }\r\n\r\n var highlightDataPoint: ColumnChartDataPoint = {\r\n categoryValue: categoryValue,\r\n value: value,\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: originalPosition,\r\n originalValueAbsolute: valueAbsolute,\r\n drawThinner: highlightsOverflow,\r\n identity: highlightIdentity,\r\n key: highlightIdentity.getKey(),\r\n tooltipInfo: tooltipInfo,\r\n labelFormatString: metadata.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 columnSeries.push(series);\r\n }\r\n\r\n return {\r\n series: columnSeries,\r\n categoriesWidth: columnWidth,\r\n hasHighlights: hasHighlights,\r\n hasDynamicSeries: hasDynamicSeries,\r\n };\r\n }\r\n\r\n private static getDataPointColor(\r\n legendItem: MekkoLegendDataPoint,\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 var colorOverride = DataViewObjects.getFillColor(dataPointObjects[categoryIndex], columnChartProps.dataPoint.fill);\r\n if (colorOverride) {\r\n return colorOverride;\r\n }\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 var lastValue = !(isNegative && seriesIndex === seriesCount - 1 && seriesCount !== 1);\r\n //run for the next series and check if current series is last\r\n for (var i: number = seriesIndex + 1; i < seriesCount; i++) {\r\n var nextValues: number = 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 var newSeries: ColumnChartSeries[] = [];\r\n if (series && series.length > 0) {\r\n for (var i = 0, len = series.length; i < len; i++) {\r\n var iNewSeries = newSeries[i] = Prototype.inherit(series[i]);\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 public static getInteractiveColumnChartDomElement(element: JQuery): HTMLElement {\r\n return element.children(\"svg\").get(0);\r\n }\r\n\r\n public getColumnsWidth(): number[] {\r\n var data: MekkoColumnChartData = this.data;\r\n if (!data ||\r\n !data.series ||\r\n !data.series[0] ||\r\n !data.series[0].data) {\r\n return [];\r\n }\r\n\r\n return data.categoriesWidth;\r\n }\r\n\r\n public getBorderWidth(): number {\r\n return MekkoColumnChart.getBorderWidth(this.data.borderSettings);\r\n }\r\n\r\n public setData(dataViews: DataView[]): void {\r\n debug.assertValue(dataViews, \"dataViews\");\r\n var is100PctStacked: boolean = true;\r\n this.data = {\r\n categories: [],\r\n categoriesWidth: [],\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 borderSettings: null,\r\n labelSettings: dataLabelUtils.getDefaultColumnLabelSettings(is100PctStacked),\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 var dataView = dataViews[0];\r\n\r\n if (dataView && dataView.categorical) {\r\n var dataViewCat = this.dataViewCat = dataView.categorical;\r\n /*\r\n var dvCategories = dataViewCat.categories;\r\n var categoryMetadata = (dvCategories && dvCategories.length > 0)\r\n ? dvCategories[0].source\r\n : null;\r\n var categoryType = AxisHelper.getCategoryValueType(categoryMetadata);\r\n */\r\n this.data = MekkoColumnChart.converter(\r\n dataViewCat,\r\n this.cartesianVisualHost.getSharedColors(),\r\n true,//s100PctStacked,\r\n false,//CartesianChart.getIsScalar(dataView.metadata ? dataView.metadata.objects : null, columnChartProps.categoryAxis.axisType, categoryType),\r\n this.supportsOverflow,\r\n dataView.metadata,\r\n this.chartType);\r\n\r\n var series: ColumnChartSeries[] = this.data.series;\r\n for (var i: number = 0, ilen: number = series.length; i < ilen; i++) {\r\n var currentSeries: ColumnChartSeries = series[i];\r\n if (this.interactivityService) {\r\n this.interactivityService.applySelectionStateToData(currentSeries.data);\r\n }\r\n }\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.createInteractiveMekkoLegendDataPoints(0);\r\n }\r\n var legendData = this.data ? this.data.legendData : null;\r\n var MekkoLegendDataPoints = legendData ? legendData.dataPoints : [];\r\n\r\n if (ArrayExtensions.isUndefinedOrEmpty(MekkoLegendDataPoints))\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 (!GradientUtils.hasGradientRole(this.dataViewCat))\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 var data = this.data,\r\n labelSettings = this.data.labelSettings,\r\n seriesCount = data.series.length;\r\n\r\n //Draw default settings\r\n dataLabelUtils.enumerateDataLabels(this.getLabelSettingsOptions(enumeration, labelSettings, false));\r\n\r\n if (seriesCount === 0) {\r\n return;\r\n }\r\n\r\n //Draw series settings\r\n if (!data.hasDynamicSeries && (seriesCount > 1 || !data.categoryMetadata)) {\r\n for (var i = 0; i < seriesCount; i++) {\r\n var series: ColumnChartSeries = 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, true, series));\r\n //enumeration.popContainer();\r\n }\r\n }\r\n }\r\n\r\n private getLabelSettingsOptions(enumeration: ObjectEnumerationBuilder, labelSettings: VisualDataLabelsSettings, isSeries: boolean, series?: ColumnChartSeries): VisualDataLabelsSettingsOptions {\r\n var is100PctStacked: boolean = true;\r\n return {\r\n enumeration: enumeration,\r\n dataLabelsSettings: labelSettings,\r\n show: !isSeries,\r\n displayUnits: is100PctStacked,\r\n precision: true,\r\n selector: series && series.identity ? series.identity.getSelector() : null\r\n };\r\n }\r\n\r\n private enumerateDataPoints(enumeration: ObjectEnumerationBuilder): void {\r\n var data: MekkoColumnChartData = this.data;\r\n if (!data || !data.series) {\r\n return;\r\n }\r\n\r\n var seriesCount = data.series.length;\r\n\r\n if (seriesCount === 0) {\r\n return;\r\n }\r\n\r\n if (data.hasDynamicSeries || seriesCount > 1 || !data.categoryMetadata) {\r\n for (var i: number = 0; i < seriesCount; i++) {\r\n var series: ColumnChartSeries = 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 var singleSeriesData: ColumnChartDataPoint[] = data.series[0].data;\r\n var categoryFormatter: IValueFormatter = 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 for (var i: number = 0; i < singleSeriesData.length; i++) {\r\n var 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 public calculateAxesProperties(options: MekkoCalculateScaleAndDomainOptions): IAxisProperties[] {\r\n var data: MekkoColumnChartData = this.data;\r\n this.currentViewport = options.viewport;\r\n var margin: IMargin = this.margin = options.margin;\r\n\r\n var origCatgSize = (data && data.categories) ? data.categories.length : 0;\r\n var 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 this.columnChart.setData(data);\r\n\r\n var preferredPlotArea = this.getPreferredPlotArea(chartLayout.isScalar, chartLayout.categoryCount, chartLayout.categoryThickness);\r\n\r\n /* preferredPlotArea would be same as currentViewport width when there is no scrollbar.\r\n In that case we want to calculate the available plot area for the shapes by subtracting the margin from available viewport */\r\n if (preferredPlotArea.width === this.currentViewport.width) {\r\n preferredPlotArea.width -= (margin.left + margin.right);\r\n }\r\n preferredPlotArea.height -= (margin.top + margin.bottom);\r\n\r\n var is100Pct: boolean = true;\r\n\r\n // When the category axis is scrollable the height of the category axis and value axis will be different\r\n // The height of the value axis would be same as viewportHeight\r\n var chartContext: MekkoColumnChartContext = {\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 labelGraphicsContext: this.labelGraphicsContext,\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: true,\r\n };\r\n this.ApplyInteractivity(chartContext);\r\n this.columnChart.setupVisualProps(chartContext);\r\n\r\n var isBarChart = EnumExtensions.hasFlag(this.chartType, flagBar);\r\n\r\n if (isBarChart) {\r\n var temp = options.forcedXDomain;\r\n options.forcedXDomain = options.forcedYDomain;\r\n options.forcedYDomain = temp;\r\n }\r\n\r\n this.xAxisProperties = this.columnChart.setXScale(is100Pct, options.forcedTickCount, options.forcedXDomain, isBarChart ? options.valueAxisScaleType : options.categoryAxisScaleType);\r\n this.yAxisProperties = this.columnChart.setYScale(is100Pct, options.forcedTickCount, options.forcedYDomain, isBarChart ? options.categoryAxisScaleType : options.valueAxisScaleType);\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 var viewport: IViewport = {\r\n height: this.currentViewport.height,\r\n width: this.currentViewport.width\r\n };\r\n\r\n if (this.isScrollable && !isScalar) {\r\n var preferredWidth = CartesianChart.getPreferredCategorySpan(categoryCount, categoryThickness);\r\n if (EnumExtensions.hasFlag(this.chartType, flagBar)) {\r\n viewport.height = Math.max(preferredWidth, viewport.height);\r\n }\r\n else\r\n viewport.width = Math.max(preferredWidth, viewport.width);\r\n }\r\n return viewport;\r\n }\r\n\r\n private ApplyInteractivity(chartContext: MekkoColumnChartContext): void {\r\n var 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 var dragMove = () => {\r\n var mousePoint = d3.mouse(this.mainGraphicsContext[0][0]); // get the x and y for the column area itself\r\n var x: number = mousePoint[0];\r\n var y: number = mousePoint[1];\r\n var index: number = this.columnChart.getClosestColumnIndex(x, y);\r\n this.selectColumn(index);\r\n };\r\n\r\n var 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).on('click', dragMove);\r\n var 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 var legendData: LegendData = this.createInteractiveMekkoLegendDataPoints(indexOfColumnSelected);\r\n var MekkoLegendDataPoints: MekkoLegendDataPoint[] = legendData.dataPoints;\r\n this.cartesianVisualHost.updateLegend(legendData);\r\n if (MekkoLegendDataPoints.length > 0) {\r\n this.columnChart.selectColumn(indexOfColumnSelected, this.lastInteractiveSelectedColumnIndex);\r\n }\r\n this.lastInteractiveSelectedColumnIndex = indexOfColumnSelected;\r\n }\r\n\r\n private createInteractiveMekkoLegendDataPoints(columnIndex: number): LegendData {\r\n var data: MekkoColumnChartData = this.data;\r\n if (!data || ArrayExtensions.isUndefinedOrEmpty(data.series)) {\r\n return { dataPoints: [] };\r\n }\r\n\r\n var formatStringProp = columnChartProps.general.formatString;\r\n var MekkoLegendDataPoints: MekkoLegendDataPoint[] = [];\r\n var category = data.categories && data.categories[columnIndex];\r\n var allSeries: ColumnChartSeries[] = data.series;\r\n var dataPoints = data.legendData && data.legendData.dataPoints;\r\n var converterStrategy = new ColumnChartConverterHelper(this.dataViewCat);\r\n\r\n for (var i: number = 0, len = allSeries.length; i < len; i++) {\r\n var measure = converterStrategy.getValueBySeriesAndCategory(i, columnIndex);\r\n var valueMetadata = data.valuesMetadata[i];\r\n var formattedLabel = converterHelper.getFormattedLegendLabel(valueMetadata, this.dataViewCat.values, formatStringProp);\r\n var dataPointColor: string;\r\n if (allSeries.length === 1) {\r\n var 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 MekkoLegendDataPoints.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: MekkoLegendDataPoints };\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): MekkoVisualRenderResult {\r\n var MekkoColumnChartDrawInfo = this.columnChart.drawColumns(!suppressAnimations /* useAnimations */);\r\n var data: MekkoColumnChartData = this.data;\r\n\r\n var margin = this.margin;\r\n var viewport = this.currentViewport;\r\n var height = viewport.height - (margin.top + margin.bottom);\r\n var 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 TooltipManager.addTooltip(MekkoColumnChartDrawInfo.shapesSelection, (tooltipEvent: TooltipEvent) => tooltipEvent.data.tooltipInfo);\r\n var allDataPoints: ColumnChartDataPoint[] = [];\r\n var behaviorOptions: ColumnBehaviorOptions = undefined;\r\n if (this.interactivityService) {\r\n for (var i: number = 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 bars: MekkoColumnChartDrawInfo.shapesSelection,\r\n hasHighlights: data.hasHighlights,\r\n eventGroup: this.mainGraphicsContext,\r\n mainGraphicsContext: this.mainGraphicsContext,\r\n viewport: MekkoColumnChartDrawInfo.viewport,\r\n axisOptions: MekkoColumnChartDrawInfo.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(0, true); // start with the first column\r\n }\r\n }\r\n SVGUtil.flushAllD3TransitionsIfNeeded(this.options);\r\n return {\r\n dataPoints: allDataPoints,\r\n behaviorOptions: behaviorOptions,\r\n labelDataPoints: MekkoColumnChartDrawInfo.labelDataPoints,\r\n labelsAreNumeric: true\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\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 var metaDataColumn = this.data ? this.data.categoryMetadata : undefined;\r\n var valueType = AxisHelper.getCategoryValueType(metaDataColumn);\r\n var 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 var 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\r\n class ColumnChartConverterHelper implements IColumnChartConverterStrategy {\r\n private dataView: DataViewCategorical;\r\n\r\n constructor(dataView: DataViewCategorical) {\r\n this.dataView = dataView;\r\n }\r\n\r\n\t\t private static hasRole(column: DataViewMetadataColumn, name: string): boolean {\r\n var roles = column.roles;\r\n return roles && roles[name];\r\n }\r\n\r\n public getLegend(colors: IDataColorPalette, defaultColor?: string): LegendSeriesInfo {\r\n var legend: MekkoLegendDataPoint[] = [];\r\n var seriesSources: DataViewMetadataColumn[] = [];\r\n var seriesObjects: DataViewObjects[][] = [];\r\n var grouped: boolean = false;\r\n\r\n var colorHelper = new ColorHelper(colors, columnChartProps.dataPoint.fill, defaultColor);\r\n var legendTitle = undefined;\r\n if (this.dataView && this.dataView.values) {\r\n var allValues = this.dataView.values;\r\n var valueGroups = allValues.grouped();\r\n\r\n var hasDynamicSeries = !!(allValues && allValues.source);\r\n\r\n var formatStringProp = columnChartProps.general.formatString;\r\n for (var valueGroupsIndex = 0, valueGroupsLen = valueGroups.length; valueGroupsIndex < valueGroupsLen; valueGroupsIndex++) {\r\n var valueGroup = valueGroups[valueGroupsIndex],\r\n valueGroupObjects = valueGroup.objects,\r\n values = valueGroup.values;\r\n\r\n for (var valueIndex = 0, valuesLen = values.length; valueIndex < valuesLen; valueIndex++) {\r\n var series: DataViewValueColumn = values[valueIndex];\r\n var source: DataViewMetadataColumn = series.source;\r\n // Gradient measures do not create series.\r\n if (ColumnChartConverterHelper.hasRole(source, 'Width') && !ColumnChartConverterHelper.hasRole(source, 'Y')) {\r\n continue;\r\n }\r\n\r\n seriesSources.push(source);\r\n seriesObjects.push(series.objects);\r\n\r\n var selectionId = series.identity ?\r\n SelectionId.createWithIdAndMeasure(series.identity, source.queryName) :\r\n SelectionId.createWithMeasure(this.getMeasureNameByIndex(valueIndex));\r\n\r\n var label = converterHelper.getFormattedLegendLabel(source, allValues, formatStringProp);\r\n\r\n var color = hasDynamicSeries\r\n ? colorHelper.getColorForSeriesValue(valueGroupObjects || source.objects, allValues.identityFields, source.groupName)\r\n : colorHelper.getColorForMeasure(valueGroupObjects || 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 var dvValues: DataViewValueColumns = this.dataView.values;\r\n legendTitle = dvValues && dvValues.source ? dvValues.source.displayName : \"\";\r\n }\r\n\r\n var legendData = {\r\n title: legendTitle,\r\n dataPoints: legend,\r\n grouped: grouped,\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.dataView.values[series].values[category];\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 var 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 export interface MekkoBehaviorOptions {\r\n layerOptions: any[];\r\n clearCatcher: D3.Selection;\r\n }\r\n\r\n export class MekkoChartBehavior 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: MekkoBehaviorOptions, selectionHandler: ISelectionHandler): void {\r\n var behaviors = this.behaviors;\r\n for (var i: number = 0, ilen: number = 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 (var i: number = 0; i < this.behaviors.length; i++) {\r\n this.behaviors[i].renderSelection(hasSelection);\r\n }\r\n }\r\n }\r\n}\r\n\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/CustomVisuals/visuals/mekkoChart/visual/mekkoChart.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 import ClassAndSelector = jsCommon.CssConstants.ClassAndSelector;\r\n import ValueFormatter = powerbi.visuals.valueFormatter;\r\n import pixelConverterFromPoint = jsCommon.PixelConverter.fromPoint;\r\n\r\n export interface SankeyDiagramConstructorOptions {\r\n svg?: D3.Selection;\r\n margin?: IMargin;\r\n curvatureOfLinks?: number;\r\n }\r\n\r\n export interface SankeyDiagramLabel {\r\n name: string;\r\n formattedName: string;\r\n width: number;\r\n height: number;\r\n colour: string;\r\n }\r\n\r\n export interface SankeyDiagramTooltipData {\r\n tooltipData: TooltipDataItem[];\r\n }\r\n\r\n export interface SankeyDiagramScale {\r\n x: number;\r\n y: number;\r\n }\r\n\r\n export interface SankeyDiagramSettings {\r\n scale?: SankeyDiagramScale;\r\n fontSize: number;\r\n isVisibleLabels?: boolean;\r\n colourOfLabels: string;\r\n }\r\n\r\n export interface SankeyDiagramNode extends SankeyDiagramTooltipData {\r\n label: SankeyDiagramLabel;\r\n inputWeight: number;\r\n outputWeight: number;\r\n links: SankeyDiagramLink[];\r\n x?: number;\r\n y?: number;\r\n width?: number;\r\n height?: number;\r\n colour: string;\r\n selectionIds: SelectionId[];\r\n }\r\n\r\n export interface SankeyDiagramLink extends SankeyDiagramTooltipData {\r\n source: SankeyDiagramNode;\r\n destination: SankeyDiagramNode;\r\n weigth: number;\r\n height?: number;\r\n dySource?: number;\r\n dyDestination?: number;\r\n colour: string;\r\n selectionId: SelectionId;\r\n }\r\n\r\n export interface SankeyDiagramColumn {\r\n countOfNodes: number;\r\n sumValueOfNodes: number;\r\n }\r\n\r\n export interface SankeyDiagramDataView {\r\n nodes: SankeyDiagramNode[];\r\n links: SankeyDiagramLink[];\r\n columns: SankeyDiagramColumn[];\r\n settings: SankeyDiagramSettings;\r\n }\r\n\r\n export interface SankeyDiagramRoleNames {\r\n rows: string;\r\n columns: string;\r\n values: string;\r\n }\r\n\r\n interface SankeyDiagramDataPoint {\r\n source: any;\r\n destination: any;\r\n weigth: number;\r\n }\r\n\r\n interface SankeyDiagramProperty {\r\n [propertyName: string]: DataViewObjectPropertyIdentifier;\r\n }\r\n\r\n interface SankeyDiagramProperties {\r\n [objectName: string]: SankeyDiagramProperty;\r\n }\r\n\r\n export class SankeyDiagram implements IVisual {\r\n private static ClassName: string = \"sankeyDiagram\";\r\n\r\n private static Nodes: ClassAndSelector = {\r\n \"class\": \"nodes\",\r\n selector: \".nodes\"\r\n };\r\n\r\n private static Node: ClassAndSelector = {\r\n \"class\": \"node\",\r\n selector: \".node\"\r\n };\r\n\r\n private static NodeRect: ClassAndSelector = {\r\n \"class\": \"nodeRect\",\r\n selector: \".nodeRect\"\r\n };\r\n\r\n private static NodeLabel: ClassAndSelector = {\r\n \"class\": \"nodeLabel\",\r\n selector: \".nodeLabel\"\r\n };\r\n\r\n private static Links: ClassAndSelector = {\r\n \"class\": \"links\",\r\n selector: \".links\"\r\n };\r\n\r\n private static Link: ClassAndSelector = {\r\n \"class\": \"link\",\r\n selector: \".link\"\r\n };\r\n\r\n private static DefaultColourOfNode: string = \"rgb(62, 187, 162)\";\r\n private static DefaultColourOfLink: string = \"black\";\r\n\r\n private static DefaultSettings: SankeyDiagramSettings = {\r\n isVisibleLabels: true,\r\n scale: { x: 1, y: 1 },\r\n colourOfLabels: \"black\",\r\n fontSize: 12\r\n };\r\n\r\n private static MinWidthOfLabel: number = 35;\r\n\r\n private static NodeBottomMargin: number = 5; // 5%\r\n\r\n private static NodeMargin: number = 5;\r\n private static LabelMargin: number = 4;\r\n\r\n public static RoleNames: SankeyDiagramRoleNames = {\r\n rows: \"Source\",\r\n columns: \"Destination\",\r\n values: \"Weight\"\r\n };\r\n\r\n public static capabilities: VisualCapabilities = {\r\n dataRoles: [{\r\n name: SankeyDiagram.RoleNames.rows,\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: SankeyDiagram.RoleNames.rows\r\n }, {\r\n name: SankeyDiagram.RoleNames.columns,\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: SankeyDiagram.RoleNames.columns\r\n }, {\r\n name: SankeyDiagram.RoleNames.values,\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: SankeyDiagram.RoleNames.values\r\n }],\r\n dataViewMappings: [{\r\n conditions: [\r\n { \"Source\": { min: 0, max: 1 }, \"Destination\": { min: 0, max: 1 }, \"Weight\": { min: 0, max: 0 } },\r\n { \"Source\": { min: 0, max: 1 }, \"Destination\": { min: 0, max: 1 }, \"Weight\": { min: 1, max: 1 } }\r\n ],\r\n categorical: {\r\n categories: { \r\n for: { in: SankeyDiagram.RoleNames.rows },\r\n dataReductionAlgorithm: { top: {} }\r\n },\r\n values: {\r\n select: [\r\n { bind: { to: SankeyDiagram.RoleNames.columns } },\r\n { bind: { to: SankeyDiagram.RoleNames.values } }\r\n ]\r\n }\r\n }\r\n }],\r\n objects: {\r\n general: {\r\n displayName: data.createDisplayNameGetter(\"Visual_General\"),\r\n properties: {\r\n formatString: { type: { formatting: { formatString: true } } }\r\n }\r\n },\r\n labels: {\r\n displayName: data.createDisplayNameGetter('Visual_DataPointsLabels'),\r\n properties: {\r\n show: {\r\n displayName: data.createDisplayNameGetter(\"Visual_Show\"),\r\n type: { bool: true }\r\n },\r\n fill: {\r\n displayName: data.createDisplayNameGetter('Visual_Fill'),\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 }\r\n },\r\n links: {\r\n displayName: \"Links\",\r\n properties: {\r\n fill: {\r\n displayName: data.createDisplayNameGetter('Visual_Fill'),\r\n type: { fill: { solid: { color: true } } }\r\n }\r\n }\r\n }\r\n }\r\n };\r\n\r\n private static Properties: SankeyDiagramProperties = SankeyDiagram.getProperties(SankeyDiagram.capabilities);\r\n\r\n public static getProperties(capabilities: VisualCapabilities): any {\r\n var result = {};\r\n\r\n for (var objectKey in capabilities.objects) {\r\n result[objectKey] = {};\r\n\r\n for (var propKey in capabilities.objects[objectKey].properties) {\r\n result[objectKey][propKey] = <DataViewObjectPropertyIdentifier> { \r\n objectName: objectKey,\r\n propertyName: propKey\r\n };\r\n }\r\n }\r\n\r\n return result;\r\n }\r\n\r\n private margin: IMargin = {\r\n top: 10,\r\n right: 10,\r\n bottom: 10,\r\n left: 10\r\n };\r\n\r\n private nodeWidth: number = 21.5;\r\n private curvatureOfLinks: number = 0.5;\r\n\r\n private root: D3.Selection;\r\n private svg: D3.Selection;\r\n private main: D3.Selection;\r\n private nodes: D3.Selection;\r\n private links: D3.Selection;\r\n\r\n private colours: IDataColorPalette;\r\n\r\n private viewport: IViewport;\r\n\r\n private get textProperties(): TextProperties{\r\n return {\r\n fontFamily: this.root.style(\"font-family\"),\r\n fontSize: pixelConverterFromPoint(this.dataView\r\n ? this.dataView.settings.fontSize\r\n : SankeyDiagram.DefaultSettings.fontSize)\r\n };\r\n }\r\n\r\n private dataView: SankeyDiagramDataView;\r\n\r\n private selectionManager: SelectionManager;\r\n\r\n constructor(constructorOptions?: SankeyDiagramConstructorOptions) {\r\n if (constructorOptions) {\r\n this.svg = constructorOptions.svg;\r\n this.margin = constructorOptions.margin || this.margin;\r\n this.curvatureOfLinks = constructorOptions.curvatureOfLinks || this.curvatureOfLinks;\r\n }\r\n }\r\n\r\n public init(visualsInitOptions: VisualInitOptions): void {\r\n if (this.svg) {\r\n this.root = this.svg;\r\n } else {\r\n this.root = d3.select(visualsInitOptions.element.get(0))\r\n .append(\"svg\");\r\n }\r\n\r\n this.selectionManager = new SelectionManager({ hostServices: visualsInitOptions.host });\r\n\r\n var style: IVisualStyle = visualsInitOptions.style;\r\n\r\n this.colours = style && style.colorPalette\r\n ? style.colorPalette.dataColors\r\n : new DataColorPalette();\r\n\r\n this.root.classed(SankeyDiagram.ClassName, true);\r\n\r\n this.main = this.root.append(\"g\");\r\n\r\n this.links = this.main\r\n .append(\"g\")\r\n .classed(SankeyDiagram.Links[\"class\"], true);\r\n\r\n this.nodes = this.main\r\n .append(\"g\")\r\n .classed(SankeyDiagram.Nodes[\"class\"], true);\r\n }\r\n\r\n public update(visualUpdateOptions: VisualUpdateOptions): void {\r\n if (!visualUpdateOptions ||\r\n !visualUpdateOptions.dataViews) {\r\n return;\r\n }\r\n\r\n var dataView: DataView = visualUpdateOptions.dataViews[0],\r\n sankeyDiagramDataView: SankeyDiagramDataView;\r\n\r\n this.updateViewport(visualUpdateOptions.viewport);\r\n\r\n sankeyDiagramDataView = this.converter(dataView);\r\n\r\n this.computePositions(sankeyDiagramDataView);\r\n\r\n this.dataView = sankeyDiagramDataView;\r\n\r\n this.render(sankeyDiagramDataView);\r\n }\r\n\r\n private updateViewport(viewport: IViewport): void {\r\n var height: number,\r\n width: number;\r\n\r\n height = this.getPositiveNumber(viewport.height);\r\n width = this.getPositiveNumber(viewport.width);\r\n\r\n this.viewport = {\r\n height: this.getPositiveNumber(height - this.margin.top - this.margin.bottom),\r\n width: this.getPositiveNumber(width - this.margin.left - this.margin.right)\r\n };\r\n\r\n this.updateElements(height, width);\r\n }\r\n\r\n /**\r\n * Public for testability.\r\n */\r\n public getPositiveNumber(value: number): number {\r\n return value < 0 || isNaN(value) || value === null || value === Infinity || value === -Infinity\r\n ? 0\r\n : value;\r\n }\r\n\r\n private updateElements(height: number, width: number): void {\r\n this.root.attr({\r\n \"height\": height,\r\n \"width\": width\r\n });\r\n\r\n this.main.attr(\"transform\", SVGUtil.translate(this.margin.left, this.margin.top));\r\n }\r\n\r\n public converter(dataView: DataView): SankeyDiagramDataView {\r\n if (!dataView ||\r\n !dataView.categorical ||\r\n !dataView.categorical.categories ||\r\n !dataView.categorical.categories[0] ||\r\n !dataView.categorical.categories[1] ||\r\n !dataView.categorical.categories[0].values ||\r\n !dataView.categorical.categories[1].values) {\r\n return {\r\n nodes: [],\r\n links: [],\r\n columns: [],\r\n settings: {\r\n scale: { x: 1, y: 1 },\r\n colourOfLabels: SankeyDiagram.DefaultSettings.colourOfLabels,\r\n fontSize: SankeyDiagram.DefaultSettings.fontSize\r\n }\r\n };\r\n }\r\n\r\n var nodes: SankeyDiagramNode[] = [],\r\n links: SankeyDiagramLink[] = [],\r\n dataPoints: SankeyDiagramDataPoint[] = [],\r\n categories: any[] = dataView.categorical.categories[0].values,\r\n secondCategories: any[] = dataView.categorical.categories[1].values,\r\n valuesColumn: DataViewValueColumn = dataView.categorical.values && dataView.categorical.values[0],\r\n weightValues: number[] = [],\r\n allCategories: any[],\r\n valueFormatterForCategories: IValueFormatter,\r\n formatOfWeigth: string = \"g\",\r\n valuesFormatterForWeigth: IValueFormatter,\r\n objects: DataViewObjects,\r\n linksObjects: DataViewObjects[] = dataView.categorical.categories[0].objects || [],\r\n labelColour: string,\r\n settings: SankeyDiagramSettings,\r\n shiftOfColour: number,\r\n identities: DataViewScopeIdentity[] = [];\r\n\r\n if (valuesColumn && valuesColumn.values && valuesColumn.values.map) {\r\n weightValues = valuesColumn.values.map((x: any)=> {\r\n return x ? x : 0;\r\n });\r\n }\r\n\r\n if (dataView.categorical.categories[0].identity) {\r\n identities = identities.concat(dataView.categorical.categories[0].identity);\r\n }\r\n\r\n if (dataView.categorical.categories[1].identity) {\r\n identities = identities.concat(dataView.categorical.categories[1].identity);\r\n }\r\n\r\n objects = this.getObjectsFromDataView(dataView);\r\n\r\n labelColour = this.getColour(\r\n SankeyDiagram.Properties[\"labels\"][\"fill\"],\r\n SankeyDiagram.DefaultSettings.colourOfLabels,\r\n objects);\r\n\r\n if (valuesColumn && valuesColumn.source) {\r\n formatOfWeigth = ValueFormatter.getFormatString(\r\n valuesColumn.source,\r\n SankeyDiagram.Properties[\"general\"][\"formatString\"]);\r\n }\r\n\r\n dataPoints = categories.map((item: any, index: number) => {\r\n return {\r\n source: item,\r\n destination: secondCategories[index],\r\n weigth: valuesColumn ? Math.max(weightValues[index] || 0, 0) : 1\r\n };\r\n });\r\n\r\n allCategories = categories.concat(secondCategories);\r\n\r\n valueFormatterForCategories = ValueFormatter.create({\r\n format: ValueFormatter.getFormatString(\r\n dataView.categorical.categories[0].source,\r\n SankeyDiagram.Properties[\"general\"][\"formatString\"]),\r\n value: allCategories[0],\r\n value2: allCategories[allCategories.length - 1]\r\n });\r\n\r\n valuesFormatterForWeigth = ValueFormatter.create({\r\n format: formatOfWeigth,\r\n value: Math.max(d3.max(weightValues) || 1, 1),\r\n });\r\n\r\n allCategories.forEach((item: any, index: number) => {\r\n if (!nodes.some((node: SankeyDiagramNode) => {\r\n if (item === node.label.name) {\r\n node.selectionIds.push(SelectionId.createWithId(identities[index]));\r\n\r\n return true;\r\n }\r\n\r\n return false;\r\n })) {\r\n var formattedValue: string = valueFormatterForCategories.format(item),\r\n label: SankeyDiagramLabel,\r\n selectionId: SelectionId,\r\n textProperties: TextProperties = {\r\n text: formattedValue,\r\n fontFamily: this.textProperties.fontFamily,\r\n fontSize: this.textProperties.fontSize\r\n };\r\n\r\n label = {\r\n name: item,\r\n formattedName: valueFormatterForCategories.format(item),\r\n width: TextMeasurementService.measureSvgTextWidth(textProperties),\r\n height: TextMeasurementService.estimateSvgTextHeight(textProperties),\r\n colour: labelColour\r\n };\r\n\r\n selectionId = SelectionId.createWithId(identities[index]);\r\n\r\n nodes.push({\r\n label: label,\r\n links: [],\r\n inputWeight: 0,\r\n outputWeight: 0,\r\n width: this.nodeWidth,\r\n height: 0,\r\n colour: SankeyDiagram.DefaultColourOfNode,\r\n tooltipData: [],\r\n selectionIds: [selectionId]\r\n });\r\n }\r\n });\r\n\r\n shiftOfColour = this.colours.getAllColors().length / nodes.length;\r\n\r\n nodes.forEach((node: SankeyDiagramNode, index: number) => {\r\n node.colour = this.colours.getColorByIndex(Math.floor(index * shiftOfColour)).value;\r\n });\r\n\r\n dataPoints.forEach((dataPoint: SankeyDiagramDataPoint, index: number) => {\r\n var sourceNode: SankeyDiagramNode,\r\n destinationNode: SankeyDiagramNode,\r\n link: SankeyDiagramLink,\r\n linkColour: string;\r\n\r\n if (dataPoint.source === dataPoint.destination) {\r\n return;\r\n }\r\n\r\n nodes.forEach((node: SankeyDiagramNode) => {\r\n if (node.label.name === dataPoint.source) {\r\n sourceNode = node;\r\n }\r\n\r\n if (node.label.name === dataPoint.destination) {\r\n destinationNode = node;\r\n }\r\n });\r\n\r\n linkColour = this.getColour(\r\n SankeyDiagram.Properties[\"links\"][\"fill\"],\r\n SankeyDiagram.DefaultColourOfLink,\r\n linksObjects[index]);\r\n\r\n link = {\r\n source: sourceNode,\r\n destination: destinationNode,\r\n weigth: dataPoint.weigth,\r\n height: dataPoint.weigth,\r\n colour: linkColour,\r\n tooltipData: this.getTooltipDataForLink(\r\n valuesFormatterForWeigth,\r\n sourceNode.label.formattedName,\r\n destinationNode.label.formattedName,\r\n dataPoint.weigth),\r\n selectionId: SelectionId.createWithId(identities[index])\r\n };\r\n\r\n links.push(link);\r\n\r\n sourceNode.links.push(link);\r\n destinationNode.links.push(link);\r\n\r\n this.updateValueOfNode(sourceNode);\r\n this.updateValueOfNode(destinationNode);\r\n\r\n sourceNode.tooltipData = this.getTooltipForNode(\r\n valuesFormatterForWeigth,\r\n sourceNode.label.formattedName,\r\n sourceNode.inputWeight ? sourceNode.inputWeight : sourceNode.outputWeight);\r\n\r\n destinationNode.tooltipData = this.getTooltipForNode(\r\n valuesFormatterForWeigth,\r\n destinationNode.label.formattedName,\r\n destinationNode.inputWeight ? destinationNode.inputWeight : destinationNode.outputWeight);\r\n });\r\n\r\n settings = this.parseSettings(objects);\r\n\r\n settings.colourOfLabels = labelColour;\r\n\r\n return {\r\n nodes: nodes,\r\n links: links,\r\n settings: settings,\r\n columns: []\r\n };\r\n }\r\n\r\n private getObjectsFromDataView(dataView: DataView): DataViewObjects {\r\n if (!dataView ||\r\n !dataView.metadata ||\r\n !dataView.metadata.columns ||\r\n !dataView.metadata.objects) {\r\n return null;\r\n }\r\n\r\n return dataView.metadata.objects;\r\n }\r\n\r\n private getColour(properties: DataViewObjectPropertyIdentifier, defaultColor: string, objects: DataViewObjects): string {\r\n var colorHelper: ColorHelper;\r\n\r\n colorHelper = new ColorHelper(this.colours, properties, defaultColor);\r\n\r\n return colorHelper.getColorForMeasure(objects, \"\");\r\n }\r\n\r\n private getTooltipDataForLink(\r\n valueFormatter: IValueFormatter,\r\n sourceNodeName: string,\r\n destinationNodeName: string,\r\n linkWeight: number): TooltipDataItem[] {\r\n\r\n var formattedLinkWeight: string;\r\n\r\n if (valueFormatter && valueFormatter.format) {\r\n formattedLinkWeight = valueFormatter.format(linkWeight);\r\n } else {\r\n formattedLinkWeight = linkWeight.toString();\r\n }\r\n\r\n return [{\r\n displayName: SankeyDiagram.RoleNames.rows,\r\n value: sourceNodeName\r\n }, {\r\n displayName: SankeyDiagram.RoleNames.columns,\r\n value: destinationNodeName\r\n }, {\r\n displayName: SankeyDiagram.RoleNames.values,\r\n value: formattedLinkWeight\r\n }];\r\n }\r\n\r\n private updateValueOfNode(node: SankeyDiagramNode): void {\r\n node.inputWeight = node.links.reduce((previousValue: number, currentValue: SankeyDiagramLink) => {\r\n return previousValue + (currentValue.destination === node ? currentValue.weigth : 0);\r\n }, 0);\r\n\r\n node.outputWeight = node.links.reduce((previousValue: number, currentValue: SankeyDiagramLink) => {\r\n return previousValue + (currentValue.source === node ? currentValue.weigth : 0);\r\n }, 0);\r\n }\r\n\r\n private getTooltipForNode(\r\n valueFormatter: IValueFormatter,\r\n nodeName: string,\r\n nodeWeight: number): TooltipDataItem[] {\r\n\r\n var formattedNodeWeigth: string;\r\n\r\n if (valueFormatter && valueFormatter.format) {\r\n formattedNodeWeigth = valueFormatter.format(nodeWeight);\r\n } else {\r\n formattedNodeWeigth = nodeWeight.toString();\r\n }\r\n\r\n return [{\r\n displayName: \"Name\",\r\n value: nodeName\r\n }, {\r\n displayName: SankeyDiagram.RoleNames.values,\r\n value: formattedNodeWeigth\r\n }];\r\n }\r\n\r\n private parseSettings(objects: DataViewObjects): SankeyDiagramSettings {\r\n var isVisibleLabels: boolean = false;\r\n\r\n isVisibleLabels = DataViewObjects.getValue(\r\n objects,\r\n SankeyDiagram.Properties[\"labels\"][\"show\"],\r\n SankeyDiagram.DefaultSettings.isVisibleLabels);\r\n\r\n return {\r\n isVisibleLabels: isVisibleLabels,\r\n scale: {\r\n x: SankeyDiagram.DefaultSettings.scale.x,\r\n y: SankeyDiagram.DefaultSettings.scale.y\r\n },\r\n colourOfLabels: SankeyDiagram.DefaultSettings.colourOfLabels,\r\n fontSize: DataViewObjects.getValue<number>(objects, \r\n SankeyDiagram.Properties[\"labels\"][\"fontSize\"],\r\n SankeyDiagram.DefaultSettings.fontSize)\r\n };\r\n }\r\n\r\n private computePositions(sankeyDiagramDataView: SankeyDiagramDataView): void {\r\n var maxXPosition: number,\r\n maxColumn: SankeyDiagramColumn,\r\n columns: SankeyDiagramColumn[];\r\n\r\n maxXPosition = this.computeXPositions(sankeyDiagramDataView);\r\n\r\n this.sortNodesByX(sankeyDiagramDataView.nodes);\r\n\r\n columns = this.getColumns(sankeyDiagramDataView.nodes);\r\n maxColumn = this.getMaxColumn(columns);\r\n\r\n sankeyDiagramDataView.settings.scale.x = this.getScaleByAxisX(maxXPosition);\r\n sankeyDiagramDataView.settings.scale.y = this.getScaleByAxisY(maxColumn.sumValueOfNodes);\r\n\r\n this.scalePositionsByAxes(\r\n sankeyDiagramDataView.nodes,\r\n columns,\r\n sankeyDiagramDataView.settings.scale,\r\n this.viewport.height);\r\n\r\n this.computeYPosition(\r\n sankeyDiagramDataView.nodes,\r\n sankeyDiagramDataView.settings.scale.y);\r\n }\r\n\r\n private computeXPositions(sankeyDiagramDataView: SankeyDiagramDataView): number {\r\n var nodes: SankeyDiagramNode[] = sankeyDiagramDataView.nodes,\r\n nextNodes: SankeyDiagramNode[] = [],\r\n previousNodes: SankeyDiagramNode[] = [],\r\n x: number = 0,\r\n isRecursiveDependencies: boolean = false;\r\n\r\n while (nodes.length > 0) {\r\n nextNodes = [];\r\n\r\n nodes.forEach((node: SankeyDiagramNode) => {\r\n node.x = x;\r\n\r\n node.links.forEach((link: SankeyDiagramLink) => {\r\n if (node === link.source && node !== link.destination) {\r\n if (nextNodes.every((item: SankeyDiagramNode) => {\r\n return item !== link.destination;\r\n })) {\r\n nextNodes.push(link.destination);\r\n }\r\n }\r\n });\r\n });\r\n\r\n isRecursiveDependencies = nextNodes.length === previousNodes.length && \r\n previousNodes.every((previousNode: SankeyDiagramNode) => {\r\n return nextNodes.some((nextNode: SankeyDiagramNode) => {\r\n return nextNode === previousNode;\r\n });\r\n });\r\n\r\n if (isRecursiveDependencies) {\r\n previousNodes.forEach((element: SankeyDiagramNode) => {\r\n element.x = x;\r\n\r\n x++;\r\n });\r\n\r\n nodes = [];\r\n } else {\r\n nodes = nextNodes;\r\n\r\n previousNodes = nodes;\r\n\r\n x++;\r\n }\r\n }\r\n\r\n return x - 1;\r\n }\r\n\r\n private getScaleByAxisX(numberOfColumns: number = 1): number {\r\n return this.getPositiveNumber((this.viewport.width - this.nodeWidth) / numberOfColumns);\r\n }\r\n\r\n /**\r\n * Public for testability.\r\n */\r\n public sortNodesByX(nodes: SankeyDiagramNode[]): SankeyDiagramNode[] {\r\n return nodes.sort((firstNode: SankeyDiagramNode, secondNode: SankeyDiagramNode) => {\r\n return firstNode.x - secondNode.x;\r\n });\r\n }\r\n\r\n /**\r\n * Public for testability.\r\n */\r\n public getColumns(nodes: SankeyDiagramNode[]): SankeyDiagramColumn[] {\r\n var columns: SankeyDiagramColumn[] = [],\r\n currentX: number = -Number.MAX_VALUE;\r\n\r\n nodes.forEach((node: SankeyDiagramNode, index: number) => {\r\n if (currentX !== node.x) {\r\n columns.push({\r\n countOfNodes: 0,\r\n sumValueOfNodes: 0\r\n });\r\n\r\n currentX = node.x;\r\n }\r\n\r\n if (columns[node.x]) {\r\n columns[node.x].sumValueOfNodes += Math.max(node.inputWeight, node.outputWeight);\r\n columns[node.x].countOfNodes++;\r\n }\r\n });\r\n\r\n return columns;\r\n }\r\n\r\n /**\r\n * Public for testability.\r\n */\r\n public getMaxColumn(columns: SankeyDiagramColumn[] = []): SankeyDiagramColumn {\r\n var currentMaxColumn: SankeyDiagramColumn = { sumValueOfNodes: 0, countOfNodes: 0 };\r\n\r\n columns.forEach((column: SankeyDiagramColumn) => {\r\n if (column && column.sumValueOfNodes > currentMaxColumn.sumValueOfNodes) {\r\n currentMaxColumn = column;\r\n }\r\n });\r\n\r\n return currentMaxColumn;\r\n }\r\n\r\n private getScaleByAxisY(sumValueOfNodes: number): number {\r\n return this.getPositiveNumber((this.viewport.height - this.getAvailableSumNodeMarginByY()) / sumValueOfNodes);\r\n }\r\n\r\n private getAvailableSumNodeMarginByY(): number {\r\n return this.viewport\r\n ? this.viewport.height * SankeyDiagram.NodeBottomMargin / 100\r\n : 0;\r\n }\r\n\r\n private scalePositionsByAxes(\r\n nodes: SankeyDiagramNode[],\r\n columns: SankeyDiagramColumn[],\r\n scale: SankeyDiagramScale,\r\n viewportHeight: number): void {\r\n\r\n var shiftByAxisY: number = 0,\r\n currentX: number = 0,\r\n index: number = 0;\r\n\r\n nodes.forEach((node: SankeyDiagramNode) => {\r\n var offsetByY: number = 0,\r\n availableHeight: number = 0;\r\n\r\n if (currentX !== node.x) {\r\n currentX = node.x;\r\n shiftByAxisY = 0;\r\n index = 0;\r\n }\r\n\r\n if (columns[currentX]) {\r\n availableHeight = viewportHeight - columns[currentX].sumValueOfNodes * scale.y;\r\n\r\n offsetByY = availableHeight / columns[currentX].countOfNodes;\r\n }\r\n\r\n node.x *= scale.x;\r\n\r\n node.height = Math.max(node.inputWeight, node.outputWeight) * scale.y;\r\n\r\n node.y = shiftByAxisY + offsetByY * index;\r\n\r\n shiftByAxisY += node.height;\r\n\r\n index++;\r\n });\r\n }\r\n\r\n // TODO: Update this method to improve a distribution by height.\r\n private computeYPosition(\r\n nodes: SankeyDiagramNode[],\r\n scale: number): void {\r\n nodes.forEach((node: SankeyDiagramNode) => {\r\n node.links = node.links.sort((firstLink: SankeyDiagramLink, secondLink: SankeyDiagramLink) => {\r\n var firstY: number,\r\n secondY: number;\r\n\r\n firstY = firstLink.source === node\r\n ? firstLink.destination.y\r\n : firstLink.source.y;\r\n\r\n secondY = secondLink.source === node\r\n ? secondLink.destination.y\r\n : secondLink.source.y;\r\n\r\n return firstY - secondY;\r\n });\r\n\r\n var shiftByAxisYOfLeftLink: number = 0,\r\n shiftByAxisYOfRightLink: number = 0;\r\n\r\n node.links.forEach((link: SankeyDiagramLink) => {\r\n var shiftByAxisY: number = 0;\r\n\r\n link.height = link.weigth * scale;\r\n\r\n if (link.source.x < node.x || link.destination.x < node.x) {\r\n shiftByAxisY = shiftByAxisYOfLeftLink;\r\n\r\n shiftByAxisYOfLeftLink += link.height;\r\n } else if (link.source.x > node.x || link.destination.x > node.x) {\r\n shiftByAxisY = shiftByAxisYOfRightLink;\r\n\r\n shiftByAxisYOfRightLink += link.height;\r\n }\r\n\r\n if (link.source === node) {\r\n link.dySource = shiftByAxisY;\r\n } else if (link.destination === node) {\r\n link.dyDestination = shiftByAxisY;\r\n }\r\n });\r\n });\r\n }\r\n\r\n private render(sankeyDiagramDataView: SankeyDiagramDataView): void {\r\n var nodesSelection: D3.UpdateSelection,\r\n linksSelection: D3.UpdateSelection;\r\n\r\n linksSelection = this.renderLinks(sankeyDiagramDataView);\r\n\r\n this.renderTooltip(linksSelection);\r\n\r\n nodesSelection = this.renderNodes(sankeyDiagramDataView);\r\n\r\n this.renderTooltip(nodesSelection);\r\n\r\n this.bindSelectionHandler(sankeyDiagramDataView, nodesSelection, linksSelection);\r\n }\r\n\r\n private renderNodes(sankeyDiagramDataView: SankeyDiagramDataView): D3.UpdateSelection {\r\n var nodesEnterSelection: D3.Selection,\r\n nodesSelection: D3.UpdateSelection,\r\n nodeElements: D3.Selection;\r\n\r\n nodeElements = this.main\r\n .select(SankeyDiagram.Nodes.selector)\r\n .selectAll(SankeyDiagram.Node.selector);\r\n\r\n nodesSelection = nodeElements.data(sankeyDiagramDataView.nodes.filter(x => x.height > 0));\r\n\r\n nodesEnterSelection = nodesSelection\r\n .enter()\r\n .append(\"g\");\r\n\r\n nodesSelection\r\n .attr(\"transform\", (node: SankeyDiagramNode) => {\r\n return SVGUtil.translate(node.x, node.y);\r\n })\r\n .classed(SankeyDiagram.Node[\"class\"], true);\r\n\r\n nodesEnterSelection\r\n .append(\"rect\")\r\n .classed(SankeyDiagram.NodeRect[\"class\"], true);\r\n\r\n nodesEnterSelection\r\n .append(\"text\")\r\n .classed(SankeyDiagram.NodeLabel[\"class\"], true);\r\n\r\n nodesSelection\r\n .select(SankeyDiagram.NodeRect.selector)\r\n .style({\r\n \"fill\": (node: SankeyDiagramNode) => node.colour,\r\n \"stroke\": (node: SankeyDiagramNode) => d3.rgb(node.colour).darker(1.5)\r\n })\r\n .attr({\r\n x: 0,\r\n y: 0,\r\n height: (node: SankeyDiagramNode) => node.height,\r\n width: (node: SankeyDiagramNode) => node.width\r\n });\r\n\r\n nodesSelection\r\n .select(SankeyDiagram.NodeLabel.selector)\r\n .attr({\r\n x: (node: SankeyDiagramNode) => this.getLabelPositionByAxisX(node),\r\n y: (node: SankeyDiagramNode) => node.height / 2,\r\n dy: \"0.35em\"\r\n })\r\n .style(\"fill\", (node: SankeyDiagramNode) => node.label.colour)\r\n .style(\"font-size\", this.textProperties.fontSize)\r\n .style(\"display\", (node: SankeyDiagramNode) => {\r\n var isNotVisibleLabel: boolean = false, \r\n labelPositionByAxisX: number = this.getCurrentPositionOfLabelByAxisX(node);\r\n\r\n isNotVisibleLabel = \r\n labelPositionByAxisX >= this.viewport.width ||\r\n labelPositionByAxisX <= 0 ||\r\n (node.height + SankeyDiagram.NodeMargin) < node.label.height;\r\n\r\n if (isNotVisibleLabel || !sankeyDiagramDataView.settings.isVisibleLabels\r\n || sankeyDiagramDataView.settings.scale.x / 2 < SankeyDiagram.MinWidthOfLabel) {\r\n return \"none\";\r\n }\r\n\r\n return null;\r\n })\r\n .style(\"text-anchor\", (node: SankeyDiagramNode) => {\r\n if (this.isLabelLargerThanWidth(node)) {\r\n return \"end\";\r\n }\r\n\r\n return null;\r\n })\r\n .text((node: SankeyDiagramNode) => {\r\n var maxWidth: number = sankeyDiagramDataView.settings.scale.x / 2 - node.width - SankeyDiagram.NodeMargin;\r\n\r\n if (this.getCurrentPositionOfLabelByAxisX(node) > maxWidth) {\r\n return TextMeasurementService.getTailoredTextOrDefault({\r\n text: node.label.formattedName,\r\n fontFamily: this.textProperties.fontFamily,\r\n fontSize: this.textProperties.fontSize\r\n }, maxWidth);\r\n }\r\n\r\n return node.label.formattedName;\r\n });\r\n\r\n nodesSelection\r\n .exit()\r\n .remove();\r\n\r\n return nodesSelection;\r\n }\r\n\r\n private getLabelPositionByAxisX(node: SankeyDiagramNode): number {\r\n if (this.isLabelLargerThanWidth(node)) {\r\n return -(SankeyDiagram.LabelMargin);\r\n }\r\n\r\n return node.width + SankeyDiagram.LabelMargin;\r\n }\r\n\r\n private isLabelLargerThanWidth(node: SankeyDiagramNode): boolean {\r\n var shiftByAxisX: number = node.x + node.width + SankeyDiagram.LabelMargin;\r\n\r\n return shiftByAxisX + node.label.width > this.viewport.width;\r\n }\r\n\r\n private getCurrentPositionOfLabelByAxisX(node: SankeyDiagramNode): number {\r\n var labelPositionByAxisX: number = this.getLabelPositionByAxisX(node);\r\n\r\n labelPositionByAxisX = labelPositionByAxisX > 0\r\n ? labelPositionByAxisX + node.x + node.label.width + node.width\r\n : node.x + labelPositionByAxisX - node.label.width - node.width;\r\n\r\n return labelPositionByAxisX;\r\n }\r\n\r\n private renderLinks(sankeyDiagramDataView: SankeyDiagramDataView): D3.UpdateSelection {\r\n var linksSelection: D3.UpdateSelection,\r\n linksElements: D3.Selection;\r\n\r\n linksElements = this.main\r\n .select(SankeyDiagram.Links.selector)\r\n .selectAll(SankeyDiagram.Link.selector);\r\n\r\n linksSelection = linksElements.data(sankeyDiagramDataView.links.filter(x => x.height > 0));\r\n\r\n linksSelection\r\n .enter()\r\n .append(\"path\")\r\n .classed(SankeyDiagram.Link[\"class\"], true);\r\n\r\n linksSelection\r\n .attr(\"d\", (link: SankeyDiagramLink) => {\r\n return this.getSvgPath(link);\r\n })\r\n .style({\r\n \"stroke-width\": (link: SankeyDiagramLink) => link.height,\r\n \"stroke\": (link: SankeyDiagramLink) => link.colour\r\n });\r\n\r\n linksSelection\r\n .exit()\r\n .remove();\r\n\r\n return linksSelection;\r\n }\r\n\r\n private getSvgPath(link: SankeyDiagramLink): string {\r\n var x0: number,\r\n x1: number,\r\n xi: D3.Transition.BaseInterpolate,\r\n x2: number,\r\n x3: number,\r\n y0: number,\r\n y1: number;\r\n\r\n if (link.destination.x < link.source.x) {\r\n x0 = link.source.x;\r\n x1 = link.destination.x + link.destination.width;\r\n } else {\r\n x0 = link.source.x + link.source.width;\r\n x1 = link.destination.x;\r\n }\r\n\r\n xi = d3.interpolateNumber(x0, x1);\r\n x2 = xi(this.curvatureOfLinks);\r\n x3 = xi(1 - this.curvatureOfLinks);\r\n y0 = link.source.y + link.dySource + link.height / 2;\r\n y1 = link.destination.y + link.dyDestination + link.height / 2;\r\n\r\n return `M ${x0} ${y0} C ${x2} ${y0}, ${x3} ${y1}, ${x1} ${y1}`;\r\n }\r\n\r\n private renderTooltip(selection: D3.UpdateSelection): void {\r\n TooltipManager.addTooltip(selection, (tooltipEvent: TooltipEvent) => {\r\n return (<SankeyDiagramTooltipData> tooltipEvent.data).tooltipData;\r\n });\r\n }\r\n\r\n private bindSelectionHandler(\r\n sankeyDiagramDataView: SankeyDiagramDataView,\r\n nodesSelection: D3.UpdateSelection,\r\n linksSelection: D3.UpdateSelection): void {\r\n\r\n nodesSelection.on(\"click\", (node: SankeyDiagramNode) => {\r\n var selectionIds = _.isEqual(\r\n node.selectionIds.map(x=>x.getKey()),\r\n this.selectionManager.getSelectionIds().map(x=>x.getKey()))\r\n ? []\r\n : node.selectionIds;\r\n\r\n this.selectMany(selectionIds, true).then(() => this.setSelection(nodesSelection, linksSelection));\r\n\r\n d3.event.stopPropagation();\r\n });\r\n\r\n linksSelection.on(\"click\", (link: SankeyDiagramLink) => {\r\n this.selectionManager.select(link.selectionId, d3.event.ctrlKey).then(() =>\r\n this.setSelection(nodesSelection, linksSelection));\r\n\r\n d3.event.stopPropagation();\r\n });\r\n\r\n this.root.on(\"click\", () => {\r\n this.selectionManager.clear().then(() => this.setSelection(nodesSelection, linksSelection));\r\n });\r\n\r\n this.setSelection(nodesSelection, linksSelection);\r\n }\r\n\r\n private selectMany(selectionIds: SelectionId[], clear: boolean = false): JQueryPromise<{}> {\r\n if(clear) {\r\n return this.selectionManager.clear().then(() => this.selectMany(selectionIds, false));\r\n }\r\n\r\n var selectionDeffered = selectionIds.map(id => this.selectionManager.select(id, true));\r\n return (<JQueryPromise<{}>>$.when.apply(null, selectionDeffered));\r\n }\r\n\r\n private setSelection(nodes: D3.UpdateSelection, links: D3.UpdateSelection): void {\r\n\r\n var selectionIds: SelectionId[] = this.selectionManager.getSelectionIds();\r\n\r\n nodes.classed(\"selected\", selectionIds.length > 0);\r\n links.classed(\"selected\", false);\r\n\r\n if (selectionIds.length === 0) {\r\n return;\r\n }\r\n\r\n var selectedNodesSelection = nodes.filter((node: SankeyDiagramNode) => selectionIds\r\n .some((selectedId: SelectionId) => node.selectionIds\r\n .some( x => x.getKey() === selectedId.getKey())));\r\n\r\n var selectedLinksSelection = links.filter((link: SankeyDiagramLink) =>\r\n selectionIds.some((selectionId: SelectionId) => selectionId.getKey() === link.selectionId.getKey()));\r\n\r\n selectedNodesSelection.classed(\"selected\", false);\r\n selectedLinksSelection.classed(\"selected\", true);\r\n }\r\n\r\n public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstanceEnumeration {\r\n var enumeration = new ObjectEnumerationBuilder();\r\n\r\n if (!this.dataView) {\r\n return [];\r\n }\r\n\r\n switch (options.objectName) {\r\n case \"labels\": {\r\n this.enumerateLabels(enumeration);\r\n break;\r\n }\r\n case \"links\": {\r\n this.enumerateLinks(enumeration);\r\n break;\r\n }\r\n }\r\n\r\n return enumeration.complete();\r\n }\r\n\r\n private enumerateLabels(enumeration: ObjectEnumerationBuilder): void {\r\n var settings: SankeyDiagramSettings = this.dataView.settings,\r\n labels: VisualObjectInstance;\r\n\r\n if (!settings) {\r\n return;\r\n }\r\n\r\n labels = {\r\n objectName: \"labels\",\r\n displayName: \"labels\",\r\n selector: null,\r\n properties: {\r\n show: settings.isVisibleLabels,\r\n fill: settings.colourOfLabels,\r\n fontSize: settings.fontSize\r\n }\r\n };\r\n\r\n enumeration.pushInstance(labels);\r\n }\r\n\r\n private enumerateLinks(enumeration: ObjectEnumerationBuilder): void {\r\n var links: SankeyDiagramLink[] = this.dataView.links;\r\n\r\n if (!links || !(links.length > 0)) {\r\n return;\r\n }\r\n\r\n links.forEach((link: SankeyDiagramLink) => {\r\n enumeration.pushInstance({\r\n objectName: \"links\",\r\n displayName: `${link.source.label.formattedName} - ${link.destination.label.formattedName}`,\r\n selector: ColorHelper.normalizeSelector(link.selectionId.getSelector(), false),\r\n properties: {\r\n fill: { solid: { color: link.colour } }\r\n }\r\n });\r\n });\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/CustomVisuals/visuals/sankeyDiagram/visual/sankeyDiagram.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 PixelConverter = jsCommon.PixelConverter;\r\n\r\n export let bulletChartProps = {\r\n values: {\r\n targetValue: <DataViewObjectPropertyIdentifier>{ objectName: 'values', propertyName: 'targetValue' },\r\n minimumPercent: <DataViewObjectPropertyIdentifier>{ objectName: 'values', propertyName: 'minimumPercent' },\r\n needsImprovementPercent: <DataViewObjectPropertyIdentifier>{ objectName: 'values', propertyName: 'needsImprovementPercent' },\r\n satisfactoryPercent: <DataViewObjectPropertyIdentifier>{ objectName: 'values', propertyName: 'satisfactoryPercent' },\r\n goodPercent: <DataViewObjectPropertyIdentifier>{ objectName: 'values', propertyName: 'goodPercent' },\r\n veryGoodPercent: <DataViewObjectPropertyIdentifier>{ objectName: 'values', propertyName: 'veryGoodPercent' },\r\n maximumPercent: <DataViewObjectPropertyIdentifier>{ objectName: 'values', propertyName: 'maximumPercent' },\r\n targetValue2: <DataViewObjectPropertyIdentifier>{ objectName: 'values', propertyName: 'targetValue2' },\r\n secondTargetVisibility: <DataViewObjectPropertyIdentifier>{ objectName: 'values', propertyName: 'secondTargetVisibility' },\r\n },\r\n orientation: {\r\n orientation: <DataViewObjectPropertyIdentifier>{ objectName: 'orientation', propertyName: 'orientation' },\r\n },\r\n colors: {\r\n badColor: <DataViewObjectPropertyIdentifier>{ objectName: 'colors', propertyName: 'badColor' },\r\n needsImprovementColor: <DataViewObjectPropertyIdentifier>{ objectName: 'colors', propertyName: 'needsImprovementColor' },\r\n satisfactoryColor: <DataViewObjectPropertyIdentifier>{ objectName: 'colors', propertyName: 'satisfactoryColor' },\r\n goodColor: <DataViewObjectPropertyIdentifier>{ objectName: 'colors', propertyName: 'goodColor' },\r\n veryGoodColor: <DataViewObjectPropertyIdentifier>{ objectName: 'colors', propertyName: 'veryGoodColor' },\r\n bulletColor: <DataViewObjectPropertyIdentifier>{ objectName: 'colors', propertyName: 'bulletColor' },\r\n },\r\n axis: {\r\n axis: <DataViewObjectPropertyIdentifier>{ objectName: 'axis', propertyName: 'axis' },\r\n axisColor: <DataViewObjectPropertyIdentifier>{ objectName: 'axis', propertyName: 'axisColor' },\r\n measureUnits: <DataViewObjectPropertyIdentifier>{ objectName: 'axis', propertyName: 'measureUnits' },\r\n unitsColor: <DataViewObjectPropertyIdentifier>{ objectName: 'axis', propertyName: 'unitsColor' },\r\n },\r\n formatString: <DataViewObjectPropertyIdentifier>{ objectName: 'general', propertyName: 'formatString' },\r\n labels: {\r\n fontSize: <DataViewObjectPropertyIdentifier>{ objectName: 'labels', propertyName: 'fontSize' },\r\n show: <DataViewObjectPropertyIdentifier>{ objectName: 'labels', propertyName: 'show' },\r\n labelColor: <DataViewObjectPropertyIdentifier>{ objectName: 'labels', propertyName: 'labelColor' }\r\n }\r\n };\r\n\r\n export interface BarData {\r\n scale: any;\r\n barIndex: number;\r\n categoryLabel: string;\r\n axis: any;\r\n x: number;\r\n y: number;\r\n key: string;\r\n }\r\n\r\n export interface BarRect extends SelectableDataPoint {\r\n barIndex: number;\r\n start: number;\r\n end: number;\r\n fill: string;\r\n tooltipInfo?: TooltipDataItem[];\r\n key: string;\r\n highlight?: boolean;\r\n }\r\n\r\n export interface TargetValue {\r\n barIndex: number;\r\n value: number;\r\n value2: number;\r\n fill: string;\r\n key: string;\r\n }\r\n\r\n export interface ScaledValues {\r\n firstScale: number;\r\n secondScale: number;\r\n thirdScale: number;\r\n fourthScale: number;\r\n fifthScale: number;\r\n }\r\n\r\n export interface BarValueRect extends BarRect {\r\n }\r\n\r\n export interface BulletChartSettings {\r\n values: {\r\n targetValue: number;\r\n minimumPercent: number;\r\n needsImprovementPercent: number;\r\n satisfactoryPercent: number;\r\n goodPercent: number;\r\n veryGoodPercent: number;\r\n maximumPercent: number;\r\n targetValue2: number;\r\n secondTargetVisibility: boolean;\r\n };\r\n orientation: {\r\n orientation: string;\r\n reverse: boolean;\r\n vertical: boolean;\r\n };\r\n colors: {\r\n badColor: string;\r\n needsImprovementColor: string;\r\n satisfactoryColor: string;\r\n goodColor: string;\r\n veryGoodColor: string;\r\n bulletColor: string;\r\n };\r\n\r\n axis: {\r\n axis: boolean;\r\n axisColor: string;\r\n measureUnits: string;\r\n unitsColor: string;\r\n };\r\n labelSettings: VisualDataLabelsSettings;\r\n }\r\n\r\n //Model\r\n export interface BulletChartModel {\r\n bars: BarData[];\r\n bulletChartSettings: BulletChartSettings;\r\n bulletValueFormatString: string;\r\n barRects: BarRect[];\r\n valueRects: BarValueRect[];\r\n targetValues: TargetValue[];\r\n hasHighlights: boolean;\r\n viewportLength: number;\r\n labelHeight: number;\r\n labelHeightTop: number;\r\n spaceRequiredForBarHorizontally: number;\r\n }\r\n\r\n export let bulletChartRoleNames = {\r\n value: 'Value',\r\n targetValue: 'TargetValue',\r\n minValue: 'Minimum',\r\n needsImprovementValue: 'NeedsImprovement',\r\n satisfactoryValue: 'Satisfactory',\r\n goodValue: 'Good',\r\n veryGoodValue: 'VeryGood',\r\n maxValue: 'Maximum',\r\n targetValue2: 'TargetValue2',\r\n };\r\n\r\n module Orientation {\r\n export const HORIZONTALLEFT: string = 'Horizontal Left';\r\n export const HORIZONTALRIGHT: string = 'Horizontal Right';\r\n export const VERTICALTOP: string = 'Vertical Top';\r\n export const VERTICALBOTTOM: string = 'Vertical Bottom';\r\n\r\n export var type: IEnumType = createEnumType([\r\n { value: HORIZONTALLEFT, displayName: HORIZONTALLEFT },\r\n { value: HORIZONTALRIGHT, displayName: HORIZONTALRIGHT },\r\n { value: VERTICALTOP, displayName: VERTICALTOP },\r\n { value: VERTICALBOTTOM, displayName: VERTICALBOTTOM }\r\n ]);\r\n }\r\n\r\n export class BulletChart implements IVisual {\r\n private static ScrollBarSize = 22;\r\n private static SpaceRequiredForBarVertically = 100;\r\n private static XMarginHorizontal = 20;\r\n private static YMarginHorizontal = 30;\r\n private static XMarginVertical = 50;\r\n private static YMarginVertical = 10;\r\n private static BulletSize = 25;\r\n private static DefaultSubtitleFontSizeInPt = 9;\r\n private static BarMargin = 10;\r\n private static MaxLabelWidth = 80;\r\n private static MaxMeasureUnitWidth = BulletChart.MaxLabelWidth - 20;\r\n private static SubtitleMargin = 10;\r\n private static AxisFontSizeInPt = 8;\r\n private static SecondTargetLineSize = 7;\r\n\r\n private static MarkerMarginHorizontal = BulletChart.BulletSize / 3;\r\n private static MarkerMarginVertical = BulletChart.BulletSize / 4;\r\n\r\n private static FontFamily: string = \"Segoe UI\";\r\n private baselineDelta: number = 0;\r\n\r\n public static capabilities: VisualCapabilities = {\r\n dataRoles: [\r\n {\r\n name: 'Category',\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: 'Category',\r\n }, {\r\n name: 'Value',\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: 'Value',\r\n }, {\r\n name: 'TargetValue',\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: 'Target Value',\r\n }, {\r\n name: 'Minimum',\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: 'Minimum',\r\n }, {\r\n name: 'NeedsImprovement',\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: 'Needs Improvement',\r\n }, {\r\n name: 'Satisfactory',\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: 'Satisfactory',\r\n }, {\r\n name: 'Good',\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: 'Good',\r\n }, {\r\n name: 'VeryGood',\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: 'Very Good',\r\n }, {\r\n name: 'Maximum',\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: 'Maximum',\r\n }, {\r\n name: 'TargetValue2',\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: 'Target Value 2'\r\n }\r\n ],\r\n objects: {\r\n general: {\r\n displayName: data.createDisplayNameGetter('Visual_General'),\r\n properties: {\r\n formatString: {\r\n type: { formatting: { formatString: true } },\r\n },\r\n },\r\n },\r\n values: {\r\n displayName: 'Data values',\r\n properties: {\r\n targetValue: {\r\n displayName: 'Target Value',\r\n type: { numeric: true }\r\n },\r\n targetValue2: {\r\n displayName: 'Target Value 2',\r\n type: { numeric: true },\r\n },\r\n secondTargetVisibility: {\r\n displayName: 'Second Target Visibility',\r\n type: { bool: true },\r\n },\r\n minimumPercent: {\r\n displayName: 'Minimum %',\r\n type: { numeric: true }\r\n },\r\n needsImprovementPercent: {\r\n displayName: 'Needs Improvement %',\r\n type: { numeric: true },\r\n },\r\n satisfactoryPercent: {\r\n displayName: 'Satisfactory %',\r\n type: { numeric: true }\r\n },\r\n goodPercent: {\r\n displayName: 'Good %',\r\n type: { numeric: true }\r\n },\r\n veryGoodPercent: {\r\n displayName: 'Very Good %',\r\n type: { numeric: true },\r\n },\r\n maximumPercent: {\r\n displayName: 'Maximum %',\r\n type: { numeric: true }\r\n },\r\n }\r\n },\r\n labels: {\r\n displayName: 'Category labels',\r\n properties: {\r\n show: {\r\n displayName: data.createDisplayNameGetter('Visual_Show'),\r\n type: { bool: true },\r\n },\r\n labelColor: {\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 fontSize: {\r\n displayName: data.createDisplayNameGetter('Visual_TextSize'),\r\n type: { formatting: { fontSize: true } },\r\n },\r\n },\r\n },\r\n orientation: {\r\n displayName: 'Orientation',\r\n properties: {\r\n orientation: {\r\n displayName: 'Orientation',\r\n type: { enumeration: Orientation.type }\r\n }\r\n }\r\n },\r\n colors: {\r\n displayName: 'Colors',\r\n properties: {\r\n badColor: {\r\n type: { fill: { solid: { color: true } } },\r\n displayName: 'Bad Color'\r\n },\r\n needsImprovementColor: {\r\n type: { fill: { solid: { color: true } } },\r\n displayName: 'Needs Improvement Color',\r\n },\r\n satisfactoryColor: {\r\n type: { fill: { solid: { color: true } } },\r\n displayName: 'Satisfactory Color'\r\n },\r\n goodColor: {\r\n type: { fill: { solid: { color: true } } },\r\n displayName: 'Good Color'\r\n },\r\n veryGoodColor: {\r\n type: { fill: { solid: { color: true } } },\r\n displayName: 'Very Good Color',\r\n },\r\n bulletColor: {\r\n type: { fill: { solid: { color: true } } },\r\n displayName: 'Bullet Color'\r\n }\r\n },\r\n },\r\n axis: {\r\n displayName: 'Axis',\r\n properties: {\r\n axis: {\r\n displayName: 'Axis',\r\n type: { bool: true }\r\n },\r\n axisColor: {\r\n type: { fill: { solid: { color: true } } },\r\n displayName: 'Axis Color'\r\n },\r\n measureUnits: {\r\n type: { text: true },\r\n displayName: 'Measure Units '\r\n },\r\n unitsColor: {\r\n type: { fill: { solid: { color: true } } },\r\n displayName: 'Units Color'\r\n },\r\n }\r\n }\r\n },\r\n dataViewMappings: [{\r\n conditions: [\r\n {\r\n 'Category': { max: 1 }, 'Value': { max: 1 }, 'TargetValue': { max: 1 }, 'Minimum': { max: 1 }, 'NeedsImprovement': { max: 1 },\r\n 'Satisfactory': { max: 1 }, 'Good': { max: 1 }, 'VeryGood': { max: 1 }, 'Maximum': { max: 1 }, 'TargetValue2': { max: 1 },\r\n },\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 { bind: { to: 'Value' } },\r\n { bind: { to: 'TargetValue' } },\r\n { bind: { to: 'TargetValue2' } },\r\n { bind: { to: 'Minimum' } },\r\n { bind: { to: 'NeedsImprovement' } },\r\n { bind: { to: 'Satisfactory' } },\r\n { bind: { to: 'Good' } },\r\n { bind: { to: 'VeryGood' } },\r\n { bind: { to: 'Maximum' } },\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 //Variables\r\n private clearCatcher: D3.Selection;\r\n private bulletBody: D3.Selection;\r\n private scrollContainer: D3.Selection;\r\n private labelGraphicsContext: D3.Selection;\r\n private bulletGraphicsContext: D3.Selection;\r\n private model: BulletChartModel;\r\n private behavior: BulletWebBehavior;\r\n private interactivityService: IInteractivityService;\r\n private hostService: IVisualHostServices;\r\n\r\n private get reverse(): boolean {\r\n return this.model.bulletChartSettings.orientation.reverse;\r\n }\r\n\r\n private get vertical(): boolean {\r\n return this.model.bulletChartSettings.orientation.vertical;\r\n }\r\n\r\n public static DefaultStyleProperties(): BulletChartSettings {\r\n return {\r\n values: {\r\n targetValue: 0,\r\n targetValue2: 0,\r\n minimumPercent: 0,\r\n needsImprovementPercent: 25,\r\n satisfactoryPercent: 50,\r\n goodPercent: 100,\r\n veryGoodPercent: 125,\r\n maximumPercent: 200,\r\n secondTargetVisibility: false,\r\n },\r\n orientation: {\r\n orientation: Orientation.HORIZONTALLEFT,\r\n reverse: false,\r\n vertical: false\r\n },\r\n colors: {\r\n badColor: 'Darkred',\r\n needsImprovementColor: 'Red',\r\n satisfactoryColor: 'Yellow',\r\n goodColor: 'Green',\r\n veryGoodColor: 'Darkgreen',\r\n bulletColor: 'Black'\r\n },\r\n axis: {\r\n axis: true,\r\n axisColor: 'Grey',\r\n measureUnits: '',\r\n unitsColor: 'Grey',\r\n },\r\n labelSettings: {\r\n fontSize: 11,\r\n show: true,\r\n labelColor: 'Black',\r\n },\r\n };\r\n }\r\n\r\n private viewport: IViewport;\r\n\r\n private get viewportIn(): IViewport {\r\n return <IViewport>{\r\n width: this.viewport.width,\r\n height: this.viewport.height\r\n };\r\n }\r\n\r\n private get viewportScroll(): IViewport {\r\n let viewportIn = this.viewportIn;\r\n return <IViewport>{\r\n width: viewportIn.width - BulletChart.ScrollBarSize,\r\n height: viewportIn.height - BulletChart.ScrollBarSize\r\n };\r\n }\r\n\r\n private static getTextProperties(text: string, fontSize: number): TextProperties {\r\n return <TextProperties>{\r\n fontFamily: BulletChart.FontFamily,\r\n fontSize: PixelConverter.fromPoint(fontSize),\r\n text: text,\r\n };\r\n }\r\n\r\n // Convert a DataView into a view model\r\n public static converter(dataView: DataView, options: VisualUpdateOptions): BulletChartModel {\r\n let defaultSettings = BulletChart.DefaultStyleProperties();\r\n let bulletModel: BulletChartModel = <BulletChartModel>{\r\n bulletValueFormatString: null,\r\n bulletChartSettings: defaultSettings,\r\n bars: [],\r\n barRects: [],\r\n valueRects: [],\r\n targetValues: [],\r\n viewportLength: 0\r\n };\r\n\r\n if (!dataView || !dataView.categorical || !dataView.categorical.values || dataView.categorical.values.length === 0\r\n || !dataView.metadata || !dataView.metadata.columns || dataView.metadata.columns.length === 0) {\r\n return bulletModel;\r\n }\r\n\r\n let objects = dataView.metadata.objects;\r\n let settings = bulletModel.bulletChartSettings;\r\n\r\n if (objects) {\r\n settings.values.targetValue = DataViewObjects.getValue<number>(objects, bulletChartProps.values.targetValue, defaultSettings.values.targetValue);\r\n settings.values.targetValue2 = DataViewObjects.getValue<number>(objects, bulletChartProps.values.targetValue2, defaultSettings.values.targetValue2);\r\n settings.values.secondTargetVisibility = DataViewObjects.getValue<boolean>(objects, bulletChartProps.values.secondTargetVisibility, defaultSettings.values.secondTargetVisibility);\r\n settings.values.minimumPercent = DataViewObjects.getValue<number>(objects, bulletChartProps.values.minimumPercent, defaultSettings.values.minimumPercent);\r\n settings.values.needsImprovementPercent = DataViewObjects.getValue<number>(objects, bulletChartProps.values.needsImprovementPercent, defaultSettings.values.needsImprovementPercent);\r\n settings.values.satisfactoryPercent = DataViewObjects.getValue<number>(objects, bulletChartProps.values.satisfactoryPercent, defaultSettings.values.satisfactoryPercent);\r\n settings.values.goodPercent = DataViewObjects.getValue<number>(objects, bulletChartProps.values.goodPercent, defaultSettings.values.goodPercent);\r\n settings.values.veryGoodPercent = DataViewObjects.getValue<number>(objects, bulletChartProps.values.veryGoodPercent, defaultSettings.values.veryGoodPercent);\r\n settings.values.maximumPercent = DataViewObjects.getValue<number>(objects, bulletChartProps.values.maximumPercent, defaultSettings.values.maximumPercent);\r\n\r\n settings.orientation.orientation = DataViewObjects.getValue<string>(objects, bulletChartProps.orientation.orientation, defaultSettings.orientation.orientation);\r\n\r\n settings.colors.badColor = DataViewObjects.getFillColor(objects, bulletChartProps.colors.badColor, defaultSettings.colors.badColor);\r\n settings.colors.needsImprovementColor = DataViewObjects.getFillColor(objects, bulletChartProps.colors.needsImprovementColor, defaultSettings.colors.needsImprovementColor);\r\n settings.colors.satisfactoryColor = DataViewObjects.getFillColor(objects, bulletChartProps.colors.satisfactoryColor, defaultSettings.colors.satisfactoryColor);\r\n settings.colors.goodColor = DataViewObjects.getFillColor(objects, bulletChartProps.colors.goodColor, defaultSettings.colors.goodColor);\r\n settings.colors.veryGoodColor = DataViewObjects.getFillColor(objects, bulletChartProps.colors.veryGoodColor, defaultSettings.colors.veryGoodColor);\r\n settings.colors.bulletColor = DataViewObjects.getFillColor(objects, bulletChartProps.colors.bulletColor, defaultSettings.colors.bulletColor);\r\n\r\n settings.axis.axis = DataViewObjects.getValue<boolean>(objects, bulletChartProps.axis.axis, defaultSettings.axis.axis);\r\n settings.axis.axisColor = DataViewObjects.getFillColor(objects, bulletChartProps.axis.axisColor, defaultSettings.axis.axisColor);\r\n settings.axis.measureUnits = TextMeasurementService.getTailoredTextOrDefault(BulletChart.getTextProperties(\r\n DataViewObjects.getValue<string>(objects, bulletChartProps.axis.measureUnits, defaultSettings.axis.measureUnits), BulletChart.DefaultSubtitleFontSizeInPt), BulletChart.MaxLabelWidth);\r\n settings.axis.unitsColor = DataViewObjects.getFillColor(objects, bulletChartProps.axis.unitsColor, defaultSettings.axis.unitsColor);\r\n\r\n settings.labelSettings.fontSize = DataViewObjects.getValue<number>(objects, bulletChartProps.labels.fontSize, defaultSettings.labelSettings.fontSize);\r\n settings.labelSettings.show = DataViewObjects.getValue<boolean>(objects, bulletChartProps.labels.show, defaultSettings.labelSettings.show);\r\n settings.labelSettings.labelColor = DataViewObjects.getFillColor(objects, bulletChartProps.labels.labelColor, defaultSettings.labelSettings.labelColor);\r\n }\r\n if (settings.orientation.orientation === Orientation.HORIZONTALRIGHT || settings.orientation.orientation === Orientation.VERTICALBOTTOM)\r\n settings.orientation.reverse = true;\r\n\r\n if (settings.orientation.orientation === Orientation.VERTICALTOP || settings.orientation.orientation === Orientation.VERTICALBOTTOM)\r\n settings.orientation.vertical = true;\r\n\r\n let categories: DataViewCategoryColumn,\r\n categoryValues: any[],\r\n categoryValuesLen: number = 1,\r\n categoryFormatString: string;\r\n\r\n if (dataView.categorical.categories) {\r\n categories = dataView.categorical.categories[0];\r\n categoryValues = categories.values;\r\n categoryValuesLen = categoryValues.length;\r\n categoryFormatString = valueFormatter.getFormatString(categories.source, bulletChartProps.formatString);\r\n }\r\n\r\n bulletModel.labelHeight = (settings.labelSettings.show || 0) && parseFloat(PixelConverter.fromPoint(settings.labelSettings.fontSize));\r\n bulletModel.labelHeightTop = (settings.labelSettings.show || 0) && parseFloat(PixelConverter.fromPoint(settings.labelSettings.fontSize))/1.4;\r\n bulletModel.spaceRequiredForBarHorizontally = Math.max(60, bulletModel.labelHeight + 20);\r\n bulletModel.bulletValueFormatString = valueFormatter.getFormatString(dataView.categorical.values[0].source, bulletChartProps.formatString);\r\n bulletModel.viewportLength = (settings.orientation.vertical\r\n ? (options.viewport.height - bulletModel.labelHeightTop - BulletChart.SubtitleMargin - 20 - BulletChart.YMarginVertical * 2)\r\n : (options.viewport.width - BulletChart.MaxLabelWidth - BulletChart.XMarginHorizontal * 3)) - BulletChart.ScrollBarSize;\r\n\r\n for (let idx = 0; idx < categoryValuesLen; idx++) {\r\n let toolTipItems = [];\r\n let category: string, value: number, targetValue: number, targetValue2: number, minimum: number, satisfactory: number,\r\n good: number, maximum: number, needsImprovement: number, veryGood: number;\r\n let highlight: boolean = false,\r\n categoryIdentity: DataViewScopeIdentity;\r\n\r\n if (categoryValues) {\r\n let categoryValue = categoryValues[idx];\r\n\r\n category = valueFormatter.format(categoryValue, categoryFormatString);\r\n categoryIdentity = categories.identity ? categories.identity[idx] : null;\r\n\r\n let textProperties = BulletChart.getTextProperties(category, settings.labelSettings.fontSize);\r\n category = TextMeasurementService.getTailoredTextOrDefault(textProperties, BulletChart.MaxLabelWidth);\r\n }\r\n\r\n let values = dataView.categorical.values;\r\n targetValue = settings.values.targetValue;\r\n targetValue2 = settings.values.targetValue2;\r\n\r\n bulletModel.hasHighlights = !!(values.length > 0 && values[0].highlights);\r\n\r\n for (let i = 0; i < values.length; i++) {\r\n let col = values[i].source;\r\n let currentVal = values[i].values[idx] || 0;\r\n\r\n if (col && col.roles) {\r\n if (col.roles[bulletChartRoleNames.value]) {\r\n if (values[i].highlights)\r\n highlight = values[i].highlights[idx] !== null;\r\n toolTipItems.push({ value: currentVal, metadata: values[i] });\r\n value = currentVal;\r\n } else if (col.roles[bulletChartRoleNames.targetValue]) {\r\n toolTipItems.push({ value: currentVal, metadata: values[i] });\r\n targetValue = currentVal;\r\n } else if (col.roles[bulletChartRoleNames.targetValue2]) {\r\n toolTipItems.push({ value: currentVal, metadata: values[i] });\r\n targetValue2 = currentVal;\r\n } else if (col.roles[bulletChartRoleNames.minValue])\r\n minimum = currentVal;\r\n else if (col.roles[bulletChartRoleNames.needsImprovementValue])\r\n needsImprovement = currentVal;\r\n else if (col.roles[bulletChartRoleNames.satisfactoryValue])\r\n satisfactory = currentVal;\r\n else if (col.roles[bulletChartRoleNames.goodValue])\r\n good = currentVal;\r\n else if (col.roles[bulletChartRoleNames.veryGoodValue])\r\n veryGood = currentVal;\r\n else if (col.roles[bulletChartRoleNames.maxValue])\r\n maximum = currentVal;\r\n }\r\n }\r\n\r\n if (!minimum)\r\n minimum = settings.values.minimumPercent * targetValue / 100;\r\n if (!needsImprovement)\r\n needsImprovement = settings.values.needsImprovementPercent * targetValue / 100;\r\n if (!satisfactory)\r\n satisfactory = settings.values.satisfactoryPercent * targetValue / 100;\r\n if (!good)\r\n good = settings.values.goodPercent * targetValue / 100;\r\n if (!veryGood)\r\n veryGood = settings.values.veryGoodPercent * targetValue / 100;\r\n if (!maximum)\r\n maximum = settings.values.maximumPercent * targetValue / 100;\r\n\r\n let sortedRanges = [minimum, needsImprovement, satisfactory, good, veryGood, maximum].sort(d3.descending);\r\n let scale = (d3.scale.linear()\r\n .clamp(true)\r\n .domain([minimum, Math.max(sortedRanges[0], targetValue, value)])\r\n .range(settings.orientation.vertical ? [bulletModel.viewportLength, 0] : [0, bulletModel.viewportLength]));\r\n\r\n // Scalles without\r\n let firstScale = scale(minimum);\r\n let secondScale = scale(needsImprovement);\r\n let thirdScale = scale(satisfactory);\r\n let fourthScale = scale(good);\r\n let fifthScale = scale(veryGood);\r\n let lastScale = scale(maximum);\r\n let valueScale = scale(value);\r\n\r\n let firstColor = settings.colors.badColor, secondColor = settings.colors.needsImprovementColor,\r\n thirdColor = settings.colors.satisfactoryColor, fourthColor = settings.colors.goodColor, lastColor = settings.colors.veryGoodColor;\r\n\r\n BulletChart.addItemToBarArray(bulletModel.barRects, idx, firstScale, secondScale, firstColor, toolTipItems, categoryIdentity, highlight);\r\n BulletChart.addItemToBarArray(bulletModel.barRects, idx, secondScale, thirdScale, secondColor, toolTipItems, categoryIdentity, highlight);\r\n BulletChart.addItemToBarArray(bulletModel.barRects, idx, thirdScale, fourthScale, thirdColor, toolTipItems, categoryIdentity, highlight);\r\n BulletChart.addItemToBarArray(bulletModel.barRects, idx, fourthScale, fifthScale, fourthColor, toolTipItems, categoryIdentity, highlight);\r\n BulletChart.addItemToBarArray(bulletModel.barRects, idx, fifthScale, lastScale, lastColor, toolTipItems, categoryIdentity, highlight);\r\n BulletChart.addItemToBarArray(bulletModel.valueRects, idx, firstScale, valueScale, settings.colors.bulletColor, toolTipItems, categoryIdentity, highlight);\r\n\r\n // markerValue\r\n bulletModel.targetValues.push({\r\n barIndex: idx,\r\n value: scale(targetValue),\r\n fill: settings.colors.bulletColor,\r\n key: SelectionId.createWithIdAndMeasure(categoryIdentity, scale(targetValue).toString()).getKey(),\r\n value2: scale(targetValue2),\r\n });\r\n\r\n let xAxis = null;\r\n if (settings.axis.axis) {\r\n xAxis = d3.svg.axis();\r\n xAxis.orient(settings.orientation.vertical ? \"left\" : \"bottom\");\r\n let minTickSize = Math.round(Math.max(3, bulletModel.viewportLength / 100));\r\n let axisValues = [value, targetValue, good, satisfactory, maximum, minimum, needsImprovement, veryGood]\r\n .filter(x => !isNaN(x));\r\n xAxis.tickFormat(valueFormatter.create({\r\n format: bulletModel.bulletValueFormatString,\r\n value: axisValues.length ? Math.max.apply(null, axisValues) : 0\r\n }).format);\r\n xAxis.ticks(minTickSize);\r\n xAxis.scale(scale);\r\n }\r\n\r\n let bar: BarData = {\r\n scale: scale,\r\n barIndex: idx,\r\n categoryLabel: category,\r\n x: (settings.orientation.vertical) ? BulletChart.XMarginVertical + BulletChart.SpaceRequiredForBarVertically * idx : BulletChart.XMarginHorizontal,\r\n y: (settings.orientation.vertical) ? BulletChart.YMarginVertical : BulletChart.YMarginHorizontal + bulletModel.spaceRequiredForBarHorizontally * idx,\r\n axis: xAxis,\r\n key: SelectionId.createWithIdAndMeasure(categoryIdentity, idx.toString()).getKey(),\r\n };\r\n\r\n bulletModel.bars.push(bar);\r\n }\r\n\r\n return bulletModel;\r\n }\r\n\r\n private static addItemToBarArray(collection: BarRect[], barIndex: number, start: number, end: number, fill: string, tooltipInfo?: any[], categoryIdentity?: any, highlight?: boolean): void {\r\n if (!isNaN(start) && !isNaN(end))\r\n collection.push({\r\n barIndex: barIndex,\r\n start: start,\r\n end: end,\r\n fill: fill,\r\n tooltipInfo: TooltipBuilder.createTooltipInfo(bulletChartProps.formatString, null, null, null, null, tooltipInfo),\r\n selected: false,\r\n identity: SelectionId.createWithId(categoryIdentity),\r\n key: SelectionId.createWithIdAndMeasure(categoryIdentity, start + \" \" + end).getKey(),\r\n highlight: highlight,\r\n });\r\n }\r\n \r\n /* One time setup*/\r\n public init(options: VisualInitOptions): void {\r\n let body = d3.select(options.element.get(0));\r\n this.hostService = options.host;\r\n\r\n this.bulletBody = body\r\n .append('div')\r\n .classed('bulletChart', true)\r\n .attr(\"drag-resize-disabled\", true);\r\n\r\n this.scrollContainer = this.bulletBody.append('svg')\r\n .classed('bullet-scroll-region', true);\r\n this.clearCatcher = appendClearCatcher(this.scrollContainer);\r\n\r\n this.labelGraphicsContext = this.scrollContainer.append('g');\r\n this.bulletGraphicsContext = this.scrollContainer.append('g');\r\n\r\n this.behavior = new BulletWebBehavior();\r\n\r\n this.interactivityService = createInteractivityService(options.host);\r\n }\r\n\r\n /* Called for data, size, formatting changes*/\r\n public update(options: VisualUpdateOptions) {\r\n if (!options.dataViews || !options.dataViews[0]) {\r\n return;\r\n }\r\n\r\n let dataView = options.dataViews[0];\r\n this.viewport = options.viewport;\r\n this.model = BulletChart.converter(dataView, options);\r\n\r\n //TODO: Calculating the baseline delta of the text. needs to be removed once the TExtMeasurementService.estimateSVGTextBaselineDelta is available.\r\n this.baselineDelta = TextMeasurementHelper.estimateSvgTextBaselineDelta(BulletChart.getTextProperties(\"1\", this.model.bulletChartSettings.labelSettings.fontSize));\r\n\r\n this.ClearViewport();\r\n if (!this.model) {\r\n return;\r\n }\r\n\r\n if (this.interactivityService) {\r\n this.interactivityService.applySelectionStateToData(this.model.barRects);\r\n }\r\n\r\n this.bulletBody.style({\r\n 'height': this.viewportIn.height + 'px',\r\n 'width': this.viewportIn.width + 'px',\r\n });\r\n if (this.vertical) {\r\n this.scrollContainer.attr({\r\n width: (this.model.bars.length * BulletChart.SpaceRequiredForBarVertically + BulletChart.XMarginVertical) + 'px',\r\n height: this.viewportScroll.height + 'px'\r\n });\r\n }\r\n else {\r\n this.scrollContainer.attr({\r\n height: (this.model.bars.length * this.model.spaceRequiredForBarHorizontally) + 'px',\r\n width: this.viewportScroll.width + 'px'\r\n });\r\n }\r\n\r\n if (this.vertical)\r\n this.setUpBulletsVertically(this.bulletBody, this.model, this.reverse);\r\n else\r\n this.setUpBulletsHorizontally(this.bulletBody, this.model, this.reverse);\r\n }\r\n\r\n private ClearViewport() {\r\n this.labelGraphicsContext.selectAll(\"text\").remove();\r\n this.bulletGraphicsContext.selectAll(\"rect\").remove();\r\n this.bulletGraphicsContext.selectAll(\"text\").remove();\r\n this.bulletGraphicsContext.selectAll('axis').remove();\r\n this.bulletGraphicsContext.selectAll('path').remove();\r\n this.bulletGraphicsContext.selectAll('line').remove();\r\n this.bulletGraphicsContext.selectAll('tick').remove();\r\n this.bulletGraphicsContext.selectAll('g').remove();\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 private calculateLabelWidth(barData: BarData, bar?: BarRect, reversed?: boolean) {\r\n return (reversed ? 0 : barData.x + BulletChart.MaxLabelWidth) + BulletChart.XMarginHorizontal + (bar ? bar.start : 0);\r\n }\r\n\r\n private calculateLabelHeight(barData: BarData, bar?: BarRect, reversed?: boolean) {\r\n return BulletChart.YMarginVertical + (reversed ? 5 :\r\n barData.y + this.model.labelHeightTop + BulletChart.BarMargin + BulletChart.SubtitleMargin)\r\n + (bar ? bar.end : 0);\r\n }\r\n\r\n private setUpBulletsHorizontally(bulletBody: D3.Selection, model: BulletChartModel, reveresed: boolean) {\r\n let bars = model.bars;\r\n let rects = model.barRects;\r\n let valueRects = model.valueRects;\r\n let targetValues = model.targetValues;\r\n let barSelection = this.labelGraphicsContext.selectAll('text').data(bars, (d: BarData) => d.key);\r\n let rectSelection = this.bulletGraphicsContext.selectAll('rect.range').data(rects, (d: BarRect) => d.key);\r\n\r\n let hasSelection: boolean = this.interactivityService && this.interactivityService.hasSelection();\r\n let hasHighlights: boolean = model.hasHighlights;\r\n\r\n // Draw bullets\r\n let bullets = rectSelection.enter().append('rect').attr({\r\n 'x': ((d: BarRect) => { return this.calculateLabelWidth(bars[d.barIndex], d, reveresed); }),\r\n 'y': ((d: BarRect) => bars[d.barIndex].y - BulletChart.BulletSize / 2),\r\n 'width': ((d: BarRect) => d.end - d.start),\r\n 'height': BulletChart.BulletSize,\r\n }).classed('range', true).style({\r\n 'fill': (d: BarRect) => d.fill,\r\n 'opacity': (d: BarRect) => ColumnUtil.getFillOpacity(d.selected, d.highlight, hasSelection, hasHighlights),\r\n });\r\n\r\n rectSelection.exit();\r\n\r\n // Draw value rects\r\n let valueSelection = this.bulletGraphicsContext.selectAll('rect').data(valueRects, (d: BarValueRect) => d.key);\r\n valueSelection.enter().append('rect').attr({\r\n 'x': ((d: BarValueRect) => { return this.calculateLabelWidth(bars[d.barIndex], d, reveresed); }),\r\n 'y': ((d: BarValueRect) => bars[d.barIndex].y - BulletChart.BulletSize / 8),\r\n 'width': ((d: BarValueRect) => d.end - d.start),\r\n 'height': BulletChart.BulletSize * 1 / 4,\r\n }).classed('value', true).style({\r\n 'fill': (d: BarValueRect) => d.fill,\r\n 'opacity': (d: BarRect) => ColumnUtil.getFillOpacity(d.selected, d.highlight, hasSelection, hasHighlights),\r\n });\r\n\r\n valueSelection.exit();\r\n // Draw markers\r\n let markerSelection = this.bulletGraphicsContext.selectAll('values').data(targetValues, (d: TargetValue) => {\r\n if (!isNaN(d.value) && !isNaN(d.value2))\r\n return d.key;\r\n });\r\n markerSelection.enter().append('line').attr({\r\n 'x1': ((d: TargetValue) => { return this.calculateLabelWidth(bars[d.barIndex], null, reveresed) + d.value; }),\r\n 'x2': ((d: TargetValue) => { return this.calculateLabelWidth(bars[d.barIndex], null, reveresed) + d.value; }),\r\n 'y1': ((d: TargetValue) => bars[d.barIndex].y - BulletChart.MarkerMarginHorizontal),\r\n 'y2': ((d: TargetValue) => bars[d.barIndex].y + BulletChart.MarkerMarginHorizontal),\r\n }).style({\r\n 'stroke': ((d: TargetValue) => d.fill),\r\n 'stroke-width': 2,\r\n });\r\n\r\n if (model.bulletChartSettings.values.secondTargetVisibility) {\r\n this.drawSecondTarget(\r\n markerSelection.enter(),\r\n (d: TargetValue) => this.calculateLabelWidth(bars[d.barIndex], null, reveresed) + d.value2,\r\n (d: TargetValue) => bars[d.barIndex].y);\r\n }\r\n\r\n markerSelection.exit();\r\n\r\n // Draw axes\r\n if (model.bulletChartSettings.axis.axis) {\r\n // Using var instead of let since you can't pass let parameters to functions inside loops.\r\n // needs to be changed to let when typescript 1.8 comes out.\r\n for (var idx = 0; idx < bars.length; idx++) {\r\n var bar = bars[idx];\r\n this.bulletGraphicsContext.append(\"g\").attr({\r\n 'transform': () => {\r\n let xLocation = this.calculateLabelWidth(bar, null, reveresed);\r\n let yLocation = bar.y + BulletChart.BulletSize / 2;\r\n\r\n return 'translate(' + xLocation + ',' + yLocation + ')';\r\n },\r\n }).classed(\"axis\", true).call(bar.axis.scale(bar.scale)).style({\r\n 'fill': model.bulletChartSettings.axis.axisColor,\r\n 'font-size': PixelConverter.fromPoint(BulletChart.AxisFontSizeInPt)\r\n }).selectAll('line').style({\r\n 'stroke': model.bulletChartSettings.axis.axisColor,\r\n });\r\n }\r\n }\r\n\r\n // Draw Labels\r\n if (model.bulletChartSettings.labelSettings.show) {\r\n barSelection.enter().append('text').classed(\"title\", true).attr({\r\n 'x': ((d: BarData) => {\r\n if (reveresed)\r\n\t\t\t\t\t\t\treturn BulletChart.XMarginHorizontal * 2 + model.viewportLength;\r\n return d.x;\r\n }),\r\n 'y': ((d: BarData) => d.y + this.baselineDelta),\r\n 'fill': model.bulletChartSettings.labelSettings.labelColor,\r\n 'font-size': PixelConverter.fromPoint(model.bulletChartSettings.labelSettings.fontSize),\r\n }).text((d: BarData) => d.categoryLabel);\r\n }\r\n\r\n let measureUnitsText = TextMeasurementService.getTailoredTextOrDefault(\r\n BulletChart.getTextProperties(model.bulletChartSettings.axis.measureUnits, BulletChart.DefaultSubtitleFontSizeInPt),\r\n BulletChart.MaxMeasureUnitWidth);\r\n\r\n // Draw measure label\r\n if (model.bulletChartSettings.axis.measureUnits) {\r\n barSelection.enter().append('text').attr({\r\n 'x': ((d: BarData) => {\r\n if (reveresed)\r\n return BulletChart.XMarginHorizontal * 2 + model.viewportLength + BulletChart.SubtitleMargin;\r\n return d.x - BulletChart.SubtitleMargin;\r\n }),\r\n 'y': ((d: BarData) => d.y + this.model.labelHeight/2 + 12),\r\n 'fill': model.bulletChartSettings.axis.unitsColor,\r\n 'font-size': PixelConverter.fromPoint(BulletChart.DefaultSubtitleFontSizeInPt)\r\n }).text(measureUnitsText);\r\n }\r\n\r\n if (this.interactivityService) {\r\n let behaviorOptions: BulletBehaviorOptions = {\r\n rects: bullets,\r\n valueRects: valueSelection,\r\n clearCatcher: this.clearCatcher,\r\n interactivityService: this.interactivityService,\r\n bulletChartSettings: this.model.bulletChartSettings,\r\n hasHighlights: false,\r\n };\r\n\r\n let targetCollection = this.model.barRects.concat(this.model.valueRects);\r\n this.interactivityService.bind(targetCollection, this.behavior, behaviorOptions);\r\n }\r\n\r\n barSelection.exit();\r\n TooltipManager.addTooltip(valueSelection, (tooltipEvent: TooltipEvent) => tooltipEvent.data.tooltipInfo, true);\r\n TooltipManager.addTooltip(rectSelection, (tooltipEvent: TooltipEvent) => tooltipEvent.data.tooltipInfo, true);\r\n }\r\n\r\n private setUpBulletsVertically(bulletBody: D3.Selection, model: BulletChartModel, reveresed: boolean) {\r\n let bars = model.bars;\r\n let rects = model.barRects;\r\n let valueRects = model.valueRects;\r\n let targetValues = model.targetValues;\r\n let barSelection = this.labelGraphicsContext.selectAll('text').data(bars, (d: BarData) => d.key);\r\n let rectSelection = this.bulletGraphicsContext.selectAll('rect.range').data(rects, (d: BarRect) => d.key);\r\n\r\n let hasSelection: boolean = this.interactivityService && this.interactivityService.hasSelection();\r\n let hasHighlights: boolean = model.hasHighlights;\r\n\r\n // Draw bullets\r\n let bullets = rectSelection.enter().append('rect').attr({\r\n 'x': ((d: BarRect) => bars[d.barIndex].x),\r\n 'y': ((d: BarRect) => { return this.calculateLabelHeight(bars[d.barIndex], d, reveresed); }),\r\n 'height': ((d: BarRect) => d.start - d.end),\r\n 'width': BulletChart.BulletSize,\r\n }).classed('range', true).style({\r\n 'fill': (d: BarRect) => d.fill,\r\n 'opacity': (d: BarRect) => ColumnUtil.getFillOpacity(d.selected, d.highlight, hasSelection, hasHighlights),\r\n });\r\n\r\n rectSelection.exit();\r\n\r\n // Draw value rects\r\n let valueSelection = this.bulletGraphicsContext.selectAll('rect').data(valueRects, (d: BarValueRect) => d.key);\r\n valueSelection.enter().append('rect').attr({\r\n 'x': ((d: BarValueRect) => bars[d.barIndex].x + BulletChart.BulletSize / 3),\r\n 'y': ((d: BarValueRect) => { return this.calculateLabelHeight(bars[d.barIndex], d, reveresed); }),\r\n 'height': ((d: BarValueRect) => d.start - d.end),\r\n 'width': BulletChart.BulletSize * 1 / 4,\r\n }).classed('value', true).style({\r\n 'fill': (d: BarValueRect) => d.fill,\r\n 'opacity': (d: BarRect) => ColumnUtil.getFillOpacity(d.selected, d.highlight, hasSelection, hasHighlights),\r\n });\r\n\r\n valueSelection.exit();\r\n\r\n // Draw markers\r\n let markerSelection = this.bulletGraphicsContext.selectAll('values').data(targetValues, (d: TargetValue) => {\r\n if (!isNaN(d.value) && !isNaN(d.value2))\r\n return d.key;\r\n });\r\n markerSelection.enter().append('line').attr({\r\n 'x2': ((d: TargetValue) => bars[d.barIndex].x + (BulletChart.MarkerMarginVertical * 3)),\r\n 'x1': ((d: TargetValue) => bars[d.barIndex].x + BulletChart.MarkerMarginVertical),\r\n 'y2': ((d: TargetValue) => { return this.calculateLabelHeight(bars[d.barIndex], null, reveresed) + d.value; }),\r\n 'y1': ((d: TargetValue) => { return this.calculateLabelHeight(bars[d.barIndex], null, reveresed) + d.value; }),\r\n }).style({\r\n 'stroke': ((d: TargetValue) => d.fill),\r\n 'stroke-width': 2,\r\n });\r\n\r\n if (model.bulletChartSettings.values.secondTargetVisibility) {\r\n this.drawSecondTarget(markerSelection.enter(),\r\n (d: TargetValue) => bars[d.barIndex].x + BulletChart.BulletSize / 3 + BulletChart.BulletSize/8,\r\n (d: TargetValue) => this.calculateLabelHeight(bars[d.barIndex], null, reveresed) + d.value2);\r\n }\r\n\r\n markerSelection.exit();\r\n\r\n // // Draw axes\r\n if (model.bulletChartSettings.axis.axis) {\r\n\r\n // Using var instead of let since you can't pass let parameters to functions inside loops.\r\n // needs to be changed to let when typescript 1.8 comes out.\r\n for (var idx = 0; idx < bars.length; idx++) {\r\n var bar = bars[idx];\r\n this.bulletGraphicsContext.append(\"g\").attr({\r\n 'transform': () => {\r\n let xLocation = bar.x;\r\n let yLocation = this.calculateLabelHeight(bar, null, reveresed);\r\n // let yLocation = bar.y + BulletChart.BulletSize / 2;\r\n return 'translate(' + xLocation + ',' + yLocation + ')';\r\n },\r\n }).classed(\"axis\", true).call(bar.axis.scale(bar.scale)).style({\r\n 'fill': model.bulletChartSettings.axis.axisColor,\r\n 'font-size': PixelConverter.fromPoint(BulletChart.AxisFontSizeInPt),\r\n }).selectAll('line').style({\r\n 'stroke': model.bulletChartSettings.axis.axisColor,\r\n });\r\n }\r\n }\r\n\r\n\t\t\tlet labelsStartPos = BulletChart.YMarginVertical + (reveresed ? model.viewportLength + 15 : 0) + this.model.labelHeightTop;\r\n\r\n // Draw Labels\r\n if (model.bulletChartSettings.labelSettings.show) {\r\n barSelection.enter().append('text').classed(\"title\", true).attr({\r\n 'x': ((d: BarData) => d.x),\r\n 'y': ((d: BarData) => {\r\n return labelsStartPos;\r\n }),\r\n 'fill': model.bulletChartSettings.labelSettings.labelColor,\r\n 'font-size': PixelConverter.fromPoint(model.bulletChartSettings.labelSettings.fontSize),\r\n }).text((d: BarData) => d.categoryLabel);\r\n }\r\n\r\n let measureUnitsText = TextMeasurementService.getTailoredTextOrDefault(\r\n BulletChart.getTextProperties(model.bulletChartSettings.axis.measureUnits,BulletChart.DefaultSubtitleFontSizeInPt),\r\n BulletChart.MaxMeasureUnitWidth);\r\n\r\n // Draw measure label\r\n if (model.bulletChartSettings.axis.measureUnits) {\r\n barSelection.enter().append('text').attr({\r\n 'x': ((d: BarData) => d.x + BulletChart.BulletSize),\r\n 'y': ((d: BarData) => {\r\n return labelsStartPos + BulletChart.SubtitleMargin + 12;\r\n }),\r\n 'fill': model.bulletChartSettings.axis.unitsColor,\r\n 'font-size': PixelConverter.fromPoint(BulletChart.DefaultSubtitleFontSizeInPt)\r\n }).text(measureUnitsText);\r\n }\r\n\r\n if (this.interactivityService) {\r\n let behaviorOptions: BulletBehaviorOptions = {\r\n rects: bullets,\r\n valueRects: valueSelection,\r\n clearCatcher: this.clearCatcher,\r\n interactivityService: this.interactivityService,\r\n bulletChartSettings: this.model.bulletChartSettings,\r\n hasHighlights: false,\r\n };\r\n\r\n let targetCollection = this.model.barRects.concat(this.model.valueRects);\r\n this.interactivityService.bind(targetCollection, this.behavior, behaviorOptions);\r\n }\r\n\r\n barSelection.exit();\r\n TooltipManager.addTooltip(valueSelection, (tooltipEvent: TooltipEvent) => tooltipEvent.data.tooltipInfo, true);\r\n TooltipManager.addTooltip(rectSelection, (tooltipEvent: TooltipEvent) => tooltipEvent.data.tooltipInfo, true);\r\n }\r\n\r\n private drawSecondTarget(\r\n selection: D3.EnterSelection,\r\n getX: (d: TargetValue) => number,\r\n getY: (d: TargetValue) => number): void {\r\n \r\n let targetStyle = { \r\n 'stroke': ((d: TargetValue) => d.fill),\r\n 'stroke-width': 2\r\n };\r\n\r\n selection.append('line').attr({\r\n 'x1': ((d: TargetValue) => getX(d) - BulletChart.SecondTargetLineSize),\r\n 'y1': ((d: TargetValue) => getY(d) - BulletChart.SecondTargetLineSize),\r\n 'x2': ((d: TargetValue) => getX(d) + BulletChart.SecondTargetLineSize),\r\n 'y2': ((d: TargetValue) => getY(d) + BulletChart.SecondTargetLineSize),\r\n }).style(targetStyle);\r\n\r\n selection.append('line').attr({\r\n 'x1': ((d: TargetValue) => getX(d) + BulletChart.SecondTargetLineSize),\r\n 'y1': ((d: TargetValue) => getY(d) - BulletChart.SecondTargetLineSize),\r\n 'x2': ((d: TargetValue) => getX(d) - BulletChart.SecondTargetLineSize),\r\n 'y2': ((d: TargetValue) => getY(d) + BulletChart.SecondTargetLineSize),\r\n }).style(targetStyle);\r\n }\r\n\r\n /*About to remove your visual, do clean up here */\r\n public destroy() { }\r\n\r\n public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstance[] {\r\n let data = this.model;\r\n if (!data) {\r\n return;\r\n }\r\n\r\n let objectName = options.objectName;\r\n switch (objectName) {\r\n case 'labels':\r\n return this.enumerateLabels(data);\r\n case 'values':\r\n return this.enumerateValues(data);\r\n case 'orientation':\r\n return this.enumerateOrientation(data);\r\n case 'axis':\r\n return this.enumerateAxis(data);\r\n case 'colors':\r\n return this.enumerateColors(data);\r\n }\r\n }\r\n\r\n private enumerateLabels(data: BulletChartModel): VisualObjectInstance[] {\r\n return [{\r\n selector: null,\r\n objectName: 'labels',\r\n properties: {\r\n show: this.model.bulletChartSettings.labelSettings.show,\r\n labelColor: this.model.bulletChartSettings.labelSettings.labelColor,\r\n fontSize: this.model.bulletChartSettings.labelSettings.fontSize,\r\n }\r\n }];\r\n }\r\n\r\n private enumerateValues(data: BulletChartModel): VisualObjectInstance[] {\r\n return [{\r\n selector: null,\r\n objectName: 'values',\r\n properties: {\r\n targetValue: this.model.bulletChartSettings.values.targetValue,\r\n targetValue2: this.model.bulletChartSettings.values.targetValue2,\r\n secondTargetVisibility: this.model.bulletChartSettings.values.secondTargetVisibility,\r\n minimumPercent: this.model.bulletChartSettings.values.minimumPercent,\r\n needsImprovementPercent: this.model.bulletChartSettings.values.needsImprovementPercent,\r\n satisfactoryPercent: this.model.bulletChartSettings.values.satisfactoryPercent,\r\n goodPercent: this.model.bulletChartSettings.values.goodPercent,\r\n veryGoodPercent: this.model.bulletChartSettings.values.veryGoodPercent,\r\n maximumPercent: this.model.bulletChartSettings.values.maximumPercent,\r\n }\r\n }];\r\n }\r\n\r\n private enumerateOrientation(data: BulletChartModel): VisualObjectInstance[] {\r\n return [{\r\n selector: null,\r\n objectName: 'orientation',\r\n properties: {\r\n orientation: this.model.bulletChartSettings.orientation.orientation\r\n }\r\n }];\r\n }\r\n\r\n private enumerateAxis(data: BulletChartModel): VisualObjectInstance[] {\r\n return [{\r\n selector: null,\r\n objectName: 'axis',\r\n properties: {\r\n axis: this.model.bulletChartSettings.axis.axis,\r\n axisColor: this.model.bulletChartSettings.axis.axisColor,\r\n measureUnits: this.model.bulletChartSettings.axis.measureUnits,\r\n unitsColor: this.model.bulletChartSettings.axis.unitsColor,\r\n }\r\n }];\r\n }\r\n\r\n private enumerateColors(data: BulletChartModel): VisualObjectInstance[] {\r\n return [{\r\n selector: null,\r\n objectName: 'colors',\r\n properties: {\r\n badColor: this.model.bulletChartSettings.colors.badColor,\r\n needsImprovementColor: this.model.bulletChartSettings.colors.needsImprovementColor,\r\n satisfactoryColor: this.model.bulletChartSettings.colors.satisfactoryColor,\r\n goodColor: this.model.bulletChartSettings.colors.goodColor,\r\n veryGoodColor: this.model.bulletChartSettings.colors.veryGoodColor,\r\n bulletColor: this.model.bulletChartSettings.colors.bulletColor,\r\n }\r\n }];\r\n }\r\n }\r\n\r\n //TODO: This module should be removed once TextMeasruementService exports the \"estimateSvgTextBaselineDelta\" function.\r\n export module TextMeasurementHelper {\r\n\r\n interface CanvasContext {\r\n font: string;\r\n measureText(text: string): { width: number };\r\n }\r\n\r\n interface CanvasElement extends HTMLElement {\r\n getContext(name: string);\r\n }\r\n\r\n let spanElement: JQuery;\r\n let svgTextElement: D3.Selection;\r\n let canvasCtx: CanvasContext;\r\n\r\n export function estimateSvgTextBaselineDelta(textProperties: TextProperties): number {\r\n let rect = estimateSvgTextRect(textProperties);\r\n return rect.y + rect.height;\r\n }\r\n\r\n function ensureDOM(): void {\r\n if (spanElement)\r\n return;\r\n\r\n spanElement = $('<span/>');\r\n $('body').append(spanElement);\r\n //The style hides the svg element from the canvas, preventing canvas from scrolling down to show svg black square.\r\n svgTextElement = d3.select($('body').get(0))\r\n .append('svg')\r\n .style({\r\n 'height': '0px',\r\n 'width': '0px',\r\n 'position': 'absolute'\r\n })\r\n .append('text');\r\n canvasCtx = (<CanvasElement>$('<canvas/>').get(0)).getContext(\"2d\");\r\n }\r\n\r\n function measureSvgTextRect(textProperties: TextProperties): SVGRect {\r\n debug.assertValue(textProperties, 'textProperties');\r\n\r\n ensureDOM();\r\n\r\n svgTextElement.style(null);\r\n svgTextElement\r\n .text(textProperties.text)\r\n .attr({\r\n 'visibility': 'hidden',\r\n 'font-family': textProperties.fontFamily,\r\n 'font-size': textProperties.fontSize,\r\n 'font-weight': textProperties.fontWeight,\r\n 'font-style': textProperties.fontStyle,\r\n 'white-space': textProperties.whiteSpace || 'nowrap'\r\n });\r\n\r\n // We're expecting the browser to give a synchronous measurement here\r\n // We're using SVGTextElement because it works across all browsers \r\n return svgTextElement.node<SVGTextElement>().getBBox();\r\n }\r\n\r\n function estimateSvgTextRect(textProperties: TextProperties): SVGRect {\r\n debug.assertValue(textProperties, 'textProperties');\r\n\r\n let estimatedTextProperties: TextProperties = {\r\n fontFamily: textProperties.fontFamily,\r\n fontSize: textProperties.fontSize,\r\n text: \"M\",\r\n };\r\n\r\n let rect = measureSvgTextRect(estimatedTextProperties);\r\n\r\n return rect;\r\n }\r\n }\r\n\r\n export interface BulletBehaviorOptions {\r\n rects: D3.Selection;\r\n valueRects: D3.Selection;\r\n clearCatcher: D3.Selection;\r\n interactivityService: IInteractivityService;\r\n bulletChartSettings: BulletChartSettings;\r\n hasHighlights: boolean;\r\n }\r\n\r\n export class BulletWebBehavior implements IInteractiveBehavior {\r\n private options: BulletBehaviorOptions;\r\n\r\n public bindEvents(options: BulletBehaviorOptions, selectionHandler: ISelectionHandler) {\r\n this.options = options;\r\n let clearCatcher = options.clearCatcher;\r\n\r\n options.valueRects.on('click', (d: BarValueRect, i: number) => {\r\n d3.event.stopPropagation();\r\n selectionHandler.handleSelection(d, d3.event.ctrlKey);\r\n });\r\n\r\n options.rects.on('click', (d: BarRect, i: number) => {\r\n d3.event.stopPropagation();\r\n selectionHandler.handleSelection(d, d3.event.ctrlKey);\r\n });\r\n\r\n clearCatcher.on('click', () => {\r\n selectionHandler.handleClearSelection();\r\n });\r\n }\r\n\r\n public renderSelection(hasSelection: boolean) {\r\n let options = this.options;\r\n let hasHighlights = options.hasHighlights;\r\n\r\n options.valueRects.style(\"opacity\", (d: BarValueRect) =>\r\n ColumnUtil.getFillOpacity(d.selected, d.highlight, !d.highlight && hasSelection, !d.selected && hasHighlights));\r\n\r\n options.rects.style(\"opacity\", (d: BarRect) =>\r\n ColumnUtil.getFillOpacity(d.selected, d.highlight, !d.highlight && hasSelection, !d.selected && hasHighlights));\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/CustomVisuals/visuals/bulletChart/visual/bulletChart.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// Word cloud layout by Jason Davies, https://www.jasondavies.com/wordcloud/ https://github.com/jasondavies/d3-cloud\r\n// Algorithm due to Jonathan Feinberg, http://static.mrfeinberg.com/bv_ch03.pdf\r\n\r\n/// <reference path=\"../../../_references.ts\"/>\r\n\r\nmodule powerbi.visuals.samples {\r\n import ValueFormatter = powerbi.visuals.valueFormatter;\r\n import getAnimationDuration = AnimatorCommon.GetAnimationDuration;\r\n import ClassAndSelector = jsCommon.CssConstants.ClassAndSelector;\r\n\r\n type D3Element =\r\n D3.UpdateSelection\r\n | D3.Selection\r\n | D3.Selectors\r\n | D3.Transition.Transition;\r\n\r\n export enum WordCloudScaleType {\r\n logn,\r\n sqrt,\r\n value\r\n };\r\n\r\n export interface WordCloudText {\r\n text: string;\r\n textGroup: string;\r\n count: number;\r\n index: number;\r\n selectionId: SelectionId;\r\n color: string;\r\n }\r\n\r\n export interface WordCloudDataPoint extends IPoint {\r\n text: string;\r\n xOff: number;\r\n yOff: number;\r\n rotate?: number;\r\n size?: number;\r\n padding: number;\r\n width: number;\r\n height: number;\r\n sprite?: number[];\r\n x0: number;\r\n y0: number;\r\n x1: number;\r\n y1: number;\r\n color: string;\r\n selectionIds: SelectionId[];\r\n wordIndex: number;\r\n widthOfWord?: number;\r\n count: number;\r\n }\r\n\r\n export interface WordCloudData {\r\n dataView: DataView;\r\n settings: WordCloudSettings;\r\n texts: WordCloudText[];\r\n dataPoints: WordCloudDataPoint[];\r\n }\r\n\r\n export interface WordCloudDataView {\r\n data: WordCloudDataPoint[];\r\n leftBorder: IPoint;\r\n rightBorder: IPoint;\r\n }\r\n\r\n export interface WordCloudConstructorOptions {\r\n svg?: D3.Selection;\r\n animator?: IGenericAnimator;\r\n margin?: IMargin;\r\n }\r\n\r\n class CustomSelectionManager {\r\n private selectionIdsValue: SelectionId[] = [];\r\n private hostServices: IVisualHostServices;\r\n\r\n public constructor(hostServices: IVisualHostServices) {\r\n this.hostServices = hostServices;\r\n }\r\n\r\n public get selectionIds(): SelectionId[] {\r\n return this.selectionIdsValue;\r\n }\r\n\r\n public get hasSelection(): boolean {\r\n return this.selectionIds.length > 0;\r\n }\r\n\r\n public selectAndSendSelection(selectionId: SelectionId[] | SelectionId, multiSelect: boolean = false): JQueryDeferred<SelectionId[]> {\r\n var selectionIds = <SelectionId[]>(_.isArray(selectionId) ? selectionId : [selectionId]);\r\n if (this.hostServices.shouldRetainSelection()) {\r\n return this.sendSelectionToHost(selectionIds);\r\n } else {\r\n this.selectInternal(selectionIds, multiSelect);\r\n return this.sendSelection();\r\n }\r\n }\r\n\r\n public select(selectionId: SelectionId[] | SelectionId, multiSelect: boolean = false) {\r\n var selectionIds = <SelectionId[]>(_.isArray(selectionId) ? selectionId : [selectionId]);\r\n this.selectInternal(selectionIds, multiSelect);\r\n }\r\n\r\n public isSelected(selectionId: SelectionId[] | SelectionId): boolean {\r\n var selectionIds = <SelectionId[]>(_.isArray(selectionId) ? selectionId : [selectionId]);\r\n return selectionIds.every(x => utility.SelectionManager.containsSelection(this.selectionIds, x));\r\n }\r\n\r\n public sendSelection(): JQueryDeferred<SelectionId[]> {\r\n return this.sendSelectionToHost(this.selectionIds);\r\n }\r\n\r\n public clear(sendToHost: boolean = true): JQueryDeferred<{}> {\r\n this.selectionIds.length = 0;\r\n\r\n if(sendToHost) {\r\n return this.sendSelection();\r\n }\r\n\r\n return $.Deferred().resolve();\r\n }\r\n\r\n private selectInternal(selectionIds: SelectionId[] , multiSelect: boolean) {\r\n var resultSelectionIds = [];\r\n\r\n if (selectionIds.every(x => this.isSelected(x))) {\r\n resultSelectionIds = multiSelect\r\n ? this.selectionIds.filter(x => !utility.SelectionManager.containsSelection(selectionIds, x))\r\n : this.selectionIds.length === selectionIds.length ? [] : selectionIds;\r\n } else {\r\n resultSelectionIds = multiSelect\r\n ? selectionIds.filter(x => !this.isSelected(x)).concat(this.selectionIds)\r\n : selectionIds;\r\n }\r\n\r\n this.selectionIds.length = 0;\r\n resultSelectionIds.forEach(x => this.selectionIds.push(x));\r\n }\r\n\r\n private sendSelectionToHost(ids: SelectionId[]): JQueryDeferred<SelectionId[]> {\r\n var deferred: JQueryDeferred<data.Selector[]> = $.Deferred();\r\n\r\n var 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 var data2 = this.getSelectorsByColumn(ids);\r\n\r\n if (!_.isEmpty(data2)) {\r\n selectArgs.data2 = data2;\r\n }\r\n\r\n this.hostServices.onSelect(selectArgs);\r\n\r\n deferred.resolve(this.selectionIds);\r\n return deferred;\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\r\n class VisualLayout {\r\n private marginValue: IMargin;\r\n private viewportValue: IViewport;\r\n private viewportInValue: IViewport;\r\n private minViewportValue: IViewport;\r\n private originalViewportValue: IViewport;\r\n private previousOriginalViewportValue: IViewport;\r\n\r\n public defaultMargin: IMargin;\r\n public defaultViewport: IViewport;\r\n\r\n constructor(defaultViewport?: IViewport, defaultMargin?: IMargin) {\r\n this.defaultViewport = defaultViewport || { width: 0, height: 0 };\r\n this.defaultMargin = defaultMargin || { top: 0, bottom: 0, right: 0, left: 0 };\r\n }\r\n\r\n public get viewport(): IViewport {\r\n return this.viewportValue || (this.viewportValue = this.defaultViewport);\r\n }\r\n\r\n public get viewportCopy(): IViewport {\r\n return _.clone(this.viewport);\r\n }\r\n\r\n //Returns viewport minus margin\r\n public get viewportIn(): IViewport {\r\n return this.viewportInValue || this.viewport;\r\n }\r\n\r\n public get minViewport(): IViewport {\r\n return this.minViewportValue || { width: 0, height: 0 };\r\n }\r\n\r\n public get margin(): IMargin {\r\n return this.marginValue || (this.marginValue = this.defaultMargin);\r\n }\r\n\r\n public set minViewport(value: IViewport) {\r\n this.setUpdateObject(value, v => this.minViewportValue = v, VisualLayout.restrictToMinMax);\r\n }\r\n\r\n public set viewport(value: IViewport) {\r\n this.previousOriginalViewportValue = _.clone(this.originalViewportValue);\r\n this.originalViewportValue = _.clone(value);\r\n this.setUpdateObject(value,\r\n v => this.viewportValue = v,\r\n o => VisualLayout.restrictToMinMax(o, this.minViewport));\r\n }\r\n\r\n public set margin(value: IMargin) {\r\n this.setUpdateObject(value, v => this.marginValue = v, VisualLayout.restrictToMinMax);\r\n }\r\n\r\n //Returns true if viewport has updated after last change.\r\n public get viewportChanged(): boolean {\r\n return !!this.originalViewportValue && (!this.previousOriginalViewportValue\r\n || this.previousOriginalViewportValue.height !== this.originalViewportValue.height\r\n || this.previousOriginalViewportValue.width !== this.originalViewportValue.width);\r\n }\r\n\r\n public get viewportInIsZero(): boolean {\r\n return this.viewportIn.width === 0 || this.viewportIn.height === 0;\r\n }\r\n\r\n public resetMargin(): void {\r\n this.margin = this.defaultMargin;\r\n }\r\n\r\n private update(): void {\r\n this.viewportInValue = VisualLayout.restrictToMinMax({\r\n width: this.viewport.width - (this.margin.left + this.margin.right),\r\n height: this.viewport.height - (this.margin.top + this.margin.bottom)\r\n }, this.minViewportValue);\r\n }\r\n\r\n private setUpdateObject<T>(object: T, setObjectFn: (T) => void, beforeUpdateFn?: (T) => void): void {\r\n object = _.clone(object);\r\n setObjectFn(VisualLayout.createNotifyChangedObject(object, o => {\r\n if(beforeUpdateFn) beforeUpdateFn(object);\r\n this.update();\r\n }));\r\n\r\n if(beforeUpdateFn) beforeUpdateFn(object);\r\n this.update();\r\n }\r\n\r\n private static createNotifyChangedObject<T>(object: T, objectChanged: (o?: T, key?: string) => void): T {\r\n var result: T = <any>{};\r\n _.keys(object).forEach(key => Object.defineProperty(result, key, {\r\n get: () => object[key],\r\n set: (value) => { object[key] = value; objectChanged(object, key); },\r\n enumerable: true,\r\n configurable: true\r\n }));\r\n return result;\r\n }\r\n\r\n private static restrictToMinMax<T>(value: T, minValue?: T): T {\r\n _.keys(value).forEach(x => value[x] = Math.max(minValue && minValue[x] || 0, value[x]));\r\n return value;\r\n }\r\n }\r\n\r\n export class WordCloudSettings {\r\n public static get Default() { \r\n return new this();\r\n }\r\n\r\n public static parse(dataView: DataView, capabilities: VisualCapabilities) {\r\n var settings = new this();\r\n if(!dataView || !dataView.metadata || !dataView.metadata.objects) {\r\n return settings;\r\n }\r\n\r\n var properties = this.getProperties(capabilities);\r\n for(var objectKey in capabilities.objects) {\r\n for(var propKey in capabilities.objects[objectKey].properties) {\r\n if(!settings[objectKey] || !_.has(settings[objectKey], propKey)) {\r\n continue;\r\n }\r\n\r\n var type = capabilities.objects[objectKey].properties[propKey].type;\r\n var getValueFn = this.getValueFnByType(type);\r\n settings[objectKey][propKey] = getValueFn(\r\n dataView.metadata.objects,\r\n properties[objectKey][propKey],\r\n settings[objectKey][propKey]);\r\n }\r\n }\r\n\r\n return settings;\r\n }\r\n\r\n public static getProperties(capabilities: VisualCapabilities)\r\n : { [i: string]: { [i: string]: DataViewObjectPropertyIdentifier } } & { \r\n general: { formatString: DataViewObjectPropertyIdentifier },\r\n dataPoint: { fill: DataViewObjectPropertyIdentifier } } {\r\n var objects = _.merge({ \r\n general: { properties: { formatString: {} } } \r\n }, capabilities.objects);\r\n var properties = <any>{};\r\n for(var objectKey in objects) {\r\n properties[objectKey] = {};\r\n for(var propKey in objects[objectKey].properties) {\r\n properties[objectKey][propKey] = <DataViewObjectPropertyIdentifier> {\r\n objectName: objectKey,\r\n propertyName: propKey\r\n };\r\n }\r\n }\r\n\r\n return properties;\r\n }\r\n\r\n public static createEnumTypeFromEnum(type: any): IEnumType {\r\n var even: any = false;\r\n return createEnumType(Object.keys(type)\r\n .filter((key,i) => ((!!(i % 2)) === even && type[key] === key\r\n && !void(even = !even)) || (!!(i % 2)) !== even)\r\n .map(x => <IEnumMember>{ value: x, displayName: x }));\r\n }\r\n\r\n private static getValueFnByType(type: powerbi.data.DataViewObjectPropertyTypeDescriptor) {\r\n switch(_.keys(type)[0]) {\r\n case \"fill\": \r\n return DataViewObjects.getFillColor;\r\n default:\r\n return DataViewObjects.getValue;\r\n }\r\n }\r\n\r\n public static enumerateObjectInstances(\r\n settings = new this(),\r\n options: EnumerateVisualObjectInstancesOptions,\r\n capabilities: VisualCapabilities): ObjectEnumerationBuilder {\r\n\r\n var enumeration = new ObjectEnumerationBuilder();\r\n var object = settings && settings[options.objectName];\r\n if(!object) {\r\n return enumeration;\r\n }\r\n\r\n var instance = <VisualObjectInstance>{\r\n objectName: options.objectName,\r\n selector: null,\r\n properties: {}\r\n };\r\n\r\n for(var key in object) {\r\n if(_.has(object,key)) {\r\n instance.properties[key] = object[key];\r\n }\r\n }\r\n\r\n enumeration.pushInstance(instance);\r\n return enumeration;\r\n }\r\n\r\n public originalSettings: WordCloudSettings;\r\n public createOriginalSettings(): void {\r\n this.originalSettings = _.cloneDeep(this);\r\n }\r\n\r\n //Default Settings\r\n public general = {\r\n maxNumberOfWords: 200,\r\n minFontSize: 20 / WordCloud.FontSizePercentageCoefficent,\r\n maxFontSize: 100 / WordCloud.FontSizePercentageCoefficent,\r\n isBrokenText: true\r\n };\r\n public stopWords = {\r\n show: true,\r\n isDefaultStopWords: false,\r\n words: null \r\n };\r\n public rotateText = {\r\n show: true,\r\n minAngle: -60,\r\n maxAngle: 90,\r\n maxNumberOfOrientations: 2\r\n };\r\n }\r\n\r\n export class WordCloudColumns<T> {\r\n public static Roles = Object.freeze(\r\n _.mapValues(new WordCloudColumns<string>(), (x, i) => i));\r\n\r\n public static getColumnSources(dataView: DataView) {\r\n return this.getColumnSourcesT<DataViewMetadataColumn>(dataView);\r\n }\r\n\r\n public static getTableValues(dataView: DataView) {\r\n var table = dataView && dataView.table;\r\n var columns = this.getColumnSourcesT<any[]>(dataView);\r\n return columns && table && _.mapValues(\r\n columns, (n: DataViewMetadataColumn, i) => n && table.rows.map(row => row[n.index]));\r\n }\r\n\r\n public static getTableRows(dataView: DataView) {\r\n var table = dataView && dataView.table;\r\n var columns = this.getColumnSourcesT<any[]>(dataView);\r\n return columns && table && table.rows.map(row =>\r\n _.mapValues(columns, (n: DataViewMetadataColumn, i) => n && row[n.index]));\r\n }\r\n\r\n public static getCategoricalValues(dataView: DataView) {\r\n var categorical = dataView && dataView.categorical;\r\n var categories = categorical && categorical.categories || [];\r\n var values = categorical && categorical.values || <DataViewValueColumns>[];\r\n var series: string[] = categorical && values.source && this.getSeriesValues(dataView);\r\n return categorical && _.mapValues(new this<any[]>(), (n, i) =>\r\n (<DataViewCategoricalColumn[]>_.toArray(categories)).concat(_.toArray(values))\r\n .filter(x => x.source.roles && x.source.roles[i]).map(x => x.values)[0]\r\n || values.source && values.source.roles && values.source.roles[i] && series);\r\n }\r\n\r\n public static getSeriesValues(dataView: DataView) {\r\n return dataView && dataView.categorical && dataView.categorical.values\r\n && dataView.categorical.values.map(x => converterHelper.getSeriesName(x.source));\r\n }\r\n\r\n public static getCategoricalColumns(dataView: DataView) {\r\n var categorical = dataView && dataView.categorical;\r\n var categories = categorical && categorical.categories || [];\r\n var values = categorical && categorical.values || <DataViewValueColumns>[];\r\n return categorical && _.mapValues(\r\n new this<DataViewCategoryColumn & DataViewValueColumn[] & DataViewValueColumns>(),\r\n (n, i) => categories.filter(x => x.source.roles && x.source.roles[i])[0]\r\n || values.source && values.source.roles && values.source.roles[i]\r\n || values.filter(x => x.source.roles && x.source.roles[i]));\r\n }\r\n\r\n private static getColumnSourcesT<T>(dataView: DataView) {\r\n var columns = dataView && dataView.metadata && dataView.metadata.columns;\r\n return columns && _.mapValues(\r\n new this<T>(), (n, i) => columns.filter(x => x.roles && x.roles[i])[0]);\r\n }\r\n\r\n //Data Roles\r\n public Category: T = null;\r\n public Values: T = null;\r\n }\r\n\r\n export class WordCloud implements IVisual {\r\n private static ClassName: string = \"wordCloud\";\r\n\r\n private static Words: ClassAndSelector = {\r\n \"class\": \"words\",\r\n selector: \".words\"\r\n };\r\n\r\n private static WordGroup: ClassAndSelector = {\r\n \"class\": \"word\",\r\n selector: \".word\"\r\n };\r\n\r\n private static Size: string = \"px\";\r\n private static StopWordsDelemiter: string = \" \";\r\n\r\n private static Radians: number = Math.PI / 180;\r\n\r\n private static MinAngle: number = -180;\r\n private static MaxAngle: number = 180;\r\n\r\n private static MaxNumberOfWords: number = 2500;\r\n\r\n private static MinOpacity: number = 0.2;\r\n private static MaxOpacity: number = 1;\r\n\r\n public static FontSizePercentageCoefficent = 1;\r\n\r\n public static capabilities: VisualCapabilities = {\r\n dataRoles: [{\r\n name: WordCloudColumns.Roles.Category,\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: \"Category\"\r\n }, {\r\n name: WordCloudColumns.Roles.Values,\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: \"Values\"\r\n }],\r\n dataViewMappings: [{\r\n conditions: [{\r\n \"Category\": {\r\n min: 1,\r\n max: 1\r\n },\r\n \"Values\": {\r\n min: 0,\r\n max: 1\r\n }\r\n }],\r\n categorical: {\r\n categories: {\r\n for: { in: \"Category\" },\r\n dataReductionAlgorithm: { top: { count: WordCloud.MaxNumberOfWords } }\r\n },\r\n values: {\r\n for: { in: \"Values\" }\r\n }\r\n }\r\n }],\r\n sorting: {\r\n implicit: {\r\n clauses: [{\r\n role: \"Values\",\r\n direction: 2 /*SortDirection.Descending*/ //Constant SortDirection.Descending currently is not supported on the msit\r\n }]\r\n }\r\n },\r\n objects: {\r\n general: {\r\n displayName: \"General\",\r\n properties: {\r\n formatString: {\r\n type: {\r\n formatting: {\r\n formatString: true\r\n }\r\n }\r\n },\r\n maxNumberOfWords: {\r\n displayName: \"Max number of words\",\r\n type: { numeric: true }\r\n },\r\n minFontSize: {\r\n displayName: \"Min Font\",\r\n type: { formatting: { fontSize: true } }\r\n },\r\n maxFontSize: {\r\n displayName: \"Max Font\",\r\n type: { formatting: { fontSize: true } }\r\n },\r\n isBrokenText: {\r\n displayName: \"Word-breaking\",\r\n type: { bool: true }\r\n },\r\n }\r\n },\r\n dataPoint: {\r\n displayName: \"Data colors\",\r\n properties: {\r\n fill: {\r\n displayName: \"Fill\",\r\n type: { fill: { solid: { color: true } } }\r\n }\r\n }\r\n },\r\n stopWords: {\r\n displayName: \"Stop Words\",\r\n properties: {\r\n show: {\r\n displayName: \"Show\",\r\n type: { bool: true }\r\n },\r\n isDefaultStopWords: {\r\n displayName: \"Default Stop Words\",\r\n type: { bool: true }\r\n },\r\n words: {\r\n displayName: \"Words\",\r\n type: { text: true }\r\n }\r\n }\r\n },\r\n rotateText: {\r\n displayName: \"Rotate Text\",\r\n properties: {\r\n show: {\r\n displayName: \"Show\",\r\n type: { bool: true }\r\n },\r\n minAngle: {\r\n displayName: \"Min Angle\",\r\n type: { numeric: true }\r\n },\r\n maxAngle: {\r\n displayName: \"Max Angle\",\r\n type: { numeric: true }\r\n },\r\n maxNumberOfOrientations: {\r\n displayName: \"Max number of orientations\",\r\n type: { numeric: true }\r\n }\r\n }\r\n }\r\n }\r\n };\r\n\r\n private static Punctuation: string[] = [\r\n \"!\", \".\", \":\", \"'\", \";\", \",\", \"!\",\r\n \"@\", \"#\", \"$\", \"%\", \"^\", \"&\", \"*\",\r\n \"(\", \")\", \"[\", \"]\", \"\\\"\", \"\\\\\", \"/\",\r\n \"-\", \"_\", \"+\", \"=\"\r\n ];\r\n\r\n private static StopWords: string[] = [\r\n \"a\", \"able\", \"about\", \"across\", \"after\", \"all\", \"almost\", \"also\", \"am\", \"among\", \"an\",\r\n \"and\", \"any\", \"are\", \"as\", \"at\", \"be\", \"because\", \"been\", \"but\", \"by\", \"can\", \"cannot\",\r\n \"could\", \"did\", \"do\", \"does\", \"either\", \"else\", \"ever\", \"every\", \"for\", \"from\", \"get\",\r\n \"got\", \"had\", \"has\", \"have\", \"he\", \"her\", \"hers\", \"him\", \"his\", \"how\", \"however\", \"i\",\r\n \"if\", \"in\", \"into\", \"is\", \"it\", \"its\", \"just\", \"least\", \"let\", \"like\", \"likely\", \"may\",\r\n \"me\", \"might\", \"most\", \"must\", \"my\", \"neither\", \"no\", \"nor\", \"not\", \"of\", \"off\", \"often\",\r\n \"on\", \"only\", \"or\", \"other\", \"our\", \"own\", \"rather\", \"said\", \"say\", \"says\", \"she\", \"should\",\r\n \"since\", \"so\", \"some\", \"than\", \"that\", \"the\", \"their\", \"them\", \"then\", \"there\", \"these\",\r\n \"they\", \"this\", \"tis\", \"to\", \"too\", \"twas\", \"us\", \"wants\", \"was\", \"we\", \"were\", \"what\",\r\n \"when\", \"where\", \"which\", \"while\", \"who\", \"whom\", \"why\", \"will\", \"with\", \"would\", \"yet\",\r\n \"you\", \"your\"\r\n ];\r\n\r\n private static DefaultMargin: IMargin = {\r\n top: 10,\r\n right: 10,\r\n bottom: 10,\r\n left: 10\r\n };\r\n\r\n public static converter(dataView: DataView, colors: IDataColorPalette, previousData: WordCloudData): WordCloudData {\r\n var categorical = WordCloudColumns.getCategoricalColumns(dataView);\r\n if(!categorical || !categorical.Category || _.isEmpty(categorical.Category.values)) {\r\n return null;\r\n }\r\n\r\n var catValues = WordCloudColumns.getCategoricalValues(dataView);\r\n var properties = WordCloudSettings.getProperties(WordCloud.capabilities);\r\n var settings: WordCloudSettings = WordCloud.parseSettings(dataView, previousData && previousData.settings);\r\n\r\n var wordValueFormatter = ValueFormatter.create({\r\n format: ValueFormatter.getFormatString(categorical.Category.source, properties.general.formatString),\r\n value: catValues.Category[0]\r\n });\r\n\r\n var stopWords = _.isString(settings.stopWords.words) ? settings.stopWords.words.split(WordCloud.StopWordsDelemiter) : [];\r\n stopWords = settings.stopWords.isDefaultStopWords ? stopWords.concat(WordCloud.StopWords) : stopWords;\r\n\r\n var colorHelper = new ColorHelper(colors, properties.dataPoint.fill, explore.util.getRandomColor());\r\n var texts = catValues.Category.map((item: string, index: number) => {\r\n var color;\r\n if (categorical.Category.objects && categorical.Category.objects[index]) {\r\n color = explore.util.hexToRgb(colorHelper.getColorForMeasure(categorical.Category.objects[index], \"\"));\r\n } else {\r\n color = previousData && previousData.texts && previousData.texts[index]\r\n ? previousData.texts[index].color\r\n : explore.util.getRandomColor();\r\n }\r\n\r\n return <WordCloudText>{\r\n text: item,\r\n count: (catValues.Values && catValues.Values[index] && !isNaN(catValues.Values[index])) ? catValues.Values[index] : 1,\r\n index: index,\r\n selectionId: SelectionId.createWithId(dataView.categorical.categories[0].identity[index]),\r\n color: color,\r\n textGroup: item\r\n };\r\n });\r\n\r\n var reducedTexts = WordCloud.getReducedText(texts, stopWords, settings);\r\n var dataPoints = WordCloud.getDataPoints(reducedTexts, settings, wordValueFormatter);\r\n return <WordCloudData>{\r\n dataView: dataView,\r\n settings: settings,\r\n texts: texts,\r\n dataPoints: dataPoints\r\n };\r\n }\r\n\r\n private static parseSettings(dataView: DataView, previousSettings: WordCloudSettings): WordCloudSettings {\r\n var settings = WordCloudSettings.parse(dataView, WordCloud.capabilities);\r\n settings.general.minFontSize = Math.max(settings.general.minFontSize, 1);\r\n settings.general.maxFontSize = Math.max(settings.general.maxFontSize, 1);\r\n settings.general.maxFontSize = Math.max(settings.general.maxFontSize, settings.general.minFontSize);\r\n\r\n settings.rotateText.minAngle = Math.max(Math.min(settings.rotateText.minAngle, WordCloud.MaxAngle), WordCloud.MinAngle);\r\n settings.rotateText.maxAngle = Math.max(Math.min(settings.rotateText.maxAngle, WordCloud.MaxAngle), WordCloud.MinAngle);\r\n settings.rotateText.maxAngle = Math.max(settings.rotateText.maxAngle, settings.rotateText.minAngle);\r\n\r\n settings.general.maxNumberOfWords = Math.max(\r\n Math.min(settings.general.maxNumberOfWords, WordCloud.MaxNumberOfWords), 1);\r\n settings.rotateText.maxNumberOfOrientations = Math.max(\r\n Math.min(settings.rotateText.maxNumberOfOrientations, WordCloud.MaxNumberOfWords), 1);\r\n\r\n settings.createOriginalSettings();\r\n return settings;\r\n }\r\n\r\n private static getReducedText(texts: WordCloudText[], stopWords: string[], settings: WordCloudSettings): WordCloudText[][] {\r\n var brokenStrings: WordCloudText[] = WordCloud.getBrokenWords(texts, stopWords, settings);\r\n var result = <WordCloudText[][]>_.values(_.groupBy(brokenStrings, x => x.text));\r\n result = result.map(texts => _.sortBy(texts, x => x.textGroup.length));\r\n return result;\r\n }\r\n\r\n private static getBrokenWords(words: WordCloudText[], stopWords: string[], settings: WordCloudSettings): WordCloudText[] {\r\n var brokenStrings: WordCloudText[] = [];\r\n var whiteSpaceRegExp: RegExp = /\\s/;\r\n var punctuatuinRegExp: RegExp = new RegExp(`[${WordCloud.Punctuation.join(\"\\\\\")}]`, \"gim\");\r\n\r\n if (!settings.general.isBrokenText) {\r\n return words;\r\n }\r\n\r\n words.forEach((item: WordCloudText) => {\r\n if (typeof item.text === \"string\") {\r\n var words = item.text.replace(punctuatuinRegExp, \" \").split(whiteSpaceRegExp);\r\n\r\n if (settings.stopWords.show) {\r\n words = words.filter((value: string) =>\r\n value.length > 0 && !stopWords.some((removeWord: string) =>\r\n value.toLocaleLowerCase() === removeWord.toLocaleLowerCase()));\r\n }\r\n\r\n words.forEach((element: string) => {\r\n if (element.length > 0 && !whiteSpaceRegExp.test(element)) {\r\n brokenStrings.push({\r\n text: element,\r\n textGroup: item.textGroup,\r\n count: item.count,\r\n index: item.index,\r\n selectionId: item.selectionId,\r\n color: item.color\r\n });\r\n }\r\n });\r\n } else {\r\n brokenStrings.push(item);\r\n }\r\n });\r\n\r\n return brokenStrings; \r\n }\r\n\r\n private static getDataPoints(\r\n textGroups: WordCloudText[][],\r\n settings: WordCloudSettings,\r\n wordValueFormatter: IValueFormatter): WordCloudDataPoint[] {\r\n\r\n if (_.isEmpty(textGroups)) {\r\n return [];\r\n }\r\n\r\n var returnValues = textGroups.map((values: WordCloudText[]) => {\r\n return <WordCloudDataPoint>{\r\n text: wordValueFormatter.format(values[0].text),\r\n x: 0,\r\n y: 0,\r\n rotate: WordCloud.getAngle(settings),\r\n padding: 1,\r\n width: 0,\r\n height: 0,\r\n xOff: 0,\r\n yOff: 0,\r\n x0: 0,\r\n y0: 0,\r\n x1: 0,\r\n y1: 0,\r\n color: values[0].color,\r\n selectionIds: values.map(x => x.selectionId),\r\n wordIndex: values[0].index,\r\n count: _.sum(values, x => x.count)\r\n };\r\n });\r\n\r\n var minValue = _.min(returnValues, x => x.count).count;\r\n var maxValue = _.max(returnValues, x => x.count).count;\r\n var texts = textGroups.map(x => x[0]);\r\n returnValues.forEach(x => x.size = WordCloud.getWordFontSize(texts, settings, x.count, minValue, maxValue));\r\n\r\n return returnValues.sort((a,b) => b.count - a.count);\r\n }\r\n\r\n private static getWordFontSize(\r\n texts: WordCloudText[],\r\n settings: WordCloudSettings,\r\n value: number,\r\n minValue: number,\r\n maxValue: number,\r\n scaleType: WordCloudScaleType = WordCloudScaleType.value) {\r\n\r\n var weight: number, fontSize: number;\r\n var minFontSize = settings.general.minFontSize * WordCloud.FontSizePercentageCoefficent;\r\n var maxFontSize = settings.general.maxFontSize * WordCloud.FontSizePercentageCoefficent;\r\n\r\n if (texts.length < 2) {\r\n return maxFontSize;\r\n }\r\n\r\n switch (scaleType) {\r\n case WordCloudScaleType.logn: {\r\n weight = Math.log(value);\r\n }\r\n case WordCloudScaleType.sqrt: {\r\n weight = Math.sqrt(value);\r\n }\r\n case WordCloudScaleType.value: {\r\n weight = value;\r\n }\r\n }\r\n\r\n if (weight > minValue) {\r\n fontSize = (maxValue - minValue) !== 0\r\n ? (maxFontSize * (weight - minValue)) / (maxValue - minValue)\r\n : 0;\r\n } else {\r\n fontSize = 0;\r\n }\r\n\r\n fontSize = (fontSize * 100) / maxFontSize;\r\n\r\n fontSize = (fontSize * (maxFontSize - minFontSize)) / 100 + minFontSize;\r\n\r\n return fontSize;\r\n }\r\n\r\n private static getAngle(settings: WordCloudSettings): number {\r\n if (!settings.rotateText.show) {\r\n return 0;\r\n }\r\n\r\n var angle = ((settings.rotateText.maxAngle - settings.rotateText.minAngle)\r\n / settings.rotateText.maxNumberOfOrientations)\r\n * Math.floor(Math.random() * settings.rotateText.maxNumberOfOrientations);\r\n\r\n return settings.rotateText.minAngle + angle;\r\n }\r\n\r\n private get settings(): WordCloudSettings {\r\n return this.data && this.data.settings;\r\n }\r\n\r\n private data: WordCloudData;\r\n private durationAnimations: number = 500;\r\n private specialViewport: IViewport;\r\n\r\n private fakeViewport: IViewport = {\r\n width: 1500,\r\n height: 1000\r\n };\r\n\r\n private canvasViewport: IViewport = {\r\n width: 128,\r\n height: 2048\r\n };\r\n\r\n private colors: IDataColorPalette;\r\n private root: D3.Selection;\r\n private svg: D3.Selection;\r\n private main: D3.Selection;\r\n private wordsContainerSelection: D3.Selection;\r\n private wordsGroupUpdateSelection: D3.UpdateSelection;\r\n private wordsTextUpdateSelection: D3.UpdateSelection;\r\n\r\n private canvas: HTMLCanvasElement;\r\n\r\n private fontFamily: string;\r\n\r\n private animator: IGenericAnimator;\r\n\r\n private layout: VisualLayout;\r\n\r\n private hostService: IVisualHostServices;\r\n private selectionManager: CustomSelectionManager;\r\n\r\n private visualUpdateOptions: VisualUpdateOptions;\r\n\r\n private isUpdating: boolean;\r\n private incomingUpdateOptions: VisualUpdateOptions;\r\n\r\n constructor(options?: WordCloudConstructorOptions) {\r\n if (options) {\r\n this.svg = options.svg || this.svg;\r\n this.layout = new VisualLayout(null, options.margin || WordCloud.DefaultMargin);\r\n\r\n if (options.animator)\r\n this.animator = options.animator;\r\n }\r\n this.isUpdating = false;\r\n }\r\n\r\n public init(options: VisualInitOptions): void {\r\n if (this.svg) {\r\n this.root = this.svg;\r\n } else {\r\n this.root = d3.select(options.element.get(0)).append(\"svg\");\r\n }\r\n\r\n this.colors = options.style.colorPalette.dataColors;\r\n this.hostService = options.host;\r\n this.selectionManager = new CustomSelectionManager(this.hostService);\r\n\r\n this.layout = new VisualLayout(null, WordCloud.DefaultMargin);\r\n\r\n this.root.classed(WordCloud.ClassName, true);\r\n\r\n this.root.on(\"click\", () => {\r\n this.setSelection(null);\r\n });\r\n\r\n this.fontFamily = this.root.style(\"font-family\");\r\n\r\n this.main = this.root.append(\"g\");\r\n\r\n this.wordsContainerSelection = this.main\r\n .append(\"g\")\r\n .classed(WordCloud.Words[\"class\"], true);\r\n\r\n this.canvas = document.createElement(\"canvas\");\r\n }\r\n\r\n public update(visualUpdateOptions: VisualUpdateOptions): void {\r\n if (!visualUpdateOptions ||\r\n !visualUpdateOptions.viewport ||\r\n !visualUpdateOptions.dataViews ||\r\n !visualUpdateOptions.dataViews[0] ||\r\n !visualUpdateOptions.viewport ||\r\n !(visualUpdateOptions.viewport.height >= 0) ||\r\n !(visualUpdateOptions.viewport.width >= 0))\r\n return;\r\n\r\n if (visualUpdateOptions !== this.visualUpdateOptions) {\r\n this.incomingUpdateOptions = visualUpdateOptions;\r\n }\r\n\r\n if (!this.isUpdating && (this.incomingUpdateOptions !== this.visualUpdateOptions)) {\r\n this.visualUpdateOptions = this.incomingUpdateOptions;\r\n this.layout.viewport = this.visualUpdateOptions.viewport;\r\n var dataView: DataView = visualUpdateOptions.dataViews[0];\r\n\r\n if (this.layout.viewportInIsZero) {\r\n return;\r\n }\r\n\r\n this.durationAnimations = getAnimationDuration(this.animator, visualUpdateOptions.suppressAnimations);\r\n this.UpdateSize();\r\n\r\n var data = WordCloud.converter(dataView, this.colors, this.data);\r\n if (!data) {\r\n //ClearVisual?\r\n return;\r\n }\r\n\r\n this.data = data;\r\n\r\n this.computePositions((wordCloudDataView: WordCloudDataView) => this.render(wordCloudDataView));\r\n }\r\n }\r\n\r\n private computePositions(onPositionsComputed: (WordCloudDataView) => void): void {\r\n var words = this.data.dataPoints;\r\n\r\n if (_.isEmpty(words)) {\r\n return null;\r\n }\r\n\r\n requestAnimationFrame(() => {\r\n var surface: number[] = _.range(0, (this.specialViewport.width >> 5) * this.specialViewport.height, 0);\r\n if (words.length > this.settings.general.maxNumberOfWords) {\r\n words = words.slice(0, this.settings.general.maxNumberOfWords);\r\n }\r\n\r\n words.forEach(data => {\r\n data.widthOfWord = TextMeasurementService.measureSvgTextWidth(<TextProperties>{ \r\n fontFamily: this.fontFamily,\r\n fontSize: (data.size + 1) + WordCloud.Size,\r\n //fontWeight: \"normal\",\r\n //fontStyle: \"normal\",\r\n text: data.text\r\n }) + 2;\r\n });\r\n\r\n this.computeCycle(words, this.getCanvasContext(), surface, null, onPositionsComputed, [], 0);\r\n });\r\n }\r\n\r\n private computeCycle(\r\n words: WordCloudDataPoint[],\r\n context: CanvasRenderingContext2D,\r\n surface: number[],\r\n borders: IPoint[],\r\n onPositionsComputed: (WordCloudDataView) => void,\r\n wordsForDraw: WordCloudDataPoint[] = [],\r\n index: number = 0): void {\r\n var word: WordCloudDataPoint = words[index],\r\n ratio: number = 1;\r\n\r\n if (words.length <= 10)\r\n ratio = 5;\r\n else if (words.length <= 25)\r\n ratio = 3;\r\n else if (words.length <= 75)\r\n ratio = 1.5;\r\n else if (words.length <= 100)\r\n ratio = 1.25;\r\n\r\n word.x = (this.specialViewport.width / ratio * (Math.random() + 0.5)) >> 1;\r\n word.y = (this.specialViewport.height / ratio * (Math.random() + 0.5)) >> 1;\r\n\r\n if(!word.sprite) {\r\n this.generateSprites(context, words, index);\r\n }\r\n\r\n if (word.sprite && this.findPosition(surface, word, borders)) {\r\n wordsForDraw.push(word);\r\n\r\n borders = this.updateBorders(word, borders);\r\n word.x -= this.specialViewport.width >> 1;\r\n word.y -= this.specialViewport.height >> 1;\r\n }\r\n\r\n if (++index < words.length && this.root) {\r\n this.computeCycle(words, context, surface, borders, onPositionsComputed, wordsForDraw, index);\r\n } else {\r\n onPositionsComputed({\r\n data: wordsForDraw,\r\n leftBorder: borders && borders[0],\r\n rightBorder: borders && borders[1]\r\n });\r\n }\r\n }\r\n\r\n private updateBorders(word: WordCloudDataPoint, borders: IPoint[]): IPoint[] {\r\n if (borders && borders.length === 2) {\r\n var leftBorder: IPoint = borders[0],\r\n rightBorder: IPoint = borders[1];\r\n\r\n if (word.x + word.x0 < leftBorder.x)\r\n leftBorder.x = word.x + word.x0;\r\n\r\n if (word.y + word.y0 < leftBorder.y)\r\n leftBorder.y = word.y + word.y0;\r\n\r\n if (word.x + word.x1 > rightBorder.x)\r\n rightBorder.x = word.x + word.x1;\r\n\r\n if (word.y + word.y1 > rightBorder.y)\r\n rightBorder.y = word.y + word.y1;\r\n } else {\r\n borders = [\r\n {\r\n x: word.x + word.x0,\r\n y: word.y + word.y0\r\n }, {\r\n x: word.x + word.x1,\r\n y: word.y + word.y1\r\n }\r\n ];\r\n }\r\n\r\n return borders;\r\n }\r\n\r\n private generateSprites(\r\n context: CanvasRenderingContext2D,\r\n words: WordCloudDataPoint[],\r\n startIndex: number): void {\r\n\r\n context.clearRect(0, 0, this.canvasViewport.width << 5, this.canvasViewport.height);\r\n\r\n var x: number = 0,\r\n y: number = 0,\r\n maxHeight: number = 0;\r\n\r\n for (var i: number = startIndex, length = words.length; i < length; i++) {\r\n var currentWordData: WordCloudDataPoint = words[i];\r\n var widthOfWord: number = currentWordData.widthOfWord;\r\n var heightOfWord: number = currentWordData.size << 1;\r\n\r\n if (currentWordData.rotate) {\r\n var sr: number = Math.sin(currentWordData.rotate * WordCloud.Radians),\r\n cr: number = Math.cos(currentWordData.rotate * WordCloud.Radians),\r\n widthCr: number = widthOfWord * cr,\r\n widthSr: number = widthOfWord * sr,\r\n heightCr: number = heightOfWord * cr,\r\n heightSr: number = heightOfWord * sr;\r\n\r\n widthOfWord = (Math.max(Math.abs(widthCr + heightSr), Math.abs(widthCr - heightSr)) + 31) >> 5 << 5;\r\n heightOfWord = Math.floor(Math.max(Math.abs(widthSr + heightCr), Math.abs(widthSr - heightCr)));\r\n } else {\r\n widthOfWord = (widthOfWord + 31) >> 5 << 5;\r\n }\r\n\r\n if (heightOfWord > maxHeight) {\r\n maxHeight = heightOfWord;\r\n }\r\n\r\n if (x + widthOfWord >= (this.canvasViewport.width << 5)) {\r\n x = 0;\r\n y += maxHeight;\r\n maxHeight = 0;\r\n }\r\n\r\n context.save();\r\n context.font = \"normal normal \" + (currentWordData.size + 1) + WordCloud.Size + \" \" + this.fontFamily;\r\n context.translate((x + (widthOfWord >> 1)), (y + (heightOfWord >> 1)));\r\n\r\n if (currentWordData.rotate) {\r\n context.rotate(currentWordData.rotate * WordCloud.Radians);\r\n }\r\n\r\n context.fillText(currentWordData.text, 0, 0);\r\n\r\n if (currentWordData.padding) {\r\n context.lineWidth = 2 * currentWordData.padding;\r\n context.strokeText(currentWordData.text, 0, 0);\r\n }\r\n\r\n context.restore();\r\n\r\n currentWordData.width = widthOfWord;\r\n currentWordData.height = heightOfWord;\r\n currentWordData.xOff = x;\r\n currentWordData.yOff = y;\r\n currentWordData.x1 = widthOfWord >> 1;\r\n currentWordData.y1 = heightOfWord >> 1;\r\n currentWordData.x0 = -currentWordData.x1;\r\n currentWordData.y0 = -currentWordData.y1;\r\n\r\n x += widthOfWord;\r\n }\r\n\r\n this.setSprites(context, words);\r\n }\r\n\r\n private setSprites(context: CanvasRenderingContext2D, words: WordCloudDataPoint[]) {\r\n var pixels = context.getImageData(0, 0, this.canvasViewport.width << 5, this.canvasViewport.height).data;\r\n\r\n var sprites: number[] = [];\r\n\r\n for (var i = words.length - 1; i >= 0; i--) {\r\n var currentWordData: WordCloudDataPoint = words[i],\r\n width: number = currentWordData.width,\r\n width32: number = width >> 5,\r\n height: number = currentWordData.y1 - currentWordData.y0,\r\n x: number = 0,\r\n y: number = 0,\r\n seen: number = 0,\r\n seenRow: number = 0;\r\n\r\n if (currentWordData.xOff + width >= (this.canvasViewport.width << 5) ||\r\n currentWordData.yOff + height >= this.canvasViewport.height) {\r\n currentWordData.sprite = null;\r\n\r\n continue;\r\n }\r\n\r\n for (var j = 0; j < height * width32; j++) {\r\n sprites[j] = 0;\r\n }\r\n\r\n if (currentWordData.xOff !== null) {\r\n x = currentWordData.xOff;\r\n } else {\r\n return;\r\n }\r\n\r\n y = currentWordData.yOff;\r\n\r\n seen = 0;\r\n seenRow = -1;\r\n\r\n for (var j = 0; j < height; j++) {\r\n for (var k = 0; k < width; k++) {\r\n var l: number = width32 * j + (k >> 5);\r\n var index: number = ((y + j) * (this.canvasViewport.width << 5) + (x + k)) << 2;\r\n var m: number = pixels[index]\r\n ? 1 << (31 - (k % 32))\r\n : 0;\r\n\r\n sprites[l] |= m;\r\n seen |= m;\r\n }\r\n\r\n if (seen) {\r\n seenRow = j;\r\n } else {\r\n currentWordData.y0++;\r\n height--;\r\n j--;\r\n y++;\r\n }\r\n }\r\n\r\n currentWordData.y1 = currentWordData.y0 + seenRow;\r\n currentWordData.sprite = sprites.slice(0, (currentWordData.y1 - currentWordData.y0) * width32);\r\n }\r\n }\r\n\r\n private findPosition(surface: number[], word: WordCloudDataPoint, borders: IPoint[]): boolean {\r\n var startPoint: IPoint = { x: word.x, y: word.y },\r\n delta = Math.sqrt(this.specialViewport.width * this.specialViewport.width + this.specialViewport.height * this.specialViewport.height),\r\n point: IPoint,\r\n dt: number = Math.random() < 0.5 ? 1 : -1,\r\n shift: number = -dt,\r\n dx: number,\r\n dy: number;\r\n\r\n while (true) {\r\n shift += dt;\r\n\r\n point = this.archimedeanSpiral(shift);\r\n\r\n dx = Math.floor(point.x);\r\n dy = Math.floor(point.y);\r\n\r\n if (Math.min(Math.abs(dx), Math.abs(dy)) >= delta) {\r\n break;\r\n }\r\n\r\n word.x = startPoint.x + dx;\r\n word.y = startPoint.y + dy;\r\n\r\n if (word.x + word.x0 < 0 ||\r\n word.y + word.y0 < 0 ||\r\n word.x + word.x1 > this.specialViewport.width ||\r\n word.y + word.y1 > this.specialViewport.height)\r\n continue;\r\n\r\n if (!borders || !this.checkIntersect(word, surface)) {\r\n if (!borders || this.checkIntersectOfRectangles(word, borders[0], borders[1])) {\r\n var sprite: number[] = word.sprite,\r\n width: number = word.width >> 5,\r\n shiftWidth: number = this.specialViewport.width >> 5,\r\n lx: number = word.x - (width << 4),\r\n sx: number = lx & 127,\r\n msx: number = 32 - sx,\r\n height: number = word.y1 - word.y0,\r\n x: number = (word.y + word.y0) * shiftWidth + (lx >> 5);\r\n\r\n for (var i: number = 0; i < height; i++) {\r\n var lastSprite: number = 0;\r\n\r\n for (var j: number = 0; j <= width; j++) {\r\n var leftMask: number = lastSprite << msx,\r\n rightMask: number;\r\n\r\n if (j < width)\r\n lastSprite = sprite[i * width + j];\r\n\r\n rightMask = j < width\r\n ? lastSprite >>> sx\r\n : 0;\r\n\r\n surface[x + j] |= leftMask | rightMask;\r\n }\r\n\r\n x += shiftWidth;\r\n }\r\n\r\n word.sprite = null;\r\n\r\n return true;\r\n }\r\n }\r\n }\r\n\r\n return false;\r\n }\r\n\r\n private archimedeanSpiral(value: number): IPoint {\r\n var ratio: number = this.specialViewport.width / this.specialViewport.height;\r\n\r\n value = value * 0.1;\r\n\r\n return {\r\n x: ratio * value * Math.cos(value),\r\n y: value * Math.sin(value)\r\n };\r\n }\r\n\r\n private checkIntersect(word: WordCloudDataPoint, surface: number[]): boolean {\r\n var shiftWidth: number = this.specialViewport.width >> 5,\r\n sprite: number[] = word.sprite,\r\n widthOfWord = word.width >> 5,\r\n lx: number = word.x - (widthOfWord << 4),\r\n sx: number = lx & 127,\r\n msx: number = 32 - sx,\r\n heightOfWord = word.y1 - word.y0,\r\n x: number = (word.y + word.y0) * shiftWidth + (lx >> 5);\r\n\r\n for (var i = 0; i < heightOfWord; i++) {\r\n var lastSprite: number = 0;\r\n\r\n for (var j = 0; j <= widthOfWord; j++) {\r\n var mask: number = 0,\r\n leftMask: number,\r\n intersectMask: number = 0;\r\n\r\n leftMask = lastSprite << msx;\r\n\r\n if (j < widthOfWord)\r\n lastSprite = sprite[i * widthOfWord + j];\r\n\r\n mask = j < widthOfWord\r\n ? lastSprite >>> sx\r\n : 0;\r\n\r\n intersectMask = (leftMask | mask) & surface[x + j];\r\n\r\n if (intersectMask)\r\n return true;\r\n }\r\n\r\n x += shiftWidth;\r\n }\r\n\r\n return false;\r\n }\r\n\r\n private checkIntersectOfRectangles(word: WordCloudDataPoint, leftBorder: IPoint, rightBorder: IPoint): boolean {\r\n return (word.x + word.x1) > leftBorder.x &&\r\n (word.x + word.x0) < rightBorder.x &&\r\n (word.y + word.y1) > leftBorder.y &&\r\n (word.y + word.y0) < rightBorder.y;\r\n }\r\n\r\n private getCanvasContext(): CanvasRenderingContext2D {\r\n if (!this.canvasViewport)\r\n return null;\r\n\r\n this.canvas.width = 1;\r\n this.canvas.height = 1;\r\n\r\n var context: CanvasRenderingContext2D = this.canvas.getContext(\"2d\");\r\n\r\n this.canvas.width = this.canvasViewport.width << 5;\r\n this.canvas.height = this.canvasViewport.height;\r\n\r\n context = this.canvas.getContext(\"2d\");\r\n context.fillStyle = context.strokeStyle = \"red\";\r\n context.textAlign = \"center\";\r\n\r\n return context;\r\n }\r\n\r\n private UpdateSize(): void {\r\n var fakeWidth: number,\r\n fakeHeight: number,\r\n ratio: number;\r\n\r\n ratio = Math.sqrt((this.fakeViewport.width * this.fakeViewport.height)\r\n / (this.layout.viewportIn.width * this.layout.viewportIn.height));\r\n\r\n if (isNaN(ratio)) {\r\n fakeHeight = fakeWidth = 1;\r\n } else {\r\n fakeHeight = this.layout.viewportIn.height * ratio;\r\n fakeWidth = this.layout.viewportIn.width * ratio;\r\n }\r\n\r\n this.specialViewport = {\r\n height: fakeHeight,\r\n width: fakeWidth\r\n };\r\n\r\n this.root.attr({\r\n \"height\": this.layout.viewport.height,\r\n \"width\": this.layout.viewport.width\r\n });\r\n }\r\n\r\n private render(wordCloudDataView: WordCloudDataView): void {\r\n if (!wordCloudDataView || !wordCloudDataView.data) {\r\n return;\r\n }\r\n\r\n this.scaleMainView(wordCloudDataView);\r\n\r\n this.wordsGroupUpdateSelection = this.main\r\n .select(WordCloud.Words.selector)\r\n .selectAll(\"g\")\r\n .data(wordCloudDataView.data);\r\n\r\n var wordGroupEnterSelection = this.wordsGroupUpdateSelection\r\n .enter()\r\n .append(\"svg:g\")\r\n .classed(WordCloud.WordGroup.class, true);\r\n\r\n wordGroupEnterSelection\r\n .append(\"svg:text\")\r\n .style(\"font-size\", \"1px\")\r\n .attr('pointer-events', \"none\");\r\n wordGroupEnterSelection\r\n .append(\"svg:rect\");\r\n\r\n this.wordsGroupUpdateSelection.exit().remove();\r\n\r\n this.wordsGroupUpdateSelection\r\n .attr('transform', (d: WordCloudDataPoint) => `${SVGUtil.translate(d.x, d.y)} rotate(${d.rotate})`)\r\n .sort((a: WordCloudDataPoint, b: WordCloudDataPoint) => b.height * b.width - a.height * a.width);\r\n\r\n this.wordsTextUpdateSelection = this.wordsGroupUpdateSelection.selectAll(\"text\").data(d => [d]);\r\n this.wordsTextUpdateSelection.text((d: WordCloudDataPoint) => d.text);\r\n\r\n this.animation(this.wordsTextUpdateSelection, this.durationAnimations)\r\n .style({\r\n \"font-size\": ((item: WordCloudDataPoint): string => `${item.size}${WordCloud.Size}`),\r\n \"fill\": ((item: WordCloudDataPoint): string => item.color),\r\n });\r\n\r\n this.wordsGroupUpdateSelection.selectAll(\"rect\").data(d => [d])\r\n .attr({\r\n x: (d: WordCloudDataPoint) => -d.widthOfWord * 0.5,\r\n width: (d: WordCloudDataPoint) => d.widthOfWord,\r\n y: (d: WordCloudDataPoint) => -d.size * 0.75,\r\n height: (d: WordCloudDataPoint) => d.size * 0.85,\r\n fill: (d: WordCloudDataPoint) => \"rgba(63, 191, 191, 0.0)\",\r\n })\r\n .on(\"click\", d => { this.setSelection(d); d3.event.stopPropagation(); });\r\n\r\n this.renderSelection();\r\n\r\n this.isUpdating = false;\r\n if (this.incomingUpdateOptions !== this.visualUpdateOptions) {\r\n this.update(this.incomingUpdateOptions);\r\n }\r\n }\r\n\r\n private setSelection(dataPoint: WordCloudDataPoint) {\r\n if(!dataPoint) {\r\n this.selectionManager.clear().then(() => this.renderSelection());\r\n return;\r\n }\r\n\r\n var selectionIds = dataPoint.selectionIds;\r\n\r\n if(this.selectionManager.isSelected(selectionIds) && d3.event.ctrlKey) {\r\n var dataPoints: WordCloudDataPoint[] = this.wordsGroupUpdateSelection.data()\r\n .filter((d: WordCloudDataPoint) => d.text !== dataPoint.text);\r\n selectionIds = selectionIds.filter(x => !dataPoints.some(d => \r\n this.selectionManager.isSelected(d.selectionIds)\r\n && utility.SelectionManager.containsSelection(d.selectionIds, x)));\r\n }\r\n\r\n this.selectionManager.selectAndSendSelection(selectionIds, d3.event.ctrlKey);\r\n\r\n this.renderSelection();\r\n }\r\n\r\n private scaleMainView(wordCloudDataView: WordCloudDataView) {\r\n var rectangles = wordCloudDataView.data.map(d => {\r\n var hw = d.width/2;\r\n var hh = d.height/2;\r\n return <ClientRect>{ left: d.x - hw, top: d.y - hh, right: d.x + hw, bottom: d.y + hh };\r\n });\r\n var rectangle = <ClientRect>{\r\n left: _.min(rectangles, x => x.left).left,\r\n top: _.min(rectangles, x => x.top).top,\r\n right: _.max(rectangles, x => x.right).right,\r\n bottom: _.max(rectangles, x => x.bottom).bottom\r\n };\r\n\r\n rectangle.width = rectangle.right - rectangle.left;\r\n rectangle.height = rectangle.bottom - rectangle.top;\r\n\r\n var scaleByX = this.layout.viewportIn.width / (rectangle.width);\r\n var scaleByY = this.layout.viewportIn.height / (rectangle.height);\r\n\r\n var scale = Math.min(scaleByX, scaleByY);\r\n\r\n var x = -rectangle.left * scale + 5;\r\n var y = -rectangle.top * scale + 5;\r\n\r\n this.main\r\n .style(\"line-height\", \"5px\");//TODO: This construction fixes bug #6343.\r\n this.main\r\n .attr(\"transform\", `${SVGUtil.translate(x, y)} scale(${scale})`)\r\n .style(\"line-height\", \"10px\");//TODO: This construction fixes bug #6343.\r\n }\r\n\r\n private renderSelection(): void {\r\n if (this.selectionManager.selectionIds.some(x =>\r\n !this.wordsGroupUpdateSelection.data().some((y: WordCloudDataPoint) =>\r\n y.selectionIds.some(z => z.getKey() === x.getKey())))) {\r\n this.selectionManager.clear(false);\r\n }\r\n\r\n if (!this.selectionManager.hasSelection) {\r\n this.setOpacity(this.wordsTextUpdateSelection, WordCloud.MaxOpacity);\r\n return;\r\n }\r\n\r\n var selectedColumns = this.wordsTextUpdateSelection.filter((x: WordCloudDataPoint) =>\r\n this.selectionManager.isSelected(x.selectionIds[0]));\r\n\r\n this.setOpacity(this.wordsTextUpdateSelection, WordCloud.MinOpacity);\r\n this.setOpacity(selectedColumns, WordCloud.MaxOpacity);\r\n }\r\n\r\n private setOpacity(element: D3.Selection, opacityValue: number): void {\r\n element.style(\"fill-opacity\", opacityValue);\r\n\r\n if (this.main) {//TODO: This construction fixes bug #6343.\r\n this.main.style(\"line-height\", \"14px\");\r\n\r\n this.animation(this.main, 0, this.durationAnimations)\r\n .style(\"line-height\", \"15px\");\r\n }\r\n }\r\n\r\n public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions) {\r\n var instances = WordCloudSettings.enumerateObjectInstances(\r\n this.settings && this.settings.originalSettings,\r\n options,\r\n WordCloud.capabilities);\r\n\r\n switch (options.objectName) {\r\n case \"dataPoint\": if (this.data && this.data.dataPoints) {\r\n var wordCategoriesIndex: number[] = [];\r\n _.unique(this.data.dataPoints, x => x.wordIndex).forEach((item: WordCloudDataPoint) => {\r\n if (wordCategoriesIndex.indexOf(item.wordIndex) === -1) {\r\n wordCategoriesIndex.push(item.wordIndex);\r\n instances.pushInstance({\r\n objectName: options.objectName,\r\n displayName: this.data.texts[item.wordIndex].text,\r\n selector: ColorHelper.normalizeSelector(item.selectionIds[0].getSelector(), false),\r\n properties: { fill: { solid: { color: item.color } } }\r\n });\r\n }\r\n });\r\n }\r\n\r\n break;\r\n }\r\n\r\n return instances.complete();\r\n }\r\n\r\n private animation<T extends D3.Selection>(\r\n element: T,\r\n duration: number = 0,\r\n delay: number = 0,\r\n callback?: (data: any, index: number) => void): D3.Transition.Transition {\r\n return element\r\n .transition()\r\n .delay(delay)\r\n .duration(duration)\r\n .each(\"end\", callback);\r\n }\r\n\r\n public destroy(): void {\r\n this.root = null;\r\n this.canvas = null;\r\n }\r\n }\r\n\r\n module explore.util {\r\n export function hexToRgb(hex): string {\r\n // Expand shorthand form (e.g. \"03F\") to full form (e.g. \"0033FF\")\r\n var shorthandRegex = /^#?([a-f\\d])([a-f\\d])([a-f\\d])$/i;\r\n hex = hex.replace(shorthandRegex, function(m, r, g, b) {\r\n return r + r + g + g + b + b;\r\n });\r\n\r\n var result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\r\n return result ? `rgb(${parseInt(result[1], 16)},${parseInt(result[2], 16)},${parseInt(result[3], 16)})` : null;\r\n }\r\n\r\n export function getRandomColor(): string {\r\n var red: number = Math.floor(Math.random() * 255),\r\n green: number = Math.floor(Math.random() * 255),\r\n blue: number = Math.floor(Math.random() * 255);\r\n\r\n return `rgb(${red},${green},${blue})`;\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/CustomVisuals/visuals/wordCloud/visual/wordCloud.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 import ClassAndSelector = jsCommon.CssConstants.ClassAndSelector;\r\n import createClassAndSelector = jsCommon.CssConstants.createClassAndSelector;\r\n import PixelConverter = jsCommon.PixelConverter;\r\n import SemanticFilter = powerbi.data.SemanticFilter;\r\n import SQExprConverter = powerbi.data.SQExprConverter;\r\n import SelectionIdBuilder = powerbi.visuals.SelectionIdBuilder;\r\n\r\n export interface ITableView {\r\n data(data: any[], dataIdFunction: (d) => {}, dataAppended: boolean): ITableView;\r\n rowHeight(rowHeight: number): ITableView;\r\n columnWidth(columnWidth: number): ITableView;\r\n orientation(orientation: string): ITableView;\r\n rows(rows: number): ITableView;\r\n columns(columns: number): ITableView;\r\n viewport(viewport: IViewport): ITableView;\r\n render(): void;\r\n empty(): void;\r\n }\r\n\r\n export module TableViewFactory {\r\n export function createTableView(options): ITableView {\r\n return new TableView(options);\r\n }\r\n }\r\n\r\n export interface TableViewViewOptions {\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 columnWidth: number;\r\n orientation: string;\r\n rows: number;\r\n columns: number;\r\n viewport: IViewport;\r\n scrollEnabled: 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 TableView implements ITableView {\r\n private getDatumIndex: (d: any) => {};\r\n private _data: any[];\r\n private _totalRows: number;\r\n private _totalColumns: number;\r\n\r\n private options: TableViewViewOptions;\r\n private visibleGroupContainer: D3.Selection;\r\n private scrollContainer: D3.Selection;\r\n\r\n private static defaultRowHeight = 0;\r\n private static defaultColumns = 1;\r\n\r\n public constructor(options: TableViewViewOptions) {\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.options.baseContainer\r\n .style('overflow-y', 'auto')\r\n .attr('drag-resize-disabled', true);\r\n\r\n this.scrollContainer = options.baseContainer\r\n .append('div')\r\n .attr('class', 'scrollRegion');\r\n this.visibleGroupContainer = this.scrollContainer\r\n .append('div')\r\n .attr('class', 'visibleGroup');\r\n\r\n TableView.SetDefaultOptions(options);\r\n }\r\n\r\n private static SetDefaultOptions(options: TableViewViewOptions) {\r\n options.rowHeight = options.rowHeight || TableView.defaultRowHeight;\r\n }\r\n\r\n public rowHeight(rowHeight: number): TableView {\r\n this.options.rowHeight = Math.ceil(rowHeight);\r\n return this;\r\n }\r\n public columnWidth(columnWidth: number): TableView {\r\n this.options.columnWidth = Math.ceil(columnWidth);\r\n return this;\r\n }\r\n\r\n public orientation(orientation: string): TableView {\r\n this.options.orientation = orientation;\r\n return this;\r\n }\r\n\r\n public rows(rows: number): TableView {\r\n this.options.rows = Math.ceil(rows);\r\n return this;\r\n }\r\n\r\n public columns(columns: number): TableView {\r\n this.options.columns = Math.ceil(columns);\r\n return this;\r\n }\r\n\r\n public data(data: any[], getDatumIndex: (d) => {}, dataReset: boolean = false): ITableView {\r\n this._data = data;\r\n this.getDatumIndex = getDatumIndex;\r\n this.setTotalRows();\r\n if (dataReset) {\r\n $(this.options.baseContainer.node()).scrollTop(0);\r\n }\r\n return this;\r\n }\r\n\r\n public viewport(viewport: IViewport): ITableView {\r\n this.options.viewport = viewport;\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 private setTotalRows(): void {\r\n var count = this._data.length;\r\n var rows = Math.min(this.options.rows, count);\r\n var columns = Math.min(this.options.columns, count);\r\n\r\n if ((columns > 0) && (rows > 0)) {\r\n this._totalColumns = columns;\r\n this._totalRows = rows;\r\n } else if (rows > 0) {\r\n this._totalRows = rows;\r\n this._totalColumns = Math.ceil(count / rows);\r\n } else if (columns > 0) {\r\n this._totalColumns = columns;\r\n this._totalRows = Math.ceil(count / columns);\r\n } else {\r\n this._totalColumns = TableView.defaultColumns;\r\n this._totalRows = Math.ceil(count / TableView.defaultColumns);\r\n }\r\n }\r\n\r\n public render(): void {\r\n var options = this.options;\r\n var visibleGroupContainer = this.visibleGroupContainer;\r\n var rowHeight = options.rowHeight || TableView.defaultRowHeight;\r\n var groupedData: any[] = [];\r\n var totalRows = options.rows;\r\n var totalColumns = options.columns;\r\n var totalItems: number = this._data.length;\r\n var totalRows = options.rows > totalItems ? totalItems : options.rows;\r\n var totalColumns = options.columns > totalItems ? totalItems : options.columns;\r\n\r\n if (totalColumns === 0 && totalRows === 0) {\r\n if (options.orientation === Orientation.HORIZONTAL) {\r\n totalColumns = totalItems;\r\n totalRows = 1;\r\n } else {\r\n totalColumns = 1;\r\n totalRows = totalItems;\r\n }\r\n } else if (totalColumns === 0 && totalRows > 0) {\r\n totalColumns = Math.ceil(totalItems / totalRows);\r\n } else if (totalColumns > 0 && totalRows === 0) {\r\n totalRows = Math.ceil(totalItems / totalColumns);\r\n }\r\n\r\n if (this.options.orientation === Orientation.VERTICAL) {\r\n var n = totalRows;\r\n totalRows = totalColumns;\r\n totalColumns = n;\r\n } else if (this.options.orientation === Orientation.HORIZONTAL) {\r\n if (totalRows === 0)\r\n totalRows = this._totalRows;\r\n if (totalColumns === 0)\r\n totalColumns = this._totalColumns;\r\n }\r\n\r\n var m: number = 0;\r\n var k: number = 0;\r\n for (var i: number = 0; i < totalRows; i++) {\r\n if (this.options.orientation === Orientation.VERTICAL\r\n && options.rows === 0\r\n && totalItems % options.columns > 0\r\n && options.columns <= totalItems) {\r\n if (totalItems % options.columns > i) {\r\n m = i * Math.ceil(totalItems / options.columns);\r\n k = m + Math.ceil(totalItems / options.columns);\r\n groupedData.push(this._data.slice(m, k));\r\n } else {\r\n groupedData.push(this._data.slice(k, k + Math.floor(totalItems / options.columns)));\r\n k = k + Math.floor(totalItems / options.columns);\r\n }\r\n } else if (this.options.orientation === Orientation.HORIZONTAL\r\n && options.columns === 0\r\n && totalItems % options.rows > 0\r\n && options.rows <= totalItems) {\r\n if (totalItems % options.rows > i) {\r\n m = i * Math.ceil(totalItems / options.rows);\r\n k = m + Math.ceil(totalItems / options.rows);\r\n groupedData.push(this._data.slice(m, k));\r\n } else {\r\n groupedData.push(this._data.slice(k, k + Math.floor(totalItems / options.rows)));\r\n k = k + Math.floor(totalItems / options.rows);\r\n }\r\n } else {\r\n var k: number = i * totalColumns;\r\n groupedData.push(this._data.slice(k, k + totalColumns));\r\n }\r\n }\r\n\r\n visibleGroupContainer.selectAll(\".row\").remove();\r\n var cellSelection = visibleGroupContainer.selectAll(\".row\")\r\n .data(groupedData)\r\n .enter()\r\n .append(\"div\")\r\n .classed('row', true)\r\n .selectAll(\".cell\")\r\n .data(d => d);\r\n\r\n cellSelection\r\n .enter()\r\n .append('div')\r\n .classed('cell', true)\r\n .call(d => options.enter(d));\r\n cellSelection.order();\r\n\r\n var cellUpdateSelection = visibleGroupContainer.selectAll('.cell:not(.transitioning)');\r\n\r\n cellUpdateSelection.call(d => options.update(d));\r\n cellUpdateSelection.style({ 'height': (rowHeight > 0) ? rowHeight + 'px' : 'auto' });\r\n\r\n if (this.options.orientation === Orientation.VERTICAL) {\r\n var realColumnNumber = 0;\r\n for (var i: number = 0; i < groupedData.length; i++) {\r\n if (groupedData[i].length !== 0)\r\n realColumnNumber = i + 1;\r\n }\r\n\r\n cellUpdateSelection.style({ 'width': '100%' });\r\n var rowUpdateSelection = visibleGroupContainer.selectAll('div.row');\r\n rowUpdateSelection.style({ 'width': (options.columnWidth > 0) ? options.columnWidth + 'px' : (100 / realColumnNumber) + '%' });\r\n }\r\n else {\r\n cellUpdateSelection.style({\r\n 'width': (options.columnWidth > 0) ? options.columnWidth + 'px' : (100 / totalColumns) + '%'\r\n });\r\n }\r\n\r\n cellSelection\r\n .exit()\r\n .call(d => options.exit(d))\r\n .remove();\r\n }\r\n }\r\n\r\n // TODO: Generate these from above, defining twice just introduces potential for error\r\n export var chicletSlicerProps = {\r\n general: {\r\n orientation: <DataViewObjectPropertyIdentifier>{ objectName: 'general', propertyName: 'orientation' },\r\n columns: <DataViewObjectPropertyIdentifier>{ objectName: 'general', propertyName: 'columns' },\r\n rows: <DataViewObjectPropertyIdentifier>{ objectName: 'general', propertyName: 'rows' },\r\n showDisabled: <DataViewObjectPropertyIdentifier>{ objectName: 'general', propertyName: 'showDisabled' },\r\n multiselect: <DataViewObjectPropertyIdentifier>{ objectName: 'general', propertyName: 'multiselect' },\r\n selection: <DataViewObjectPropertyIdentifier>{ objectName: 'general', propertyName: 'selection' },\r\n selfFilterEnabled: <DataViewObjectPropertyIdentifier>{ objectName: 'general', propertyName: 'selfFilterEnabled' },\r\n },\r\n header: {\r\n show: <DataViewObjectPropertyIdentifier>{ objectName: 'header', propertyName: 'show' },\r\n title: <DataViewObjectPropertyIdentifier>{ objectName: 'header', propertyName: 'title' },\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 outlineColor: <DataViewObjectPropertyIdentifier>{ objectName: 'header', propertyName: 'outlineColor' },\r\n outlineWeight: <DataViewObjectPropertyIdentifier>{ objectName: 'header', propertyName: 'outlineWeight' }\r\n },\r\n rows: {\r\n fontColor: <DataViewObjectPropertyIdentifier>{ objectName: 'rows', propertyName: 'fontColor' },\r\n textSize: <DataViewObjectPropertyIdentifier>{ objectName: 'rows', propertyName: 'textSize' },\r\n height: <DataViewObjectPropertyIdentifier>{ objectName: 'rows', propertyName: 'height' },\r\n width: <DataViewObjectPropertyIdentifier>{ objectName: 'rows', propertyName: 'width' },\r\n background: <DataViewObjectPropertyIdentifier>{ objectName: 'rows', propertyName: 'background' },\r\n transparency: <DataViewObjectPropertyIdentifier>{ objectName: 'rows', propertyName: 'transparency' },\r\n selectedColor: <DataViewObjectPropertyIdentifier>{ objectName: 'rows', propertyName: 'selectedColor' },\r\n hoverColor: <DataViewObjectPropertyIdentifier>{ objectName: 'rows', propertyName: 'hoverColor' },\r\n unselectedColor: <DataViewObjectPropertyIdentifier>{ objectName: 'rows', propertyName: 'unselectedColor' },\r\n disabledColor: <DataViewObjectPropertyIdentifier>{ objectName: 'rows', propertyName: 'disabledColor' },\r\n outline: <DataViewObjectPropertyIdentifier>{ objectName: 'rows', propertyName: 'outline' },\r\n outlineColor: <DataViewObjectPropertyIdentifier>{ objectName: 'rows', propertyName: 'outlineColor' },\r\n outlineWeight: <DataViewObjectPropertyIdentifier>{ objectName: 'rows', propertyName: 'outlineWeight' },\r\n borderStyle: <DataViewObjectPropertyIdentifier>{ objectName: 'rows', propertyName: 'borderStyle' },\r\n },\r\n images: {\r\n imageSplit: <DataViewObjectPropertyIdentifier>{ objectName: 'images', propertyName: 'imageSplit' },\r\n stretchImage: <DataViewObjectPropertyIdentifier>{ objectName: 'images', propertyName: 'stretchImage' },\r\n bottomImage: <DataViewObjectPropertyIdentifier>{ objectName: 'images', propertyName: 'bottomImage' },\r\n },\r\n selectedPropertyIdentifier: <DataViewObjectPropertyIdentifier>{ objectName: 'general', propertyName: 'selected' },\r\n filterPropertyIdentifier: <DataViewObjectPropertyIdentifier>{ objectName: 'general', propertyName: 'filter' },\r\n formatString: <DataViewObjectPropertyIdentifier>{ objectName: 'general', propertyName: 'formatString' },\r\n hasSavedSelection: true,\r\n };\r\n\r\n module ChicletBorderStyle {\r\n export var ROUNDED: string = 'Rounded';\r\n export var CUT: string = 'Cut';\r\n export var SQUARE: string = 'Square';\r\n\r\n export var type: IEnumType = createEnumType([\r\n { value: ROUNDED, displayName: ChicletBorderStyle.ROUNDED },\r\n { value: CUT, displayName: ChicletBorderStyle.CUT },\r\n { value: SQUARE, displayName: ChicletBorderStyle.SQUARE },\r\n ]);\r\n }\r\n\r\n module ChicletSlicerShowDisabled {\r\n export var INPLACE: string = 'Inplace';\r\n export var BOTTOM: string = 'Bottom';\r\n export var HIDE: string = 'Hide';\r\n\r\n export var type: IEnumType = createEnumType([\r\n { value: INPLACE, displayName: ChicletSlicerShowDisabled.INPLACE },\r\n { value: BOTTOM, displayName: ChicletSlicerShowDisabled.BOTTOM },\r\n { value: HIDE, displayName: ChicletSlicerShowDisabled.HIDE },\r\n ]);\r\n }\r\n\r\n module Orientation {\r\n export var HORIZONTAL: string = 'Horizontal';\r\n export var VERTICAL: string = 'Vertical';\r\n\r\n export var type: IEnumType = createEnumType([\r\n { value: HORIZONTAL, displayName: HORIZONTAL },\r\n { value: VERTICAL, displayName: VERTICAL }\r\n ]);\r\n }\r\n\r\n export interface ChicletSlicerConstructorOptions {\r\n behavior?: ChicletSlicerWebBehavior;\r\n }\r\n\r\n export interface ChicletSlicerData {\r\n categorySourceName: string;\r\n formatString: string;\r\n slicerDataPoints: ChicletSlicerDataPoint[];\r\n slicerSettings: ChicletSlicerSettings;\r\n hasSelectionOverride?: boolean;\r\n }\r\n\r\n export interface ChicletSlicerDataPoint extends SelectableDataPoint {\r\n category?: string;\r\n value?: number;\r\n mouseOver?: boolean;\r\n mouseOut?: boolean;\r\n isSelectAllDataPoint?: boolean;\r\n imageURL?: string;\r\n selectable?: boolean;\r\n filtered?: boolean;\r\n }\r\n\r\n export interface ChicletSlicerSettings {\r\n general: {\r\n orientation: string;\r\n columns: number;\r\n rows: number;\r\n multiselect: boolean;\r\n showDisabled: string;\r\n selection: string;\r\n selfFilterEnabled: boolean;\r\n getSavedSelection?: () => string[];\r\n setSavedSelection?: (filter: SemanticFilter, selectionIds: string[]) => void;\r\n };\r\n margin: IMargin;\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 outlineColor: string;\r\n outlineWeight: number;\r\n title: string;\r\n };\r\n headerText: {\r\n marginLeft: number;\r\n marginTop: number;\r\n };\r\n slicerText: {\r\n textSize: number;\r\n height: number;\r\n width: number;\r\n fontColor: string;\r\n selectedColor: string;\r\n hoverColor: string;\r\n unselectedColor: string;\r\n disabledColor: string;\r\n marginLeft: number;\r\n outline: string;\r\n background?: string;\r\n transparency: number;\r\n outlineColor: string;\r\n outlineWeight: number;\r\n borderStyle: string;\r\n };\r\n slicerItemContainer: {\r\n marginTop: number;\r\n marginLeft: number;\r\n };\r\n images: {\r\n imageSplit: number;\r\n stretchImage: boolean;\r\n bottomImage: boolean;\r\n };\r\n }\r\n\r\n export class ChicletSlicer implements IVisual {\r\n public static capabilities: VisualCapabilities = {\r\n dataRoles: [\r\n {\r\n name: 'Category',\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: 'Category',\r\n },\r\n {\r\n name: 'Values',\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: 'Values',\r\n },\r\n {\r\n name: 'Image',\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: 'Image',\r\n },\r\n ],\r\n objects: {\r\n general: {\r\n displayName: data.createDisplayNameGetter('Visual_General'),\r\n properties: {\r\n selection: {\r\n displayName: \"Selection\",\r\n type: { text: true }\r\n },\r\n orientation: {\r\n displayName: 'Orientation',\r\n type: { enumeration: Orientation.type }\r\n },\r\n columns: {\r\n displayName: 'Columns',\r\n type: { numeric: true }\r\n },\r\n rows: {\r\n displayName: 'Rows',\r\n type: { numeric: true }\r\n },\r\n showDisabled: {\r\n displayName: 'Show Disabled',\r\n type: { enumeration: ChicletSlicerShowDisabled.type }\r\n },\r\n multiselect: {\r\n displayName: 'Multiple selection',\r\n type: { bool: true }\r\n },\r\n selected: {\r\n type: { bool: true }\r\n },\r\n filter: {\r\n type: { filter: {} },\r\n },\r\n selfFilter: {\r\n type: { filter: { selfFilter: true } },\r\n },\r\n selfFilterEnabled: {\r\n type: { operations: { searchEnabled: true } }\r\n },\r\n formatString: {\r\n type: { formatting: { formatString: true } },\r\n },\r\n },\r\n },\r\n header: {\r\n displayName: data.createDisplayNameGetter('Visual_Header'),\r\n properties: {\r\n show: {\r\n displayName: data.createDisplayNameGetter('Visual_Show'),\r\n type: { bool: true }\r\n },\r\n title: {\r\n displayName: 'Title',\r\n type: { text: true }\r\n },\r\n fontColor: {\r\n displayName: data.createDisplayNameGetter('Visual_FontColor'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n background: {\r\n displayName: data.createDisplayNameGetter('Visual_Background'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n outline: {\r\n displayName: data.createDisplayNameGetter('Visual_Outline'),\r\n type: { formatting: { outline: true } }\r\n },\r\n textSize: {\r\n displayName: data.createDisplayNameGetter('Visual_TextSize'),\r\n type: { numeric: true }\r\n },\r\n outlineColor: {\r\n displayName: 'Outline Color',\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n outlineWeight: {\r\n displayName: 'Outline Weight',\r\n type: { numeric: true }\r\n }\r\n }\r\n },\r\n rows: {\r\n displayName: 'Chiclets',\r\n properties: {\r\n fontColor: {\r\n displayName: 'Text color',\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n textSize: {\r\n displayName: data.createDisplayNameGetter('Visual_TextSize'),\r\n type: { numeric: true }\r\n },\r\n height: {\r\n displayName: 'Height',\r\n type: { numeric: true }\r\n },\r\n width: {\r\n displayName: 'Width',\r\n type: { numeric: true }\r\n },\r\n selectedColor: {\r\n displayName: 'Selected Color',\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n hoverColor: {\r\n displayName: 'Hover Color',\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n unselectedColor: {\r\n displayName: 'Unselected Color',\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n disabledColor: {\r\n displayName: 'Disabled Color',\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n background: {\r\n displayName: data.createDisplayNameGetter('Visual_Background'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n transparency: {\r\n displayName: \"Transparency\",\r\n description: \"Set transparency for background color\",\r\n type: { numeric: true }\r\n },\r\n outline: {\r\n displayName: data.createDisplayNameGetter('Visual_Outline'),\r\n type: { formatting: { outline: true } }\r\n },\r\n outlineColor: {\r\n displayName: 'Outline Color',\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n outlineWeight: {\r\n displayName: 'Outline Weight',\r\n type: { numeric: true }\r\n },\r\n borderStyle: {\r\n displayName: 'Outline Style',\r\n type: { enumeration: ChicletBorderStyle.type }\r\n },\r\n }\r\n },\r\n images: {\r\n displayName: 'Images',\r\n properties: {\r\n imageSplit: {\r\n displayName: 'Image Split',\r\n type: { numeric: true }\r\n },\r\n stretchImage: {\r\n displayName: 'Stretch image',\r\n type: { bool: true }\r\n },\r\n bottomImage: {\r\n displayName: 'Bottom image',\r\n type: { bool: true }\r\n },\r\n }\r\n },\r\n },\r\n dataViewMappings: [{\r\n conditions: [\r\n { 'Category': { max: 1 }, 'Image': { min: 0, max: 1 }, 'Values': { min: 0, max: 1 } }],\r\n categorical: {\r\n categories: {\r\n for: { in: 'Category' },\r\n dataReductionAlgorithm: { top: { count: 10000 } }\r\n },\r\n values: {\r\n group: {\r\n by: 'Image',\r\n select: [{ bind: { to: 'Values' } },\r\n ],\r\n dataReductionAlgorithm: { top: { count: 10000 } }\r\n }\r\n },\r\n includeEmptyGroups: true\r\n }\r\n }],\r\n supportsHighlight: true,\r\n sorting: {\r\n default: {},\r\n },\r\n suppressDefaultTitle: true,\r\n };\r\n private element: JQuery;\r\n private searchHeader: JQuery;\r\n private searchInput: JQuery;\r\n private currentViewport: IViewport;\r\n private dataView: DataView;\r\n private slicerHeader: D3.Selection;\r\n private slicerBody: D3.Selection;\r\n private tableView: ITableView;\r\n private slicerData: ChicletSlicerData;\r\n private settings: ChicletSlicerSettings;\r\n private interactivityService: IInteractivityService;\r\n private behavior: ChicletSlicerWebBehavior;\r\n private hostServices: IVisualHostServices;\r\n private waitingForData: boolean;\r\n private isSelectionLoaded: boolean;\r\n private isSelectionSaved: boolean;\r\n\r\n public static DefaultFontFamily: string = 'Segoe UI, Tahoma, Verdana, Geneva, sans-serif';\r\n public static DefaultFontSizeInPt: number = 11;\r\n private static cellTotalInnerPaddings = 8;\r\n private static cellTotalInnerBorders = 2;\r\n private static chicletTotalInnerRightLeftPaddings = 14;\r\n\r\n private static ItemContainer: ClassAndSelector = createClassAndSelector('slicerItemContainer');\r\n private static HeaderText: ClassAndSelector = createClassAndSelector('headerText');\r\n private static Container: ClassAndSelector = createClassAndSelector('chicletSlicer');\r\n private static LabelText: ClassAndSelector = createClassAndSelector('slicerText');\r\n private static Header: ClassAndSelector = createClassAndSelector('slicerHeader');\r\n private static Input: ClassAndSelector = createClassAndSelector('slicerCheckbox');\r\n private static Clear: ClassAndSelector = createClassAndSelector('clear');\r\n private static Body: ClassAndSelector = createClassAndSelector('slicerBody');\r\n\r\n public static DefaultStyleProperties(): ChicletSlicerSettings {\r\n return {\r\n general: {\r\n orientation: Orientation.VERTICAL,\r\n columns: 3,\r\n rows: 0,\r\n multiselect: true,\r\n showDisabled: ChicletSlicerShowDisabled.INPLACE,\r\n selection: null,\r\n selfFilterEnabled: false\r\n },\r\n margin: {\r\n top: 50,\r\n bottom: 50,\r\n right: 50,\r\n left: 50\r\n },\r\n header: {\r\n borderBottomWidth: 1,\r\n show: true,\r\n outline: 'BottomOnly',\r\n fontColor: '#a6a6a6',\r\n background: null,\r\n textSize: 10,\r\n outlineColor: '#a6a6a6',\r\n outlineWeight: 1,\r\n title: '',\r\n },\r\n headerText: {\r\n marginLeft: 8,\r\n marginTop: 0\r\n },\r\n slicerText: {\r\n textSize: 10,\r\n height: 0,\r\n width: 0,\r\n fontColor: '#666666',\r\n hoverColor: '#212121',\r\n selectedColor: '#BDD7EE',\r\n unselectedColor: '#ffffff',\r\n disabledColor: 'grey',\r\n marginLeft: 8,\r\n outline: 'Frame',\r\n background: null,\r\n transparency: 0,\r\n outlineColor: '#000000',\r\n outlineWeight: 1,\r\n borderStyle: 'Cut',\r\n\r\n },\r\n slicerItemContainer: {\r\n // The margin is assigned in the less file. This is needed for the height calculations.\r\n marginTop: 5,\r\n marginLeft: 0,\r\n },\r\n images: {\r\n imageSplit: 50,\r\n stretchImage: false,\r\n bottomImage: false\r\n }\r\n };\r\n }\r\n\r\n constructor(options?: ChicletSlicerConstructorOptions) {\r\n if (options) {\r\n if (options.behavior) {\r\n this.behavior = options.behavior;\r\n }\r\n }\r\n if (!this.behavior) {\r\n this.behavior = new ChicletSlicerWebBehavior();\r\n }\r\n }\r\n\r\n public static converter(dataView: DataView, localizedSelectAllText: string, searchText: string, interactivityService: IInteractivityService): ChicletSlicerData {\r\n if (!dataView ||\r\n !dataView.categorical ||\r\n !dataView.categorical.categories ||\r\n !dataView.categorical.categories[0] ||\r\n !dataView.categorical.categories[0].values ||\r\n !(dataView.categorical.categories[0].values.length > 0)) {\r\n return;\r\n }\r\n var converter = new ChicletSlicerChartConversion.ChicletSlicerConverter(dataView, interactivityService);\r\n converter.convert();\r\n var slicerData: ChicletSlicerData;\r\n var defaultSettings: ChicletSlicerSettings = this.DefaultStyleProperties();\r\n var objects: DataViewObjects = dataView.metadata.objects;\r\n if (objects) {\r\n defaultSettings.general.orientation = DataViewObjects.getValue<string>(objects, chicletSlicerProps.general.orientation, defaultSettings.general.orientation);\r\n defaultSettings.general.columns = DataViewObjects.getValue<number>(objects, chicletSlicerProps.general.columns, defaultSettings.general.columns);\r\n defaultSettings.general.rows = DataViewObjects.getValue<number>(objects, chicletSlicerProps.general.rows, defaultSettings.general.rows);\r\n defaultSettings.general.multiselect = DataViewObjects.getValue<boolean>(objects, chicletSlicerProps.general.multiselect, defaultSettings.general.multiselect);\r\n defaultSettings.general.showDisabled = DataViewObjects.getValue<string>(objects, chicletSlicerProps.general.showDisabled, defaultSettings.general.showDisabled);\r\n defaultSettings.general.selection = DataViewObjects.getValue(dataView.metadata.objects, chicletSlicerProps.general.selection, defaultSettings.general.selection);\r\n defaultSettings.general.selfFilterEnabled = DataViewObjects.getValue<boolean>(objects, chicletSlicerProps.general.selfFilterEnabled, defaultSettings.general.selfFilterEnabled);\r\n\r\n defaultSettings.header.show = DataViewObjects.getValue<boolean>(objects, chicletSlicerProps.header.show, defaultSettings.header.show);\r\n defaultSettings.header.title = DataViewObjects.getValue<string>(objects, chicletSlicerProps.header.title, defaultSettings.header.title);\r\n defaultSettings.header.fontColor = DataViewObjects.getFillColor(objects, chicletSlicerProps.header.fontColor, defaultSettings.header.fontColor);\r\n defaultSettings.header.background = DataViewObjects.getFillColor(objects, chicletSlicerProps.header.background, defaultSettings.header.background);\r\n defaultSettings.header.textSize = DataViewObjects.getValue<number>(objects, chicletSlicerProps.header.textSize, defaultSettings.header.textSize);\r\n defaultSettings.header.outline = DataViewObjects.getValue<string>(objects, chicletSlicerProps.header.outline, defaultSettings.header.outline);\r\n defaultSettings.header.outlineColor = DataViewObjects.getFillColor(objects, chicletSlicerProps.header.outlineColor, defaultSettings.header.outlineColor);\r\n defaultSettings.header.outlineWeight = DataViewObjects.getValue<number>(objects, chicletSlicerProps.header.outlineWeight, defaultSettings.header.outlineWeight);\r\n\r\n defaultSettings.slicerText.textSize = DataViewObjects.getValue<number>(objects, chicletSlicerProps.rows.textSize, defaultSettings.slicerText.textSize);\r\n defaultSettings.slicerText.height = DataViewObjects.getValue<number>(objects, chicletSlicerProps.rows.height, defaultSettings.slicerText.height);\r\n defaultSettings.slicerText.width = DataViewObjects.getValue<number>(objects, chicletSlicerProps.rows.width, defaultSettings.slicerText.width);\r\n defaultSettings.slicerText.selectedColor = DataViewObjects.getFillColor(objects, chicletSlicerProps.rows.selectedColor, defaultSettings.slicerText.selectedColor);\r\n defaultSettings.slicerText.hoverColor = DataViewObjects.getFillColor(objects, chicletSlicerProps.rows.hoverColor, defaultSettings.slicerText.hoverColor);\r\n defaultSettings.slicerText.unselectedColor = DataViewObjects.getFillColor(objects, chicletSlicerProps.rows.unselectedColor, defaultSettings.slicerText.unselectedColor);\r\n defaultSettings.slicerText.disabledColor = DataViewObjects.getFillColor(objects, chicletSlicerProps.rows.disabledColor, defaultSettings.slicerText.disabledColor);\r\n defaultSettings.slicerText.background = DataViewObjects.getFillColor(objects, chicletSlicerProps.rows.background, defaultSettings.slicerText.background);\r\n defaultSettings.slicerText.transparency = DataViewObjects.getValue<number>(objects, chicletSlicerProps.rows.transparency, defaultSettings.slicerText.transparency);\r\n defaultSettings.slicerText.fontColor = DataViewObjects.getFillColor(objects, chicletSlicerProps.rows.fontColor, defaultSettings.slicerText.fontColor);\r\n defaultSettings.slicerText.outline = DataViewObjects.getValue<string>(objects, chicletSlicerProps.rows.outline, defaultSettings.slicerText.outline);\r\n defaultSettings.slicerText.outlineColor = DataViewObjects.getFillColor(objects, chicletSlicerProps.rows.outlineColor, defaultSettings.slicerText.outlineColor);\r\n defaultSettings.slicerText.outlineWeight = DataViewObjects.getValue<number>(objects, chicletSlicerProps.rows.outlineWeight, defaultSettings.slicerText.outlineWeight);\r\n defaultSettings.slicerText.borderStyle = DataViewObjects.getValue<string>(objects, chicletSlicerProps.rows.borderStyle, defaultSettings.slicerText.borderStyle);\r\n\r\n defaultSettings.images.imageSplit = DataViewObjects.getValue<number>(objects, chicletSlicerProps.images.imageSplit, defaultSettings.images.imageSplit);\r\n defaultSettings.images.stretchImage = DataViewObjects.getValue<boolean>(objects, chicletSlicerProps.images.stretchImage, defaultSettings.images.stretchImage);\r\n defaultSettings.images.bottomImage = DataViewObjects.getValue<boolean>(objects, chicletSlicerProps.images.bottomImage, defaultSettings.images.bottomImage);\r\n }\r\n\r\n if(defaultSettings.general.selfFilterEnabled && searchText) {\r\n searchText = searchText.toLowerCase();\r\n converter.dataPoints.forEach(x => x.filtered = x.category.toLowerCase().indexOf(searchText) < 0);\r\n }\r\n\r\n var categories: DataViewCategoricalColumn = dataView.categorical.categories[0];\r\n slicerData = {\r\n categorySourceName: categories.source.displayName,\r\n formatString: valueFormatter.getFormatString(categories.source, chicletSlicerProps.formatString),\r\n slicerSettings: defaultSettings,\r\n slicerDataPoints: converter.dataPoints,\r\n };\r\n\r\n // Override hasSelection if a objects contained more scopeIds than selections we found in the data\r\n slicerData.hasSelectionOverride = converter.hasSelectionOverride;\r\n\r\n return slicerData;\r\n }\r\n\r\n public init(options: VisualInitOptions): void {\r\n this.element = options.element;\r\n this.currentViewport = options.viewport;\r\n if (this.behavior) {\r\n this.interactivityService = createInteractivityService(options.host);\r\n }\r\n this.hostServices = options.host;\r\n this.hostServices.canSelect = ChicletSlicer.canSelect;\r\n this.settings = ChicletSlicer.DefaultStyleProperties();\r\n\r\n this.initContainer();\r\n }\r\n\r\n private static canSelect(args: SelectEventArgs): boolean {\r\n var selectors = args.data;\r\n // We can't have multiple selections if any include more than one identity\r\n if (selectors && (selectors.length > 1)) {\r\n if (selectors.some((value: data.Selector) => value && value.data && value.data.length > 1)) {\r\n return false;\r\n }\r\n }\r\n\r\n // Todo: check for cases of trying to select a category and a series (not the intersection)\r\n return true;\r\n }\r\n\r\n public update(options: VisualUpdateOptions) {\r\n if (!options ||\r\n !options.dataViews ||\r\n !options.dataViews[0] ||\r\n !options.viewport) {\r\n return;\r\n }\r\n\r\n var existingDataView = this.dataView;\r\n this.dataView = options.dataViews[0];\r\n\r\n var resetScrollbarPosition: boolean = true;\r\n if (existingDataView) {\r\n resetScrollbarPosition = !DataViewAnalysis.hasSameCategoryIdentity(existingDataView, this.dataView);\r\n }\r\n\r\n if (options.viewport.height === this.currentViewport.height\r\n && options.viewport.width === this.currentViewport.width) {\r\n this.waitingForData = false;\r\n }\r\n else {\r\n this.currentViewport = options.viewport;\r\n }\r\n\r\n this.updateInternal(resetScrollbarPosition);\r\n }\r\n\r\n public onResizing(finalViewport: IViewport): void {\r\n this.currentViewport = finalViewport;\r\n this.updateInternal(false /* resetScrollbarPosition */);\r\n }\r\n\r\n public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstance[] {\r\n var data: ChicletSlicerData = this.slicerData;\r\n if (!data) {\r\n return;\r\n }\r\n\r\n var objectName = options.objectName;\r\n switch (objectName) {\r\n case 'rows':\r\n return this.enumerateRows(data);\r\n case 'header':\r\n return this.enumerateHeader(data);\r\n case 'general':\r\n return this.enumerateGeneral(data);\r\n case 'images':\r\n return this.enumerateImages(data);\r\n }\r\n }\r\n\r\n private enumerateHeader(data: ChicletSlicerData): VisualObjectInstance[] {\r\n var slicerSettings: ChicletSlicerSettings = this.settings;\r\n return [{\r\n selector: null,\r\n objectName: 'header',\r\n properties: {\r\n show: slicerSettings.header.show,\r\n title: slicerSettings.header.title,\r\n fontColor: slicerSettings.header.fontColor,\r\n background: slicerSettings.header.background,\r\n textSize: slicerSettings.header.textSize,\r\n outline: slicerSettings.header.outline,\r\n outlineColor: slicerSettings.header.outlineColor,\r\n outlineWeight: slicerSettings.header.outlineWeight\r\n }\r\n }];\r\n }\r\n\r\n private enumerateRows(data: ChicletSlicerData): VisualObjectInstance[] {\r\n var slicerSettings: ChicletSlicerSettings = this.settings;\r\n return [{\r\n selector: null,\r\n objectName: 'rows',\r\n properties: {\r\n textSize: slicerSettings.slicerText.textSize,\r\n height: slicerSettings.slicerText.height,\r\n width: slicerSettings.slicerText.width,\r\n background: slicerSettings.slicerText.background,\r\n transparency: slicerSettings.slicerText.transparency,\r\n selectedColor: slicerSettings.slicerText.selectedColor,\r\n hoverColor: slicerSettings.slicerText.hoverColor,\r\n unselectedColor: slicerSettings.slicerText.unselectedColor,\r\n disabledColor: slicerSettings.slicerText.disabledColor,\r\n outline: slicerSettings.slicerText.outline,\r\n outlineColor: slicerSettings.slicerText.outlineColor,\r\n outlineWeight: slicerSettings.slicerText.outlineWeight,\r\n fontColor: slicerSettings.slicerText.fontColor,\r\n borderStyle: slicerSettings.slicerText.borderStyle,\r\n }\r\n }];\r\n }\r\n\r\n private enumerateGeneral(data: ChicletSlicerData): VisualObjectInstance[] {\r\n var slicerSettings: ChicletSlicerSettings = this.settings;\r\n\r\n return [{\r\n selector: null,\r\n objectName: 'general',\r\n properties: {\r\n orientation: slicerSettings.general.orientation,\r\n columns: slicerSettings.general.columns,\r\n rows: slicerSettings.general.rows,\r\n showDisabled: slicerSettings.general.showDisabled,\r\n multiselect: slicerSettings.general.multiselect,\r\n selfFilterEnabled: slicerSettings.general.selfFilterEnabled\r\n }\r\n }];\r\n }\r\n\r\n private enumerateImages(data: ChicletSlicerData): VisualObjectInstance[] {\r\n var slicerSettings: ChicletSlicerSettings = this.settings;\r\n return [{\r\n selector: null,\r\n objectName: 'images',\r\n properties: {\r\n imageSplit: slicerSettings.images.imageSplit,\r\n stretchImage: slicerSettings.images.stretchImage,\r\n bottomImage: slicerSettings.images.bottomImage,\r\n }\r\n }];\r\n }\r\n private updateInternal(resetScrollbarPosition: boolean) {\r\n this.updateSlicerBodyDimensions();\r\n\r\n var localizedSelectAllText: string = 'Select All';\r\n var data = ChicletSlicer.converter(this.dataView, localizedSelectAllText, this.searchInput.val(), this.interactivityService);\r\n if (!data) {\r\n this.tableView.empty();\r\n return;\r\n }\r\n\r\n if (this.interactivityService) {\r\n this.interactivityService.applySelectionStateToData(data.slicerDataPoints);\r\n }\r\n\r\n data.slicerSettings.header.outlineWeight = data.slicerSettings.header.outlineWeight < 0 ? 0 : data.slicerSettings.header.outlineWeight;\r\n data.slicerSettings.slicerText.outlineWeight = data.slicerSettings.slicerText.outlineWeight < 0 ? 0 : data.slicerSettings.slicerText.outlineWeight;\r\n data.slicerSettings.slicerText.height = data.slicerSettings.slicerText.height < 0 ? 0 : data.slicerSettings.slicerText.height;\r\n data.slicerSettings.slicerText.width = data.slicerSettings.slicerText.width < 0 ? 0 : data.slicerSettings.slicerText.width;\r\n data.slicerSettings.images.imageSplit = data.slicerSettings.images.imageSplit < 0 ? 0 : data.slicerSettings.images.imageSplit;\r\n\r\n data.slicerSettings.general.columns = data.slicerSettings.general.columns < 0 ? 0 : data.slicerSettings.general.columns;\r\n data.slicerSettings.general.rows = data.slicerSettings.general.rows < 0 ? 0 : data.slicerSettings.general.rows;\r\n\r\n data.slicerSettings.general.getSavedSelection = () => {\r\n try {\r\n return JSON.parse(this.slicerData.slicerSettings.general.selection) || [];\r\n } catch(ex) {\r\n return [];\r\n }\r\n };\r\n\r\n data.slicerSettings.general.setSavedSelection = (filter: SemanticFilter, selectionIds: string[]): void => {\r\n this.isSelectionSaved = true;\r\n this.hostServices.persistProperties(<VisualObjectInstancesToPersist>{\r\n merge: [{\r\n objectName: \"general\",\r\n selector: null,\r\n properties: {\r\n filter: filter,\r\n selection: selectionIds && JSON.stringify(selectionIds) || \"\" }\r\n }]\r\n });\r\n };\r\n\r\n if (this.slicerData) {\r\n if (this.isSelectionSaved) {\r\n this.isSelectionLoaded = true;\r\n } else {\r\n this.isSelectionLoaded = this.slicerData.slicerSettings.general.selection === data.slicerSettings.general.selection;\r\n }\r\n } else {\r\n this.isSelectionLoaded = false;\r\n }\r\n\r\n this.slicerData = data;\r\n this.settings = this.slicerData.slicerSettings;\r\n if (this.settings.general.showDisabled === ChicletSlicerShowDisabled.BOTTOM) {\r\n data.slicerDataPoints.sort(function (a, b) {\r\n if (a.selectable === b.selectable) {\r\n return 0;\r\n } else if (a.selectable && !b.selectable) {\r\n return -1;\r\n } else {\r\n return 1;\r\n }\r\n });\r\n } else if (this.settings.general.showDisabled === ChicletSlicerShowDisabled.HIDE) {\r\n data.slicerDataPoints = data.slicerDataPoints.filter(x => x.selectable);\r\n }\r\n\r\n var height: number = this.settings.slicerText.height;\r\n if (height === 0) {\r\n var extraSpaceForCell = ChicletSlicer.cellTotalInnerPaddings + ChicletSlicer.cellTotalInnerBorders;\r\n var textProperties = ChicletSlicer.getChicletTextProperties(this.settings.slicerText.textSize);\r\n height = TextMeasurementService.estimateSvgTextHeight(textProperties) + TextMeasurementService.estimateSvgTextBaselineDelta(textProperties) + extraSpaceForCell;\r\n var hasImage = _.any(data.slicerDataPoints, x => x.imageURL !== '' && typeof x.imageURL !== \"undefined\");\r\n if (hasImage)\r\n height += 100;\r\n }\r\n\r\n this.tableView\r\n .rowHeight(height)\r\n .columnWidth(this.settings.slicerText.width)\r\n .orientation(this.settings.general.orientation)\r\n .rows(this.settings.general.rows)\r\n .columns(this.settings.general.columns)\r\n .data(data.slicerDataPoints.filter(x => !x.filtered),\r\n (d: ChicletSlicerDataPoint) => $.inArray(d, data.slicerDataPoints),\r\n resetScrollbarPosition)\r\n .viewport(this.getSlicerBodyViewport(this.currentViewport))\r\n .render();\r\n\r\n // if(!selectedItems.length && String(savedSelection).length && this.slicerData && this.slicerData.hasSelectionOverride){\r\n // var arrSelection = String(savedSelection).split('&');\r\n // var arrSelected = jQuery.map(data.slicerDataPoints, function (d, index) {\r\n // if (arrSelection.indexOf(d.category) > -1) return d;\r\n // });\r\n // data.slicerDataPoints.forEach(function (d, index) {\r\n // if (arrSelection.indexOf(d.category) > -1){\r\n // d.selected = true;\r\n // // console.error('>>>>@@@', d, index);\r\n // }\r\n // });\r\n // if(!arrSelection.length){\r\n // this.slicerData.hasSelectionOverride = false\r\n // }\r\n // // console.error('>>> 2', 'RESTORE', savedSelection, arrSelected, data.slicerDataPoints )\r\n // }\r\n\r\n this.updateSearchHeader();\r\n }\r\n\r\n private initContainer() {\r\n var settings: ChicletSlicerSettings = this.settings;\r\n var slicerBodyViewport: IViewport = this.getSlicerBodyViewport(this.currentViewport);\r\n var slicerContainer: D3.Selection = d3.select(this.element.get(0))\r\n .append('div')\r\n .classed(ChicletSlicer.Container.class, true);\r\n\r\n this.slicerHeader = slicerContainer\r\n .append('div')\r\n .classed(ChicletSlicer.Header.class, true);\r\n\r\n this.slicerHeader\r\n .append('span')\r\n .classed(ChicletSlicer.Clear.class, true)\r\n .attr('title', 'Clear');\r\n\r\n this.slicerHeader\r\n .append('div')\r\n .classed(ChicletSlicer.HeaderText.class, true)\r\n .style({\r\n 'margin-left': PixelConverter.toString(settings.headerText.marginLeft),\r\n 'margin-top': PixelConverter.toString(settings.headerText.marginTop),\r\n 'border-style': this.getBorderStyle(settings.header.outline),\r\n 'border-color': settings.header.outlineColor,\r\n 'border-width': this.getBorderWidth(settings.header.outline, settings.header.outlineWeight),\r\n 'font-size': PixelConverter.fromPoint(settings.header.textSize),\r\n });\r\n\r\n this.createSearchHeader($(slicerContainer.node()));\r\n\r\n this.slicerBody = slicerContainer\r\n .append('div').classed(ChicletSlicer.Body.class, true)\r\n .classed('slicerBody-horizontal', settings.general.orientation === Orientation.HORIZONTAL)\r\n .classed('slicerBody-vertical', settings.general.orientation === Orientation.VERTICAL)\r\n .style({\r\n 'height': PixelConverter.toString(slicerBodyViewport.height),\r\n 'width': '100%',\r\n });\r\n\r\n var rowEnter = (rowSelection: D3.Selection) => {\r\n var settings: ChicletSlicerSettings = this.settings;\r\n var listItemElement = rowSelection\r\n .append('ul')\r\n .append('li')\r\n .classed(ChicletSlicer.ItemContainer.class, true)\r\n .style({\r\n 'margin-left': PixelConverter.toString(settings.slicerItemContainer.marginLeft),\r\n });\r\n\r\n listItemElement.append('div')\r\n .classed('slicer-img-wrapper', true);\r\n\r\n listItemElement.append('div')\r\n .classed('slicer-text-wrapper', true)\r\n .append('span')\r\n .classed(ChicletSlicer.LabelText.class, true)\r\n .style({\r\n 'font-size': PixelConverter.fromPoint(settings.slicerText.textSize),\r\n });\r\n };\r\n\r\n var rowUpdate = (rowSelection: D3.Selection) => {\r\n var settings: ChicletSlicerSettings = this.settings;\r\n var data = this.slicerData;\r\n if (data && settings) {\r\n this.slicerHeader.classed('hidden', !settings.header.show);\r\n this.slicerHeader.select(ChicletSlicer.HeaderText.selector)\r\n .text(settings.header.title.trim() !== \"\" ? settings.header.title.trim() : this.slicerData.categorySourceName)\r\n .style({\r\n 'border-style': this.getBorderStyle(settings.header.outline),\r\n 'border-color': settings.header.outlineColor,\r\n 'border-width': this.getBorderWidth(settings.header.outline, settings.header.outlineWeight),\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 this.slicerBody\r\n .classed('slicerBody-horizontal', settings.general.orientation === Orientation.HORIZONTAL)\r\n .classed('slicerBody-vertical', settings.general.orientation === Orientation.VERTICAL);\r\n\r\n var slicerText = rowSelection.selectAll(ChicletSlicer.LabelText.selector);\r\n var textProperties = ChicletSlicer.getChicletTextProperties(settings.slicerText.textSize);\r\n\r\n var formatString = data.formatString;\r\n slicerText.text((d: ChicletSlicerDataPoint) => {\r\n var text = valueFormatter.format(d.category, formatString);\r\n textProperties.text = text;\r\n if (this.settings.slicerText.width === 0)\r\n return powerbi.TextMeasurementService.getTailoredTextOrDefault(textProperties, (this.currentViewport.width / this.settings.general.columns) - ChicletSlicer.chicletTotalInnerRightLeftPaddings - ChicletSlicer.cellTotalInnerBorders - settings.slicerText.outlineWeight);\r\n else\r\n return TextMeasurementService.getTailoredTextOrDefault(textProperties, this.settings.slicerText.width - ChicletSlicer.chicletTotalInnerRightLeftPaddings - ChicletSlicer.cellTotalInnerBorders - settings.slicerText.outlineWeight);\r\n });\r\n\r\n var slicerImg = rowSelection.selectAll('.slicer-img-wrapper');\r\n slicerImg\r\n .style('height', settings.images.imageSplit + '%')\r\n .classed('hidden', (d: ChicletSlicerDataPoint) => {\r\n if (!(d.imageURL)) {\r\n return true;\r\n }\r\n if (settings.images.imageSplit < 10) {\r\n return true;\r\n }\r\n })\r\n .style('display', (d: ChicletSlicerDataPoint) => (d.imageURL) ? 'flex' : 'none')\r\n .classed('stretchImage', settings.images.stretchImage)\r\n .classed('bottomImage', settings.images.bottomImage)\r\n .style('background-image', (d: ChicletSlicerDataPoint) => {\r\n return d.imageURL ? `url(${d.imageURL})` : '';\r\n });\r\n\r\n rowSelection.selectAll('.slicer-text-wrapper')\r\n .style('height', (d: ChicletSlicerDataPoint) => {\r\n return d.imageURL ? (100 - settings.images.imageSplit) + '%' : '100%';\r\n })\r\n .classed('hidden', (d: ChicletSlicerDataPoint) => {\r\n if (settings.images.imageSplit > 90) {\r\n return true;\r\n }\r\n });\r\n\r\n rowSelection.selectAll('.slicerItemContainer').style({\r\n 'color': settings.slicerText.fontColor,\r\n 'border-style': this.getBorderStyle(settings.slicerText.outline),\r\n 'border-color': settings.slicerText.outlineColor,\r\n 'border-width': this.getBorderWidth(settings.slicerText.outline, settings.slicerText.outlineWeight),\r\n 'font-size': PixelConverter.fromPoint(settings.slicerText.textSize),\r\n 'border-radius': this.getBorderRadius(settings.slicerText.borderStyle),\r\n });\r\n\r\n if (settings.slicerText.background)\r\n this.slicerBody.style('background-color', explore.util.hexToRGBString(settings.slicerText.background, (100 - settings.slicerText.transparency) / 100));\r\n else\r\n this.slicerBody.style('background-color', null);\r\n\r\n if (this.interactivityService && this.slicerBody) {\r\n this.interactivityService.applySelectionStateToData(data.slicerDataPoints);\r\n\r\n var slicerBody = this.slicerBody.attr('width', this.currentViewport.width);\r\n var slicerItemContainers = slicerBody.selectAll(ChicletSlicer.ItemContainer.selector);\r\n var slicerItemLabels = slicerBody.selectAll(ChicletSlicer.LabelText.selector);\r\n var slicerItemInputs = slicerBody.selectAll(ChicletSlicer.Input.selector);\r\n var slicerClear = this.slicerHeader.select(ChicletSlicer.Clear.selector);\r\n\r\n var behaviorOptions: ChicletSlicerBehaviorOptions = {\r\n dataPoints: data.slicerDataPoints,\r\n slicerItemContainers: slicerItemContainers,\r\n slicerItemLabels: slicerItemLabels,\r\n slicerItemInputs: slicerItemInputs,\r\n slicerClear: slicerClear,\r\n interactivityService: this.interactivityService,\r\n slicerSettings: data.slicerSettings,\r\n isSelectionLoaded: this.isSelectionLoaded\r\n };\r\n\r\n this.interactivityService.bind(data.slicerDataPoints, this.behavior, behaviorOptions, {\r\n overrideSelectionFromData: true,\r\n hasSelectionOverride: data.hasSelectionOverride,\r\n });\r\n this.behavior.styleSlicerInputs(rowSelection.select(ChicletSlicer.ItemContainer.selector),\r\n this.interactivityService.hasSelection());\r\n }\r\n else {\r\n this.behavior.styleSlicerInputs(rowSelection.select(ChicletSlicer.ItemContainer.selector), false);\r\n }\r\n }\r\n };\r\n\r\n var rowExit = (rowSelection: D3.Selection) => {\r\n rowSelection.remove();\r\n };\r\n\r\n var tableViewOptions: TableViewViewOptions = {\r\n rowHeight: this.getRowHeight(),\r\n columnWidth: this.settings.slicerText.width,\r\n orientation: this.settings.general.orientation,\r\n rows: this.settings.general.rows,\r\n columns: this.settings.general.columns,\r\n enter: rowEnter,\r\n exit: rowExit,\r\n update: rowUpdate,\r\n loadMoreData: () => this.onLoadMoreData(),\r\n scrollEnabled: true,\r\n viewport: this.getSlicerBodyViewport(this.currentViewport),\r\n baseContainer: this.slicerBody,\r\n };\r\n\r\n this.tableView = TableViewFactory.createTableView(tableViewOptions);\r\n }\r\n\r\n private createSearchHeader(container: JQuery): void {\r\n this.searchHeader = $(\"<div>\")\r\n .appendTo(container)\r\n .addClass(\"searchHeader\")\r\n .addClass(\"collapsed\");\r\n\r\n $(\"<div>\").appendTo(this.searchHeader)\r\n .attr(\"title\", \"Search\")\r\n .addClass(\"search\");\r\n\r\n var counter = 0;\r\n this.searchInput = $(\"<input>\").appendTo(this.searchHeader)\r\n .attr(\"type\", \"text\")\r\n .attr(\"drag-resize-disabled\", \"true\")\r\n .addClass(\"searchInput\")\r\n .on(\"input\", () => this.hostServices.persistProperties(<VisualObjectInstancesToPersist>{\r\n merge: [{\r\n objectName: \"general\",\r\n selector: null,\r\n properties: {\r\n counter: counter++\r\n }}]\r\n }));\r\n }\r\n\r\n private updateSearchHeader(): void {\r\n this.searchHeader.toggleClass(\"show\", this.slicerData.slicerSettings.general.selfFilterEnabled);\r\n this.searchHeader.toggleClass(\"collapsed\", !this.slicerData.slicerSettings.general.selfFilterEnabled);\r\n }\r\n\r\n private onLoadMoreData(): void {\r\n if (!this.waitingForData && this.dataView.metadata && this.dataView.metadata.segment) {\r\n this.hostServices.loadMoreData();\r\n this.waitingForData = true;\r\n }\r\n }\r\n\r\n private getSlicerBodyViewport(currentViewport: IViewport): IViewport {\r\n var settings = this.settings;\r\n var headerHeight = (settings.header.show) ? this.getHeaderHeight() : 0;\r\n var slicerBodyHeight = currentViewport.height - (headerHeight + settings.header.borderBottomWidth);\r\n return {\r\n height: slicerBodyHeight,\r\n width: currentViewport.width\r\n };\r\n }\r\n\r\n private updateSlicerBodyDimensions(): void {\r\n var slicerViewport: IViewport = this.getSlicerBodyViewport(this.currentViewport);\r\n this.slicerBody\r\n .style({\r\n 'height': PixelConverter.toString(slicerViewport.height),\r\n 'width': '100%',\r\n });\r\n }\r\n\r\n public static getChicletTextProperties(textSize?: number): TextProperties {\r\n return <TextProperties>{\r\n fontFamily: ChicletSlicer.DefaultFontFamily,\r\n fontSize: PixelConverter.fromPoint(textSize || ChicletSlicer.DefaultFontSizeInPt),\r\n };\r\n }\r\n\r\n private getHeaderHeight(): number {\r\n return TextMeasurementService.estimateSvgTextHeight(\r\n ChicletSlicer.getChicletTextProperties(this.settings.header.textSize));\r\n }\r\n\r\n private getRowHeight(): number {\r\n var textSettings = this.settings.slicerText;\r\n return textSettings.height !== 0\r\n ? textSettings.height\r\n : TextMeasurementService.estimateSvgTextHeight(ChicletSlicer.getChicletTextProperties(textSettings.textSize));\r\n }\r\n\r\n private getBorderStyle(outlineElement: string): string {\r\n return outlineElement === '0px' ? 'none' : 'solid';\r\n }\r\n\r\n private getBorderWidth(outlineElement: string, outlineWeight: number): string {\r\n switch (outlineElement) {\r\n case 'None':\r\n return '0px';\r\n case 'BottomOnly':\r\n return '0px 0px ' + outlineWeight + 'px 0px';\r\n case 'TopOnly':\r\n return outlineWeight + 'px 0px 0px 0px';\r\n case 'TopBottom':\r\n return outlineWeight + 'px 0px ' + outlineWeight + 'px 0px';\r\n case 'LeftRight':\r\n return '0px ' + outlineWeight + 'px 0px ' + outlineWeight + 'px';\r\n case 'Frame':\r\n return outlineWeight + 'px';\r\n default:\r\n return outlineElement.replace(\"1\", outlineWeight.toString());\r\n }\r\n }\r\n\r\n private getBorderRadius(borderType: string): string {\r\n switch (borderType) {\r\n case ChicletBorderStyle.ROUNDED:\r\n return \"10px\";\r\n case ChicletBorderStyle.SQUARE:\r\n return \"0px\";\r\n default:\r\n return \"5px\";\r\n }\r\n }\r\n }\r\n\r\n module ChicletSlicerChartConversion {\r\n export class ChicletSlicerConverter {\r\n private dataViewCategorical: DataViewCategorical;\r\n private dataViewMetadata: DataViewMetadata;\r\n private category: DataViewCategoryColumn;\r\n private categoryIdentities: DataViewScopeIdentity[];\r\n private categoryValues: any[];\r\n private categoryColumnRef: data.SQExpr[];\r\n private categoryFormatString: string;\r\n private interactivityService: IInteractivityService;\r\n\r\n public numberOfCategoriesSelectedInData: number;\r\n public dataPoints: ChicletSlicerDataPoint[];\r\n public hasSelectionOverride: boolean;\r\n\r\n public constructor(dataView: DataView, interactivityService: IInteractivityService) {\r\n\r\n var dataViewCategorical = dataView.categorical;\r\n this.dataViewCategorical = dataViewCategorical;\r\n this.dataViewMetadata = dataView.metadata;\r\n\r\n if (dataViewCategorical.categories && dataViewCategorical.categories.length > 0) {\r\n this.category = dataViewCategorical.categories[0];\r\n this.categoryIdentities = this.category.identity;\r\n this.categoryValues = this.category.values;\r\n this.categoryColumnRef = <data.SQExpr[]>this.category.identityFields;\r\n this.categoryFormatString = valueFormatter.getFormatString(this.category.source, chicletSlicerProps.formatString);\r\n }\r\n\r\n this.dataPoints = [];\r\n\r\n this.interactivityService = interactivityService;\r\n this.hasSelectionOverride = false;\r\n }\r\n\r\n public convert(): void {\r\n this.dataPoints = [];\r\n this.numberOfCategoriesSelectedInData = 0;\r\n // If category exists, we render labels using category values. If not, we render labels\r\n // using measure labels.\r\n if (this.categoryValues) {\r\n var objects = this.dataViewMetadata ? <any>this.dataViewMetadata.objects : undefined;\r\n\r\n var isInvertedSelectionMode = undefined;\r\n var numberOfScopeIds: number;\r\n if (objects && objects.general && objects.general.filter) {\r\n if (!this.categoryColumnRef)\r\n return;\r\n var filter = <SemanticFilter>objects.general.filter;\r\n var scopeIds = SQExprConverter.asScopeIdsContainer(filter, this.categoryColumnRef);\r\n if (scopeIds) {\r\n isInvertedSelectionMode = scopeIds.isNot;\r\n numberOfScopeIds = scopeIds.scopeIds ? scopeIds.scopeIds.length : 0;\r\n }\r\n else {\r\n isInvertedSelectionMode = false;\r\n }\r\n }\r\n\r\n if (this.interactivityService) {\r\n if (isInvertedSelectionMode === undefined) {\r\n // The selection state is read from the Interactivity service in case of SelectAll or Clear when query doesn't update the visual\r\n isInvertedSelectionMode = this.interactivityService.isSelectionModeInverted();\r\n }\r\n else {\r\n this.interactivityService.setSelectionModeInverted(isInvertedSelectionMode);\r\n }\r\n }\r\n\r\n var hasSelection: boolean = undefined;\r\n\r\n for (var idx = 0; idx < this.categoryValues.length; idx++) {\r\n var selected = isCategoryColumnSelected(chicletSlicerProps.selectedPropertyIdentifier, this.category, idx);\r\n if (selected != null) {\r\n hasSelection = selected;\r\n break;\r\n }\r\n }\r\n\r\n var dataViewCategorical = this.dataViewCategorical;\r\n var formatStringProp = chicletSlicerProps.formatString;\r\n var value: number = -Infinity;\r\n var imageURL: string = '';\r\n\r\n for (var categoryIndex: number = 0, categoryCount = this.categoryValues.length; categoryIndex < categoryCount; categoryIndex++) {\r\n //var categoryIdentity = this.category.identity ? this.category.identity[categoryIndex] : null;\r\n var categoryIsSelected = isCategoryColumnSelected(chicletSlicerProps.selectedPropertyIdentifier, this.category, categoryIndex);\r\n var selectable: boolean = true;\r\n\r\n if (hasSelection != null) {\r\n if (isInvertedSelectionMode) {\r\n if (this.category.objects == null)\r\n categoryIsSelected = undefined;\r\n\r\n if (categoryIsSelected != null) {\r\n categoryIsSelected = hasSelection;\r\n }\r\n else if (categoryIsSelected == null)\r\n categoryIsSelected = !hasSelection;\r\n }\r\n else {\r\n if (categoryIsSelected == null) {\r\n categoryIsSelected = !hasSelection;\r\n }\r\n }\r\n }\r\n\r\n if (categoryIsSelected) {\r\n this.numberOfCategoriesSelectedInData++;\r\n }\r\n\r\n var categoryValue = this.categoryValues[categoryIndex];\r\n var categoryLabel = valueFormatter.format(categoryValue, this.categoryFormatString);\r\n\r\n if (this.dataViewCategorical.values) {\r\n\r\n // Series are either measures in the multi-measure case, or the single series otherwise\r\n for (var seriesIndex: number = 0; seriesIndex < this.dataViewCategorical.values.length; seriesIndex++) {\r\n var seriesData = dataViewCategorical.values[seriesIndex];\r\n if (seriesData.values[categoryIndex] != null) {\r\n value = seriesData.values[categoryIndex];\r\n if (seriesData.highlights) {\r\n selectable = !(seriesData.highlights[categoryIndex] === null);\r\n }\r\n if (seriesData.source.groupName && seriesData.source.groupName !== '') {\r\n imageURL = converterHelper.getFormattedLegendLabel(seriesData.source, dataViewCategorical.values, formatStringProp);\r\n if (!/^(ftp|http|https):\\/\\/[^ \"]+$/.test(imageURL)) {\r\n imageURL = undefined;\r\n }\r\n }\r\n }\r\n }\r\n }\r\n var categorySelectionId: SelectionId = SelectionIdBuilder.builder().withCategory(this.category, categoryIndex).createSelectionId();\r\n this.dataPoints.push({\r\n identity: categorySelectionId,\r\n category: categoryLabel,\r\n imageURL: imageURL,\r\n value: value,\r\n selected: categoryIsSelected,\r\n selectable: selectable\r\n });\r\n }\r\n if (numberOfScopeIds != null && numberOfScopeIds > this.numberOfCategoriesSelectedInData) {\r\n this.hasSelectionOverride = true;\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n //TODO: This module should be removed once TextMeasruementService exports the \"estimateSvgTextBaselineDelta\" function.\r\n export module ChicletSlicerTextMeasurementHelper {\r\n interface CanvasContext {\r\n font: string;\r\n measureText(text: string): { width: number };\r\n }\r\n\r\n interface CanvasElement extends HTMLElement {\r\n getContext(name: string);\r\n }\r\n\r\n var spanElement: JQuery;\r\n var svgTextElement: D3.Selection;\r\n var canvasCtx: CanvasContext;\r\n\r\n export function estimateSvgTextBaselineDelta(textProperties: TextProperties): number {\r\n var rect = estimateSvgTextRect(textProperties);\r\n return rect.y + rect.height;\r\n }\r\n\r\n function ensureDOM(): void {\r\n if (spanElement)\r\n return;\r\n\r\n spanElement = $('<span/>');\r\n $('body').append(spanElement);\r\n //The style hides the svg element from the canvas, preventing canvas from scrolling down to show svg black square.\r\n svgTextElement = d3.select($('body').get(0))\r\n .append('svg')\r\n .style({\r\n 'height': '0px',\r\n 'width': '0px',\r\n 'position': 'absolute'\r\n })\r\n .append('text');\r\n canvasCtx = (<CanvasElement>$('<canvas/>').get(0)).getContext(\"2d\");\r\n }\r\n\r\n function measureSvgTextRect(textProperties: TextProperties): SVGRect {\r\n debug.assertValue(textProperties, 'textProperties');\r\n\r\n ensureDOM();\r\n\r\n svgTextElement.style(null);\r\n svgTextElement\r\n .text(textProperties.text)\r\n .attr({\r\n 'visibility': 'hidden',\r\n 'font-family': textProperties.fontFamily,\r\n 'font-size': textProperties.fontSize,\r\n 'font-weight': textProperties.fontWeight,\r\n 'font-style': textProperties.fontStyle,\r\n 'white-space': textProperties.whiteSpace || 'nowrap'\r\n });\r\n\r\n // We're expecting the browser to give a synchronous measurement here\r\n // We're using SVGTextElement because it works across all browsers\r\n return svgTextElement.node<SVGTextElement>().getBBox();\r\n }\r\n\r\n function estimateSvgTextRect(textProperties: TextProperties): SVGRect {\r\n debug.assertValue(textProperties, 'textProperties');\r\n\r\n var estimatedTextProperties: TextProperties = {\r\n fontFamily: textProperties.fontFamily,\r\n fontSize: textProperties.fontSize,\r\n text: \"M\",\r\n };\r\n\r\n var rect = measureSvgTextRect(estimatedTextProperties);\r\n\r\n return rect;\r\n }\r\n }\r\n\r\n export interface ChicletSlicerBehaviorOptions {\r\n slicerItemContainers: D3.Selection;\r\n slicerItemLabels: D3.Selection;\r\n slicerItemInputs: D3.Selection;\r\n slicerClear: D3.Selection;\r\n dataPoints: ChicletSlicerDataPoint[];\r\n interactivityService: IInteractivityService;\r\n slicerSettings: ChicletSlicerSettings;\r\n isSelectionLoaded: boolean;\r\n }\r\n\r\n export class ChicletSlicerWebBehavior implements IInteractiveBehavior {\r\n private slicers: D3.Selection;\r\n private slicerItemLabels: D3.Selection;\r\n private slicerItemInputs: D3.Selection;\r\n private dataPoints: ChicletSlicerDataPoint[];\r\n private interactivityService: IInteractivityService;\r\n private slicerSettings: ChicletSlicerSettings;\r\n private options: ChicletSlicerBehaviorOptions;\r\n\r\n public bindEvents(options: ChicletSlicerBehaviorOptions, selectionHandler: ISelectionHandler): void {\r\n var slicers = this.slicers = options.slicerItemContainers;\r\n this.slicerItemLabels = options.slicerItemLabels;\r\n this.slicerItemInputs = options.slicerItemInputs;\r\n var slicerClear = options.slicerClear;\r\n this.dataPoints = options.dataPoints;\r\n this.interactivityService = options.interactivityService;\r\n this.slicerSettings = options.slicerSettings;\r\n this.options = options;\r\n\r\n if (!this.options.isSelectionLoaded) {\r\n this.loadSelection(selectionHandler);\r\n }\r\n\r\n slicers.on(\"mouseover\", (d: ChicletSlicerDataPoint) => {\r\n if (d.selectable) {\r\n d.mouseOver = true;\r\n d.mouseOut = false;\r\n this.renderMouseover();\r\n }\r\n });\r\n\r\n slicers.on(\"mouseout\", (d: ChicletSlicerDataPoint) => {\r\n if (d.selectable) {\r\n d.mouseOver = false;\r\n d.mouseOut = true;\r\n this.renderMouseover();\r\n }\r\n });\r\n\r\n slicers.on(\"click\", (d: ChicletSlicerDataPoint, index) => {\r\n if (!d.selectable) {\r\n return;\r\n }\r\n var settings: ChicletSlicerSettings = this.slicerSettings;\r\n d3.event.preventDefault();\r\n if (d3.event.altKey && settings.general.multiselect) {\r\n var selectedIndexes = jQuery.map(this.dataPoints, function (d, index) { if (d.selected) return index; });\r\n var selIndex = selectedIndexes.length > 0 ? (selectedIndexes[selectedIndexes.length - 1]) : 0;\r\n if (selIndex > index) {\r\n var temp = index;\r\n index = selIndex;\r\n selIndex = temp;\r\n }\r\n selectionHandler.handleClearSelection();\r\n for (var i = selIndex; i <= index; i++) {\r\n selectionHandler.handleSelection(this.dataPoints[i], true /* isMultiSelect */);\r\n }\r\n }\r\n else if (d3.event.ctrlKey && settings.general.multiselect) {\r\n selectionHandler.handleSelection(d, true /* isMultiSelect */);\r\n }\r\n else {\r\n selectionHandler.handleSelection(d, false /* isMultiSelect */);\r\n }\r\n this.saveSelection(selectionHandler);\r\n });\r\n\r\n slicerClear.on(\"click\", (d: SelectableDataPoint) => {\r\n selectionHandler.handleClearSelection();\r\n this.saveSelection(selectionHandler);\r\n });\r\n }\r\n\r\n public loadSelection(selectionHandler: ISelectionHandler): void {\r\n selectionHandler.handleClearSelection();\r\n var savedSelectionIds = this.slicerSettings.general.getSavedSelection();\r\n if (savedSelectionIds.length) {\r\n var selectedDataPoints = this.dataPoints.filter(d => savedSelectionIds.some(x => d.identity.getKey() === x));\r\n selectedDataPoints.forEach(x => selectionHandler.handleSelection(x, true));\r\n selectionHandler.persistSelectionFilter(chicletSlicerProps.filterPropertyIdentifier);\r\n }\r\n }\r\n\r\n private static getFilterFromSelectors(selectionHandler: ISelectionHandler, isSelectionModeInverted: boolean): SemanticFilter {\r\n var selectors: data.Selector[] = [];\r\n var selectedIds: SelectionId[] = <SelectionId[]>(<any>selectionHandler).selectedIds;\r\n\r\n if (selectedIds.length > 0) {\r\n selectors = _.chain(selectedIds)\r\n .filter((value: SelectionId) => value.hasIdentity())\r\n .map((value: SelectionId) => value.getSelector())\r\n .value();\r\n }\r\n\r\n var filter: SemanticFilter = powerbi.data.Selector.filterFromSelector(selectors, isSelectionModeInverted);\r\n return filter;\r\n }\r\n\r\n public saveSelection(selectionHandler: ISelectionHandler): void {\r\n var filter: SemanticFilter = ChicletSlicerWebBehavior.getFilterFromSelectors(selectionHandler, this.interactivityService.isSelectionModeInverted());\r\n var selectionIdKeys = (<SelectionId[]>(<any>selectionHandler).selectedIds).map(x => x.getKey());\r\n this.slicerSettings.general.setSavedSelection(filter, selectionIdKeys);\r\n }\r\n\r\n public renderSelection(hasSelection: boolean): void {\r\n if (!hasSelection && !this.interactivityService.isSelectionModeInverted()) {\r\n this.slicers.style('background', this.slicerSettings.slicerText.unselectedColor);\r\n }\r\n else {\r\n this.styleSlicerInputs(this.slicers, hasSelection);\r\n }\r\n }\r\n\r\n private renderMouseover(): void {\r\n this.slicerItemLabels.style({\r\n 'color': (d: ChicletSlicerDataPoint) => {\r\n if (d.mouseOver)\r\n return this.slicerSettings.slicerText.hoverColor;\r\n\r\n if (d.mouseOut) {\r\n if (d.selected)\r\n return this.slicerSettings.slicerText.fontColor;\r\n else\r\n return this.slicerSettings.slicerText.fontColor;\r\n }\r\n }\r\n });\r\n }\r\n\r\n public styleSlicerInputs(slicers: D3.Selection, hasSelection: boolean) {\r\n var settings = this.slicerSettings;\r\n var selectedItems = [];\r\n slicers.each(function (d: ChicletSlicerDataPoint) {\r\n // get selected items\r\n if (d.selectable && d.selected) {\r\n selectedItems.push(d);\r\n }\r\n\r\n d3.select(this).style({\r\n 'background': d.selectable ? (d.selected ? settings.slicerText.selectedColor : settings.slicerText.unselectedColor)\r\n : settings.slicerText.disabledColor\r\n });\r\n d3.select(this).classed('slicerItem-disabled', !d.selectable);\r\n });\r\n }\r\n }\r\n\r\n module explore.util {\r\n export function hexToRGBString(hex: string, transparency?: number): string {\r\n\r\n // Expand shorthand form (e.g. \"03F\") to full form (e.g. \"0033FF\")\r\n var shorthandRegex = /^#?([a-f\\d])([a-f\\d])([a-f\\d])$/i;\r\n hex = hex.replace(shorthandRegex, function (m, r, g, b) {\r\n return r + r + g + g + b + b;\r\n });\r\n\r\n // Hex format which return the format r-g-b\r\n var result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\r\n\r\n var rgb = result ? {\r\n r: parseInt(result[1], 16),\r\n g: parseInt(result[2], 16),\r\n b: parseInt(result[3], 16)\r\n } : null;\r\n\r\n // Wrong input\r\n if (rgb === null) {\r\n return '';\r\n }\r\n\r\n if (!transparency && transparency !== 0) {\r\n return \"rgb(\" + rgb.r + \",\" + rgb.g + \",\" + rgb.b + \")\";\r\n }\r\n else {\r\n return \"rgba(\" + rgb.r + \",\" + rgb.g + \",\" + rgb.b + \",\" + transparency + \")\";\r\n }\r\n }\r\n }\r\n}\r\n\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/CustomVisuals/visuals/chicletSlicer/visual/chicletSlicer.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 ClassAndSelector = jsCommon.CssConstants.ClassAndSelector;\r\n import PixelConverter = jsCommon.PixelConverter;\r\n\r\n export interface ChordChartData {\r\n settings: ChordChartSettings;\r\n dataView: DataView;\r\n dataMatrix: number[][];\r\n labelDataPoints: ChordArcDescriptor[];\r\n legendData?: LegendData;\r\n tooltipData: ChordTooltipData[][];\r\n sliceTooltipData: ChordTooltipData[];\r\n tickUnit: number;\r\n differentFromTo: boolean;\r\n defaultDataPointColor?: string;\r\n prevAxisVisible: boolean;\r\n }\r\n\r\n export interface ChordArcDescriptor extends D3.Layout.ArcDescriptor, IDataLabelInfo {\r\n data: ChordArcLabelData;\r\n }\r\n\r\n export interface ChordTicksArcDescriptor extends D3.Layout.ArcDescriptor {\r\n angleLabels: { angle: number, label: string }[];\r\n }\r\n\r\n export interface ChordArcLabelData extends LabelEnabledDataPoint, SelectableDataPoint {\r\n label: string;\r\n labelColor: string;\r\n barColor: string;\r\n isCategory: boolean;\r\n }\r\n\r\n export interface ChordTooltipData {\r\n tooltipInfo: TooltipDataItem[];\r\n }\r\n\r\n class VisualLayout {\r\n private marginValue: IMargin;\r\n private viewportValue: IViewport;\r\n private viewportInValue: IViewport;\r\n private minViewportValue: IViewport;\r\n\r\n public defaultMargin: IMargin;\r\n public defaultViewport: IViewport;\r\n\r\n constructor(defaultViewport?: IViewport, defaultMargin?: IMargin) {\r\n this.defaultViewport = defaultViewport || { width: 0, height: 0 };\r\n this.defaultMargin = defaultMargin || { top: 0, bottom: 0, right: 0, left: 0 };\r\n }\r\n\r\n public get margin(): IMargin {\r\n return this.marginValue || (this.margin = this.defaultMargin);\r\n }\r\n\r\n public set margin(value: IMargin) {\r\n this.marginValue = VisualLayout.restrictToMinMax(value);\r\n this.update();\r\n }\r\n\r\n public get viewport(): IViewport {\r\n return this.viewportValue || (this.viewportValue = this.defaultViewport);\r\n }\r\n\r\n public set viewport(value: IViewport) {\r\n this.viewportValue = VisualLayout.restrictToMinMax(value, this.minViewport);\r\n this.update();\r\n }\r\n\r\n public get viewportIn(): IViewport {\r\n return this.viewportInValue || this.viewport;\r\n }\r\n\r\n public get minViewport(): IViewport {\r\n return this.minViewportValue;\r\n }\r\n\r\n public set minViewport(value: IViewport) {\r\n this.minViewportValue = value;\r\n }\r\n\r\n public get viewportInIsZero(): boolean {\r\n return this.viewportIn.width === 0 || this.viewportIn.height === 0;\r\n }\r\n\r\n public resetMargin(): void {\r\n this.margin = this.defaultMargin;\r\n }\r\n\r\n private update(): void {\r\n this.viewportInValue = VisualLayout.restrictToMinMax({\r\n width: this.viewport.width - (this.margin.left + this.margin.right),\r\n height: this.viewport.height - (this.margin.top + this.margin.bottom)\r\n }, this.minViewportValue);\r\n }\r\n\r\n private static restrictToMinMax<T>(value: T, minValue?: T): T {\r\n var result = $.extend({}, value);\r\n _.keys(value).forEach(x => result[x] = Math.max(minValue && minValue[x] || 0, value[x]));\r\n return result;\r\n }\r\n }\r\n\r\n class ChordChartHelpers {\r\n public static interpolateArc(arc: D3.Svg.Arc) {\r\n return function (data) {\r\n if (!this.oldData) {\r\n this.oldData = data;\r\n return () => arc(data);\r\n }\r\n\r\n var interpolation = d3.interpolate(this.oldData, data);\r\n this.oldData = interpolation(0);\r\n return (x) => arc(interpolation(x));\r\n };\r\n }\r\n\r\n public static addContext(context: any, fn: Function): any {\r\n return <any>function() {\r\n return fn.apply(context, [this].concat(_.toArray(arguments)));\r\n };\r\n }\r\n }\r\n\r\n export class ChordChartSettings {\r\n public static get Default() { \r\n return new this();\r\n }\r\n\r\n public static parse(dataView: DataView, capabilities: VisualCapabilities) {\r\n var settings = new this();\r\n if(!dataView || !dataView.metadata || !dataView.metadata.objects) {\r\n return settings;\r\n }\r\n\r\n var properties = this.getProperties(capabilities);\r\n for(var objectKey in capabilities.objects) {\r\n for(var propKey in capabilities.objects[objectKey].properties) {\r\n if(!settings[objectKey] || !_.has(settings[objectKey], propKey)) {\r\n continue;\r\n }\r\n\r\n var type = capabilities.objects[objectKey].properties[propKey].type;\r\n var getValueFn = this.getValueFnByType(type);\r\n settings[objectKey][propKey] = getValueFn(\r\n dataView.metadata.objects,\r\n properties[objectKey][propKey],\r\n settings[objectKey][propKey]);\r\n }\r\n }\r\n\r\n return settings;\r\n }\r\n\r\n public static getProperties(capabilities: VisualCapabilities)\r\n : { [i: string]: { [i: string]: DataViewObjectPropertyIdentifier } } & { \r\n general: { formatString: DataViewObjectPropertyIdentifier },\r\n dataPoint: { fill: DataViewObjectPropertyIdentifier } } {\r\n var objects = _.merge({ \r\n general: { properties: { formatString: {} } } \r\n }, capabilities.objects);\r\n var properties = <any>{};\r\n for(var objectKey in objects) {\r\n properties[objectKey] = {};\r\n for(var propKey in objects[objectKey].properties) {\r\n properties[objectKey][propKey] = <DataViewObjectPropertyIdentifier> {\r\n objectName: objectKey,\r\n propertyName: propKey\r\n };\r\n }\r\n }\r\n\r\n return properties;\r\n }\r\n\r\n public static createEnumTypeFromEnum(type: any): IEnumType {\r\n var even: any = false;\r\n return createEnumType(Object.keys(type)\r\n .filter((key,i) => ((!!(i % 2)) === even && type[key] === key\r\n && !void(even = !even)) || (!!(i % 2)) !== even)\r\n .map(x => <IEnumMember>{ value: x, displayName: x }));\r\n }\r\n\r\n private static getValueFnByType(type: powerbi.data.DataViewObjectPropertyTypeDescriptor) {\r\n switch(_.keys(type)[0]) {\r\n case 'fill': \r\n return DataViewObjects.getFillColor;\r\n default:\r\n return DataViewObjects.getValue;\r\n }\r\n }\r\n\r\n public static enumerateObjectInstances(\r\n settings: any,\r\n options: EnumerateVisualObjectInstancesOptions,\r\n capabilities: VisualCapabilities): ObjectEnumerationBuilder {\r\n\r\n var enumeration = new ObjectEnumerationBuilder();\r\n var object = settings && settings[options.objectName];\r\n if(!object) {\r\n return enumeration;\r\n }\r\n\r\n var instance = <VisualObjectInstance>{\r\n objectName: options.objectName,\r\n selector: null,\r\n properties: {}\r\n };\r\n\r\n for(var key in object) {\r\n if(_.has(object,key)) {\r\n instance.properties[key] = object[key];\r\n }\r\n }\r\n\r\n enumeration.pushInstance(instance);\r\n return enumeration;\r\n }\r\n\r\n //Default Settings\r\n public dataPoint = {\r\n defaultColor: null,\r\n showAllDataPoints: false\r\n };\r\n public axis = {\r\n show: true\r\n };\r\n public labels = {\r\n show: true,\r\n color: dataLabelUtils.defaultLabelColor,\r\n fontSize: dataLabelUtils.DefaultFontSizeInPt\r\n };\r\n }\r\n\r\n export class ChordChartColumns<T> {\r\n public static Roles = Object.freeze(\r\n _.mapValues(new ChordChartColumns<string>(), (x, i) => i));\r\n\r\n public static getColumnSources(dataView: DataView) {\r\n return this.getColumnSourcesT<DataViewMetadataColumn>(dataView);\r\n }\r\n\r\n public static getTableValues(dataView: DataView) {\r\n var table = dataView && dataView.table;\r\n var columns = this.getColumnSourcesT<any[]>(dataView);\r\n return columns && table && _.mapValues(\r\n columns, (n: DataViewMetadataColumn, i) => n && table.rows.map(row => row[n.index]));\r\n }\r\n\r\n public static getTableRows(dataView: DataView) {\r\n var table = dataView && dataView.table;\r\n var columns = this.getColumnSourcesT<any[]>(dataView);\r\n return columns && table && table.rows.map(row =>\r\n _.mapValues(columns, (n: DataViewMetadataColumn, i) => n && row[n.index]));\r\n }\r\n\r\n public static getCategoricalValues(dataView: DataView) {\r\n var categorical = dataView && dataView.categorical;\r\n var categories = categorical && categorical.categories || [];\r\n var values = categorical && categorical.values || <DataViewValueColumns>[];\r\n var series: string[] = categorical && values.source && this.getSeriesValues(dataView);\r\n return categorical && _.mapValues(new this<any[]>(), (n, i) =>\r\n (<DataViewCategoricalColumn[]>_.toArray(categories)).concat(_.toArray(values))\r\n .filter(x => x.source.roles && x.source.roles[i]).map(x => x.values)[0]\r\n || values.source && values.source.roles && values.source.roles[i] && series);\r\n }\r\n\r\n public static getSeriesValues(dataView: DataView) {\r\n return dataView && dataView.categorical && dataView.categorical.values\r\n && dataView.categorical.values.map(x => converterHelper.getSeriesName(x.source));\r\n }\r\n\r\n public static getCategoricalColumns(dataView: DataView) {\r\n var categorical = dataView && dataView.categorical;\r\n var categories = categorical && categorical.categories || [];\r\n var values = categorical && categorical.values || <DataViewValueColumns>[];\r\n return categorical && _.mapValues(\r\n new this<DataViewCategoryColumn & DataViewValueColumn[] & DataViewValueColumns>(),\r\n (n, i) => categories.filter(x => x.source.roles && x.source.roles[i])[0]\r\n || values.source && values.source.roles && values.source.roles[i]\r\n || values.filter(x => x.source.roles && x.source.roles[i]));\r\n }\r\n\r\n private static getColumnSourcesT<T>(dataView: DataView) {\r\n var columns = dataView && dataView.metadata && dataView.metadata.columns;\r\n return columns && _.mapValues(\r\n new this<T>(), (n, i) => columns.filter(x => x.roles && x.roles[i])[0]);\r\n }\r\n\r\n //Data Roles\r\n public Category: T = null;\r\n public Series: T = null;\r\n public Y: T = null;\r\n }\r\n\r\n export class ChordChart implements IVisual {\r\n public static capabilities: VisualCapabilities = {\r\n dataRoles: [\r\n {\r\n name: ChordChartColumns.Roles.Category,\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: 'From',\r\n }, {\r\n name: ChordChartColumns.Roles.Series,\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: 'To',\r\n }, {\r\n name: ChordChartColumns.Roles.Y,\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: \"Values\",\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 { 'Category': { max: 1 }, 'Series': { max: 0 }, 'Y': { min: 0, 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 objects: {\r\n dataPoint: {\r\n displayName: data.createDisplayNameGetter('Visual_DataPoint'),\r\n properties: {\r\n defaultColor: {\r\n displayName: data.createDisplayNameGetter('Visual_DefaultColor'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n showAllDataPoints: {\r\n displayName: data.createDisplayNameGetter('Visual_DataPoint_Show_All'),\r\n type: { bool: true }\r\n },\r\n fill: {\r\n displayName: data.createDisplayNameGetter('Visual_Fill'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n },\r\n },\r\n axis: {\r\n displayName: 'Axis',\r\n properties: {\r\n show: {\r\n type: { bool: true }\r\n },\r\n },\r\n },\r\n labels: {\r\n displayName: 'Labels',\r\n properties: {\r\n show: {\r\n type: { bool: true }\r\n },\r\n color: {\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 fontSize: {\r\n displayName: data.createDisplayNameGetter('Visual_TextSize'),\r\n type: { formatting: { fontSize: true } },\r\n },\r\n },\r\n }\r\n }\r\n };\r\n\r\n public static PolylineOpacity = 0.5;\r\n\r\n private static OuterArcRadiusRatio = 0.9;\r\n private static InnerArcRadiusRatio = 0.8;\r\n private static LabelMargin = 10;\r\n private static DefaultMargin: IMargin = { left: 10, right: 10, top: 10, bottom: 10 };\r\n private static VisualClassName = 'chordChart';\r\n private static TicksFontSize = 12;\r\n\r\n private static sliceClass: ClassAndSelector = {\r\n class: 'slice',\r\n selector: '.slice',\r\n };\r\n\r\n private static chordClass: ClassAndSelector = {\r\n class: 'chord',\r\n selector: '.chord',\r\n };\r\n\r\n private static sliceTicksClass: ClassAndSelector = {\r\n class: 'slice-ticks',\r\n selector: '.slice-ticks'\r\n };\r\n\r\n private static tickPairClass: ClassAndSelector = {\r\n class: 'tick-pair',\r\n selector: '.tick-pair'\r\n };\r\n\r\n private static tickLineClass: ClassAndSelector = {\r\n class: 'tick-line',\r\n selector: '.tick-line'\r\n };\r\n\r\n private static tickTextClass: ClassAndSelector = {\r\n class: 'tick-text',\r\n selector: '.tick-text'\r\n };\r\n\r\n private static labelGraphicsContextClass: ClassAndSelector = {\r\n class: 'labels',\r\n selector: '.labels',\r\n };\r\n\r\n private static labelsClass: ClassAndSelector = {\r\n class: 'data-labels',\r\n selector: '.data-labels',\r\n };\r\n\r\n private static linesGraphicsContextClass: ClassAndSelector = {\r\n class: 'lines',\r\n selector: '.lines',\r\n };\r\n\r\n private static lineClass: ClassAndSelector = {\r\n class: 'line-label',\r\n selector: '.line-label',\r\n };\r\n\r\n private chordLayout: D3.Layout.ChordLayout;\r\n private element: JQuery;\r\n\r\n private svg: D3.Selection;\r\n private mainGraphicsContext: D3.Selection;\r\n private slices: D3.Selection;\r\n private labels: D3.Selection;\r\n private lines: D3.Selection;\r\n\r\n private data: ChordChartData;\r\n private get settings(): ChordChartSettings {\r\n return this.data && this.data.settings;\r\n }\r\n private layout: VisualLayout;\r\n private duration: number;\r\n private colors: IDataColorPalette;\r\n private selectionManager: utility.SelectionManager;\r\n\r\n private radius: number;\r\n private get innerRadius(): number {\r\n return this.radius * ChordChart.InnerArcRadiusRatio;\r\n }\r\n private get outerRadius(): number {\r\n return this.radius * ChordChart.OuterArcRadiusRatio;\r\n }\r\n\r\n /* Convert a DataView into a view model */\r\n public static converter(dataView: DataView, colors: IDataColorPalette, prevAxisVisible: boolean): ChordChartData {\r\n var properties = ChordChartSettings.getProperties(ChordChart.capabilities);\r\n var settings = ChordChart.parseSettings(dataView);\r\n var columns = ChordChartColumns.getCategoricalColumns(dataView);\r\n var sources = ChordChartColumns.getColumnSources(dataView);\r\n var catValues = ChordChartColumns.getCategoricalValues(dataView);\r\n\r\n if (!catValues || _.isEmpty(catValues.Category) || _.isEmpty(catValues.Y)) {\r\n return null;\r\n }\r\n\r\n catValues.Series = catValues.Series || ChordChartColumns.getSeriesValues(dataView);\r\n\r\n var dataMatrix: number[][] = [];\r\n var legendData: LegendData = {\r\n dataPoints: [],\r\n title: sources.Y.displayName || \"\",\r\n };\r\n var toolTipData: ChordTooltipData[][] = [];\r\n var sliceTooltipData: ChordTooltipData[] = [];\r\n var max: number = 1000;\r\n var seriesIndex: number[] = _.mapValues(_.invert(catValues.Series), parseFloat); /* series index array */\r\n var catIndex: number[] = _.mapValues(_.invert(catValues.Category), parseFloat); /* index array for category names */\r\n var isDiffFromTo: boolean = false; /* boolean variable indicates that From and To are different */\r\n var labelData: ChordArcLabelData[] = []; /* label data: !important */\r\n var colorHelper = new ColorHelper(colors, properties.dataPoint.fill, settings.dataPoint.defaultColor);\r\n var totalFields: any[] = this.union_arrays(catValues.Category, catValues.Series).reverse();\r\n\r\n if (ChordChart.getValidArrayLength(totalFields) ===\r\n ChordChart.getValidArrayLength(catValues.Category) + ChordChart.getValidArrayLength(catValues.Series)) {\r\n isDiffFromTo = true;\r\n }\r\n\r\n var categoryColumnFormatter = valueFormatter.create({\r\n format: valueFormatter.getFormatString(sources.Category, properties.general.formatString, true)\r\n || sources.Category.format\r\n });\r\n var valueColumnFormatter = valueFormatter.create({\r\n format: valueFormatter.getFormatString(sources.Y, properties.general.formatString, true)\r\n || sources.Y.format\r\n });\r\n\r\n for (var i: number = 0, iLen = totalFields.length; i < iLen; i++) {\r\n var id: SelectionId = null;\r\n var color: string = '';\r\n var isCategory: boolean = false;\r\n\r\n if (catIndex[totalFields[i]] !== undefined) {\r\n var index = catIndex[totalFields[i]];\r\n id = SelectionIdBuilder\r\n .builder()\r\n .withCategory(columns.Category, catIndex[totalFields[i]])\r\n .createSelectionId();\r\n isCategory = true;\r\n var thisCategoryObjects = columns.Category.objects ? columns.Category.objects[index] : undefined;\r\n\r\n color = colorHelper.getColorForSeriesValue(thisCategoryObjects, /* cat.identityFields */ undefined, catValues.Category[index]);\r\n\r\n } else if (seriesIndex[totalFields[i]] !== undefined) {\r\n var index = seriesIndex[totalFields[i]];\r\n\r\n var seriesData = columns.Y[index];\r\n var seriesObjects = seriesData && seriesData.objects && seriesData.objects[0];\r\n var seriesNameStr = converterHelper.getSeriesName(seriesData.source);\r\n\r\n id = SelectionId.createWithId(seriesData.identity);\r\n isCategory = false;\r\n\r\n color = colorHelper.getColorForSeriesValue(seriesObjects, /* values.identityFields */ undefined, seriesNameStr);\r\n }\r\n\r\n labelData.push({\r\n label: totalFields[i],\r\n labelColor: settings.labels.color,\r\n barColor: color,\r\n isCategory: isCategory,\r\n identity: id,\r\n selected: false,\r\n labelFontSize: PixelConverter.fromPointToPixel(settings.labels.fontSize)\r\n });\r\n\r\n dataMatrix.push([]);\r\n toolTipData.push([]);\r\n\r\n for (var j = 0, jLen = totalFields.length; j < jLen; j++) {\r\n var elementValue: number = 0;\r\n var tooltipInfo: TooltipDataItem[] = [];\r\n\r\n if (catIndex[totalFields[i]] !== undefined &&\r\n seriesIndex[totalFields[j]] !== undefined) {\r\n var row: number = catIndex[totalFields[i]];\r\n var col: number = seriesIndex[totalFields[j]];\r\n if (columns.Y[col].values[row] !== null) {\r\n elementValue = columns.Y[col].values[row];\r\n\r\n if (elementValue > max)\r\n max = elementValue;\r\n\r\n tooltipInfo = TooltipBuilder.createTooltipInfo(\r\n properties.general.formatString,\r\n dataView.categorical,\r\n categoryColumnFormatter.format(catValues.Category[i]),\r\n valueColumnFormatter.format(elementValue),\r\n null,\r\n null,\r\n col,\r\n row);\r\n }\r\n } else if (isDiffFromTo && catIndex[totalFields[j]] !== undefined &&\r\n seriesIndex[totalFields[i]] !== undefined) {\r\n var row: number = catIndex[totalFields[j]];\r\n var col: number = seriesIndex[totalFields[i]];\r\n if (columns.Y[col].values[row] !== null) {\r\n elementValue = columns.Y[col].values[row];\r\n }\r\n }\r\n\r\n dataMatrix[i].push(elementValue || 0);\r\n toolTipData[i].push({\r\n tooltipInfo: tooltipInfo\r\n });\r\n }\r\n\r\n var totalSum = d3.sum(dataMatrix[i]);\r\n\r\n sliceTooltipData.push({\r\n tooltipInfo: [{\r\n displayName: totalFields[i],\r\n value: valueColumnFormatter.format(totalSum)\r\n }]\r\n });\r\n }\r\n\r\n var chordLayout = d3.layout.chord()\r\n .padding(0.1)\r\n .matrix(dataMatrix);\r\n\r\n var unitLength: number = Math.round(max / 5).toString().length - 1;\r\n\r\n return {\r\n dataMatrix: dataMatrix,\r\n dataView: dataView,\r\n settings: settings,\r\n labelDataPoints: ChordChart.getChordArcDescriptors(chordLayout.groups(), labelData),\r\n legendData: legendData,\r\n tooltipData: toolTipData,\r\n sliceTooltipData: sliceTooltipData,\r\n tickUnit: Math.pow(10, unitLength),\r\n differentFromTo: isDiffFromTo,\r\n prevAxisVisible: prevAxisVisible === undefined ? settings.axis.show : prevAxisVisible,\r\n };\r\n }\r\n\r\n private static parseSettings(dataView: DataView): ChordChartSettings {\r\n return ChordChartSettings.parse(dataView, ChordChart.capabilities);\r\n }\r\n\r\n /* Check every element of the array and returns the count of elements which are valid(not undefined) */\r\n private static getValidArrayLength(array: any[]): number {\r\n var len = 0;\r\n for (var i: number = 0, iLen = array.length; i < iLen; i++) {\r\n if (array[i] !== undefined) {\r\n len++;\r\n }\r\n }\r\n return len;\r\n }\r\n\r\n private static getChordArcDescriptors(groups: D3.Layout.ArcDescriptor[], datum: ChordArcLabelData[]): ChordArcDescriptor[] {\r\n var labelDataPoints: ChordArcDescriptor[] = groups;\r\n labelDataPoints.forEach((x,i) => x.data = datum[i]);\r\n return labelDataPoints;\r\n }\r\n\r\n public init(options: VisualInitOptions): void {\r\n var element = this.element = options.element;\r\n this.selectionManager = new utility.SelectionManager({ hostServices: options.host });\r\n this.layout = new VisualLayout(options.viewport, ChordChart.DefaultMargin);\r\n this.layout.minViewport = { width: 150, height:150 };\r\n\r\n this.svg = d3.select(element.get(0))\r\n .append('svg')\r\n .style('position', 'absolute')\r\n .classed(ChordChart.VisualClassName, true);\r\n\r\n this.mainGraphicsContext = this.svg\r\n .append('g');\r\n\r\n this.mainGraphicsContext\r\n .append('g')\r\n .classed('chords', true);\r\n\r\n this.slices = this.mainGraphicsContext\r\n .append('g')\r\n .classed('slices', true);\r\n\r\n this.mainGraphicsContext\r\n .append('g')\r\n .classed('ticks', true);\r\n\r\n this.labels = this.mainGraphicsContext\r\n .append('g')\r\n .classed(ChordChart.labelGraphicsContextClass.class, true);\r\n\r\n this.lines = this.mainGraphicsContext\r\n .append('g')\r\n .classed(ChordChart.linesGraphicsContextClass.class, true);\r\n\r\n this.colors = options.style.colorPalette.dataColors;\r\n }\r\n\r\n /* Called for data, size, formatting changes*/\r\n public update(options: VisualUpdateOptions) {\r\n // assert dataView\r\n if (!options.dataViews || !options.dataViews[0]) {\r\n return;\r\n }\r\n\r\n this.layout.viewport = options.viewport;\r\n this.duration = options.suppressAnimations ? 0 : AnimatorCommon.MinervaAnimationDuration;\r\n this.data = ChordChart.converter(options.dataViews[0], this.colors, this.settings && this.settings.axis.show);\r\n if(!this.data) {\r\n this.clear();\r\n return;\r\n }\r\n\r\n this.layout.resetMargin();\r\n this.layout.margin.top = this.layout.margin.bottom = PixelConverter.fromPointToPixel(this.settings.labels.fontSize) / 2;\r\n\r\n this.render();\r\n }\r\n\r\n /* Enumerate format values */\r\n public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions) {\r\n var instances = ChordChartSettings.enumerateObjectInstances(this.settings, options, ChordChart.capabilities);\r\n\r\n if (this.settings \r\n && options.objectName === \"dataPoint\"\r\n && !_.isEmpty(this.data.labelDataPoints)\r\n && this.settings.dataPoint.showAllDataPoints) {\r\n\r\n for (var i: number = 0, length = this.data.labelDataPoints.length; i < length; i++) {\r\n var labelDataPoint: ChordArcLabelData = this.data.labelDataPoints[i].data;\r\n\r\n if (labelDataPoint.isCategory) {\r\n var colorInstance: VisualObjectInstance = {\r\n objectName: 'dataPoint',\r\n displayName: labelDataPoint.label,\r\n selector: ColorHelper.normalizeSelector(labelDataPoint.identity.getSelector()),\r\n properties: {\r\n fill: { solid: { color: labelDataPoint.barColor } }\r\n }\r\n };\r\n\r\n instances.pushInstance(colorInstance);\r\n }\r\n }\r\n }\r\n\r\n return instances.complete();\r\n }\r\n\r\n /* Calculate radius */\r\n private calculateRadius(): number {\r\n if (this.settings.labels.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 var hw = this.layout.viewportIn.height / this.layout.viewportIn.width;\r\n var denom = 2 + (1 / (1 + Math.exp(-5 * (hw - 1))));\r\n return Math.min(this.layout.viewportIn.height, this.layout.viewportIn.width) / denom;\r\n }\r\n\r\n // no labels\r\n return Math.min(this.layout.viewportIn.height, this.layout.viewportIn.width) / 2;\r\n }\r\n\r\n private drawCategoryLabels(): void {\r\n /** Multiplier to place the end point of the reference line at 0.05 * radius away from the outer edge of the chord/pie. */\r\n\r\n var arc: D3.Svg.Arc = d3.svg.arc()\r\n .innerRadius(0)\r\n .outerRadius(this.innerRadius);\r\n\r\n var outerArc: D3.Svg.Arc = d3.svg.arc()\r\n .innerRadius(this.outerRadius)\r\n .outerRadius(this.outerRadius);\r\n\r\n if (this.settings.labels.show) {\r\n var labelLayout = this.getChordChartLabelLayout(outerArc);\r\n var filteredData = this.getDataLabelManager().hideCollidedLabels(\r\n this.layout.viewportIn,\r\n this.data.labelDataPoints,\r\n labelLayout,\r\n /* addTransform */ true);\r\n\r\n this.renderLabels(filteredData, labelLayout, true);\r\n this.renderLines(filteredData, arc, outerArc);\r\n }\r\n else {\r\n dataLabelUtils.cleanDataLabels(this.labels);\r\n dataLabelUtils.cleanDataLabels(this.lines, true);\r\n }\r\n }\r\n\r\n private getDataLabelManager(): DataLabelManager {\r\n var dataLabelManager = new DataLabelManager();\r\n (<any>dataLabelManager).hasCollisions = hasCollisions.bind(dataLabelManager);\r\n return dataLabelManager;\r\n\r\n function hasCollisions(arrangeGrid: DataLabelArrangeGrid, info: IDataLabelInfo, position: IRect, size: shapes.ISize) {\r\n if (arrangeGrid.hasConflict(position)) {\r\n return true;\r\n }\r\n\r\n var intersection = { left: 0, top: position.height / 2, width: size.width, height: size.height };\r\n intersection = shapes.Rect.inflate(intersection, {\r\n left: DataLabelManager.InflateAmount,\r\n top: 0,\r\n right: DataLabelManager.InflateAmount,\r\n bottom: 0 });\r\n intersection = shapes.Rect.intersect(intersection, position);\r\n\r\n if (shapes.Rect.isEmpty(intersection)) {\r\n return true;\r\n }\r\n\r\n return powerbi.Double.lessWithPrecision(intersection.height, position.height / 2);\r\n }\r\n }\r\n\r\n private render(): void {\r\n this.chordLayout = d3.layout.chord()\r\n .padding(0.1)\r\n //.sortGroups(d3.descending)\r\n .matrix(this.data.dataMatrix);\r\n\r\n this.radius = this.calculateRadius();\r\n\r\n var arc: D3.Svg.Arc = d3.svg.arc().innerRadius(this.radius).outerRadius(this.innerRadius);\r\n\r\n this.svg\r\n .attr({\r\n 'width': this.layout.viewport.width,\r\n 'height': this.layout.viewport.height\r\n });\r\n\r\n this.mainGraphicsContext\r\n .attr('transform', SVGUtil.translate(this.layout.viewport.width / 2, this.layout.viewport.height / 2));\r\n\r\n var sliceShapes = this.slices\r\n .selectAll('path' + ChordChart.sliceClass.selector)\r\n .data(this.getChordTicksArcDescriptors());\r\n\r\n sliceShapes.enter()\r\n .insert(\"path\")\r\n .classed(ChordChart.sliceClass.class, true);\r\n\r\n sliceShapes.style('fill', (d, i) => this.data.labelDataPoints[i].data.barColor)\r\n .style(\"stroke\", (d, i) => this.data.labelDataPoints[i].data.barColor)\r\n .on('click', ChordChartHelpers.addContext(this, (context, d, i) => {\r\n this.selectionManager.select(this.data.labelDataPoints[i].data.identity).then(ids=> {\r\n if (ids.length > 0) {\r\n this.mainGraphicsContext.selectAll(\".chords path.chord\")\r\n .style(\"opacity\", 1);\r\n\r\n this.slices.selectAll(\"path.slice\")\r\n .style('opacity', 0.3);\r\n\r\n this.mainGraphicsContext.selectAll(\".chords path.chord\")\r\n .filter(d => d.source.index !== i && d.target.index !== i)\r\n .style(\"opacity\", 0.3);\r\n\r\n d3.select(context).style('opacity', 1);\r\n } else {\r\n sliceShapes.style('opacity', 1);\r\n this.mainGraphicsContext.selectAll(\".chords path.chord\")\r\n .filter(d => d.source.index !== i && d.target.index !== i)\r\n .style(\"opacity\", 1);\r\n }\r\n });\r\n\r\n d3.event.stopPropagation();\r\n }))\r\n .transition()\r\n .duration(this.duration)\r\n .attrTween('d', ChordChartHelpers.interpolateArc(arc));\r\n\r\n sliceShapes.exit()\r\n .remove();\r\n\r\n TooltipManager.addTooltip(sliceShapes, (tooltipEvent: TooltipEvent) =>\r\n this.data.sliceTooltipData[tooltipEvent.data.index].tooltipInfo);\r\n\r\n var chordShapes = this.svg.select('.chords')\r\n .selectAll('path' + ChordChart.chordClass.selector)\r\n .data(this.chordLayout.chords);\r\n\r\n chordShapes\r\n .enter().insert(\"path\")\r\n .classed(ChordChart.chordClass.class, true);\r\n\r\n chordShapes.style(\"fill\", (d, i) => this.data.labelDataPoints[d.target.index].data.barColor)\r\n .style(\"opacity\", 1)\r\n .transition()\r\n .duration(this.duration)\r\n .attr(\"d\", d3.svg.chord().radius(this.radius));\r\n\r\n chordShapes.exit()\r\n .remove();\r\n\r\n this.svg\r\n .on('click', () => this.selectionManager.clear().then(() => {\r\n sliceShapes.style('opacity', 1);\r\n chordShapes.style('opacity', 1);\r\n }));\r\n\r\n this.drawTicks();\r\n this.drawCategoryLabels();\r\n\r\n TooltipManager.addTooltip(chordShapes, (tooltipEvent: TooltipEvent) => {\r\n var tooltipInfo: TooltipDataItem[] = [];\r\n if (this.data.differentFromTo) {\r\n tooltipInfo = this.data.tooltipData[tooltipEvent.data.source.index]\r\n [tooltipEvent.data.source.subindex]\r\n .tooltipInfo;\r\n } else {\r\n tooltipInfo.push({\r\n displayName: this.data.labelDataPoints[tooltipEvent.data.source.index].data.label\r\n + '->' + this.data.labelDataPoints[tooltipEvent.data.source.subindex].data.label,\r\n value: this.data.dataMatrix[tooltipEvent.data.source.index]\r\n [tooltipEvent.data.source.subindex].toString()\r\n });\r\n tooltipInfo.push({\r\n displayName: this.data.labelDataPoints[tooltipEvent.data.target.index].data.label\r\n + '->' + this.data.labelDataPoints[tooltipEvent.data.target.subindex].data.label,\r\n value: this.data.dataMatrix[tooltipEvent.data.target.index]\r\n [tooltipEvent.data.target.subindex].toString()\r\n });\r\n }\r\n return tooltipInfo;\r\n });\r\n }\r\n\r\n private clear() {\r\n this.mainGraphicsContext.selectAll(ChordChart.sliceClass.selector).remove();\r\n this.mainGraphicsContext.selectAll(ChordChart.sliceTicksClass.selector).remove();\r\n this.mainGraphicsContext.selectAll(ChordChart.chordClass.selector).remove();\r\n this.mainGraphicsContext.selectAll(ChordChart.labelsClass.selector).remove();\r\n this.mainGraphicsContext.selectAll(ChordChart.lineClass.selector).remove();\r\n }\r\n\r\n private clearTicks() {\r\n var empty = [];\r\n var tickLines = this.mainGraphicsContext.selectAll(ChordChart.tickLineClass.selector).data(empty);\r\n tickLines.exit().remove();\r\n\r\n var tickTexts = this.mainGraphicsContext.selectAll(ChordChart.tickTextClass.selector).data(empty);\r\n tickTexts.exit().remove();\r\n\r\n this.mainGraphicsContext.selectAll(ChordChart.tickPairClass.selector).remove();\r\n this.mainGraphicsContext.selectAll(ChordChart.sliceTicksClass.selector).remove();\r\n }\r\n\r\n private getChordTicksArcDescriptors(): ChordTicksArcDescriptor[] {\r\n var groups = this.chordLayout.groups();\r\n var maxValue = !_.isEmpty(groups) && _.max(groups, x => x.value).value || 0;\r\n var minValue = !_.isEmpty(groups) && _.min(groups, x => x.value).value || 0;\r\n var radiusCoeff = this.radius/Math.abs(maxValue - minValue)*1.25;\r\n\r\n var formatter = valueFormatter.create({\r\n format: \"0.##\",\r\n value: maxValue\r\n });\r\n groups.forEach((x: ChordTicksArcDescriptor) => {\r\n var k = (x.endAngle - x.startAngle) / x.value;\r\n var absValue = Math.abs(x.value);\r\n var range = d3.range(0, absValue, absValue - 1 < 0.15 ? 0.15 : absValue - 1);\r\n if(x.value < 0) {\r\n range = range.map(x => x * -1).reverse();\r\n }\r\n\r\n for(var i = 1; i < range.length; i++) {\r\n var gapSize = Math.abs(range[i] - range[i - 1]) * radiusCoeff;\r\n if(gapSize < ChordChart.TicksFontSize) {\r\n if(range.length > 2 && i === range.length - 1) {\r\n range.splice(--i, 1);\r\n } else {\r\n range.splice(i--, 1);\r\n }\r\n }\r\n }\r\n\r\n x.angleLabels = range.map((v, i) => <any>{ angle: v * k + x.startAngle, label: formatter.format(v) });\r\n });\r\n return <ChordTicksArcDescriptor[]>groups;\r\n }\r\n\r\n /* Draw axis(ticks) around the arc */\r\n private drawTicks(): void {\r\n if (this.settings.axis.show) {\r\n var tickShapes = this.mainGraphicsContext.select('.ticks')\r\n .selectAll('g' + ChordChart.sliceTicksClass.selector)\r\n .data(this.chordLayout.groups);\r\n var animDuration = (this.data.prevAxisVisible === this.settings.axis.show) ? this.duration : 0;\r\n\r\n tickShapes.enter().insert('g')\r\n .classed(ChordChart.sliceTicksClass.class, true);\r\n\r\n var tickPairs = tickShapes.selectAll('g' + ChordChart.tickPairClass.selector)\r\n .data((d: ChordTicksArcDescriptor) => d.angleLabels);\r\n\r\n tickPairs.enter().insert('g')\r\n .classed(ChordChart.tickPairClass.class, true);\r\n\r\n tickPairs.transition()\r\n .duration(animDuration)\r\n .attr('transform', (d) =>\r\n 'rotate(' + (d.angle * 180 / Math.PI - 90) + ')' + 'translate(' + this.innerRadius + ',0)');\r\n\r\n tickPairs.selectAll('line' + ChordChart.tickLineClass.selector)\r\n .data((d) => [d])\r\n .enter().insert('line')\r\n .classed(ChordChart.tickLineClass.class, true)\r\n .style(\"stroke\", \"#000\")\r\n .attr(\"x1\", 1)\r\n .attr(\"y1\", 0)\r\n .attr(\"x2\", 5)\r\n .attr(\"y2\", 0);\r\n\r\n tickPairs.selectAll('text' + ChordChart.tickTextClass.selector)\r\n .data((d) => [d])\r\n .enter().insert('text')\r\n .classed(ChordChart.tickTextClass.class, true)\r\n .style('pointer-events', \"none\")\r\n .attr(\"x\", 8)\r\n .attr(\"dy\", \".35em\");\r\n\r\n tickPairs\r\n .selectAll('text' + ChordChart.tickTextClass.selector)\r\n .text(d => d.label)\r\n .style(\"text-anchor\", d => d.angle > Math.PI ? \"end\" : null)\r\n .attr(\"transform\", d => d.angle > Math.PI ? \"rotate(180)translate(-16)\" : null);\r\n\r\n tickPairs.exit()\r\n .remove();\r\n\r\n tickShapes.exit()\r\n .remove();\r\n\r\n } else {\r\n this.clearTicks();\r\n }\r\n\r\n }\r\n\r\n private renderLabels(\r\n filteredData: LabelEnabledDataPoint[],\r\n layout: ILabelLayout,\r\n isDonut: boolean = false,\r\n forAnimation: boolean = false): void {\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 dataLabelUtils.cleanDataLabels(this.labels, true);\r\n return null;\r\n }\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 var hasKey: boolean = (<any>filteredData)[0].key !== null;\r\n var hasDataPointIdentity: boolean = (<any>filteredData)[0].identity !== null;\r\n var getIdentifier = hasKey\r\n ? (d: any) => d.key\r\n : hasDataPointIdentity ? (d: SelectableDataPoint) => d.identity.getKey() : undefined;\r\n\r\n var dataLabels = isDonut\r\n ? this.labels.selectAll(ChordChart.labelsClass.selector)\r\n .data(filteredData, (d: DonutArcDescriptor) => d.data.identity.getKey())\r\n : getIdentifier !== null\r\n ? this.labels.selectAll(ChordChart.labelsClass.selector).data(filteredData, getIdentifier)\r\n : this.labels.selectAll(ChordChart.labelsClass.selector).data(filteredData);\r\n\r\n var newLabels = dataLabels.enter()\r\n .append('text')\r\n .classed(ChordChart.labelsClass.class, true);\r\n if (forAnimation)\r\n newLabels.style('opacity', 0);\r\n\r\n dataLabels\r\n .attr({ x: (d: LabelEnabledDataPoint) => d.labelX, y: (d: LabelEnabledDataPoint) => d.labelY, dy: '.35em' })\r\n .text((d: LabelEnabledDataPoint) => d.labeltext)\r\n .style(layout.style);\r\n\r\n dataLabels\r\n .exit()\r\n .remove();\r\n }\r\n\r\n private renderLines(filteredData: LabelEnabledDataPoint[], arc: D3.Svg.Arc, outerArc: D3.Svg.Arc): void {\r\n var lines = this.lines.selectAll('polyline')\r\n .data(filteredData, (d: ChordArcDescriptor) => d.data.identity.getKey());\r\n var innerLinePointMultiplier = 2.05;\r\n\r\n var midAngle = (d: ChordArcDescriptor) => d.startAngle + (d.endAngle - d.startAngle) / 2;\r\n\r\n lines.enter()\r\n .append('polyline')\r\n .classed(ChordChart.lineClass.class, true);\r\n\r\n lines\r\n .attr('points', d => {\r\n var textPoint = outerArc.centroid(d);\r\n textPoint[0] = (this.radius + ChordChart.LabelMargin/2) * (midAngle(d) < Math.PI ? 1 : -1);\r\n var midPoint = outerArc.centroid(d);\r\n var chartPoint = arc.centroid(d);\r\n chartPoint[0] *= innerLinePointMultiplier;\r\n chartPoint[1] *= innerLinePointMultiplier;\r\n return [chartPoint, midPoint, textPoint];\r\n }).\r\n style({\r\n 'opacity': (d: ChordArcDescriptor) => ChordChart.PolylineOpacity,\r\n 'stroke': (d: ChordArcDescriptor) => d.data.labelColor,\r\n 'pointer-events': \"none\"\r\n });\r\n\r\n lines\r\n .exit()\r\n .remove();\r\n }\r\n\r\n /* Get label layout */\r\n private getChordChartLabelLayout(outerArc: D3.Svg.Arc): ILabelLayout {\r\n var midAngle = (d: ChordArcDescriptor) => d.startAngle + (d.endAngle - d.startAngle) / 2;\r\n var maxLabelWidth: number = (this.layout.viewportIn.width - this.radius * 2 - ChordChart.LabelMargin * 2)/1.6;\r\n\r\n return {\r\n labelText: (d: DonutArcDescriptor) => {\r\n // show only category label\r\n return dataLabelUtils.getLabelFormattedText({\r\n label: d.data.label,\r\n maxWidth: maxLabelWidth,\r\n fontSize: PixelConverter.fromPointToPixel(this.settings.labels.fontSize),\r\n });\r\n },\r\n labelLayout: {\r\n x: (d: ChordArcDescriptor) =>\r\n (this.radius + ChordChart.LabelMargin) * (midAngle(d) < Math.PI ? 1 : -1),\r\n y: (d: ChordArcDescriptor) => {\r\n var pos = outerArc.centroid(d);\r\n return pos[1];\r\n },\r\n },\r\n filter: (d: ChordArcDescriptor) => (d !== null && d.data !== null && d.data.label !== null),\r\n style: {\r\n 'fill': (d: ChordArcDescriptor) => d.data.labelColor,\r\n 'text-anchor': (d: ChordArcDescriptor) => midAngle(d) < Math.PI ? 'start' : 'end',\r\n 'font-size': (d: ChordArcDescriptor) => PixelConverter.fromPoint(this.settings.labels.fontSize),\r\n },\r\n };\r\n }\r\n\r\n /* Utility function for union two arrays without duplicates */\r\n private static union_arrays(x: any[], y: any[]): any[] {\r\n var obj: Object = {};\r\n\r\n for (var i: number = 0; i < x.length; i++) {\r\n obj[x[i]] = x[i];\r\n }\r\n\r\n for (var i: number = 0; i < y.length; i++) {\r\n obj[y[i]] = y[i];\r\n }\r\n\r\n var res: string[] = [];\r\n\r\n for (var k in obj) {\r\n if (obj.hasOwnProperty(k)) { // <-- optional\r\n res.push(obj[k]);\r\n }\r\n }\r\n return res;\r\n }\r\n }\r\n}\r\n\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/CustomVisuals/visuals/chordChart/visual/chordChart.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 Fill = powerbi.Fill;\r\n import ClassAndSelector = jsCommon.CssConstants.ClassAndSelector;\r\n import PixelConverter = jsCommon.PixelConverter;\r\n import createClassAndSelector = jsCommon.CssConstants.createClassAndSelector;\r\n import getCategoryIndexOfRole = powerbi.data.DataRoleHelper.getCategoryIndexOfRole;\r\n import getMeasureIndexOfRole = powerbi.data.DataRoleHelper.getMeasureIndexOfRole;\r\n import registerStandardInteractivityHandlers = powerbi.visuals.InteractivityUtils.registerStandardInteractivityHandlers;\r\n import createDisplayNameGetter = powerbi.data.createDisplayNameGetter;\r\n import IInteractiveBehavior = powerbi.visuals.IInteractiveBehavior;\r\n import DataViewObjectDescriptor = powerbi.data.DataViewObjectDescriptor;\r\n import Lazy = jsCommon.Lazy;\r\n import ISize = powerbi.visuals.shapes.ISize;\r\n import measureSvgTextWidth = powerbi.TextMeasurementService.measureSvgTextWidth;\r\n import measureSvgTextHeight = powerbi.TextMeasurementService.measureSvgTextHeight;\r\n import estimateSvgTextHeight = powerbi.TextMeasurementService.estimateSvgTextHeight;\r\n import svgEllipsis = powerbi.TextMeasurementService.svgEllipsis;\r\n import addTooltip = powerbi.visuals.TooltipManager.addTooltip;\r\n import fromPoint = jsCommon.PixelConverter.fromPoint;\r\n import LabelTextProperties = powerbi.visuals.dataLabelUtils.LabelTextProperties;\r\n import getLabelFormattedText = powerbi.visuals.dataLabelUtils.getLabelFormattedText;\r\n import equalWithPrecision = powerbi.Double.equalWithPrecision;\r\n import GetAnimationDuration = powerbi.visuals.AnimatorCommon.GetAnimationDuration;\r\n import getTailoredTextOrDefault = powerbi.TextMeasurementService.getTailoredTextOrDefault;\r\n import DataViewMetadataColumn = powerbi.DataViewMetadataColumn;\r\n import ChartAxesLabels = powerbi.visuals.ChartAxesLabels;\r\n import DataViewValueColumn = powerbi.DataViewValueColumn;\r\n import SelectableDataPoint = powerbi.visuals.SelectableDataPoint;\r\n import TooltipEnabledDataPoint = powerbi.visuals.TooltipEnabledDataPoint;\r\n import ContentPositions = powerbi.ContentPositions;\r\n import LegendData = powerbi.visuals.LegendData;\r\n import NumberRange = powerbi.NumberRange;\r\n import PointDataLabelsSettings = powerbi.visuals.PointDataLabelsSettings;\r\n import SelectionId = powerbi.visuals.SelectionId;\r\n import DataViewObjectPropertyIdentifier = powerbi.DataViewObjectPropertyIdentifier;\r\n import IVisual = powerbi.IVisual;\r\n import TextProperties = powerbi.TextProperties;\r\n import ILegend = powerbi.visuals.ILegend;\r\n import IVisualStyle = powerbi.IVisualStyle;\r\n import IAxisProperties = powerbi.visuals.IAxisProperties;\r\n import IDataColorPalette = powerbi.IDataColorPalette;\r\n import VisualInitOptions = powerbi.VisualInitOptions;\r\n import InteractivityOptions = powerbi.InteractivityOptions;\r\n import IInteractivityService = powerbi.visuals.IInteractivityService;\r\n import DataViewObject = powerbi.DataViewObject;\r\n import IVisualHostServices = powerbi.IVisualHostServices;\r\n import IGenericAnimator = powerbi.visuals.IGenericAnimator;\r\n import IMargin = powerbi.visuals.IMargin;\r\n import IViewport = powerbi.IViewport;\r\n import VisualCapabilities = powerbi.VisualCapabilities;\r\n import VisualDataRoleKind = powerbi.VisualDataRoleKind;\r\n import legendPosition = powerbi.visuals.legendPosition;\r\n import BaseAnimator = powerbi.visuals.BaseAnimator;\r\n import yAxisPosition = powerbi.visuals.yAxisPosition;\r\n import appendClearCatcher = powerbi.visuals.appendClearCatcher;\r\n import createInteractivityService = powerbi.visuals.createInteractivityService;\r\n import createLegend = powerbi.visuals.createLegend;\r\n import AxisHelper = powerbi.visuals.AxisHelper;\r\n import DataViewMetadata = powerbi.DataViewMetadata;\r\n import DataViewObjects = powerbi.DataViewObjects;\r\n import IValueFormatter = powerbi.visuals.IValueFormatter;\r\n import DataViewScopeIdentity = powerbi.DataViewScopeIdentity;\r\n import DataViewCategorical = powerbi.DataViewCategorical;\r\n import DataViewCategoryColumn = powerbi.DataViewCategoryColumn;\r\n import DataViewValueColumns = powerbi.DataViewValueColumns;\r\n import DataViewValueColumnGroup = powerbi.DataViewValueColumnGroup;\r\n import valueFormatter = powerbi.visuals.valueFormatter;\r\n import dataLabelUtils = powerbi.visuals.dataLabelUtils;\r\n import LegendDataPoint = powerbi.visuals.LegendDataPoint;\r\n import ColorHelper = powerbi.visuals.ColorHelper;\r\n import LegendIcon = powerbi.visuals.LegendIcon;\r\n import DataViewCategoricalColumn = powerbi.DataViewCategoricalColumn;\r\n import SelectionIdBuilder = powerbi.visuals.SelectionIdBuilder;\r\n import TooltipSeriesDataItem = powerbi.visuals.TooltipSeriesDataItem;\r\n import TooltipDataItem = powerbi.visuals.TooltipDataItem;\r\n import TooltipBuilder = powerbi.visuals.TooltipBuilder;\r\n import VisualUpdateOptions = powerbi.VisualUpdateOptions;\r\n import getInvalidValueWarnings = powerbi.visuals.getInvalidValueWarnings;\r\n import legendProps = powerbi.visuals.legendProps;\r\n import LegendPosition = powerbi.visuals.LegendPosition;\r\n import Legend = powerbi.visuals.Legend;\r\n import ILabelLayout = powerbi.visuals.ILabelLayout;\r\n import SVGUtil = powerbi.visuals.SVGUtil;\r\n import TooltipEvent = powerbi.visuals.TooltipEvent;\r\n import CalculateScaleAndDomainOptions = powerbi.visuals.CalculateScaleAndDomainOptions;\r\n import ObjectEnumerationBuilder = powerbi.visuals.ObjectEnumerationBuilder;\r\n import EnumerateVisualObjectInstancesOptions = powerbi.EnumerateVisualObjectInstancesOptions;\r\n import VisualObjectInstanceEnumeration = powerbi.VisualObjectInstanceEnumeration;\r\n import GradientUtils = powerbi.visuals.GradientUtils;\r\n import axisType = powerbi.visuals.axisType;\r\n import axisScale = powerbi.visuals.axisScale;\r\n import VisualObjectInstance = powerbi.VisualObjectInstance;\r\n import axisStyle = powerbi.visuals.axisStyle;\r\n import ISelectionHandler = powerbi.visuals.ISelectionHandler;\r\n import DataView = powerbi.DataView;\r\n\r\n export interface ElementProperty {\r\n [propertyName: string]: any;\r\n }\r\n\r\n export interface ElementProperties {\r\n name: string;\r\n selector: string;\r\n className?: string;\r\n data?: any;\r\n styles?: ElementProperty;\r\n attributes?: ElementProperty;\r\n }\r\n\r\n interface EnhancedScatterChartMeasureMetadataIndexes {\r\n category?: number;\r\n x?: number;\r\n y?: number;\r\n size?: number;\r\n colorFill?: number;\r\n shape?: number;\r\n image?: number;\r\n rotation?: number;\r\n backdrop?: number;\r\n xStart?: number;\r\n xEnd?: number;\r\n yStart?: number;\r\n yEnd?: number;\r\n }\r\n\r\n interface EnhancedScatterChartMeasureMetadataColumns {\r\n x?: DataViewMetadataColumn;\r\n y?: DataViewMetadataColumn;\r\n size?: DataViewMetadataColumn;\r\n }\r\n\r\n interface EnhancedScatterChartMeasureMetadata {\r\n idx: EnhancedScatterChartMeasureMetadataIndexes;\r\n cols: EnhancedScatterChartMeasureMetadataColumns;\r\n axesLabels: ChartAxesLabels;\r\n }\r\n\r\n export interface EnhancedScatterChartRadiusData {\r\n sizeMeasure: DataViewValueColumn;\r\n index: number;\r\n }\r\n\r\n export interface EnhancedScatterChartDataPoint extends SelectableDataPoint, TooltipEnabledDataPoint {\r\n x: any;\r\n y: any;\r\n size: number | ISize;\r\n radius: EnhancedScatterChartRadiusData;\r\n fill: string;\r\n labelFill?: string;\r\n labelFontSize: any;\r\n contentPosition: ContentPositions;\r\n formattedCategory: Lazy<string>;\r\n colorFill?: string;\r\n svgurl?: string;\r\n shapeSymbolType?: (number) => string;\r\n rotation: number;\r\n backdrop?: string;\r\n xStart?: number;\r\n xEnd?: number;\r\n yStart?: number;\r\n yEnd?: number;\r\n }\r\n\r\n export interface EnhancedScatterChartBackdrop {\r\n show: boolean;\r\n url: string;\r\n }\r\n\r\n export interface EnhancedScatterChartAxesLabels {\r\n x: string;\r\n y: string;\r\n y2?: string;\r\n }\r\n\r\n export interface EnhancedScatterChartData {\r\n useShape: boolean;\r\n useCustomColor: boolean;\r\n backdrop?: EnhancedScatterChartBackdrop;\r\n outline?: boolean;\r\n crosshair?: boolean;\r\n xCol: DataViewMetadataColumn;\r\n yCol: DataViewMetadataColumn;\r\n dataPoints: EnhancedScatterChartDataPoint[];\r\n legendData: LegendData;\r\n axesLabels: EnhancedScatterChartAxesLabels;\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 selectedIds: SelectionId[];\r\n }\r\n\r\n export interface EnhancedScatterDataRange {\r\n minRange: number;\r\n maxRange: number;\r\n delta: number;\r\n }\r\n\r\n export interface EnhancedScatterChartProperty {\r\n [properyName: string]: DataViewObjectPropertyIdentifier;\r\n }\r\n\r\n export interface EnhancedScatterChartProperties {\r\n [properyName: string]: EnhancedScatterChartProperty;\r\n }\r\n\r\n export class EnhancedScatterChart implements IVisual {\r\n private static AxisGraphicsContextClassName: string = \"axisGraphicsContext\";\r\n private static ClassName: string = \"enhancedScatterChart\";\r\n private static MainGraphicsContextClassName: string = \"mainGraphicsContext\";\r\n private static LegendLabelFontSizeDefault: number = 9;\r\n private static LabelDisplayUnitsDefault: number = 0;\r\n private static AxisFontSize: number = 11;\r\n private static CrosshairTextMargin: number = 5;\r\n private static BubbleRadius = 3 * 2;\r\n\r\n private static MinSizeRange = 200;\r\n private static MaxSizeRange = 3000;\r\n\r\n private static AreaOf300By300Chart = 90000;\r\n\r\n private static DataLabelXOffset: number = 2;\r\n private static DataLabelYOffset: number = 1.8;\r\n\r\n private static DotClasses: ClassAndSelector = createClassAndSelector(\"dot\");\r\n private static ImageClasses: ClassAndSelector = createClassAndSelector(\"img\");\r\n\r\n private static TextProperties: TextProperties = {\r\n fontFamily: \"'Segoe UI', wf_segoe-ui_normal, helvetica, arial, sans-serif\",\r\n fontSize: PixelConverter.toString(EnhancedScatterChart.AxisFontSize),\r\n };\r\n\r\n public static CrosshairCanvasSelector: ClassAndSelector = createClassAndSelector(\"crosshairCanvas\");\r\n public static CrosshairLineSelector: ClassAndSelector = createClassAndSelector(\"crosshairLine\");\r\n public static CrosshairVerticalLineSelector: ClassAndSelector = createClassAndSelector(\"crosshairVerticalLine\");\r\n public static CrosshairHorizontalLineSelector: ClassAndSelector = createClassAndSelector(\"crosshairHorizontalLine\");\r\n public static CrosshairTextSelector: ClassAndSelector = createClassAndSelector(\"crosshairText\");\r\n\r\n public static MaxTranslateValue: number = 1e+25;\r\n public static MinTranslateValue: number = 1e-25;\r\n\r\n public static DefaultBubbleOpacity = 0.85;\r\n public static DimmedBubbleOpacity = 0.4;\r\n\r\n private legend: ILegend;\r\n private svgScrollable: D3.Selection;\r\n private axisGraphicsContext: D3.Selection;\r\n private axisGraphicsContextScrollable: D3.Selection;\r\n private xAxisGraphicsContext: D3.Selection;\r\n private backgroundGraphicsContext: D3.Selection;\r\n private y1AxisGraphicsContext: D3.Selection;\r\n private svg: D3.Selection;\r\n private element: JQuery;\r\n private mainGraphicsSVGSelection: D3.Selection;\r\n private mainGraphicsContext: D3.Selection;\r\n private clearCatcher: D3.Selection;\r\n private mainGraphicsG: D3.Selection;\r\n\r\n private crosshairCanvasSelection: D3.Selection;\r\n private crosshairVerticalLineSelection: D3.Selection;\r\n private crosshairHorizontalLineSelection: D3.Selection;\r\n private crosshairTextSelection: D3.Selection;\r\n\r\n private style: IVisualStyle;\r\n private data: EnhancedScatterChartData;\r\n private dataView: DataView;\r\n\r\n private xAxisProperties: IAxisProperties;\r\n private yAxisProperties: IAxisProperties;\r\n private colors: IDataColorPalette;\r\n private options: VisualInitOptions;\r\n private interactivity: InteractivityOptions;\r\n private interactivityService: IInteractivityService;\r\n private categoryAxisProperties: DataViewObject;\r\n private valueAxisProperties: DataViewObject;\r\n private yAxisOrientation: string;\r\n private scrollY: boolean;\r\n private scrollX: boolean;\r\n\r\n private dataViews: DataView[];\r\n private legendObjectProperties: DataViewObject;\r\n private hostServices: IVisualHostServices;\r\n private layerLegendData: LegendData;\r\n private legendLabelFontSize: number;\r\n private hasCategoryAxis: boolean;\r\n private yAxisIsCategorical: boolean;\r\n private bottomMarginLimit: number;\r\n private leftRightMarginLimit: number;\r\n private isXScrollBarVisible: boolean;\r\n private isYScrollBarVisible: boolean;\r\n private ScrollBarWidth = 10;\r\n private categoryAxisHasUnitType: boolean;\r\n private valueAxisHasUnitType: boolean;\r\n private svgDefaultImage: string;\r\n private oldBackdrop: string;\r\n\r\n private behavior: IInteractiveBehavior;\r\n private animator: IGenericAnimator;\r\n private keyArray: string[];\r\n\r\n private _margin: IMargin;\r\n private get margin(): IMargin {\r\n return this._margin || { left: 0, right: 0, top: 0, bottom: 0 };\r\n }\r\n\r\n private set margin(value: IMargin) {\r\n this._margin = $.extend({}, value);\r\n this._viewportIn = EnhancedScatterChart.substractMargin(this.viewport, this.margin);\r\n }\r\n\r\n private _viewport: IViewport;\r\n private get viewport(): IViewport {\r\n return this._viewport || { width: 0, height: 0 };\r\n }\r\n\r\n private set viewport(value: IViewport) {\r\n this._viewport = $.extend({}, value);\r\n this._viewportIn = EnhancedScatterChart.substractMargin(this.viewport, this.margin);\r\n }\r\n\r\n private _viewportIn: IViewport;\r\n private get viewportIn(): IViewport {\r\n return this._viewportIn || this.viewport;\r\n }\r\n\r\n private get legendViewport(): IViewport {\r\n return this.legend.getMargins();\r\n }\r\n\r\n public static ColumnCategory: string = \"Category\";\r\n public static ColumnSeries: string = \"Series\";\r\n public static ColumnX: string = \"X\";\r\n public static ColumnY: string = \"Y\";\r\n public static ColumnSize: string = \"Size\";\r\n public static ColumnGradient: string = \"Gradient\";\r\n public static ColumnColorFill: string = \"ColorFill\";\r\n public static ColumnShape: string = \"Shape\";\r\n public static ColumnImage: string = \"Image\";\r\n public static ColumnRotation: string = \"Rotation\";\r\n public static ColumnBackdrop: string = \"Backdrop\";\r\n public static ColumnXStart: string = \"X Start\";\r\n public static ColumnXEnd: string = \"X End\";\r\n public static ColumnYStart: string = \"Y Start\";\r\n public static ColumnYEnd: string = \"Y End\";\r\n\r\n public static capabilities: VisualCapabilities = {\r\n dataRoles: [\r\n {\r\n name: EnhancedScatterChart.ColumnCategory,\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: createDisplayNameGetter(\"Role_DisplayName_Details\"),\r\n }, {\r\n name: EnhancedScatterChart.ColumnSeries,\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: createDisplayNameGetter(\"Role_DisplayName_Legend\"),\r\n }, {\r\n name: EnhancedScatterChart.ColumnX,\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: createDisplayNameGetter(\"Role_DisplayName_X\"),\r\n }, {\r\n name: EnhancedScatterChart.ColumnY,\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: createDisplayNameGetter(\"Role_DisplayName_Y\"),\r\n }, {\r\n name: EnhancedScatterChart.ColumnSize,\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: createDisplayNameGetter(\"Role_DisplayName_Size\"),\r\n }, {\r\n name: EnhancedScatterChart.ColumnGradient,\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: createDisplayNameGetter(\"Role_DisplayName_Gradient\"),\r\n }, {\r\n name: EnhancedScatterChart.ColumnColorFill,\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: \"Customized Color\",\r\n }, {\r\n name: EnhancedScatterChart.ColumnShape,\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: \"Shape\",\r\n }, {\r\n name: EnhancedScatterChart.ColumnImage,\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: \"Image\",\r\n }, {\r\n name: EnhancedScatterChart.ColumnRotation,\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: \"Rotation\",\r\n }, {\r\n name: EnhancedScatterChart.ColumnBackdrop,\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: \"Backdrop\",\r\n }, {\r\n name: EnhancedScatterChart.ColumnXStart,\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: \"X Start\",\r\n }, {\r\n name: EnhancedScatterChart.ColumnXEnd,\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: \"X End\",\r\n }, {\r\n name: EnhancedScatterChart.ColumnYStart,\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: \"Y Start\",\r\n }, {\r\n name: EnhancedScatterChart.ColumnYEnd,\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: \"Y End\",\r\n }\r\n ],\r\n dataViewMappings: [{\r\n conditions: [{\r\n \"Category\": { max: 1 },\r\n \"Series\": { max: 1 },\r\n \"X\": { max: 1 },\r\n \"Y\": { max: 1 },\r\n \"Size\": { max: 1 },\r\n \"Gradient\": { max: 0 },\r\n \"ColorFill\": { max: 1 },\r\n \"Shape\": { max: 1 },\r\n \"Image\": { max: 0 },\r\n \"Rotation\": { max: 1 },\r\n \"Backdrop\": { max: 1 },\r\n \"X Start\": { max: 1 },\r\n \"X End\": { max: 1 },\r\n \"Y Start\": { max: 1 },\r\n \"Y End\": { max: 1 }\r\n }, {\r\n \"Category\": { max: 1 },\r\n \"Series\": { max: 0 },\r\n \"X\": { max: 1 },\r\n \"Y\": { max: 1 },\r\n \"Size\": { max: 1 },\r\n \"Gradient\": { max: 1 },\r\n \"ColorFill\": { max: 1 },\r\n \"Shape\": { max: 1 },\r\n \"Image\": { max: 0 },\r\n \"Rotation\": { max: 1 },\r\n \"Backdrop\": { max: 1 },\r\n \"X Start\": { max: 1 },\r\n \"X End\": { max: 1 },\r\n \"Y Start\": { max: 1 },\r\n \"Y End\": { max: 1 }\r\n }, {\r\n \"Category\": { max: 1 },\r\n \"Series\": { max: 1 },\r\n \"X\": { max: 1 },\r\n \"Y\": { max: 1 },\r\n \"Size\": { max: 1 },\r\n \"Gradient\": { max: 0 },\r\n \"ColorFill\": { max: 0 },\r\n \"Shape\": { max: 0 },\r\n \"Image\": { max: 1 },\r\n \"Rotation\": { max: 1 },\r\n \"Backdrop\": { max: 1 },\r\n \"X Start\": { max: 1 },\r\n \"X End\": { max: 1 },\r\n \"Y Start\": { max: 1 },\r\n \"Y End\": { max: 1 }\r\n }, {\r\n \"Category\": { max: 1 },\r\n \"Series\": { max: 0 },\r\n \"X\": { max: 1 },\r\n \"Y\": { max: 1 },\r\n \"Size\": { max: 1 },\r\n \"Gradient\": { max: 1 },\r\n \"ColorFill\": { max: 0 },\r\n \"Shape\": { max: 0 },\r\n \"Image\": { max: 1 },\r\n \"Rotation\": { max: 1 },\r\n \"Backdrop\": { max: 1 },\r\n \"X Start\": { max: 1 },\r\n \"X End\": { max: 1 },\r\n \"Y Start\": { max: 1 },\r\n \"Y End\": { max: 1 }\r\n }],\r\n categorical: {\r\n categories: {\r\n for: { in: EnhancedScatterChart.ColumnCategory },\r\n dataReductionAlgorithm: { sample: {} }\r\n },\r\n values: {\r\n group: {\r\n by: EnhancedScatterChart.ColumnSeries,\r\n select: [\r\n { bind: { to: EnhancedScatterChart.ColumnX } },\r\n { bind: { to: EnhancedScatterChart.ColumnY } },\r\n { bind: { to: EnhancedScatterChart.ColumnSize } },\r\n { bind: { to: EnhancedScatterChart.ColumnGradient } },\r\n { bind: { to: EnhancedScatterChart.ColumnColorFill } },\r\n { bind: { to: EnhancedScatterChart.ColumnShape } },\r\n { bind: { to: EnhancedScatterChart.ColumnImage } },\r\n { bind: { to: EnhancedScatterChart.ColumnRotation } },\r\n { bind: { to: EnhancedScatterChart.ColumnBackdrop } },\r\n { bind: { to: EnhancedScatterChart.ColumnXStart } },\r\n { bind: { to: EnhancedScatterChart.ColumnXEnd } },\r\n { bind: { to: EnhancedScatterChart.ColumnYStart } },\r\n { bind: { to: EnhancedScatterChart.ColumnYEnd } },\r\n ],\r\n dataReductionAlgorithm: { top: {} }\r\n }\r\n },\r\n rowCount: { preferred: { min: 2 } }\r\n },\r\n }],\r\n objects: {\r\n dataPoint: {\r\n displayName: createDisplayNameGetter(\"Visual_DataPoint\"),\r\n properties: {\r\n defaultColor: {\r\n displayName: createDisplayNameGetter(\"Visual_DefaultColor\"),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n showAllDataPoints: {\r\n displayName: createDisplayNameGetter(\"Visual_DataPoint_Show_All\"),\r\n type: { bool: true }\r\n },\r\n useShape: {\r\n displayName: createDisplayNameGetter(\"Visual_UseImage\"),\r\n type: { bool: true }\r\n },\r\n fill: {\r\n displayName: createDisplayNameGetter(\"Visual_Fill\"),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n fillRule: {\r\n displayName: createDisplayNameGetter(\"Visual_Gradient\"),\r\n type: { fillRule: {} },\r\n rule: {\r\n inputRole: EnhancedScatterChart.ColumnGradient,\r\n output: {\r\n property: \"fill\",\r\n selector: [EnhancedScatterChart.ColumnCategory],\r\n },\r\n }\r\n }\r\n }\r\n },\r\n general: {\r\n displayName: createDisplayNameGetter(\"Visual_General\"),\r\n properties: {\r\n formatString: {\r\n type: { formatting: { formatString: true } },\r\n },\r\n },\r\n },\r\n categoryAxis: {\r\n displayName: createDisplayNameGetter(\"Visual_XAxis\"),\r\n properties: {\r\n show: {\r\n displayName: createDisplayNameGetter(\"Visual_Show\"),\r\n type: { bool: true }\r\n },\r\n axisScale: {\r\n displayName: createDisplayNameGetter(\"Visual_Axis_Scale\"),\r\n type: { formatting: { axisScale: true } }\r\n },\r\n start: {\r\n displayName: createDisplayNameGetter(\"Visual_Axis_Start\"),\r\n type: { numeric: true }\r\n },\r\n end: {\r\n displayName: createDisplayNameGetter(\"Visual_Axis_End\"),\r\n type: { numeric: true }\r\n },\r\n showAxisTitle: {\r\n displayName: createDisplayNameGetter(\"Visual_Axis_Title\"),\r\n type: { bool: true }\r\n },\r\n axisStyle: {\r\n displayName: createDisplayNameGetter(\"Visual_Axis_Style\"),\r\n type: { formatting: { axisStyle: true } }\r\n },\r\n axisColor: {\r\n displayName: \"Color\",\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n labelDisplayUnits: {\r\n displayName: \"Display Units\",\r\n type: { formatting: { labelDisplayUnits: true } },\r\n },\r\n }\r\n },\r\n valueAxis: {\r\n displayName: createDisplayNameGetter(\"Visual_YAxis\"),\r\n properties: {\r\n show: {\r\n displayName: createDisplayNameGetter(\"Visual_Show\"),\r\n type: { bool: true }\r\n },\r\n position: {\r\n displayName: createDisplayNameGetter(\"Visual_YAxis_Position\"),\r\n type: { formatting: { yAxisPosition: true } }\r\n },\r\n axisScale: {\r\n displayName: createDisplayNameGetter(\"Visual_Axis_Scale\"),\r\n type: { formatting: { axisScale: true } }\r\n },\r\n start: {\r\n displayName: createDisplayNameGetter(\"Visual_Axis_Start\"),\r\n type: { numeric: true }\r\n },\r\n end: {\r\n displayName: createDisplayNameGetter(\"Visual_Axis_End\"),\r\n type: { numeric: true }\r\n },\r\n showAxisTitle: {\r\n displayName: createDisplayNameGetter(\"Visual_Axis_Title\"),\r\n type: { bool: true }\r\n },\r\n axisStyle: {\r\n displayName: createDisplayNameGetter(\"Visual_Axis_Style\"),\r\n type: { formatting: { axisStyle: true } }\r\n },\r\n axisColor: {\r\n displayName: \"Color\",\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n labelDisplayUnits: {\r\n displayName: \"Display Units\",\r\n type: { formatting: { labelDisplayUnits: true } },\r\n }\r\n }\r\n },\r\n legend: {\r\n displayName: createDisplayNameGetter(\"Visual_Legend\"),\r\n properties: {\r\n show: {\r\n displayName: createDisplayNameGetter(\"Visual_Show\"),\r\n type: { bool: true }\r\n },\r\n position: {\r\n displayName: createDisplayNameGetter(\"Visual_LegendPosition\"),\r\n description: createDisplayNameGetter(\"Visual_LegendPositionDescription\"),\r\n type: { enumeration: legendPosition.type },\r\n },\r\n showTitle: {\r\n displayName: createDisplayNameGetter(\"Visual_LegendShowTitle\"),\r\n description: createDisplayNameGetter(\"Visual_LegendShowTitleDescription\"),\r\n type: { bool: true }\r\n },\r\n titleText: {\r\n displayName: \"Legend Name\",\r\n description: createDisplayNameGetter(\"Visual_LegendNameDescription\"),\r\n type: { text: true }\r\n },\r\n labelColor: {\r\n displayName: \"Color\",\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n fontSize: {\r\n displayName: \"Text Size\",\r\n type: { formatting: { fontSize: true } }\r\n }\r\n }\r\n },\r\n categoryLabels: {\r\n displayName: createDisplayNameGetter(\"Visual_CategoryLabels\"),\r\n properties: {\r\n show: {\r\n displayName: createDisplayNameGetter(\"Visual_Show\"),\r\n type: { bool: true }\r\n },\r\n color: {\r\n displayName: createDisplayNameGetter(\"Visual_LabelsFill\"),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n fontSize: {\r\n displayName: \"Text Size\",\r\n type: { formatting: { fontSize: true } }\r\n },\r\n },\r\n },\r\n fillPoint: {\r\n displayName: createDisplayNameGetter(\"Visual_FillPoint\"),\r\n properties: {\r\n show: {\r\n displayName: createDisplayNameGetter(\"Visual_Fill\"),\r\n type: { bool: true }\r\n },\r\n },\r\n },\r\n backdrop: {\r\n displayName: \"Backdrop\",\r\n properties: {\r\n show: {\r\n displayName: createDisplayNameGetter(\"Visual_Show\"),\r\n type: { bool: true }\r\n },\r\n url: {\r\n displayName: \"Image URL\",\r\n type: { text: true }\r\n },\r\n },\r\n },\r\n crosshair: {\r\n displayName: \"Crosshair\",\r\n properties: {\r\n show: {\r\n displayName: \"Crosshair\",\r\n type: { bool: true }\r\n },\r\n },\r\n },\r\n outline: {\r\n displayName: \"Outline\",\r\n properties: {\r\n show: {\r\n displayName: createDisplayNameGetter(\"Visual_Outline\"),\r\n type: { bool: true }\r\n }\r\n }\r\n }\r\n }\r\n };\r\n\r\n /**\r\n * Public for testability.\r\n */\r\n public static getPropertiesByCapabilities<T>(capabilities: VisualCapabilities): T {\r\n var propertiesByCapabilities: T = <T>{};\r\n\r\n if (!capabilities) {\r\n return propertiesByCapabilities;\r\n }\r\n\r\n for (var objectName in capabilities.objects) {\r\n var objectDescriptor: DataViewObjectDescriptor = capabilities.objects[objectName];\r\n\r\n propertiesByCapabilities[objectName] = {};\r\n\r\n if (objectDescriptor) {\r\n for (var propertyName in objectDescriptor.properties) {\r\n propertiesByCapabilities[objectName][propertyName] = {\r\n objectName: objectName,\r\n propertyName: propertyName\r\n };\r\n }\r\n }\r\n }\r\n\r\n return propertiesByCapabilities;\r\n }\r\n\r\n /**\r\n * Public for testability.\r\n */\r\n public static Properties: EnhancedScatterChartProperties =\r\n EnhancedScatterChart.getPropertiesByCapabilities<EnhancedScatterChartProperties>(EnhancedScatterChart.capabilities);\r\n\r\n private static substractMargin(viewport: IViewport, margin: IMargin): IViewport {\r\n return {\r\n width: Math.max(viewport.width - (margin.left + margin.right), 0),\r\n height: Math.max(viewport.height - (margin.top + margin.bottom), 0)\r\n };\r\n }\r\n\r\n private static getCustomSymbolType(shape: any): (number) => string {\r\n var customSymbolTypes = d3.map({\r\n \"circle\": (size) => {\r\n var r = Math.sqrt(size / Math.PI);\r\n return \"M0,\" + r + \"A\" + r + \",\" + r + \" 0 1,1 0,\" + (-r) + \"A\" + r + \",\" + r + \" 0 1,1 0,\" + r + \"Z\";\r\n },\r\n\r\n \"cross\": function (size) {\r\n var r = Math.sqrt(size / 5) / 2;\r\n return \"M\" + -3 * r + \",\" + -r\r\n + \"H\" + -r + \"V\" + -3 * r + \"H\" + r + \"V\" + -r + \"H\" + 3 * r + \"V\" + r + \"H\" + r + \"V\" + 3 * r + \"H\" + -r + \"V\" + r + \"H\" + -3 * r + \"Z\";\r\n },\r\n\r\n \"diamond\": (size) => {\r\n var ry = Math.sqrt(size / (2 * Math.tan(Math.PI / 6))),\r\n rx = ry * Math.tan(Math.PI / 6);\r\n return \"M0,\" + -ry\r\n + \"L\" + rx + \",0\"\r\n + \" 0,\" + ry\r\n + \" \" + -rx + \",0\"\r\n + \"Z\";\r\n },\r\n\r\n \"square\": (size) => {\r\n var r = Math.sqrt(size) / 2;\r\n return \"M\" + -r + \",\" + -r\r\n + \"L\" + r + \",\" + -r\r\n + \" \" + r + \",\" + r\r\n + \" \" + -r + \",\" + r\r\n + \"Z\";\r\n },\r\n\r\n \"triangle-up\": (size) => {\r\n var rx = Math.sqrt(size / Math.sqrt(3)),\r\n ry = rx * Math.sqrt(3) / 2;\r\n return \"M0,\" + -ry\r\n + \"L\" + rx + \",\" + ry\r\n + \" \" + -rx + \",\" + ry\r\n + \"Z\";\r\n },\r\n\r\n \"triangle-down\": (size) => {\r\n var rx = Math.sqrt(size / Math.sqrt(3)),\r\n ry = rx * Math.sqrt(3) / 2;\r\n return \"M0,\" + ry\r\n + \"L\" + rx + \",\" + -ry\r\n + \" \" + -rx + \",\" + -ry\r\n + \"Z\";\r\n },\r\n\r\n \"star\": (size) => {\r\n var outerRadius = Math.sqrt(size / 2);\r\n var innerRadius = Math.sqrt(size / 10);\r\n var results = \"\";\r\n var angle = Math.PI / 5;\r\n for (var i = 0; i < 10; i++) {\r\n // Use outer or inner radius depending on what iteration we are in.\r\n var r = (i & 1) === 0 ? outerRadius : innerRadius;\r\n var currX = Math.cos(i * angle) * r;\r\n var currY = Math.sin(i * angle) * r;\r\n // Our first time we simply append the coordinates, subsequet times\r\n // we append a \", \" to distinguish each coordinate pair.\r\n if (i === 0) {\r\n results = \"M\" + currX + \",\" + currY + \"L\";\r\n } else {\r\n results += \" \" + currX + \",\" + currY;\r\n }\r\n }\r\n return results + \"Z\";\r\n },\r\n\r\n \"hexagon\": (size) => {\r\n var r = Math.sqrt(size / (6 * Math.sqrt(3)));\r\n var r2 = Math.sqrt(size / (2 * Math.sqrt(3)));\r\n\r\n return \"M0,\" + (2 * r) + \"L\" + (-r2) + \",\" + r + \" \" + (-r2) + \",\" + (-r) + \" 0,\" + (-2 * r) + \" \" + r2 + \",\" + (-r) + \" \" + r2 + \",\" + r + \"Z\";\r\n },\r\n\r\n \"x\": (size) => {\r\n var r = Math.sqrt(size / 10);\r\n return \"M0,\" + r + \"L\" + (-r) + \",\" + 2 * r + \" \" + (-2 * r) + \",\" + r + \" \" + (-r) + \",0 \" + (-2 * r) + \",\" + (-r) + \" \" + (-r) + \",\" + (-2 * r) + \" 0,\" + (-r) + \" \" + r + \",\" + (-2 * r) + \" \" + (2 * r) + \",\" + (-r) + \" \" + r + \",0 \" + (2 * r) + \",\" + r + \" \" + r + \",\" + (2 * r) + \"Z\";\r\n },\r\n\r\n \"uparrow\": (size) => {\r\n var r = Math.sqrt(size / 12);\r\n return \"M\" + r + \",\" + (3 * r) + \"L\" + (-r) + \",\" + (3 * r) + \" \" + (-r) + \",\" + (-r) + \" \" + (-2 * r) + \",\" + (-r) + \" 0,\" + (-3 * r) + \" \" + (2 * r) + \",\" + (-r) + \" \" + r + \",\" + (-r) + \"Z\";\r\n },\r\n\r\n \"downarrow\": (size) => {\r\n var r = Math.sqrt(size / 12);\r\n return \"M0,\" + (3 * r) + \"L\" + (-2 * r) + \",\" + r + \" \" + (-r) + \",\" + r + \" \" + (-r) + \",\" + (-3 * r) + \" \" + r + \",\" + (-3 * r) + \" \" + r + \",\" + r + \" \" + (2 * r) + \",\" + r + \"Z\";\r\n }\r\n });\r\n\r\n var defaultValue = customSymbolTypes.entries()[0].value;\r\n\r\n if (!shape) {\r\n return defaultValue;\r\n } else if (isNaN(shape)) {\r\n return customSymbolTypes[shape && shape.toString().toLowerCase()] || defaultValue;\r\n } else {\r\n var result = customSymbolTypes.entries()[Math.floor(shape)];\r\n\r\n return result ? result.value : defaultValue;\r\n }\r\n }\r\n\r\n public init(options: VisualInitOptions): void {\r\n this.options = options;\r\n this.animator = new BaseAnimator();\r\n\r\n this.behavior = new CustomVisualBehavior([new EnhancedScatterChartWebBehavior(\r\n EnhancedScatterChart.DimmedBubbleOpacity,\r\n EnhancedScatterChart.DefaultBubbleOpacity\r\n )]);\r\n\r\n var element: JQuery = this.element = options.element;\r\n\r\n this.viewport = _.clone(options.viewport);\r\n this.style = options.style;\r\n this.hostServices = options.host;\r\n this.colors = this.style.colorPalette.dataColors;\r\n this.interactivity = options.interactivity;\r\n\r\n this.margin = {\r\n top: 1,\r\n right: 1,\r\n bottom: 1,\r\n left: 1\r\n };\r\n\r\n this.yAxisOrientation = yAxisPosition.left;\r\n this.adjustMargins();\r\n\r\n var showLinesOnX: boolean = this.scrollY = true;\r\n var showLinesOnY: boolean = this.scrollX = true;\r\n\r\n var svg: D3.Selection = this.svg = d3.select(element.get(0))\r\n .append(\"svg\")\r\n .style(\"position\", \"absolute\")\r\n .classed(EnhancedScatterChart.ClassName, true);\r\n\r\n var axisGraphicsContext = this.axisGraphicsContext = svg.append(\"g\")\r\n .classed(EnhancedScatterChart.AxisGraphicsContextClassName, true);\r\n\r\n this.svgScrollable = svg.append(\"svg\")\r\n .classed(\"svgScrollable\", true)\r\n .style(\"overflow\", \"hidden\");\r\n\r\n var axisGraphicsContextScrollable = this.axisGraphicsContextScrollable = this.svgScrollable.append(\"g\")\r\n .classed(EnhancedScatterChart.AxisGraphicsContextClassName, true);\r\n\r\n this.clearCatcher = appendClearCatcher(this.axisGraphicsContextScrollable);\r\n\r\n var axisGroup: D3.Selection = showLinesOnX\r\n ? axisGraphicsContextScrollable\r\n : axisGraphicsContext;\r\n\r\n this.backgroundGraphicsContext = axisGraphicsContext.append(\"svg:image\");\r\n\r\n this.xAxisGraphicsContext = showLinesOnX\r\n ? axisGraphicsContext.append(\"g\").attr(\"class\", \"x axis\")\r\n : axisGraphicsContextScrollable.append(\"g\").attr(\"class\", \"x axis\");\r\n\r\n this.y1AxisGraphicsContext = axisGroup.append(\"g\").attr(\"class\", \"y axis\");\r\n\r\n this.xAxisGraphicsContext.classed(\"showLinesOnAxis\", showLinesOnX);\r\n this.y1AxisGraphicsContext.classed(\"showLinesOnAxis\", showLinesOnY);\r\n\r\n this.xAxisGraphicsContext.classed(\"hideLinesOnAxis\", !showLinesOnX);\r\n this.y1AxisGraphicsContext.classed(\"hideLinesOnAxis\", !showLinesOnY);\r\n this.interactivityService = createInteractivityService(this.hostServices);\r\n\r\n this.legend = createLegend(\r\n element,\r\n this.interactivity && this.interactivity.isInteractiveLegend,\r\n this.interactivityService,\r\n true);\r\n\r\n this.mainGraphicsG = this.axisGraphicsContextScrollable\r\n .append(\"g\")\r\n .classed(EnhancedScatterChart.MainGraphicsContextClassName, true);\r\n\r\n this.mainGraphicsSVGSelection = this.mainGraphicsG.append(\"svg\");\r\n this.mainGraphicsContext = this.mainGraphicsSVGSelection.append(\"g\");\r\n\r\n this.svgDefaultImage = \"\";\r\n this.keyArray = [];\r\n }\r\n\r\n private adjustMargins(): void {\r\n // Adjust margins if ticks are not going to be shown on either axis\r\n var xAxis = this.element.find(\".x.axis\");\r\n\r\n if (AxisHelper.getRecommendedNumberOfTicksForXAxis(this.viewportIn.width) === 0\r\n && AxisHelper.getRecommendedNumberOfTicksForYAxis(this.viewportIn.height) === 0) {\r\n this.margin = {\r\n top: 0,\r\n right: 0,\r\n bottom: 0,\r\n left: 0\r\n };\r\n\r\n xAxis.hide();\r\n } else {\r\n xAxis.show();\r\n }\r\n }\r\n\r\n private getValueAxisProperties(dataViewMetadata: DataViewMetadata, axisTitleOnByDefault?: boolean): DataViewObject {\r\n var toReturn: DataViewObject = {};\r\n\r\n if (!dataViewMetadata) {\r\n return toReturn;\r\n }\r\n\r\n var objects: DataViewObjects = dataViewMetadata.objects;\r\n\r\n if (objects) {\r\n var 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 axisColor: valueAxisObject[\"axisColor\"],\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 labelDisplayUnits: valueAxisObject[\"labelDisplayUnits\"],\r\n };\r\n }\r\n }\r\n return toReturn;\r\n }\r\n\r\n private getCategoryAxisProperties(dataViewMetadata: DataViewMetadata, axisTitleOnByDefault?: boolean): DataViewObject {\r\n var toReturn: DataViewObject = {};\r\n\r\n if (!dataViewMetadata) {\r\n return toReturn;\r\n }\r\n\r\n var objects: DataViewObjects = dataViewMetadata.objects;\r\n\r\n if (objects) {\r\n var 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 axisColor: categoryAxisObject[\"axisColor\"],\r\n start: categoryAxisObject[\"start\"],\r\n end: categoryAxisObject[\"end\"],\r\n showAxisTitle: categoryAxisObject[\"showAxisTitle\"] == null\r\n ? axisTitleOnByDefault : categoryAxisObject[\"showAxisTitle\"],\r\n axisStyle: categoryAxisObject[\"axisStyle\"],\r\n labelDisplayUnits: categoryAxisObject[\"labelDisplayUnits\"]\r\n };\r\n }\r\n }\r\n\r\n return toReturn;\r\n }\r\n\r\n public static converter(\r\n dataView: DataView,\r\n colorPalette: IDataColorPalette,\r\n interactivityService?: IInteractivityService,\r\n categoryAxisProperties?: DataViewObject,\r\n valueAxisProperties?: DataViewObject): EnhancedScatterChartData {\r\n\r\n if (!dataView) {\r\n return EnhancedScatterChart.getDefaultData();\r\n }\r\n\r\n var categoryValues: any[],\r\n categoryFormatter: IValueFormatter,\r\n categoryObjects: DataViewObjects[],\r\n categoryIdentities: DataViewScopeIdentity[],\r\n categoryQueryName: string,\r\n dataViewCategorical: DataViewCategorical = dataView.categorical,\r\n dataViewMetadata: DataViewMetadata = dataView.metadata,\r\n categories: DataViewCategoryColumn[] = dataViewCategorical.categories || [],\r\n dataValues: DataViewValueColumns = dataViewCategorical.values,\r\n hasDynamicSeries: boolean = !!dataValues.source,\r\n grouped: DataViewValueColumnGroup[] = dataValues.grouped(),\r\n dvSource = dataValues.source,\r\n scatterMetadata = EnhancedScatterChart.getMetadata(categories, grouped, dvSource),\r\n categoryIndex: number = scatterMetadata.idx.category,\r\n useShape: boolean = scatterMetadata.idx.image >= 0,\r\n useCustomColor: boolean = scatterMetadata.idx.colorFill >= 0;\r\n\r\n if (dataViewCategorical.categories &&\r\n dataViewCategorical.categories.length > 0 &&\r\n dataViewCategorical.categories[categoryIndex]) {\r\n\r\n var mainCategory: DataViewCategoryColumn = dataViewCategorical.categories[categoryIndex];\r\n\r\n categoryValues = mainCategory.values;\r\n\r\n categoryFormatter = valueFormatter.create({\r\n format: valueFormatter.getFormatString(\r\n mainCategory.source,\r\n EnhancedScatterChart.Properties[\"general\"][\"formatString\"]),\r\n value: categoryValues[0],\r\n value2: categoryValues[categoryValues.length - 1]\r\n });\r\n\r\n categoryIdentities = mainCategory.identity;\r\n categoryObjects = mainCategory.objects;\r\n categoryQueryName = mainCategory.source ? mainCategory.source.queryName : null;\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 var dataLabelsSettings = dataLabelUtils.getDefaultPointLabelSettings(),\r\n fillPoint = false,\r\n backdrop = { show: false, url: \"\" },\r\n crosshair = false,\r\n outline = false,\r\n defaultDataPointColor: string = \"\",\r\n showAllDataPoints = true;\r\n\r\n if (dataViewMetadata && dataViewMetadata.objects) {\r\n var objects = dataViewMetadata.objects;\r\n\r\n defaultDataPointColor = DataViewObjects.getFillColor(\r\n objects,\r\n EnhancedScatterChart.Properties[\"dataPoint\"][\"defaultColor\"]);\r\n\r\n showAllDataPoints = DataViewObjects.getValue<boolean>(\r\n objects,\r\n EnhancedScatterChart.Properties[\"dataPoint\"][\"showAllDataPoints\"]);\r\n\r\n var labelsObj = objects[\"categoryLabels\"];\r\n if (labelsObj) {\r\n dataLabelsSettings.show = (labelsObj[\"show\"] !== undefined)\r\n ? <boolean>labelsObj[\"show\"] : dataLabelsSettings.show;\r\n\r\n dataLabelsSettings.fontSize = (labelsObj[\"fontSize\"] !== undefined)\r\n ? <number>labelsObj[\"fontSize\"] : dataLabelsSettings.fontSize;\r\n\r\n if (labelsObj[\"color\"] !== undefined) {\r\n dataLabelsSettings.labelColor = (<Fill>labelsObj[\"color\"]).solid.color;\r\n }\r\n }\r\n\r\n fillPoint = DataViewObjects.getValue<boolean>(\r\n objects,\r\n EnhancedScatterChart.Properties[\"fillPoint\"][\"show\"],\r\n fillPoint);\r\n\r\n var backdropObject = objects[\"backdrop\"];\r\n if (backdropObject !== undefined) {\r\n backdrop.show = <boolean>backdropObject[\"show\"];\r\n if (backdrop.show) {\r\n backdrop.url = <string>backdropObject[\"url\"];\r\n }\r\n }\r\n\r\n var crosshairObject = objects[\"crosshair\"];\r\n if (crosshairObject !== undefined) {\r\n crosshair = <boolean>crosshairObject[\"show\"];\r\n }\r\n\r\n var outlineObject = objects[\"outline\"];\r\n if (outlineObject !== undefined) {\r\n outline = <boolean>outlineObject[\"show\"];\r\n }\r\n }\r\n\r\n var dataPoints = EnhancedScatterChart.createDataPoints(\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 hasDynamicSeries,\r\n dataLabelsSettings,\r\n defaultDataPointColor,\r\n categoryQueryName);\r\n\r\n if (interactivityService) {\r\n interactivityService.applySelectionStateToData(dataPoints);\r\n }\r\n\r\n var legendItems: LegendDataPoint[] = [];\r\n\r\n if (hasDynamicSeries) {\r\n var formatString: string = valueFormatter.getFormatString(\r\n dvSource,\r\n EnhancedScatterChart.Properties[\"general\"][\"formatString\"]);\r\n\r\n legendItems = EnhancedScatterChart.createSeriesLegend(\r\n dataValues,\r\n colorPalette,\r\n dataValues,\r\n formatString,\r\n defaultDataPointColor);\r\n }\r\n\r\n var legendTitle: string = dataValues && dvSource\r\n ? dvSource.displayName\r\n : \"\";\r\n\r\n if (!legendTitle) {\r\n legendTitle = categories &&\r\n categories[categoryIndex] &&\r\n categories[categoryIndex].source &&\r\n categories[categoryIndex].source.displayName\r\n ? categories[categoryIndex].source.displayName : \"\";\r\n }\r\n\r\n var legendData = { title: legendTitle, dataPoints: legendItems };\r\n\r\n var sizeRange = EnhancedScatterChart.getSizeRangeForGroups(grouped, scatterMetadata.idx.size);\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 (dataPoints && dataPoints[0]) {\r\n var point = dataPoints[0];\r\n if (point.backdrop != null) {\r\n backdrop.show = true;\r\n backdrop.url = point.backdrop;\r\n }\r\n if (point.xStart != null) {\r\n categoryAxisProperties[\"start\"] = point.xStart;\r\n }\r\n if (point.xEnd != null) {\r\n categoryAxisProperties[\"end\"] = point.xEnd;\r\n }\r\n if (point.yStart != null) {\r\n valueAxisProperties[\"start\"] = point.yStart;\r\n }\r\n if (point.yEnd != null) {\r\n valueAxisProperties[\"end\"] = point.yEnd;\r\n }\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 legendData: legendData,\r\n axesLabels: scatterMetadata.axesLabels,\r\n selectedIds: [],\r\n size: scatterMetadata.cols.size,\r\n sizeRange: sizeRange,\r\n dataLabelsSettings: dataLabelsSettings,\r\n defaultDataPointColor: defaultDataPointColor,\r\n hasDynamicSeries: hasDynamicSeries,\r\n showAllDataPoints: showAllDataPoints,\r\n fillPoint: fillPoint,\r\n useShape: useShape,\r\n useCustomColor: useCustomColor,\r\n backdrop: backdrop,\r\n crosshair: crosshair,\r\n outline: outline\r\n };\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 var legendItems: LegendDataPoint[] = [],\r\n grouped: DataViewValueColumnGroup[] = dataValues.grouped(),\r\n colorHelper: ColorHelper = new ColorHelper(\r\n colorPalette,\r\n EnhancedScatterChart.Properties[\"dataPoint\"][\"fill\"],\r\n defaultDataPointColor);\r\n\r\n for (var i = 0, len = grouped.length; i < len; i++) {\r\n var grouping: DataViewValueColumnGroup = grouped[i],\r\n selectionId: SelectionId,\r\n color: string;\r\n\r\n color = colorHelper.getColorForSeriesValue(grouping.objects, dataValues.identityFields, grouping.name);\r\n\r\n selectionId = grouping.identity\r\n ? SelectionId.createWithId(grouping.identity)\r\n : SelectionId.createNull();\r\n\r\n legendItems.push({\r\n color: color,\r\n icon: LegendIcon.Circle,\r\n label: valueFormatter.format(grouping.name, formatString),\r\n identity: selectionId,\r\n selected: false,\r\n });\r\n }\r\n\r\n return legendItems;\r\n }\r\n\r\n private static getSizeRangeForGroups(\r\n dataViewValueGroups: DataViewValueColumnGroup[],\r\n sizeColumnIndex: number): NumberRange {\r\n\r\n var result: NumberRange = {};\r\n\r\n if (dataViewValueGroups) {\r\n dataViewValueGroups.forEach((group) => {\r\n var sizeColumn: DataViewValueColumn = EnhancedScatterChart.getMeasureValue(sizeColumnIndex, group.values),\r\n currentRange: NumberRange = AxisHelper.getRangeForColumn(sizeColumn);\r\n\r\n if (result.min == null || result.min > currentRange.min) {\r\n result.min = currentRange.min;\r\n }\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\r\n return result;\r\n }\r\n\r\n private static getMetadata(\r\n categories: DataViewCategoryColumn[],\r\n grouped: DataViewValueColumnGroup[],\r\n source: DataViewMetadataColumn): EnhancedScatterChartMeasureMetadata {\r\n\r\n var categoryIndex: number = getCategoryIndexOfRole(categories, EnhancedScatterChart.ColumnCategory),\r\n colorFillIndex: number = getCategoryIndexOfRole(categories, EnhancedScatterChart.ColumnColorFill),\r\n imageIndex: number = getCategoryIndexOfRole(categories, EnhancedScatterChart.ColumnImage),\r\n backdropIndex: number = getCategoryIndexOfRole(categories, EnhancedScatterChart.ColumnBackdrop),\r\n xIndex: number = getMeasureIndexOfRole(grouped, EnhancedScatterChart.ColumnX),\r\n yIndex: number = getMeasureIndexOfRole(grouped, EnhancedScatterChart.ColumnY),\r\n sizeIndex: number = getMeasureIndexOfRole(grouped, EnhancedScatterChart.ColumnSize),\r\n shapeIndex: number = getMeasureIndexOfRole(grouped, EnhancedScatterChart.ColumnShape),\r\n rotationIndex: number = getMeasureIndexOfRole(grouped, EnhancedScatterChart.ColumnRotation),\r\n xStartIndex: number = getMeasureIndexOfRole(grouped, EnhancedScatterChart.ColumnXStart),\r\n xEndIndex: number = getMeasureIndexOfRole(grouped, EnhancedScatterChart.ColumnXEnd),\r\n yStartIndex: number = getMeasureIndexOfRole(grouped, EnhancedScatterChart.ColumnYStart),\r\n yEndIndex: number = getMeasureIndexOfRole(grouped, EnhancedScatterChart.ColumnYEnd),\r\n xCol: DataViewMetadataColumn,\r\n yCol: DataViewMetadataColumn,\r\n sizeCol: DataViewMetadataColumn,\r\n xAxisLabel: string = \"\",\r\n yAxisLabel: string = \"\";\r\n\r\n if (grouped && grouped.length) {\r\n var firstGroup: DataViewValueColumnGroup = grouped[0];\r\n\r\n if (xIndex >= 0) {\r\n xCol = firstGroup.values[xIndex].source;\r\n xAxisLabel = firstGroup.values[xIndex].source.displayName;\r\n }\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\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 category: categoryIndex,\r\n x: xIndex,\r\n y: yIndex,\r\n size: sizeIndex,\r\n colorFill: colorFillIndex,\r\n shape: shapeIndex,\r\n image: imageIndex,\r\n rotation: rotationIndex,\r\n backdrop: backdropIndex,\r\n xStart: xStartIndex,\r\n xEnd: xEndIndex,\r\n yStart: yStartIndex,\r\n yEnd: yEndIndex\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 public static createLazyFormattedCategory(formatter: IValueFormatter, value: string): Lazy<string> {\r\n return new Lazy(() => formatter.format(value));\r\n }\r\n\r\n private static createDataPoints(\r\n dataValues: DataViewValueColumns,\r\n metadata: EnhancedScatterChartMeasureMetadata,\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 hasDynamicSeries: boolean,\r\n labelSettings: PointDataLabelsSettings,\r\n defaultDataPointColor?: string,\r\n categoryQueryName?: string): EnhancedScatterChartDataPoint[] {\r\n\r\n var dataPoints: EnhancedScatterChartDataPoint[] = [],\r\n colorHelper: ColorHelper,\r\n indicies: EnhancedScatterChartMeasureMetadataIndexes = metadata.idx,\r\n formatStringProp: DataViewObjectPropertyIdentifier,\r\n dataValueSource: DataViewMetadataColumn = dataValues.source,\r\n grouped: DataViewValueColumnGroup[] = dataValues.grouped(),\r\n fontSizeInPx: string = PixelConverter.fromPoint(labelSettings.fontSize);\r\n\r\n formatStringProp = EnhancedScatterChart.Properties[\"general\"][\"formatString\"];\r\n\r\n colorHelper = new ColorHelper(\r\n colorPalette,\r\n EnhancedScatterChart.Properties[\"dataPoint\"][\"fill\"],\r\n defaultDataPointColor);\r\n\r\n for (var categoryIdx = 0, ilen = categoryValues.length; categoryIdx < ilen; categoryIdx++) {\r\n var categoryValue = categoryValues[categoryIdx];\r\n\r\n for (var seriesIdx = 0, len = grouped.length; seriesIdx < len; seriesIdx++) {\r\n var measureColorFill: DataViewCategoricalColumn = categories[indicies.colorFill],\r\n measureImage: DataViewCategoricalColumn = categories[indicies.image],\r\n measureBackdrop: DataViewCategoricalColumn = categories[indicies.backdrop];\r\n\r\n var grouping: DataViewValueColumnGroup = grouped[seriesIdx],\r\n seriesValues: DataViewValueColumn[] = grouping.values,\r\n measureX: DataViewValueColumn = EnhancedScatterChart.getMeasureValue(indicies.x, seriesValues),\r\n measureY: DataViewValueColumn = EnhancedScatterChart.getMeasureValue(indicies.y, seriesValues),\r\n measureSize: DataViewValueColumn = EnhancedScatterChart.getMeasureValue(indicies.size, seriesValues),\r\n measureShape: DataViewValueColumn = EnhancedScatterChart.getMeasureValue(indicies.shape, seriesValues),\r\n measureRotation: DataViewValueColumn = EnhancedScatterChart.getMeasureValue(indicies.rotation, seriesValues),\r\n measureXStart: DataViewValueColumn = EnhancedScatterChart.getMeasureValue(indicies.xStart, seriesValues),\r\n measureXEnd: DataViewValueColumn = EnhancedScatterChart.getMeasureValue(indicies.xEnd, seriesValues),\r\n measureYStart: DataViewValueColumn = EnhancedScatterChart.getMeasureValue(indicies.yStart, seriesValues),\r\n measureYEnd: DataViewValueColumn = EnhancedScatterChart.getMeasureValue(indicies.yEnd, seriesValues);\r\n\r\n //TODO: need to update (refactor) these lines below.\r\n var xVal = measureX && measureX.values && !isNaN(measureX.values[categoryIdx]) ? measureX.values[categoryIdx] : null,\r\n yVal = measureY && measureY.values && !isNaN(measureY.values[categoryIdx]) ? measureY.values[categoryIdx] : 0;\r\n\r\n var hasNullValue = (xVal == null) || (yVal == null);\r\n\r\n if (hasNullValue) {\r\n continue;\r\n }\r\n\r\n var size: number,\r\n colorFill: string,\r\n shapeSymbolType: (number) => string,\r\n image: string,\r\n rotation: number,\r\n backdrop: string,\r\n xStart: number,\r\n xEnd: number,\r\n yStart: number,\r\n yEnd: number,\r\n color: string;\r\n\r\n size = EnhancedScatterChart.getValueFromDataViewValueColumnById(measureSize, categoryIdx);\r\n\r\n colorFill = EnhancedScatterChart.getValueFromDataViewValueColumnById(\r\n measureColorFill, categoryIdx);\r\n\r\n shapeSymbolType = EnhancedScatterChart.getCustomSymbolType(\r\n EnhancedScatterChart.getValueFromDataViewValueColumnById(measureShape, categoryIdx));\r\n\r\n image = EnhancedScatterChart.getValueFromDataViewValueColumnById(measureImage, categoryIdx);\r\n rotation = EnhancedScatterChart.getNumberFromDataViewValueColumnById(measureRotation, categoryIdx);\r\n backdrop = EnhancedScatterChart.getValueFromDataViewValueColumnById(measureBackdrop, categoryIdx);\r\n xStart = EnhancedScatterChart.getValueFromDataViewValueColumnById(measureXStart, categoryIdx);\r\n xEnd = EnhancedScatterChart.getValueFromDataViewValueColumnById(measureXEnd, categoryIdx);\r\n yStart = EnhancedScatterChart.getValueFromDataViewValueColumnById(measureYStart, categoryIdx);\r\n yEnd = EnhancedScatterChart.getValueFromDataViewValueColumnById(measureYEnd, categoryIdx);\r\n\r\n if (hasDynamicSeries) {\r\n color = colorHelper.getColorForSeriesValue(grouping.objects, dataValues.identityFields, grouping.name);\r\n } else {\r\n // If we have no Size measure then use a blank query name\r\n var measureSource: string = (measureSize != null)\r\n ? measureSize.source.queryName\r\n : \"\";\r\n\r\n color = colorHelper.getColorForMeasure(categoryObjects && categoryObjects[categoryIdx], measureSource);\r\n }\r\n\r\n var category: DataViewCategoryColumn = categories && categories.length > 0\r\n ? categories[indicies.category]\r\n : null;\r\n\r\n var identity: SelectionId = SelectionIdBuilder.builder()\r\n .withCategory(category, categoryIdx)\r\n .withSeries(dataValues, grouping)\r\n .createSelectionId();\r\n\r\n //TODO: need to refactor these lines below.\r\n var seriesData: TooltipSeriesDataItem[] = [];\r\n if (dataValueSource) {\r\n // Dynamic series\r\n seriesData.push({ value: grouping.name, metadata: { source: dataValueSource, values: [] } });\r\n }\r\n\r\n if (measureX) {\r\n seriesData.push({ value: xVal, metadata: measureX });\r\n }\r\n\r\n if (measureY) {\r\n seriesData.push({ value: yVal, metadata: measureY });\r\n }\r\n\r\n if (measureSize && measureSize.values && measureSize.values.length > 0) {\r\n seriesData.push({ value: measureSize.values[categoryIdx], metadata: measureSize });\r\n }\r\n\r\n if (measureColorFill && measureColorFill.values && measureColorFill.values.length > 0) {\r\n seriesData.push({ value: measureColorFill.values[categoryIdx], metadata: measureColorFill });\r\n }\r\n\r\n if (measureShape && measureShape.values && measureShape.values.length > 0) {\r\n seriesData.push({ value: measureShape.values[categoryIdx], metadata: measureShape });\r\n }\r\n\r\n if (measureImage && measureImage.values && measureImage.values.length > 0) {\r\n seriesData.push({ value: measureImage.values[categoryIdx], metadata: measureImage });\r\n }\r\n\r\n if (measureRotation && measureRotation.values && measureRotation.values.length > 0) {\r\n seriesData.push({ value: measureRotation.values[categoryIdx], metadata: measureRotation });\r\n }\r\n\r\n if (measureBackdrop && measureBackdrop.values && measureBackdrop.values.length > 0) {\r\n seriesData.push({ value: measureBackdrop.values[categoryIdx], metadata: measureBackdrop });\r\n }\r\n\r\n if (measureXStart && measureXStart.values && measureXStart.values.length > 0) {\r\n seriesData.push({ value: measureXStart.values[categoryIdx], metadata: measureXStart });\r\n }\r\n\r\n if (measureXEnd && measureXEnd.values && measureXEnd.values.length > 0) {\r\n seriesData.push({ value: measureXEnd.values[categoryIdx], metadata: measureXEnd });\r\n }\r\n\r\n if (measureYStart && measureYStart.values && measureYStart.values.length > 0) {\r\n seriesData.push({ value: measureYStart.values[categoryIdx], metadata: measureYStart });\r\n }\r\n\r\n if (measureYEnd && measureYEnd.values && measureYEnd.values.length > 0) {\r\n seriesData.push({ value: measureYEnd.values[categoryIdx], metadata: measureYEnd });\r\n }\r\n\r\n var tooltipInfo: TooltipDataItem[] = TooltipBuilder.createTooltipInfo(\r\n formatStringProp, /* formatStringProp */\r\n undefined, /* dataViewCat */\r\n categoryValue, /* categoryValue */\r\n null, /* value */\r\n category ? [category] : undefined, /* categories */\r\n seriesData, /* seriesData */\r\n undefined /* seriesIndex */); \r\n\r\n var dataPoint: EnhancedScatterChartDataPoint = {\r\n x: xVal,\r\n y: yVal,\r\n size: size,\r\n radius: { sizeMeasure: measureSize, index: categoryIdx },\r\n fill: color,\r\n formattedCategory: this.createLazyFormattedCategory(categoryFormatter, categoryValue),\r\n selected: false,\r\n identity: identity,\r\n tooltipInfo: tooltipInfo,\r\n labelFill: labelSettings.labelColor,\r\n labelFontSize: fontSizeInPx,\r\n contentPosition: 8, //ContentPositions.MiddleLeft\r\n colorFill: colorFill,\r\n shapeSymbolType: shapeSymbolType,\r\n svgurl: image,\r\n rotation: rotation,\r\n backdrop: backdrop,\r\n xStart: xStart,\r\n xEnd: xEnd,\r\n yStart: yStart,\r\n yEnd: yEnd\r\n };\r\n\r\n dataPoints.push(dataPoint);\r\n }\r\n }\r\n\r\n return dataPoints;\r\n }\r\n\r\n private static getMeasureValue(measureIndex: number, seriesValues: DataViewValueColumn[]): DataViewValueColumn {\r\n if (seriesValues && measureIndex >= 0) {\r\n return seriesValues[measureIndex];\r\n }\r\n\r\n return null;\r\n }\r\n\r\n private static getNumberFromDataViewValueColumnById(dataViewValueColumn: DataViewCategoricalColumn, index: number): number {\r\n var value: number = EnhancedScatterChart.getValueFromDataViewValueColumnById(dataViewValueColumn, index);\r\n\r\n return value && !isNaN(value)\r\n ? value\r\n : 0;\r\n }\r\n\r\n private static getValueFromDataViewValueColumnById(dataViewValueColumn: DataViewCategoricalColumn, index: number): any {\r\n return dataViewValueColumn && dataViewValueColumn.values\r\n ? dataViewValueColumn.values[index]\r\n : null;\r\n }\r\n\r\n private static getDefaultData(): EnhancedScatterChartData {\r\n return {\r\n xCol: undefined,\r\n yCol: undefined,\r\n dataPoints: [],\r\n legendData: { dataPoints: [] },\r\n axesLabels: { x: \"\", y: \"\" },\r\n selectedIds: [],\r\n sizeRange: [],\r\n dataLabelsSettings: dataLabelUtils.getDefaultPointLabelSettings(),\r\n defaultDataPointColor: null,\r\n hasDynamicSeries: false,\r\n useShape: false,\r\n useCustomColor: false,\r\n };\r\n }\r\n\r\n public setData(dataViews: DataView[]) {\r\n this.data = EnhancedScatterChart.getDefaultData();\r\n\r\n if (dataViews && dataViews.length > 0) {\r\n var dataView: DataView = dataViews[0];\r\n\r\n if (dataView) {\r\n this.categoryAxisProperties = this.getCategoryAxisProperties(dataView.metadata, true);\r\n this.valueAxisProperties = this.getValueAxisProperties(dataView.metadata, true);\r\n\r\n this.dataView = dataView;\r\n\r\n if (dataView.categorical && dataView.categorical.values) {\r\n this.data = EnhancedScatterChart.converter(\r\n dataView,\r\n this.colors,\r\n this.interactivityService,\r\n this.categoryAxisProperties,\r\n this.valueAxisProperties);\r\n }\r\n }\r\n }\r\n }\r\n\r\n public update(options: VisualUpdateOptions) {\r\n\r\n debug.assertValue(options, \"options\");\r\n\r\n var dataViews: DataView[] = this.dataViews = options.dataViews;\r\n\r\n this.viewport = _.clone(options.viewport);\r\n\r\n if (!dataViews) {\r\n return;\r\n }\r\n\r\n if (dataViews && dataViews.length > 0) {\r\n var warnings = getInvalidValueWarnings(\r\n dataViews,\r\n false /*supportsNaN*/,\r\n false /*supportsNegativeInfinity*/,\r\n false /*supportsPositiveInfinity*/);\r\n\r\n if (warnings && warnings.length > 0)\r\n this.hostServices.setWarnings(warnings);\r\n\r\n this.populateObjectProperties(dataViews);\r\n }\r\n\r\n this.setData(dataViews);\r\n\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.options.interactivity && this.options.interactivity.isInteractiveLegend)) {\r\n this.renderLegend();\r\n }\r\n\r\n this.render(options.suppressAnimations);\r\n\r\n }\r\n\r\n private populateObjectProperties(dataViews: DataView[]) {\r\n if (dataViews && dataViews.length > 0) {\r\n var dataViewMetadata = dataViews[0].metadata;\r\n\r\n if (dataViewMetadata) {\r\n this.legendObjectProperties = DataViewObjects.getObject(dataViewMetadata.objects, \"legend\", {});\r\n }\r\n else {\r\n this.legendObjectProperties = {};\r\n }\r\n this.categoryAxisProperties = this.getCategoryAxisProperties(dataViewMetadata);\r\n this.valueAxisProperties = this.getValueAxisProperties(dataViewMetadata);\r\n var axisPosition = this.valueAxisProperties[\"position\"];\r\n this.yAxisOrientation = axisPosition ? axisPosition.toString() : yAxisPosition.left;\r\n }\r\n }\r\n\r\n private renderLegend(): void {\r\n var legendData: LegendData = { title: \"\", dataPoints: [] };\r\n var legend: ILegend = this.legend;\r\n\r\n this.layerLegendData = this.data.legendData;\r\n if (this.layerLegendData) {\r\n legendData.title = this.layerLegendData.title || \"\";\r\n legendData.dataPoints = legendData.dataPoints.concat(this.layerLegendData.dataPoints || []);\r\n legendData.fontSize = this.legendLabelFontSize ? this.legendLabelFontSize : EnhancedScatterChart.LegendLabelFontSizeDefault;\r\n if (this.layerLegendData.grouped) {\r\n legendData.grouped = true;\r\n }\r\n }\r\n\r\n var legendProperties = this.legendObjectProperties;\r\n\r\n if (legendProperties) {\r\n LegendData.update(legendData, legendProperties);\r\n var position = <string>legendProperties[legendProps.position];\r\n\r\n if (position)\r\n legend.changeOrientation(LegendPosition[position]);\r\n }\r\n else {\r\n legend.changeOrientation(LegendPosition.Top);\r\n }\r\n\r\n if (legendData.dataPoints.length === 1 && !legendData.grouped) {\r\n legendData.dataPoints = [];\r\n }\r\n\r\n var viewport = this.viewport;\r\n legend.drawLegend(legendData, { height: viewport.height, width: viewport.width });\r\n Legend.positionChartArea(this.svg, legend);\r\n }\r\n\r\n private shouldRenderAxis(axisProperties: IAxisProperties, propertyName: string = \"show\"): boolean {\r\n if (!axisProperties) {\r\n return false;\r\n }\r\n else if (axisProperties.isCategoryAxis && (!this.categoryAxisProperties || this.categoryAxisProperties[propertyName] == null || this.categoryAxisProperties[propertyName])) {\r\n return axisProperties.values && axisProperties.values.length > 0;\r\n }\r\n else if (!axisProperties.isCategoryAxis && (!this.valueAxisProperties || this.valueAxisProperties[propertyName] == null || this.valueAxisProperties[propertyName])) {\r\n return axisProperties.values && axisProperties.values.length > 0;\r\n }\r\n\r\n return false;\r\n }\r\n\r\n private getMaxMarginFactor(): number {\r\n return this.options.style.maxMarginFactor || 0.25;\r\n }\r\n\r\n private adjustViewportbyBackdrop(): void {\r\n var img = new Image();\r\n var that = this;\r\n img.src = this.data.backdrop.url;\r\n img.onload = function () {\r\n if (that.oldBackdrop !== this.src) {\r\n that.render(true);\r\n that.oldBackdrop = this.src;\r\n }\r\n };\r\n\r\n if (img.width > 0 && img.height > 0) {\r\n if (img.width * this.viewportIn.height < this.viewportIn.width * img.height) {\r\n var deltaWidth = this.viewportIn.width - this.viewportIn.height * img.width / img.height;\r\n this.viewport = { width: this.viewport.width - deltaWidth, height: this.viewport.height };\r\n } else {\r\n var deltaHeight = this.viewportIn.height - this.viewportIn.width * img.height / img.width;\r\n this.viewport = { width: this.viewport.width, height: this.viewport.height - deltaHeight };\r\n }\r\n }\r\n }\r\n\r\n public render(suppressAnimations: boolean): void {\r\n this.viewport.height -= this.legendViewport.height;\r\n this.viewport.width -= this.legendViewport.width;\r\n\r\n if (this.viewportIn.width === 0 || this.viewportIn.height === 0) {\r\n return;\r\n }\r\n\r\n var maxMarginFactor = this.getMaxMarginFactor();\r\n this.leftRightMarginLimit = this.viewport.width * maxMarginFactor;\r\n var bottomMarginLimit = this.bottomMarginLimit = Math.max(25, Math.ceil(this.viewport.height * maxMarginFactor));\r\n\r\n // reset defaults\r\n this.margin.top = 8;\r\n this.margin.bottom = bottomMarginLimit;\r\n this.margin.right = 0;\r\n\r\n this.calculateAxes(\r\n this.categoryAxisProperties,\r\n this.valueAxisProperties,\r\n EnhancedScatterChart.TextProperties,\r\n true);\r\n\r\n this.yAxisIsCategorical = this.yAxisProperties.isCategoryAxis;\r\n this.hasCategoryAxis = this.yAxisIsCategorical ? this.yAxisProperties && this.yAxisProperties.values.length > 0 : this.xAxisProperties && this.xAxisProperties.values.length > 0;\r\n\r\n var renderXAxis = this.shouldRenderAxis(this.xAxisProperties);\r\n var renderY1Axis = this.shouldRenderAxis(this.yAxisProperties);\r\n\r\n var mainAxisScale;\r\n this.isXScrollBarVisible = false;\r\n this.isYScrollBarVisible = false;\r\n var tickLabelMargins;\r\n var axisLabels: ChartAxesLabels;\r\n var chartHasAxisLabels: boolean;\r\n\r\n var yAxisOrientation = this.yAxisOrientation;\r\n var showY1OnRight = yAxisOrientation === yAxisPosition.right;\r\n\r\n this.calculateAxes(\r\n this.categoryAxisProperties,\r\n this.valueAxisProperties,\r\n EnhancedScatterChart.TextProperties, true);\r\n\r\n var doneWithMargins = false,\r\n maxIterations = 2,\r\n numIterations = 0;\r\n\r\n while (!doneWithMargins && numIterations < maxIterations) {\r\n numIterations++;\r\n\r\n tickLabelMargins = AxisHelper.getTickLabelMargins(\r\n { width: this.viewportIn.width, height: this.viewport.height },\r\n this.leftRightMarginLimit,\r\n measureSvgTextWidth,\r\n measureSvgTextHeight,\r\n { x: this.xAxisProperties, y1: this.yAxisProperties },\r\n this.bottomMarginLimit,\r\n EnhancedScatterChart.TextProperties,\r\n this.isXScrollBarVisible || this.isYScrollBarVisible,\r\n showY1OnRight,\r\n renderXAxis,\r\n renderY1Axis,\r\n false);\r\n\r\n // We look at the y axes as main and second sides, if the y axis orientation is right so the main side represents the right side\r\n var maxMainYaxisSide = showY1OnRight ? tickLabelMargins.yRight : tickLabelMargins.yLeft,\r\n maxSecondYaxisSide = showY1OnRight ? tickLabelMargins.yLeft : tickLabelMargins.yRight,\r\n xMax = tickLabelMargins.xMax;\r\n\r\n maxMainYaxisSide += 10;\r\n maxSecondYaxisSide += 10;\r\n xMax += 12;\r\n if (showY1OnRight && renderY1Axis) {\r\n maxSecondYaxisSide += 20;\r\n }\r\n\r\n if (!showY1OnRight && renderY1Axis) {\r\n maxMainYaxisSide += 20;\r\n }\r\n\r\n this.addUnitTypeToAxisLabel(this.xAxisProperties, this.yAxisProperties);\r\n\r\n axisLabels = { x: this.xAxisProperties.axisLabel, y: this.yAxisProperties.axisLabel, y2: null };\r\n chartHasAxisLabels = (axisLabels.x != null) || (axisLabels.y != null || axisLabels.y2 != null);\r\n\r\n if (axisLabels.x != null)\r\n xMax += 18;\r\n\r\n if (axisLabels.y != null)\r\n maxMainYaxisSide += 20;\r\n\r\n if (axisLabels.y2 != null)\r\n maxSecondYaxisSide += 20;\r\n\r\n this.margin.left = showY1OnRight ? maxSecondYaxisSide : maxMainYaxisSide;\r\n this.margin.right = showY1OnRight ? maxMainYaxisSide : maxSecondYaxisSide;\r\n this.margin.bottom = xMax;\r\n\r\n // re-calculate the axes with the new margins\r\n var previousTickCountY1 = this.yAxisProperties.values.length;\r\n\r\n this.calculateAxes(\r\n this.categoryAxisProperties,\r\n this.valueAxisProperties,\r\n EnhancedScatterChart.TextProperties,\r\n true);\r\n\r\n // the minor padding adjustments could have affected the chosen tick values, which would then need to calculate margins again\r\n // e.g. [0,2,4,6,8] vs. [0,5,10] the 10 is wider and needs more margin.\r\n if (this.yAxisProperties.values.length === previousTickCountY1)\r\n doneWithMargins = true;\r\n }\r\n // we have to do the above process again since changes are made to viewport.\r\n\r\n if (this.data.backdrop && this.data.backdrop.show && (this.data.backdrop.url !== undefined)) {\r\n this.adjustViewportbyBackdrop();\r\n\r\n doneWithMargins = false;\r\n maxIterations = 2;\r\n numIterations = 0;\r\n\r\n while (!doneWithMargins && numIterations < maxIterations) {\r\n numIterations++;\r\n\r\n tickLabelMargins = AxisHelper.getTickLabelMargins(\r\n { width: this.viewportIn.width, height: this.viewport.height },\r\n this.leftRightMarginLimit,\r\n measureSvgTextWidth,\r\n measureSvgTextHeight,\r\n { x: this.xAxisProperties, y1: this.yAxisProperties },\r\n this.bottomMarginLimit,\r\n EnhancedScatterChart.TextProperties,\r\n this.isXScrollBarVisible || this.isYScrollBarVisible,\r\n showY1OnRight,\r\n renderXAxis,\r\n renderY1Axis,\r\n false);\r\n\r\n // We look at the y axes as main and second sides, if the y axis orientation is right so the main side represents the right side\r\n var maxMainYaxisSide = showY1OnRight ? tickLabelMargins.yRight : tickLabelMargins.yLeft,\r\n maxSecondYaxisSide = showY1OnRight ? tickLabelMargins.yLeft : tickLabelMargins.yRight,\r\n xMax = tickLabelMargins.xMax;\r\n\r\n maxMainYaxisSide += 10;\r\n\r\n if (showY1OnRight && renderY1Axis){\r\n maxSecondYaxisSide += 15;\r\n }\r\n\r\n xMax += 12;\r\n\r\n this.addUnitTypeToAxisLabel(this.xAxisProperties, this.yAxisProperties);\r\n\r\n axisLabels = { x: this.xAxisProperties.axisLabel, y: this.yAxisProperties.axisLabel, y2: null };\r\n chartHasAxisLabels = (axisLabels.x != null) || (axisLabels.y != null || axisLabels.y2 != null);\r\n\r\n if (axisLabels.x != null)\r\n xMax += 18;\r\n\r\n if (axisLabels.y != null)\r\n maxMainYaxisSide += 20;\r\n\r\n if (axisLabels.y2 != null)\r\n maxSecondYaxisSide += 20;\r\n\r\n this.margin.left = showY1OnRight ? maxSecondYaxisSide : maxMainYaxisSide;\r\n this.margin.right = showY1OnRight ? maxMainYaxisSide : maxSecondYaxisSide;\r\n this.margin.bottom = xMax;\r\n\r\n // re-calculate the axes with the new margins\r\n var previousTickCountY1 = this.yAxisProperties.values.length;\r\n\r\n this.calculateAxes(\r\n this.categoryAxisProperties,\r\n this.valueAxisProperties,\r\n EnhancedScatterChart.TextProperties,\r\n true);\r\n\r\n // the minor padding adjustments could have affected the chosen tick values, which would then need to calculate margins again\r\n // e.g. [0,2,4,6,8] vs. [0,5,10] the 10 is wider and needs more margin.\r\n if (this.yAxisProperties.values.length === previousTickCountY1)\r\n doneWithMargins = true;\r\n }\r\n }\r\n\r\n this.renderChart(\r\n mainAxisScale,\r\n this.xAxisProperties,\r\n this.yAxisProperties,\r\n tickLabelMargins,\r\n chartHasAxisLabels,\r\n axisLabels,\r\n suppressAnimations);\r\n\r\n this.updateAxis();\r\n\r\n if (!this.data) {\r\n return;\r\n }\r\n\r\n var data: EnhancedScatterChartData = this.data,\r\n dataPoints: EnhancedScatterChartDataPoint[] = this.data.dataPoints,\r\n hasSelection: boolean = this.interactivityService && this.interactivityService.hasSelection();\r\n\r\n this.mainGraphicsSVGSelection\r\n .attr(\"width\", this.viewportIn.width)\r\n .attr(\"height\", this.viewportIn.height);\r\n\r\n var sortedData: EnhancedScatterChartDataPoint[] = dataPoints.sort((a, b) => {\r\n return b.radius.sizeMeasure\r\n ? (b.radius.sizeMeasure.values[b.radius.index] - a.radius.sizeMeasure.values[a.radius.index])\r\n : 0;\r\n });\r\n\r\n var duration: number = GetAnimationDuration(this.animator, suppressAnimations),\r\n scatterMarkers: D3.UpdateSelection = this.drawScatterMarkers(sortedData, hasSelection, data.sizeRange, duration),\r\n dataLabelsSettings: PointDataLabelsSettings = this.data.dataLabelsSettings;\r\n\r\n if (dataLabelsSettings.show) {\r\n var layout: ILabelLayout,\r\n clonedDataPoints: EnhancedScatterChartDataPoint[],\r\n labels: D3.UpdateSelection;\r\n\r\n layout = this.getEnhanchedScatterChartLabelLayout(dataLabelsSettings, this.viewportIn, data.sizeRange);\r\n\r\n clonedDataPoints = this.cloneDataPoints(dataPoints);\r\n\r\n //fix bug 3863: drawDefaultLabelsForDataPointChart add to datapoints[xxx].size = object , which causes when\r\n //category labels is on and Fill Points option off to fill the points when mouse click occures because of default size\r\n //is set to datapoints.\r\n labels = dataLabelUtils.drawDefaultLabelsForDataPointChart(\r\n clonedDataPoints,\r\n this.mainGraphicsG,\r\n layout,\r\n this.viewportIn);\r\n\r\n if (labels) {\r\n labels.attr(\"transform\", (d: EnhancedScatterChartDataPoint) => {\r\n var size: ISize = <ISize>d.size,\r\n dx: number,\r\n dy: number;\r\n\r\n dx = size.width / EnhancedScatterChart.DataLabelXOffset;\r\n dy = size.height / EnhancedScatterChart.DataLabelYOffset;\r\n\r\n return SVGUtil.translate(dx, dy);\r\n });\r\n }\r\n }\r\n else {\r\n dataLabelUtils.cleanDataLabels(this.mainGraphicsG);\r\n }\r\n\r\n this.renderCrosshair();\r\n\r\n var behaviorOptions: EnhancedScatterBehaviorOptions;\r\n\r\n if (this.interactivityService) {\r\n behaviorOptions = {\r\n dataPointsSelection: scatterMarkers,\r\n data: this.data,\r\n plotContext: this.mainGraphicsSVGSelection,\r\n };\r\n }\r\n\r\n addTooltip(scatterMarkers, (tooltipEvent: TooltipEvent) => tooltipEvent.data.tooltipInfo);\r\n\r\n SVGUtil.flushAllD3TransitionsIfNeeded(this.options);\r\n\r\n if (this.behavior) {\r\n var layerBehaviorOptions: any[] = [behaviorOptions];\r\n\r\n if (this.interactivityService) {\r\n var cbehaviorOptions: CustomVisualBehaviorOptions = {\r\n layerOptions: layerBehaviorOptions,\r\n clearCatcher: this.clearCatcher,\r\n };\r\n\r\n this.interactivityService.bind(dataPoints, this.behavior, cbehaviorOptions);\r\n }\r\n }\r\n }\r\n\r\n private cloneDataPoints(dataPoints: EnhancedScatterChartDataPoint[]): EnhancedScatterChartDataPoint[] {\r\n return dataPoints.map((dataPoint: EnhancedScatterChartDataPoint) => {\r\n return _.clone(dataPoint);\r\n });\r\n }\r\n\r\n private darkenZeroLine(g: D3.Selection): void {\r\n var 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 getCategoryAxisFill(): Fill {\r\n if (this.dataView && this.dataView.metadata.objects) {\r\n var label = this.dataView.metadata.objects[\"categoryAxis\"];\r\n if (label) {\r\n return <Fill>label[\"axisColor\"];\r\n }\r\n }\r\n return { solid: { color: \"#333\" } };\r\n }\r\n\r\n private getEnhanchedScatterChartLabelLayout(labelSettings: PointDataLabelsSettings,\r\n viewport: IViewport,\r\n sizeRange: NumberRange): ILabelLayout {\r\n\r\n var xScale = this.xAxisProperties.scale;\r\n var yScale = this.yAxisProperties.scale;\r\n var fontSizeInPx = fromPoint(labelSettings.fontSize);\r\n var fontFamily: string = LabelTextProperties.fontFamily;\r\n\r\n return {\r\n labelText: (d: EnhancedScatterChartDataPoint) => {\r\n return getLabelFormattedText({\r\n label: d.formattedCategory.getValue(),\r\n fontSize: labelSettings.fontSize,\r\n maxWidth: viewport.width,\r\n });\r\n },\r\n labelLayout: {\r\n x: (d: EnhancedScatterChartDataPoint) => xScale(d.x),\r\n y: (d: EnhancedScatterChartDataPoint) => {\r\n var margin = EnhancedScatterChart.getBubbleRadius(d.radius, sizeRange, viewport) + dataLabelUtils.labelMargin;\r\n\r\n return labelSettings.position === 0 /* Above */\r\n ? yScale(d.y) - margin\r\n : yScale(d.y) + margin;\r\n },\r\n },\r\n filter: (d: EnhancedScatterChartDataPoint) => (d != null && d.formattedCategory.getValue() != null),\r\n style: {\r\n \"fill\": (d: EnhancedScatterChartDataPoint) => d.labelFill,\r\n \"font-size\": fontSizeInPx,\r\n \"font-family\": fontFamily,\r\n },\r\n };\r\n }\r\n\r\n private static getBubbleRadius(\r\n radiusData: EnhancedScatterChartRadiusData,\r\n sizeRange: NumberRange,\r\n viewport: IViewport): number {\r\n\r\n let actualSizeDataRange = null,\r\n bubblePixelAreaSizeRange = null,\r\n measureSize = radiusData.sizeMeasure;\r\n\r\n if (!measureSize) {\r\n return EnhancedScatterChart.BubbleRadius;\r\n }\r\n\r\n let minSize = sizeRange.min ? sizeRange.min : 0,\r\n maxSize = sizeRange.max ? sizeRange.max : 0;\r\n\r\n let min = Math.min(minSize, 0),\r\n max = Math.max(maxSize, 0);\r\n\r\n actualSizeDataRange = {\r\n minRange: min,\r\n maxRange: max,\r\n delta: max - min\r\n };\r\n\r\n bubblePixelAreaSizeRange = EnhancedScatterChart.getBubblePixelAreaSizeRange(\r\n viewport,\r\n EnhancedScatterChart.MinSizeRange,\r\n EnhancedScatterChart.MaxSizeRange);\r\n\r\n if (measureSize.values) {\r\n let sizeValue = measureSize.values[radiusData.index];\r\n\r\n if (sizeValue != null) {\r\n return EnhancedScatterChart.projectSizeToPixels(\r\n sizeValue,\r\n actualSizeDataRange,\r\n bubblePixelAreaSizeRange) / 2;\r\n }\r\n }\r\n\r\n return EnhancedScatterChart.BubbleRadius;\r\n }\r\n\r\n private static getBubblePixelAreaSizeRange(\r\n viewPort: IViewport,\r\n minSizeRange: number,\r\n maxSizeRange: number): EnhancedScatterDataRange {\r\n\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) / EnhancedScatterChart.AreaOf300By300Chart;\r\n }\r\n\r\n let minRange: number = Math.round(minSizeRange * ratio),\r\n maxRange: number = Math.round(maxSizeRange * ratio);\r\n\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 projectSizeToPixels(\r\n size: number,\r\n actualSizeDataRange: EnhancedScatterDataRange,\r\n bubblePixelAreaSizeRange: EnhancedScatterDataRange): number {\r\n\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\r\n projectedSize = EnhancedScatterChart.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 project(\r\n value: number,\r\n actualSizeDataRange: EnhancedScatterDataRange,\r\n bubblePixelAreaSizeRange: EnhancedScatterDataRange): number {\r\n\r\n if (actualSizeDataRange.delta === 0 || bubblePixelAreaSizeRange.delta === 0) {\r\n return (EnhancedScatterChart.rangeContains(actualSizeDataRange, value))\r\n ? bubblePixelAreaSizeRange.minRange\r\n : null;\r\n }\r\n\r\n let relativeX = (value - actualSizeDataRange.minRange) / actualSizeDataRange.delta;\r\n\r\n return bubblePixelAreaSizeRange.minRange + relativeX * bubblePixelAreaSizeRange.delta;\r\n }\r\n\r\n public static rangeContains(range: EnhancedScatterDataRange, value: number): boolean {\r\n return range.minRange <= value && value <= range.maxRange;\r\n }\r\n\r\n private getValueAxisFill(): Fill {\r\n if (this.dataView && this.dataView.metadata.objects) {\r\n var label = this.dataView.metadata.objects[\"valueAxis\"];\r\n\r\n if (label) {\r\n return <Fill>label[\"axisColor\"];\r\n }\r\n }\r\n\r\n return { solid: { color: \"#333\" } };\r\n }\r\n\r\n /**\r\n * Public for testability.\r\n */\r\n public renderCrosshair(): D3.Selection {\r\n if (!this.mainGraphicsSVGSelection) {\r\n return;\r\n }\r\n\r\n this.crosshairCanvasSelection = this.addCrosshairCanvasToDOM(this.mainGraphicsSVGSelection);\r\n\r\n if (this.data && this.data.crosshair) {\r\n this.crosshairVerticalLineSelection = this.addCrosshairLineToDOM(\r\n this.crosshairCanvasSelection, EnhancedScatterChart.CrosshairVerticalLineSelector);\r\n\r\n this.crosshairHorizontalLineSelection = this.addCrosshairLineToDOM(\r\n this.crosshairCanvasSelection, EnhancedScatterChart.CrosshairHorizontalLineSelector);\r\n\r\n this.crosshairTextSelection = this.addCrosshairTextToDOM(this.crosshairCanvasSelection);\r\n\r\n this.bindCrosshairEvents();\r\n }\r\n\r\n return this.crosshairCanvasSelection;\r\n }\r\n\r\n /**\r\n * Public for testability.\r\n */\r\n public addCrosshairCanvasToDOM(rootElement: D3.Selection): D3.Selection {\r\n var crosshairCanvasSelector: ClassAndSelector = EnhancedScatterChart.CrosshairCanvasSelector;\r\n\r\n return this.addElementToDOM(rootElement, {\r\n name: \"g\",\r\n selector: crosshairCanvasSelector.selector,\r\n className: crosshairCanvasSelector.class,\r\n styles: { display: \"none\" }\r\n });\r\n }\r\n\r\n /**\r\n * Public for testability.\r\n */\r\n public addCrosshairLineToDOM(rootElement: D3.Selection, elementSelector: ClassAndSelector): D3.Selection {\r\n var crosshairLineSelector: ClassAndSelector = EnhancedScatterChart.CrosshairLineSelector;\r\n\r\n return this.addElementToDOM(rootElement, {\r\n name: \"line\",\r\n selector: elementSelector.selector,\r\n className: `${crosshairLineSelector.class} ${elementSelector.class}`,\r\n attributes: { x1: 0, y1: 0, x2: 0, y2: 0 }\r\n });\r\n }\r\n\r\n /**\r\n * Public for testability.\r\n */\r\n public addCrosshairTextToDOM(rootElement: D3.Selection): D3.Selection {\r\n var crosshairTextSelector: ClassAndSelector = EnhancedScatterChart.CrosshairTextSelector;\r\n\r\n return this.addElementToDOM(rootElement, {\r\n name: \"text\",\r\n selector: crosshairTextSelector.selector,\r\n className: crosshairTextSelector.class\r\n });\r\n }\r\n\r\n /**\r\n * Public for testability.\r\n */\r\n public bindCrosshairEvents(): void {\r\n if (!this.axisGraphicsContextScrollable) {\r\n return;\r\n }\r\n\r\n this.axisGraphicsContextScrollable\r\n .on(\"mousemove\", () => {\r\n var currentTarget: SVGElement = <SVGElement>d3.event.currentTarget,\r\n coordinates: number[] = d3.mouse(currentTarget),\r\n svgNode: SVGElement = currentTarget.viewportElement,\r\n scaledRect: ClientRect = svgNode.getBoundingClientRect(),\r\n domRect: SVGRect = (<any>svgNode).getBBox(),\r\n ratioX: number = scaledRect.width / domRect.width,\r\n ratioY: number = scaledRect.height / domRect.height,\r\n x: number = coordinates[0],\r\n y: number = coordinates[1];\r\n\r\n if (domRect.width > 0 && !equalWithPrecision(ratioX, 1.0, 0.00001)) {\r\n x = x / ratioX;\r\n }\r\n\r\n if (domRect.height > 0 && !equalWithPrecision(ratioY, 1.0, 0.00001)) {\r\n y = y / ratioY;\r\n }\r\n\r\n this.updateCrosshair(x, y);\r\n })\r\n .on(\"mouseover\", () => {\r\n this.crosshairCanvasSelection.style(\"display\", \"block\");\r\n })\r\n .on(\"mouseout\", () => {\r\n this.crosshairCanvasSelection.style(\"display\", \"none\");\r\n });\r\n }\r\n\r\n /**\r\n * Public for testability.\r\n */\r\n public updateCrosshair(x: number, y: number): void {\r\n if (!this.viewportIn ||\r\n !this.crosshairHorizontalLineSelection ||\r\n !this.crosshairVerticalLineSelection ||\r\n !this.crosshairTextSelection ||\r\n !this.xAxisProperties) {\r\n\r\n return;\r\n }\r\n\r\n var crosshairTextMargin: number = EnhancedScatterChart.CrosshairTextMargin,\r\n xScale = <D3.Scale.LinearScale>this.xAxisProperties.scale,\r\n yScale = <D3.Scale.LinearScale>this.yAxisProperties.scale,\r\n xFormated: number,\r\n yFormated: number;\r\n\r\n this.crosshairHorizontalLineSelection\r\n .attr({ x1: 0, y1: y, x2: this.viewportIn.width, y2: y });\r\n\r\n this.crosshairVerticalLineSelection\r\n .attr({ x1: x, y1: 0, x2: x, y2: this.viewportIn.height });\r\n\r\n xFormated = Math.round(xScale.invert(x) * 100) / 100;\r\n yFormated = Math.round(yScale.invert(y) * 100) / 100;\r\n\r\n this.crosshairTextSelection\r\n .attr({ x: x + crosshairTextMargin, y: y - crosshairTextMargin })\r\n .text(`(${xFormated}, ${yFormated})`);\r\n }\r\n\r\n /**\r\n * Public for testability.\r\n */\r\n public addElementToDOM(rootElement: D3.Selection, properties: ElementProperties): D3.Selection {\r\n if (!rootElement || !properties) {\r\n return null;\r\n }\r\n\r\n var elementSelection: D3.Selection,\r\n elementUpdateSelection: D3.UpdateSelection;\r\n\r\n elementSelection = rootElement\r\n .selectAll(properties.selector);\r\n\r\n elementUpdateSelection = elementSelection.data(properties.data || [[]]);\r\n\r\n elementUpdateSelection\r\n .enter()\r\n .append(properties.name)\r\n .attr(properties.attributes)\r\n .style(properties.styles)\r\n .classed(properties.className, true);\r\n\r\n elementUpdateSelection\r\n .exit()\r\n .remove();\r\n\r\n return elementUpdateSelection;\r\n }\r\n\r\n private renderBackground() {\r\n if (this.data.backdrop && this.data.backdrop.show && (this.data.backdrop.url !== undefined)) {\r\n this.backgroundGraphicsContext\r\n .attr(\"xlink:href\", this.data.backdrop.url)\r\n .attr(\"x\", 0)\r\n .attr(\"y\", 0)\r\n .attr(\"width\", this.viewportIn.width)\r\n .attr(\"height\", this.viewportIn.height);\r\n } else {\r\n this.backgroundGraphicsContext\r\n .attr(\"width\", 0)\r\n .attr(\"height\", 0);\r\n }\r\n }\r\n\r\n private renderChart(\r\n mainAxisScale: any,\r\n xAxis: IAxisProperties,\r\n yAxis: IAxisProperties,\r\n tickLabelMargins: any,\r\n chartHasAxisLabels: boolean,\r\n axisLabels: ChartAxesLabels,\r\n suppressAnimations: boolean,\r\n scrollScale?: any,\r\n extent?: number[]) {\r\n\r\n var bottomMarginLimit: number = this.bottomMarginLimit,\r\n leftRightMarginLimit: number = this.leftRightMarginLimit,\r\n duration: number = GetAnimationDuration(this.animator, suppressAnimations);\r\n\r\n this.renderBackground();\r\n\r\n //hide show x-axis here\r\n if (this.shouldRenderAxis(xAxis)) {\r\n xAxis.axis.orient(\"bottom\");\r\n if (!xAxis.willLabelsFit)\r\n xAxis.axis.tickPadding(5);\r\n\r\n var xAxisGraphicsElement = this.xAxisGraphicsContext;\r\n if (duration) {\r\n xAxisGraphicsElement\r\n .transition()\r\n .duration(duration)\r\n .call(xAxis.axis)\r\n .call(this.darkenZeroLine);\r\n }\r\n else {\r\n xAxisGraphicsElement\r\n .call(xAxis.axis)\r\n .call(this.darkenZeroLine);\r\n }\r\n\r\n var xZeroTick: D3.UpdateSelection = xAxisGraphicsElement\r\n .selectAll(\"g.tick\")\r\n .filter((data) => data === 0);\r\n\r\n if (xZeroTick) {\r\n var xZeroColor: Fill = this.getValueAxisFill();\r\n\r\n if (xZeroColor) {\r\n xZeroTick\r\n .selectAll(\"line\")\r\n .style({ \"stroke\": xZeroColor.solid.color });\r\n }\r\n }\r\n\r\n var xAxisTextNodes: D3.Selection = xAxisGraphicsElement.selectAll(\"text\");\r\n\r\n if (xAxis.willLabelsWordBreak) {\r\n xAxisTextNodes.call(\r\n AxisHelper.LabelLayoutStrategy.wordBreak,\r\n xAxis,\r\n bottomMarginLimit);\r\n } else {\r\n xAxisTextNodes.call(\r\n AxisHelper.LabelLayoutStrategy.rotate,\r\n bottomMarginLimit,\r\n getTailoredTextOrDefault,\r\n EnhancedScatterChart.TextProperties,\r\n !xAxis.willLabelsFit,\r\n bottomMarginLimit === tickLabelMargins.xMax,\r\n xAxis,\r\n this.margin,\r\n this.isXScrollBarVisible || this.isYScrollBarVisible);\r\n }\r\n }\r\n else {\r\n this.xAxisGraphicsContext.selectAll(\"*\").remove();\r\n }\r\n\r\n if (this.shouldRenderAxis(yAxis)) {\r\n var yAxisOrientation = this.yAxisOrientation;\r\n\r\n yAxis.axis\r\n .tickSize(-this.viewportIn.width)\r\n .tickPadding(10)\r\n .orient(yAxisOrientation.toLowerCase());\r\n\r\n var y1AxisGraphicsElement = this.y1AxisGraphicsContext;\r\n if (duration) {\r\n y1AxisGraphicsElement\r\n .transition()\r\n .duration(duration)\r\n .call(yAxis.axis)\r\n .call(this.darkenZeroLine);\r\n }\r\n else {\r\n y1AxisGraphicsElement\r\n .call(yAxis.axis)\r\n .call(this.darkenZeroLine);\r\n }\r\n\r\n var yZeroTick = y1AxisGraphicsElement.selectAll(\"g.tick\").filter((data) => data === 0);\r\n if (yZeroTick) {\r\n var yZeroColor = this.getCategoryAxisFill();\r\n if (yZeroColor) {\r\n yZeroTick.selectAll(\"line\").style({ \"stroke\": yZeroColor.solid.color });\r\n }\r\n }\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 - 10,\r\n svgEllipsis);\r\n }\r\n\r\n // TODO: clip (svgEllipsis) the Y2 labels\r\n }\r\n else {\r\n this.y1AxisGraphicsContext.selectAll(\"*\").remove();\r\n }\r\n // Axis labels\r\n //TODO: Add label for second Y axis for combo chart\r\n if (chartHasAxisLabels) {\r\n var hideXAxisTitle = !this.shouldRenderAxis(xAxis, \"showAxisTitle\");\r\n var hideYAxisTitle = !this.shouldRenderAxis(yAxis, \"showAxisTitle\");\r\n var hideY2AxisTitle = this.valueAxisProperties && this.valueAxisProperties[\"secShowAxisTitle\"] != null && this.valueAxisProperties[\"secShowAxisTitle\"] === false;\r\n\r\n this.renderAxesLabels(axisLabels, this.legendViewport.height, hideXAxisTitle, hideYAxisTitle, hideY2AxisTitle);\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\r\n private renderAxesLabels(axisLabels: ChartAxesLabels, legendMargin: number, hideXAxisTitle: boolean, hideYAxisTitle: boolean, hideY2AxisTitle: boolean): void {\r\n this.axisGraphicsContext.selectAll(\".xAxisLabel\").remove();\r\n this.axisGraphicsContext.selectAll(\".yAxisLabel\").remove();\r\n\r\n var margin = this.margin;\r\n var width = this.viewportIn.width;\r\n var height = this.viewport.height;\r\n var fontSize = EnhancedScatterChart.AxisFontSize;\r\n var yAxisOrientation = this.yAxisOrientation;\r\n var showY1OnRight = yAxisOrientation === yAxisPosition.right;\r\n\r\n if (!hideXAxisTitle) {\r\n var xAxisLabel = this.axisGraphicsContext.append(\"text\")\r\n .style(\"text-anchor\", \"middle\")\r\n .text(axisLabels.x)\r\n .call((text: D3.Selection) => {\r\n text.each(function () {\r\n var text = d3.select(this);\r\n text.attr({\r\n \"class\": \"xAxisLabel\",\r\n \"transform\": SVGUtil.translate(width / 2, height - fontSize - 2)\r\n });\r\n });\r\n });\r\n\r\n xAxisLabel.call(AxisHelper.LabelLayoutStrategy.clip,\r\n width,\r\n svgEllipsis);\r\n }\r\n\r\n if (!hideYAxisTitle) {\r\n var yAxisLabel = this.axisGraphicsContext.append(\"text\")\r\n .style(\"text-anchor\", \"middle\")\r\n .text(axisLabels.y)\r\n .call((text: D3.Selection) => {\r\n text.each(function () {\r\n var text = d3.select(this);\r\n text.attr({\r\n \"class\": \"yAxisLabel\",\r\n \"transform\": \"rotate(-90)\",\r\n \"y\": showY1OnRight ? width + margin.right - fontSize : -margin.left,\r\n \"x\": -((height - margin.top - legendMargin) / 2),\r\n \"dy\": \"1em\"\r\n });\r\n });\r\n });\r\n\r\n yAxisLabel.call(\r\n AxisHelper.LabelLayoutStrategy.clip,\r\n height - (margin.bottom + margin.top),\r\n svgEllipsis);\r\n }\r\n\r\n if (!hideY2AxisTitle && axisLabels.y2) {\r\n var y2AxisLabel = this.axisGraphicsContext.append(\"text\")\r\n .style(\"text-anchor\", \"middle\")\r\n .text(axisLabels.y2)\r\n .call((text: D3.Selection) => {\r\n text.each(function () {\r\n var text = d3.select(this);\r\n text.attr({\r\n \"class\": \"yAxisLabel\",\r\n \"transform\": \"rotate(-90)\",\r\n \"y\": showY1OnRight ? -margin.left : width + margin.right - fontSize,\r\n \"x\": -((height - margin.top - legendMargin) / 2),\r\n \"dy\": \"1em\"\r\n });\r\n });\r\n });\r\n\r\n y2AxisLabel.call(\r\n AxisHelper.LabelLayoutStrategy.clip,\r\n height - (margin.bottom + margin.top),\r\n svgEllipsis);\r\n }\r\n }\r\n\r\n private updateAxis(): void {\r\n this.adjustMargins();\r\n\r\n var yAxisOrientation = this.yAxisOrientation;\r\n var showY1OnRight = yAxisOrientation === yAxisPosition.right;\r\n\r\n this.xAxisGraphicsContext\r\n .attr(\"transform\", SVGUtil.translate(0, this.viewportIn.height));\r\n\r\n this.y1AxisGraphicsContext\r\n .attr(\"transform\", SVGUtil.translate(showY1OnRight ? this.viewportIn.width : 0, 0));\r\n\r\n this.svg.attr({\r\n \"width\": this.viewport.width,\r\n \"height\": this.viewport.height\r\n });\r\n\r\n this.svgScrollable.attr({\r\n \"width\": this.viewport.width,\r\n \"height\": this.viewport.height\r\n });\r\n\r\n this.svgScrollable.attr({\r\n \"x\": 0\r\n });\r\n\r\n var left: number = this.margin.left;\r\n var top: number = this.margin.top;\r\n\r\n this.axisGraphicsContext.attr(\"transform\", SVGUtil.translate(left, top));\r\n this.axisGraphicsContextScrollable.attr(\"transform\", SVGUtil.translate(left, top));\r\n this.clearCatcher.attr(\"transform\", SVGUtil.translate(-left, -top));\r\n\r\n if (this.isXScrollBarVisible) {\r\n this.svgScrollable.attr({\r\n \"x\": left\r\n });\r\n this.axisGraphicsContextScrollable.attr(\"transform\", SVGUtil.translate(0, top));\r\n this.svgScrollable.attr(\"width\", this.viewportIn.width);\r\n this.svg.attr(\"width\", this.viewport.width)\r\n .attr(\"height\", this.viewport.height + this.ScrollBarWidth);\r\n }\r\n else if (this.isYScrollBarVisible) {\r\n this.svgScrollable.attr(\"height\", this.viewportIn.height + top);\r\n this.svg.attr(\"width\", this.viewport.width + this.ScrollBarWidth)\r\n .attr(\"height\", this.viewport.height);\r\n }\r\n }\r\n\r\n private getUnitType(xAxis: IAxisProperties) {\r\n if (xAxis.formatter &&\r\n xAxis.formatter.displayUnit &&\r\n xAxis.formatter.displayUnit.value > 1)\r\n return xAxis.formatter.displayUnit.title;\r\n return null;\r\n }\r\n\r\n private addUnitTypeToAxisLabel(xAxis: IAxisProperties, yAxis: IAxisProperties): void {\r\n var unitType = this.getUnitType(xAxis);\r\n if (xAxis.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 (xAxis.axisLabel && unitType) {\r\n if (xAxis.isCategoryAxis) {\r\n xAxis.axisLabel = AxisHelper.createAxisLabel(this.categoryAxisProperties, xAxis.axisLabel, unitType);\r\n }\r\n else {\r\n xAxis.axisLabel = AxisHelper.createAxisLabel(this.valueAxisProperties, xAxis.axisLabel, unitType);\r\n }\r\n }\r\n\r\n unitType = this.getUnitType(yAxis);\r\n\r\n if (!yAxis.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 (yAxis.axisLabel && unitType) {\r\n if (!yAxis.isCategoryAxis) {\r\n yAxis.axisLabel = AxisHelper.createAxisLabel(this.valueAxisProperties, yAxis.axisLabel, unitType);\r\n }\r\n else {\r\n yAxis.axisLabel = AxisHelper.createAxisLabel(this.categoryAxisProperties, yAxis.axisLabel, unitType);\r\n }\r\n }\r\n }\r\n\r\n private drawScatterMarkers(\r\n scatterData: EnhancedScatterChartDataPoint[],\r\n hasSelection: boolean,\r\n sizeRange: NumberRange,\r\n duration: number): D3.UpdateSelection {\r\n\r\n var xScale = this.xAxisProperties.scale,\r\n yScale = this.yAxisProperties.scale,\r\n shouldEnableFill = (!sizeRange || !sizeRange.min) && this.data.fillPoint;\r\n\r\n var markers: D3.UpdateSelection,\r\n useCustomColor = this.data.useCustomColor;\r\n\r\n if (!this.data.useShape) {\r\n this.mainGraphicsContext\r\n .selectAll(EnhancedScatterChart.ImageClasses.selector)\r\n .remove();\r\n\r\n markers = this.mainGraphicsContext\r\n .classed(\"ScatterMarkers\", true)\r\n .selectAll(EnhancedScatterChart.DotClasses.selector)\r\n .data(scatterData, (d: EnhancedScatterChartDataPoint) => d.identity.getKey());\r\n\r\n markers\r\n .enter()\r\n .append(\"path\")\r\n .classed(EnhancedScatterChart.DotClasses.class, true)\r\n .attr(\"id\", \"markershape\");\r\n\r\n markers\r\n .style({\r\n \"stroke-opacity\": (d: EnhancedScatterChartDataPoint) => {\r\n return EnhancedScatterChart.getBubbleOpacity(d, hasSelection);\r\n },\r\n \"stroke-width\": \"1px\",\r\n \"stroke\": (d: EnhancedScatterChartDataPoint) => {\r\n var color = useCustomColor ? d.colorFill : d.fill;\r\n if (this.data.outline) {\r\n return d3.rgb(color).darker();\r\n } else {\r\n return d3.rgb(color);\r\n }\r\n },\r\n \"fill\": (d: EnhancedScatterChartDataPoint) => d3.rgb(useCustomColor ? d.colorFill : d.fill),\r\n \"fill-opacity\": (d: EnhancedScatterChartDataPoint) => (d.size != null || shouldEnableFill)\r\n ? EnhancedScatterChart.getBubbleOpacity(d, hasSelection) : 0,\r\n })\r\n .attr(\"d\", (d: EnhancedScatterChartDataPoint) => {\r\n var r: number = EnhancedScatterChart.getBubbleRadius(d.radius, sizeRange, this.viewport),\r\n area: number = 4 * r * r;\r\n\r\n return d.shapeSymbolType(area);\r\n })\r\n .transition()\r\n .duration((d) => {\r\n if (this.keyArray.indexOf(d.identity.getKey()) >= 0) {\r\n return duration;\r\n } else {\r\n return 0;\r\n }\r\n })\r\n .attr(\"transform\", function (d) { return \"translate(\" + xScale(d.x) + \",\" + yScale(d.y) + \") rotate(\" + d.rotation + \")\"; });\r\n } else {\r\n this.mainGraphicsContext\r\n .selectAll(EnhancedScatterChart.DotClasses.selector)\r\n .remove();\r\n\r\n markers = this.mainGraphicsContext\r\n .classed(\"ScatterMarkers\", true)\r\n .selectAll(EnhancedScatterChart.ImageClasses.selector)\r\n .data(scatterData, (d: EnhancedScatterChartDataPoint) => d.identity.getKey());\r\n\r\n markers\r\n .enter()\r\n .append(\"svg:image\")\r\n .classed(EnhancedScatterChart.ImageClasses.class, true)\r\n .attr(\"id\", \"markerimage\");\r\n\r\n markers\r\n .attr(\"xlink:href\", (d) => {\r\n if (d.svgurl !== undefined && d.svgurl != null && d.svgurl !== \"\") {\r\n return d.svgurl;\r\n } else {\r\n return this.svgDefaultImage;\r\n }\r\n })\r\n .attr(\"width\", (d) => {\r\n return EnhancedScatterChart.getBubbleRadius(d.radius, sizeRange, this.viewport) * 2;\r\n })\r\n .attr(\"height\", (d) => {\r\n return EnhancedScatterChart.getBubbleRadius(d.radius, sizeRange, this.viewport) * 2;\r\n })\r\n .transition()\r\n .duration((d) => {\r\n if (this.keyArray.indexOf(d.identity.getKey()) >= 0) {\r\n return duration;\r\n } else {\r\n return 0;\r\n }\r\n })\r\n .attr(\"transform\", (d) => {\r\n var radius: number = EnhancedScatterChart.getBubbleRadius(d.radius, sizeRange, this.viewport);\r\n\r\n return \"translate(\" + (xScale(d.x) - radius) + \",\" + (yScale(d.y) - radius) + \") rotate(\" + d.rotation + \",\" + radius + \",\" + radius + \")\";\r\n });\r\n }\r\n\r\n markers.exit().remove();\r\n this.keyArray = [];\r\n for (var i = 0; i < scatterData.length; i++) {\r\n this.keyArray.push(scatterData[i].identity.getKey());\r\n }\r\n\r\n return markers;\r\n }\r\n\r\n public static getBubbleOpacity(d: EnhancedScatterChartDataPoint, hasSelection: boolean): number {\r\n if (hasSelection && !d.selected) {\r\n return EnhancedScatterChart.DimmedBubbleOpacity;\r\n }\r\n return EnhancedScatterChart.DefaultBubbleOpacity;\r\n }\r\n\r\n public calculateAxes(\r\n categoryAxisProperties: DataViewObject,\r\n valueAxisProperties: DataViewObject,\r\n textProperties: TextProperties,\r\n scrollbarVisible: boolean): IAxisProperties[] {\r\n\r\n var visualOptions: CalculateScaleAndDomainOptions = {\r\n viewport: this.viewport,\r\n margin: this.margin,\r\n forcedXDomain: [categoryAxisProperties ? categoryAxisProperties[\"start\"] : null, categoryAxisProperties ? categoryAxisProperties[\"end\"] : null],\r\n forceMerge: valueAxisProperties && valueAxisProperties[\"secShow\"] === false,\r\n showCategoryAxisLabel: false,\r\n showValueAxisLabel: false,\r\n categoryAxisScaleType: categoryAxisProperties && categoryAxisProperties[\"axisScale\"] != null ? <string>categoryAxisProperties[\"axisScale\"] : null,\r\n valueAxisScaleType: valueAxisProperties && valueAxisProperties[\"axisScale\"] != null ? <string>valueAxisProperties[\"axisScale\"] : null,\r\n valueAxisDisplayUnits: valueAxisProperties && valueAxisProperties[\"labelDisplayUnits\"] != null ? <number>valueAxisProperties[\"labelDisplayUnits\"] : EnhancedScatterChart.LabelDisplayUnitsDefault,\r\n categoryAxisDisplayUnits: categoryAxisProperties && categoryAxisProperties[\"labelDisplayUnits\"] != null ? <number>categoryAxisProperties[\"labelDisplayUnits\"] : EnhancedScatterChart.LabelDisplayUnitsDefault,\r\n trimOrdinalDataOnOverflow: false\r\n };\r\n\r\n if (valueAxisProperties) {\r\n visualOptions.forcedYDomain = AxisHelper.applyCustomizedDomain(\r\n [valueAxisProperties[\"start\"], valueAxisProperties[\"end\"]],\r\n visualOptions.forcedYDomain);\r\n }\r\n\r\n visualOptions.showCategoryAxisLabel = (!!categoryAxisProperties && !!categoryAxisProperties[\"showAxisTitle\"]);\r\n\r\n visualOptions.showValueAxisLabel = true;\r\n\r\n var width = this.viewport.width - (this.margin.left + this.margin.right);\r\n\r\n var axes = this.calculateAxesProperties(visualOptions);\r\n\r\n axes[0].willLabelsFit = AxisHelper.LabelLayoutStrategy.willLabelsFit(\r\n axes[0],\r\n width,\r\n measureSvgTextWidth,\r\n textProperties);\r\n\r\n // If labels do not fit and we are not scrolling, try word breaking\r\n axes[0].willLabelsWordBreak = (!axes[0].willLabelsFit && !scrollbarVisible) && AxisHelper.LabelLayoutStrategy.willLabelsWordBreak(\r\n axes[0],\r\n this.margin,\r\n width,\r\n measureSvgTextWidth,\r\n estimateSvgTextHeight,\r\n getTailoredTextOrDefault,\r\n textProperties);\r\n\r\n return axes;\r\n }\r\n\r\n public calculateAxesProperties(options: CalculateScaleAndDomainOptions): IAxisProperties[] {\r\n var data: EnhancedScatterChartData = this.data,\r\n dataPoints: EnhancedScatterChartDataPoint[] = data.dataPoints;\r\n\r\n this.margin = options.margin;\r\n this.viewport = options.viewport;\r\n\r\n var minY: number = 0,\r\n maxY: number = 10,\r\n minX: number = 0,\r\n maxX: number = 10;\r\n\r\n if (dataPoints.length > 0) {\r\n minY = d3.min<EnhancedScatterChartDataPoint, number>(dataPoints, d => d.y);\r\n maxY = d3.max<EnhancedScatterChartDataPoint, number>(dataPoints, d => d.y);\r\n minX = d3.min<EnhancedScatterChartDataPoint, number>(dataPoints, d => d.x);\r\n maxX = d3.max<EnhancedScatterChartDataPoint, number>(dataPoints, d => d.x);\r\n }\r\n\r\n var xDomain: number[] = [minX, maxX],\r\n combinedXDomain: number[],\r\n combinedYDomain: number[],\r\n xAxisFormatString: string,\r\n yAxisFormatString: string;\r\n\r\n combinedXDomain = AxisHelper.combineDomain(\r\n this.optimizeTranslateValues(options.forcedXDomain), xDomain);\r\n\r\n xAxisFormatString = valueFormatter.getFormatString(\r\n data.xCol,\r\n EnhancedScatterChart.Properties[\"general\"][\"formatString\"]);\r\n\r\n this.xAxisProperties = AxisHelper.createAxis({\r\n pixelSpan: this.viewportIn.width,\r\n dataDomain: combinedXDomain,\r\n metaDataColumn: data.xCol,\r\n formatString: xAxisFormatString,\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 });\r\n\r\n this.xAxisProperties.axis.tickSize(-this.viewportIn.height, 0);\r\n this.xAxisProperties.axisLabel = this.data.axesLabels.x;\r\n\r\n combinedYDomain = AxisHelper.combineDomain(\r\n this.optimizeTranslateValues(options.forcedYDomain), [minY, maxY]);\r\n\r\n yAxisFormatString = valueFormatter.getFormatString(\r\n data.yCol,\r\n EnhancedScatterChart.Properties[\"general\"][\"formatString\"]);\r\n\r\n this.yAxisProperties = AxisHelper.createAxis({\r\n pixelSpan: this.viewportIn.height,\r\n dataDomain: combinedYDomain,\r\n metaDataColumn: data.yCol,\r\n formatString: yAxisFormatString,\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 });\r\n\r\n this.yAxisProperties.axisLabel = this.data.axesLabels.y;\r\n\r\n return [this.xAxisProperties, this.yAxisProperties];\r\n }\r\n\r\n /**\r\n * Public for testability.\r\n */\r\n public optimizeTranslateValues(values: number[]): number[] {\r\n if (values && values.map) {\r\n return values.map((value: number) => {\r\n return this.optimizeTranslateValue(value);\r\n });\r\n }\r\n\r\n return values;\r\n }\r\n\r\n /**\r\n * Public for testability.\r\n */\r\n public optimizeTranslateValue(value: number): number {\r\n if (value) {\r\n var numberSign: number = value >= 0 ? 1 : -1,\r\n absoluteValue: number = Math.abs(value);\r\n\r\n if (absoluteValue > EnhancedScatterChart.MaxTranslateValue) {\r\n return EnhancedScatterChart.MaxTranslateValue * numberSign;\r\n } else if (absoluteValue < EnhancedScatterChart.MinTranslateValue) {\r\n return EnhancedScatterChart.MinTranslateValue * numberSign;\r\n }\r\n }\r\n\r\n return value;\r\n }\r\n\r\n private enumerateDataPoints(enumeration: ObjectEnumerationBuilder): void {\r\n var data = this.data;\r\n if (!data)\r\n return;\r\n\r\n var seriesCount = data.dataPoints.length;\r\n\r\n if (!data.hasDynamicSeries) {\r\n var showAllDataPoints: boolean = data.showAllDataPoints;\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: {\r\n solid: { color: data.defaultDataPointColor || this.colors.getColorByIndex(0).value }\r\n }\r\n }\r\n }).pushInstance({\r\n objectName: \"dataPoint\",\r\n selector: null,\r\n properties: { showAllDataPoints: showAllDataPoints }\r\n });\r\n\r\n if (showAllDataPoints) {\r\n for (var i = 0; i < seriesCount; i++) {\r\n var 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 }\r\n else {\r\n var legendDataPointLength = data.legendData.dataPoints.length;\r\n for (var i = 0; i < legendDataPointLength; i++) {\r\n var 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 enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstanceEnumeration {\r\n var enumeration = new ObjectEnumerationBuilder();\r\n\r\n switch (options.objectName) {\r\n case \"dataPoint\":\r\n var categoricalDataView: DataViewCategorical = this.dataView && this.dataView.categorical ? this.dataView.categorical : null;\r\n if (!GradientUtils.hasGradientRole(categoricalDataView))\r\n this.enumerateDataPoints(enumeration);\r\n break;\r\n case \"categoryAxis\":\r\n this.getCategoryAxisValues(enumeration);\r\n break;\r\n case \"valueAxis\":\r\n this.getValueAxisValues(enumeration);\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 var sizeRange = this.data.sizeRange;\r\n // Check if the card should be shown or not\r\n if (sizeRange && sizeRange.min)\r\n break;\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 \"backdrop\":\r\n enumeration.pushInstance({\r\n objectName: \"backdrop\",\r\n displayName: \"Backdrop\",\r\n selector: null,\r\n properties: {\r\n show: this.data.backdrop ? this.data.backdrop.show : false,\r\n url: this.data.backdrop ? this.data.backdrop.url : null\r\n },\r\n });\r\n break;\r\n case \"crosshair\":\r\n enumeration.pushInstance({\r\n objectName: \"crosshair\",\r\n selector: null,\r\n properties: {\r\n show: this.data.crosshair\r\n },\r\n });\r\n break;\r\n case \"outline\":\r\n enumeration.pushInstance({\r\n objectName: \"outline\",\r\n selector: null,\r\n properties: {\r\n show: this.data.outline\r\n },\r\n });\r\n break;\r\n case \"legend\":\r\n this.getLegendValue(enumeration);\r\n break;\r\n }\r\n return enumeration.complete();\r\n }\r\n\r\n public hasLegend(): boolean {\r\n return this.data && this.data.hasDynamicSeries;\r\n }\r\n\r\n private getLegendValue(enumeration: ObjectEnumerationBuilder): void {\r\n if (!this.hasLegend()) {\r\n return;\r\n }\r\n\r\n var show: boolean = DataViewObject.getValue<boolean>(\r\n this.legendObjectProperties,\r\n legendProps.show,\r\n this.legend.isVisible());\r\n\r\n var showTitle: boolean = DataViewObject.getValue<boolean>(\r\n this.legendObjectProperties,\r\n legendProps.showTitle,\r\n true);\r\n\r\n var titleText: string = DataViewObject.getValue<string>(\r\n this.legendObjectProperties,\r\n legendProps.titleText,\r\n this.layerLegendData ? this.layerLegendData.title : \"\");\r\n\r\n var legendLabelColor: string = DataViewObject.getValue<string>(\r\n this.legendObjectProperties,\r\n legendProps.labelColor,\r\n LegendData.DefaultLegendLabelFillColor);\r\n\r\n this.legendLabelFontSize = DataViewObject.getValue<number>(\r\n this.legendObjectProperties,\r\n legendProps.fontSize,\r\n EnhancedScatterChart.LegendLabelFontSizeDefault);\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: legendLabelColor,\r\n fontSize: this.legendLabelFontSize,\r\n },\r\n objectName: \"legend\"\r\n });\r\n }\r\n\r\n private getCategoryAxisValues(enumeration: ObjectEnumerationBuilder): void {\r\n var supportedType = axisType.both,\r\n isScalar = true,\r\n logPossible = false,\r\n scaleOptions = [\r\n axisScale.log,\r\n axisScale.linear\r\n ];//until options can be update in propPane, show all options\r\n\r\n if (!isScalar) {\r\n if (this.categoryAxisProperties) {\r\n this.categoryAxisProperties[\"start\"] = null;\r\n this.categoryAxisProperties[\"end\"] = null;\r\n }\r\n }\r\n\r\n var instance: VisualObjectInstance = {\r\n selector: null,\r\n properties: {},\r\n objectName: \"categoryAxis\",\r\n validValues: {\r\n axisScale: scaleOptions\r\n }\r\n };\r\n\r\n instance.properties[\"show\"] = this.categoryAxisProperties && this.categoryAxisProperties[\"show\"] != null ? this.categoryAxisProperties[\"show\"] : true;\r\n if (this.yAxisIsCategorical)//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 && this.categoryAxisProperties[\"axisScale\"] != null && logPossible) ? this.categoryAxisProperties[\"axisScale\"] : axisScale.linear;\r\n instance.properties[\"start\"] = this.categoryAxisProperties ? this.categoryAxisProperties[\"start\"] : null;\r\n instance.properties[\"end\"] = this.categoryAxisProperties ? this.categoryAxisProperties[\"end\"] : null;\r\n instance.properties[\"labelDisplayUnits\"] = this.categoryAxisProperties && this.categoryAxisProperties[\"labelDisplayUnits\"] != null ? this.categoryAxisProperties[\"labelDisplayUnits\"] : EnhancedScatterChart.LabelDisplayUnitsDefault;\r\n }\r\n instance.properties[\"showAxisTitle\"] = this.categoryAxisProperties && this.categoryAxisProperties[\"showAxisTitle\"] != null ? this.categoryAxisProperties[\"showAxisTitle\"] : true;\r\n\r\n enumeration\r\n .pushInstance(instance)\r\n .pushInstance({\r\n selector: null,\r\n properties: {\r\n axisStyle: this.categoryAxisProperties && this.categoryAxisProperties[\"axisStyle\"]\r\n ? this.categoryAxisProperties[\"axisStyle\"] : axisStyle.showTitleOnly,\r\n labelColor: this.categoryAxisProperties ? this.categoryAxisProperties[\"labelColor\"] : null\r\n },\r\n objectName: \"categoryAxis\",\r\n validValues: {\r\n axisStyle: this.categoryAxisHasUnitType\r\n ? [axisStyle.showTitleOnly, axisStyle.showUnitOnly, axisStyle.showBoth]\r\n : [axisStyle.showTitleOnly]\r\n }\r\n });\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 var scaleOptions = [axisScale.log, axisScale.linear]; //until options can be update in propPane, show all options\r\n var logPossible = false;\r\n\r\n var instance: VisualObjectInstance = {\r\n selector: null,\r\n properties: {},\r\n objectName: \"valueAxis\",\r\n validValues: {\r\n axisScale: scaleOptions,\r\n secAxisScale: scaleOptions\r\n }\r\n };\r\n\r\n instance.properties[\"show\"] = this.valueAxisProperties && this.valueAxisProperties[\"show\"] != null ? this.valueAxisProperties[\"show\"] : true;\r\n\r\n if (!this.yAxisIsCategorical) {\r\n instance.properties[\"position\"] = this.valueAxisProperties && this.valueAxisProperties[\"position\"] != null ? this.valueAxisProperties[\"position\"] : yAxisPosition.left;\r\n }\r\n instance.properties[\"axisScale\"] = (this.valueAxisProperties && this.valueAxisProperties[\"axisScale\"] != null && logPossible) ? this.valueAxisProperties[\"axisScale\"] : axisScale.linear;\r\n instance.properties[\"start\"] = this.valueAxisProperties ? this.valueAxisProperties[\"start\"] : null;\r\n instance.properties[\"end\"] = this.valueAxisProperties ? this.valueAxisProperties[\"end\"] : null;\r\n instance.properties[\"showAxisTitle\"] = this.valueAxisProperties && this.valueAxisProperties[\"showAxisTitle\"] != null ? this.valueAxisProperties[\"showAxisTitle\"] : true;\r\n instance.properties[\"labelDisplayUnits\"] = this.valueAxisProperties && this.valueAxisProperties[\"labelDisplayUnits\"] != null ? this.valueAxisProperties[\"labelDisplayUnits\"] : EnhancedScatterChart.LabelDisplayUnitsDefault;\r\n\r\n enumeration\r\n .pushInstance(instance)\r\n .pushInstance({\r\n selector: null,\r\n properties: {\r\n axisStyle: this.valueAxisProperties && this.valueAxisProperties[\"axisStyle\"] != null ? this.valueAxisProperties[\"axisStyle\"] : axisStyle.showTitleOnly,\r\n labelColor: this.valueAxisProperties ? this.valueAxisProperties[\"labelColor\"] : null\r\n },\r\n objectName: \"valueAxis\",\r\n validValues: {\r\n axisStyle: this.valueAxisHasUnitType ? [axisStyle.showTitleOnly, axisStyle.showUnitOnly, axisStyle.showBoth] : [axisStyle.showTitleOnly]\r\n },\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\r\n export interface CustomVisualBehaviorOptions {\r\n layerOptions: any[];\r\n clearCatcher: D3.Selection;\r\n }\r\n\r\n export class CustomVisualBehavior 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: CustomVisualBehaviorOptions, selectionHandler: ISelectionHandler): void {\r\n let behaviors: IInteractiveBehavior[] = this.behaviors;\r\n\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\r\n export interface EnhancedScatterBehaviorOptions {\r\n dataPointsSelection: D3.Selection;\r\n data: EnhancedScatterChartData;\r\n plotContext: D3.Selection;\r\n }\r\n\r\n export class EnhancedScatterChartWebBehavior implements IInteractiveBehavior {\r\n private dimmedBubbleOpacity: number;\r\n private defaultBubbleOpacity: number;\r\n\r\n private bubbles: D3.Selection;\r\n private shouldEnableFill: boolean;\r\n private colorBorder: boolean;\r\n\r\n constructor(dimmedBubbleOpacity: number, defaultBubbleOpacity: number) {\r\n this.dimmedBubbleOpacity = dimmedBubbleOpacity;\r\n this.defaultBubbleOpacity = defaultBubbleOpacity;\r\n }\r\n\r\n public bindEvents(options: EnhancedScatterBehaviorOptions, selectionHandler: ISelectionHandler): void {\r\n let bubbles: D3.Selection = this.bubbles = options.dataPointsSelection,\r\n data: EnhancedScatterChartData = options.data;\r\n\r\n this.shouldEnableFill = (!data.sizeRange || !data.sizeRange.min) && data.fillPoint;\r\n this.colorBorder = data.colorBorder;\r\n\r\n registerStandardInteractivityHandlers(bubbles, selectionHandler);\r\n }\r\n\r\n public renderSelection(hasSelection: boolean) {\r\n let shouldEnableFill: boolean = this.shouldEnableFill,\r\n colorBorder: boolean = this.colorBorder;\r\n\r\n this.bubbles.style(\"fill-opacity\", (d: EnhancedScatterChartDataPoint) => {\r\n return this.getMarkerFillOpacity(d.size != null, shouldEnableFill, hasSelection, d.selected);\r\n });\r\n\r\n this.bubbles.style(\"stroke-opacity\", (d: EnhancedScatterChartDataPoint) => {\r\n return this.getMarkerStrokeOpacity(d.size != null, colorBorder, hasSelection, d.selected);\r\n });\r\n }\r\n\r\n private getMarkerFillOpacity(\r\n hasSize: boolean,\r\n shouldEnableFill: boolean,\r\n hasSelection: boolean,\r\n isSelected: boolean): number {\r\n\r\n if (hasSize || shouldEnableFill) {\r\n if (hasSelection && !isSelected) {\r\n return this.dimmedBubbleOpacity;\r\n }\r\n\r\n return this.defaultBubbleOpacity;\r\n } else {\r\n return 0;\r\n }\r\n }\r\n\r\n public getMarkerStrokeOpacity(\r\n hasSize: boolean,\r\n colorBorder: boolean,\r\n hasSelection: boolean,\r\n isSelected: boolean): number {\r\n\r\n if (hasSize && colorBorder) {\r\n return 1;\r\n } else {\r\n if (hasSelection && !isSelected) {\r\n return this.dimmedBubbleOpacity;\r\n }\r\n\r\n return this.defaultBubbleOpacity;\r\n }\r\n }\r\n }\r\n}\r\n\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/CustomVisuals/visuals/enhancedScatterChart/visual/enhancedScatterChart.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\nvar THREE: any;\r\nvar WebGLHeatmap;\r\nvar GlobeMapCanvasLayers: JQuery[];\r\n\r\nmodule powerbi.visuals.samples {\r\n import TouchRect = controls.TouchUtils.Rectangle;\r\n import DataRoleHelper = powerbi.data.DataRoleHelper;\r\n\r\n interface GlobeMapData {\r\n lat: number;\r\n lng: number;\r\n height: number;\r\n heightBySeries: number[];\r\n seriesToolTipData: any[];\r\n heat: number;\r\n toolTipData: any;\r\n }\r\n\r\n interface GlobeMapDataPoint extends SelectableDataPoint {\r\n label: string;\r\n color: string;\r\n category?: string;\r\n }\r\n\r\n export class GlobeMap implements IVisual {\r\n public static MercartorSphere: any;\r\n private viewport: IViewport;\r\n private container: JQuery;\r\n private domElement: HTMLElement;\r\n private camera: any;\r\n private renderer: any;\r\n private scene: any;\r\n private orbitControls: any;\r\n private earth: any;\r\n private settings: any;\r\n private data: GlobeMapData[] = [];\r\n private dataPointsToEnumerate: GlobeMapDataPoint[];\r\n private heatmap: any;\r\n private heatTexture: any;\r\n private mapTextures: any[];\r\n private barsGroup: any;\r\n private readyToRender: boolean;\r\n private categoricalView: any;\r\n private deferredRenderTimerId: any;\r\n private globeMapLocationCache: any;\r\n private locationsToLoad: number = 0;\r\n private locationsLoaded: number = 0;\r\n private renderLoopEnabled = true;\r\n private needsRender = false;\r\n private mousePosNormalized: any;\r\n private mousePos: any;\r\n private rayCaster: any;\r\n private selectedBar: any;\r\n private hoveredBar: any;\r\n private averageBarVector: any;\r\n private zoomControl: any;\r\n private colorHelper: ColorHelper;\r\n private colors: IDataColorPalette;\r\n private style: IVisualStyle;\r\n\r\n public static capabilities: 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 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 {\r\n name: 'Series',\r\n kind: powerbi.VisualDataRoleKind.Grouping,\r\n displayName: \"Legend\",\r\n },\r\n {\r\n name: 'X',\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: 'Longitude',\r\n description: 'Use to override the longitude of locations',\r\n preferredTypes: [{ geography: { longitude: true } }],\r\n },\r\n {\r\n name: 'Y',\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: 'Latitude',\r\n description: 'Use to override the latitude of locations',\r\n preferredTypes: [{ geography: { latitude: true } }],\r\n },\r\n {\r\n name: 'Height',\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: 'Bar Height',\r\n },\r\n {\r\n name: 'Heat',\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: 'Heat Intensity',\r\n }\r\n ],\r\n objects: {\r\n general: {\r\n displayName: data.createDisplayNameGetter('Visual_General'),\r\n properties: {\r\n formatString: {\r\n type: { formatting: { formatString: true } },\r\n },\r\n },\r\n },\r\n legend: {\r\n displayName: data.createDisplayNameGetter('Visual_Legend'),\r\n properties: {\r\n show: {\r\n displayName: data.createDisplayNameGetter('Visual_Show'),\r\n type: { bool: true }\r\n },\r\n position: {\r\n displayName: data.createDisplayNameGetter('Visual_LegendPosition'),\r\n type: { formatting: { legendPosition: true } }\r\n },\r\n showTitle: {\r\n displayName: data.createDisplayNameGetter('Visual_LegendShowTitle'),\r\n type: { bool: true }\r\n },\r\n titleText: {\r\n displayName: data.createDisplayNameGetter('Visual_LegendTitleText'),\r\n type: { text: true }\r\n }\r\n }\r\n },\r\n dataPoint: {\r\n displayName: data.createDisplayNameGetter('Visual_DataPoint'),\r\n properties: {\r\n defaultColor: {\r\n displayName: data.createDisplayNameGetter('Visual_DefaultColor'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n showAllDataPoints: {\r\n displayName: data.createDisplayNameGetter('Visual_DataPoint_Show_All'),\r\n type: { bool: true }\r\n },\r\n fill: {\r\n displayName: data.createDisplayNameGetter('Visual_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: ['Category'],\r\n },\r\n },\r\n }\r\n }\r\n },\r\n categoryLabels: {\r\n displayName: data.createDisplayNameGetter('Visual_CategoryLabels'),\r\n properties: {\r\n show: {\r\n displayName: data.createDisplayNameGetter('Visual_Show'),\r\n type: { bool: true }\r\n },\r\n color: {\r\n displayName: data.createDisplayNameGetter('Visual_LabelsFill'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n },\r\n },\r\n },\r\n dataViewMappings: [{\r\n conditions: [\r\n { 'Category': { max: 1 }, 'Series': { max: 1 }, 'Height': { max: 1 }, 'Heat': { 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: 'Height' } },\r\n { bind: { to: 'Heat' } },\r\n { bind: { to: 'X' } },\r\n { bind: { to: 'Y' } },\r\n ],\r\n dataReductionAlgorithm: { top: {} }\r\n }\r\n },\r\n rowCount: { preferred: { min: 2 } }\r\n },\r\n }],\r\n sorting: {\r\n custom: {},\r\n }\r\n };\r\n\r\n private static Properties: any = {\r\n general: {\r\n formatString: <DataViewObjectPropertyIdentifier>{\r\n objectName: \"general\",\r\n propertyName: \"formatString\"\r\n }\r\n },\r\n dataPoint: {\r\n fill: <DataViewObjectPropertyIdentifier>{\r\n objectName: \"dataPoint\",\r\n propertyName: \"fill\"\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 public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstanceEnumeration {\r\n var enumeration = new ObjectEnumerationBuilder();\r\n\r\n switch (options.objectName) {\r\n case 'dataPoint':\r\n this.enumerateDataPoints(enumeration);\r\n break;\r\n }\r\n return enumeration.complete();\r\n }\r\n\r\n private enumerateDataPoints(enumeration: ObjectEnumerationBuilder): void {\r\n var data = this.data;\r\n if (!data)\r\n return;\r\n\r\n var dataPoints = this.dataPointsToEnumerate;\r\n var dataPointsLength = dataPoints.length;\r\n\r\n for (var i = 0; i < dataPointsLength; i++) {\r\n var 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 public init(options: VisualInitOptions): void {\r\n this.container = options.element;\r\n this.viewport = options.viewport;\r\n this.readyToRender = false;\r\n if (!this.globeMapLocationCache) this.globeMapLocationCache = {};\r\n\r\n this.style = options.style;\r\n this.colors = this.style.colorPalette.dataColors;\r\n this.colorHelper = new ColorHelper(this.colors, GlobeMap.Properties.dataPoint.fill);\r\n\r\n if (!THREE) {\r\n loadGlobeMapLibs();\r\n }\r\n\r\n if (THREE) {\r\n this.setup();\r\n }\r\n }\r\n\r\n private setup() {\r\n this.initSettings();\r\n this.initTextures();\r\n this.initMercartorSphere();\r\n this.initZoomControl();\r\n this.initScene();\r\n this.initHeatmap();\r\n this.readyToRender = true;\r\n this.composeRenderData();\r\n this.initRayCaster();\r\n }\r\n\r\n private initSettings() {\r\n var settings = this.settings = <any>{};\r\n settings.autoRotate = false;\r\n settings.earthRadius = 30;\r\n settings.cameraRadius = 100;\r\n settings.earthSegments = 100;\r\n settings.heatmapSize = 1000;\r\n settings.heatPointSize = 7;\r\n settings.heatIntensity = 10;\r\n settings.heatmapScaleOnZoom = 0.95;\r\n settings.barWidth = 0.3;\r\n settings.barHeight = 5;\r\n settings.rotateSpeed = 0.5;\r\n settings.zoomSpeed = 0.8;\r\n settings.cameraAnimDuration = 1000; //ms\r\n settings.clickInterval = 200; //ms\r\n }\r\n\r\n private initScene() {\r\n var viewport = this.viewport;\r\n var settings = this.settings;\r\n var clock = new THREE.Clock();\r\n var renderer = this.renderer = new THREE.WebGLRenderer({ antialias: true, preserveDrawingBuffer: true });\r\n this.container.append(renderer.domElement);\r\n this.domElement = renderer.domElement;\r\n var camera = this.camera = new THREE.PerspectiveCamera(35, viewport.width / viewport.height, 0.1, 10000);\r\n var orbitControls = this.orbitControls = new THREE.OrbitControls(camera, this.domElement);\r\n var scene = this.scene = new THREE.Scene();\r\n\r\n renderer.setSize(viewport.width, viewport.height);\r\n renderer.setClearColor(0xbac4d2, 1);\r\n camera.position.z = settings.cameraRadius;\r\n\r\n orbitControls.maxDistance = settings.cameraRadius;\r\n orbitControls.minDistance = settings.earthRadius + 1;\r\n orbitControls.rotateSpeed = settings.rotateSpeed;\r\n orbitControls.zoomSpeed = settings.zoomSpeed;\r\n orbitControls.autoRotate = settings.autoRotate;\r\n\r\n var ambientLight = new THREE.AmbientLight(0x000000);\r\n var light1 = new THREE.DirectionalLight(0xffffff, 0.4);\r\n var light2 = new THREE.DirectionalLight(0xffffff, 0.4);\r\n var earth = this.earth = this.createEarth();\r\n\r\n scene.add(ambientLight);\r\n scene.add(light1);\r\n scene.add(light2);\r\n scene.add(earth);\r\n\r\n light1.position.set(20, 20, 20);\r\n light2.position.set(0, 0, -20);\r\n\r\n var _zis = this;\r\n\r\n requestAnimationFrame(function render() {\r\n try {\r\n if (_zis.renderLoopEnabled) requestAnimationFrame(render);\r\n if (!_zis.shouldRender()) return;\r\n orbitControls.update(clock.getDelta());\r\n _zis.setEarthTexture();\r\n _zis.intersectBars();\r\n if (_zis.heatmap &&_zis.heatmap.display) {\r\n _zis.heatmap.display(); // Needed for IE/Edge to behave nicely\r\n }\r\n renderer.render(scene, camera);\r\n _zis.needsRender = false;\t\r\n //console.log(\"render\");\t\t\t\r\n } catch (e) {\r\n console.error(e);\r\n }\r\n });\r\n }\r\n\r\n private shouldRender(): boolean {\r\n return this.readyToRender && this.needsRender;\r\n }\r\n\r\n private createEarth() {\r\n var geometry = new GlobeMap.MercartorSphere(this.settings.earthRadius, this.settings.earthSegments, this.settings.earthSegments);\r\n var material = new THREE.MeshPhongMaterial({\r\n map: this.mapTextures[0],\r\n side: THREE.DoubleSide,\r\n shininess: 1,\r\n emissive: 0xaaaaaa,\r\n //wireframe: true\r\n });\r\n return new THREE.Mesh(geometry, material);\r\n }\r\n\r\n public zoomClicked(zoomDirection: any) {\r\n if (this.orbitControls.enabled === false || this.orbitControls.enableZoom === false)\r\n return;\r\n\r\n if (zoomDirection === -1)\r\n this.orbitControls.constraint.dollyOut(Math.pow(0.95, this.settings.zoomSpeed));\r\n else if (zoomDirection === 1)\r\n this.orbitControls.constraint.dollyIn(Math.pow(0.95, this.settings.zoomSpeed));\r\n\r\n this.orbitControls.update();\r\n this.animateCamera(this.camera.position);\r\n }\r\n\r\n public rotateCam(deltaX: number, deltaY: number) {\r\n if (this.orbitControls.enabled === false || this.orbitControls.enableRotate === false)\r\n return;\r\n\r\n this.orbitControls.constraint.rotateLeft(2 * Math.PI * deltaX / this.domElement.offsetHeight * this.settings.rotateSpeed);\r\n this.orbitControls.constraint.rotateUp(2 * Math.PI * deltaY / this.domElement.offsetHeight * this.settings.rotateSpeed);\r\n this.orbitControls.update();\r\n this.animateCamera(this.camera.position);\r\n }\r\n\r\n private initTextures() {\r\n if (!GlobeMapCanvasLayers) {\r\n // Initialize once, since this is a CPU + Network heavy operation.\r\n GlobeMapCanvasLayers = [];\r\n\r\n for (var level = 2; level <= 5; ++level) {\r\n var canvas = this.getBingMapCanvas(level);\r\n GlobeMapCanvasLayers.push(canvas);\r\n }\r\n }\r\n\r\n // Can't execute in for loop because variable assignement gets overwritten\r\n var createTexture = (canvas: JQuery) => {\r\n var texture = new THREE.Texture(canvas.get(0));\r\n texture.needsUpdate = true;\r\n canvas.on(\"ready\", (e, resolution) => {\r\n //console.log(\"level ready\", resolution, texture)\r\n texture.needsUpdate = true;\r\n this.needsRender = true;\r\n });\r\n return texture;\r\n\r\n };\r\n\r\n this.mapTextures = [];\r\n for (var i = 0; i < GlobeMapCanvasLayers.length; ++i) {\r\n this.mapTextures.push(createTexture(GlobeMapCanvasLayers[i]));\r\n }\r\n }\r\n\r\n private initHeatmap() {\r\n var settings = this.settings;\r\n\r\n //console.log(\"initHeatmap\");\r\n try {\r\n var heatmap = this.heatmap = new WebGLHeatmap({ width: settings.heatmapSize, height: settings.heatmapSize, intensityToAlpha: true });\r\n } catch (e) {\r\n // IE & Edge will throw an error about texImage2D, we need to ignore it\r\n console.error(e);\r\n }\r\n\r\n // canvas contents will be used for a texture\r\n var texture = this.heatTexture = new THREE.Texture(heatmap.canvas);\r\n texture.needsUpdate = true;\r\n\r\n var material = new THREE.MeshBasicMaterial({ map: texture, transparent: true });\r\n var geometry = new THREE.SphereGeometry(settings.earthRadius + 0.01, settings.earthSegments, settings.earthSegments);\r\n var mesh = new THREE.Mesh(geometry, material);\r\n\r\n window[\"heatmap\"] = heatmap;\r\n window[\"heatmapTexture\"] = texture;\r\n\r\n this.scene.add(mesh);\r\n }\r\n\r\n private setEarthTexture() {\r\n //get distance as arbitrary value from 0-1\r\n if (!this.camera) return;\r\n var maxDistance = this.settings.cameraRadius - this.settings.earthRadius;\r\n var distance = (this.camera.position.length() - this.settings.earthRadius) / maxDistance;\r\n\r\n var texture;\r\n if (distance <= 1 / 5) {\r\n texture = this.mapTextures[3];\r\n } else if (distance <= 2 / 5) {\r\n texture = this.mapTextures[2];\r\n } else if (distance <= 3 / 5) {\r\n texture = this.mapTextures[1];\r\n } else {\r\n texture = this.mapTextures[0];\r\n }\r\n\r\n if (this.earth.material.map !== texture) {\r\n this.earth.material.map = texture;\r\n }\r\n\r\n if (this.selectedBar) {\r\n this.orbitControls.rotateSpeed = this.settings.rotateSpeed;\r\n } else {\r\n this.orbitControls.rotateSpeed = this.settings.rotateSpeed * distance;\r\n }\r\n\r\n //console.log(distance, this.orbitControls.rotateSpeed);\r\n }\r\n\r\n public update(options: VisualUpdateOptions) {\r\n this.needsRender = true;\r\n if (options.viewport.height !== this.viewport.height || options.viewport.width !== this.viewport.width) {\r\n var viewport = this.viewport = options.viewport;\r\n if (this.camera && this.renderer) {\r\n this.camera.aspect = viewport.width / viewport.height;\r\n this.camera.updateProjectionMatrix();\r\n this.renderer.setSize(viewport.width, viewport.height);\r\n }\r\n return;\r\n }\r\n this.cleanHeatAndBar();\r\n\r\n // PowerBI fires two update calls, one for size, one for data\r\n if (options.dataViews[0] && (options.dataViews[0].categorical || options.dataViews[0].metadata)) {\r\n this.composeRenderData(options.dataViews[0].categorical, options.dataViews[0].metadata);\r\n }\r\n }\r\n\r\n public cleanHeatAndBar() {\r\n this.heatmap.clear();\r\n this.heatTexture.needsUpdate = true;\r\n if (this.barsGroup) {\r\n this.scene.remove(this.barsGroup);\r\n }\r\n }\r\n\r\n private renderMagic() {\r\n if (!this.readyToRender) {\r\n //console.log(\"not ready to render\");\r\n this.defferedRender();\r\n return;\r\n }\r\n\r\n var renderData = this.data;\r\n var heatmap = this.heatmap;\r\n var settings = this.settings;\r\n\r\n heatmap.clear();\r\n\r\n if (this.barsGroup) {\r\n this.scene.remove(this.barsGroup);\r\n }\r\n\r\n this.barsGroup = new THREE.Object3D();\r\n this.scene.add(this.barsGroup);\r\n\r\n this.averageBarVector = new THREE.Vector3();\r\n\r\n for (var i = 0, len = renderData.length; i < len; ++i) {\r\n var renderDatum = renderData[i];\r\n\r\n if (!renderDatum.lat || !renderDatum.lng) continue;\r\n\r\n if (renderDatum.heat > 0.001) {\r\n if (renderDatum.heat < 0.1) renderDatum.heat = 0.1;\r\n var x = (180 + renderDatum.lng) / 360 * settings.heatmapSize;\r\n var y = (1 - ((90 + renderDatum.lat) / 180)) * settings.heatmapSize;\r\n heatmap.addPoint(x, y, settings.heatPointSize, renderDatum.heat * settings.heatIntensity);\r\n }\r\n\r\n if (renderDatum.height >= 0) {\r\n if (renderDatum.height < 0.01) renderDatum.height = 0.01;\r\n var latRadians = renderDatum.lat / 180 * Math.PI; //radians\r\n var lngRadians = renderDatum.lng / 180 * Math.PI;\r\n\r\n var x = Math.cos(lngRadians) * Math.cos(latRadians);\r\n var z = -Math.sin(lngRadians) * Math.cos(latRadians);\r\n var y = Math.sin(latRadians);\r\n var v = new THREE.Vector3(x, y, z);\r\n\r\n this.averageBarVector.add(v);\r\n\r\n var barHeight = settings.barHeight * renderDatum.height;\r\n //this array holds the relative series values to the actual measure for example [0.2,0.3,0.5]\r\n //this is how we draw the vectors relativly to the complete value one on top of another. \r\n var measuresBySeries = [];\r\n //this array holds the original values of the series for the tool tips\r\n var dataPointToolTip = [];\r\n if (renderDatum.heightBySeries) {\r\n for (var c = 0; c < renderDatum.heightBySeries.length; c++) {\r\n if (renderDatum.heightBySeries[c]) {\r\n measuresBySeries.push(renderDatum.heightBySeries[c]);\r\n }\r\n dataPointToolTip.push(renderDatum.seriesToolTipData[c]);\r\n }\r\n } else {\r\n //no category series so we'll just draw one value\r\n measuresBySeries.push(1);\r\n }\r\n\r\n var previousMeasureValue = 0;\r\n for (var j = 0; j < measuresBySeries.length; j++) {\r\n previousMeasureValue += measuresBySeries[j];\r\n var geometry = new THREE.CubeGeometry(settings.barWidth, settings.barWidth, barHeight * measuresBySeries[j]);\r\n var bar = new THREE.Mesh(geometry, this.getBarMaterialByIndex(j));\r\n bar.position = v.clone().multiplyScalar(settings.earthRadius + ((barHeight / 2) * previousMeasureValue));\r\n bar.lookAt(v);\r\n bar.toolTipData = dataPointToolTip.length === 0 ? renderDatum.toolTipData : this.getToolTipDataForSeries(renderDatum.toolTipData, dataPointToolTip[j]);\r\n this.barsGroup.add(bar);\r\n previousMeasureValue += measuresBySeries[j];\r\n }\r\n }\r\n }\r\n\r\n if (this.barsGroup.children.length > 0 && this.camera) {\r\n this.averageBarVector.multiplyScalar(1 / this.barsGroup.children.length);\r\n if (this.locationsLoaded === this.locationsToLoad) {\r\n this.animateCamera(this.averageBarVector);\r\n }\r\n }\r\n\r\n heatmap.update();\r\n heatmap.blur();\r\n this.heatTexture.needsUpdate = true;\r\n this.needsRender = true;\t\t\t\r\n\r\n //console.log(\"renderMagic done! locations:\", this.barsGroup.children.length, \"toload/loaded\", this.locationsToLoad, this.locationsLoaded)\r\n }\r\n\r\n private getBarMaterialByIndex(index): any {\r\n return new THREE.MeshPhongMaterial({ color: this.dataPointsToEnumerate[index].color });\r\n }\r\n\r\n private getToolTipDataForSeries(toolTipData, dataPointToolTip): any {\r\n var result = jQuery.extend(true, {\r\n series: { displayName: dataPointToolTip.displayName, value: dataPointToolTip.value }\r\n }, toolTipData);\r\n result.height.value = dataPointToolTip.dataPointValue;\r\n return result;\r\n }\r\n\r\n private createDataPointForEnumeration(seriesData, valueIndex, seriesIndex, metaData?): GlobeMapDataPoint {\r\n let source = seriesData.values[valueIndex].source;\r\n let label = converterHelper.getFormattedLegendLabel(source, seriesData.values, null);\r\n let identity = SelectionId.createWithId(seriesData.identity);\r\n let category = converterHelper.getSeriesName(source);\r\n let color = seriesData.objects && seriesData.objects.dataPoint ? seriesData.objects.dataPoint.fill.solid.color :\r\n metaData && metaData.objects ? this.colorHelper.getColorForMeasure(metaData.objects,\"\") : this.colors.getColorByIndex(seriesIndex).value;\r\n\r\n return {\r\n label: label,\r\n identity: identity,\r\n category: category,\r\n color: color,\r\n selected: null\r\n };\r\n }\r\n\r\n private composeRenderData(categoricalView?, metadataView?) {\r\n // memoize last value\r\n if (categoricalView) {\r\n this.categoricalView = categoricalView;\r\n } else {\r\n categoricalView = this.categoricalView;\r\n }\r\n\r\n this.data = [];\r\n this.dataPointsToEnumerate = [];\r\n var locations = [];\r\n var globeMapLocationCache = this.globeMapLocationCache;\r\n\r\n //console.log(\"categoricalView\", categoricalView)\r\n if (!categoricalView) return;\r\n\r\n var heightIndex = 0,\r\n intensityIndex = 0,\r\n categories = [];\r\n try {\r\n if (categoricalView.categories) {\r\n categories = categoricalView.categories;\r\n }\r\n var grouped = categoricalView.values.grouped();\r\n heightIndex = DataRoleHelper.getMeasureIndexOfRole(grouped, \"Height\");\r\n intensityIndex = DataRoleHelper.getMeasureIndexOfRole(grouped, \"Heat\");\r\n var longitudeIndex = DataRoleHelper.getMeasureIndexOfRole(grouped, \"X\");\r\n var latitudeIndex = DataRoleHelper.getMeasureIndexOfRole(grouped, \"Y\");\r\n } catch (e) { }\r\n\r\n var locationType, heights, heightsBySeries, toolTipDataBySeries, heats, latitudes, longitudes, locationDispName, heightDispName, heatDispName, heightFormat, heatFormat;\r\n\r\n if (categories && categories.length > 0 && categories[0].values) {\r\n var locationCategory = categories[0];\r\n locations = locationCategory.values;\r\n locationDispName = locationCategory.source.displayName;\r\n if (locationCategory.source.type.category) {\r\n locationType = locationCategory.source.type.category.toLowerCase();\r\n } else {\r\n locationType = \"\";\r\n }\r\n } else {\r\n locations = [];\r\n }\r\n\r\n // For debugging since devTools - salesByCountry isn't really sales by country\r\n //var places = [\"kenya\", \"india\", \"united states\", \"london\", \"australia\", \"canada\"]\r\n //heightIndex = 0;\r\n\r\n if (heightIndex !== undefined && categoricalView.values[heightIndex] && categoricalView.values !== undefined) {\r\n // heights = categoricalView.values[heightIndex].values;\r\n heightDispName = categoricalView.values[heightIndex].source.displayName;\r\n heightFormat = categoricalView.values[heightIndex].source.format;\r\n if (grouped.length > 1) {\r\n heights = new Array(locations.length);\r\n heightsBySeries = new Array(locations.length);\r\n toolTipDataBySeries = new Array(locations.length);\r\n this.dataPointsToEnumerate = new Array(grouped.length);\r\n //creating a matrix for drawing values by series later.\r\n for (var i = 0; i < grouped.length; i++) {\r\n var values = grouped[i].values[heightIndex].values;\r\n this.dataPointsToEnumerate[i] = this.createDataPointForEnumeration(grouped[i], heightIndex, i);\r\n for (var j = 0; j < values.length; j++) {\r\n if (!heights[j]) heights[j] = 0;\r\n heights[j] += values[j] ? values[j] : 0;\r\n if (!heightsBySeries[j]) heightsBySeries[j] = [];\r\n heightsBySeries[j][i] = values[j];\r\n if (!toolTipDataBySeries[j]) toolTipDataBySeries[j] = [];\r\n toolTipDataBySeries[j][i] = { displayName: categoricalView.values.source.displayName, value: grouped[i].name, dataPointValue: values[j] };\r\n }\r\n }\r\n for (var i = 0; i < grouped.length; i++) {\r\n var values = grouped[i].values[heightIndex].values;\r\n for (var j = 0; j < values.length; j++) { \r\n //calculating relative size of series\r\n heightsBySeries[j][i] = values[j] / heights[j];\r\n }\r\n }\r\n } else {\r\n heights = categoricalView.values[heightIndex].values;\r\n heightsBySeries = new Array(grouped.length);\r\n this.dataPointsToEnumerate[0] = this.createDataPointForEnumeration(grouped[0], heightIndex, 0, metadataView);\r\n }\r\n\r\n } else {\r\n heightsBySeries = new Array(locations.length);\r\n heights = new Array(locations.length);\r\n }\r\n\r\n if (intensityIndex !== undefined && categoricalView.values[intensityIndex]) {\r\n if (grouped.length > 1) {\r\n heats = new Array(locations.length);\r\n for (var i = 0; i < grouped.length; i++) {\r\n var values = grouped[i].values[intensityIndex].values;\r\n for (var j = 0; j < values.length; j++) {\r\n if (!heats[j]) heats[j] = 0;\r\n heats[j] += values[j] ? values[j] : 0;\r\n }\r\n }\r\n } else {\r\n heats = categoricalView.values[intensityIndex].values;\r\n }\r\n heatDispName = categoricalView.values[intensityIndex].source.displayName;\r\n heatFormat = categoricalView.values[intensityIndex].source.format;\r\n } else {\r\n heats = new Array(locations.length);\r\n }\r\n\r\n if (longitudeIndex !== undefined && categoricalView.values[longitudeIndex]\r\n && latitudeIndex !== undefined && categoricalView.values[latitudeIndex]) {\r\n longitudes = categoricalView.values[longitudeIndex].values;\r\n latitudes = categoricalView.values[latitudeIndex].values;\r\n }\r\n else {\r\n longitudes = null;\r\n latitudes = null;\r\n }\r\n\r\n var maxHeight = Math.max.apply(null, heights) || 1;\r\n var maxHeat = Math.max.apply(null, heats) || 1;\r\n var heatFormatter = valueFormatter.create({ format: heatFormat, value: heats[0], value2: heats[1] });\r\n var heightFormatter = valueFormatter.create({ format: heightFormat, value: heights[0], value2: heights[1] });\r\n\r\n for (var i = 0, len = locations.length; i < len; ++i) {\r\n var place = locations[i];\r\n var lat, lng, latlng, height, heat;\r\n\r\n //place = places[i];\r\n\r\n if (place && typeof (place) === \"string\") {\r\n place = place.toLowerCase();\r\n var placeKey = place + \"/\" + locationType;\r\n\r\n if (!longitudes && globeMapLocationCache[placeKey]) {\r\n latlng = globeMapLocationCache[placeKey];\r\n lat = latlng.latitude;\r\n lng = latlng.longitude;\r\n }\r\n else if (longitudes) {\r\n lat = latitudes[i];\r\n lng = longitudes[i];\r\n }\r\n\r\n height = heights[i] / maxHeight;\r\n heat = heats[i] / maxHeat;\r\n\r\n var renderDatum = {\r\n lat: lat,\r\n lng: lng,\r\n height: height ? height || 0.01 : undefined,\r\n heightBySeries: heightsBySeries[i],\r\n seriesToolTipData: toolTipDataBySeries ? toolTipDataBySeries[i] : undefined,\r\n heat: heat || 0,\r\n toolTipData: {\r\n location: { displayName: locationDispName, value: locations[i] },\r\n height: { displayName: heightDispName, value: heightFormatter.format(heights[i]) },\r\n heat: { displayName: heatDispName, value: heatFormatter.format(heats[i]) }\r\n }\r\n };\r\n\r\n this.data.push(renderDatum);\r\n\r\n if (!longitudes && !latlng) {\r\n this.geocodeRenderDatum(renderDatum, place, locationType);\r\n }\r\n }\r\n }\r\n\r\n try {\r\n this.renderMagic();\r\n } catch (e) {\r\n console.error(e);\r\n }\r\n }\r\n\r\n private geocodeRenderDatum(renderDatum, place, locationType) {\r\n var placeKey = place + \"/\" + locationType;\r\n this.globeMapLocationCache[placeKey] = {}; //store empty object so we don't send AJAX request again\r\n this.locationsToLoad++;\r\n\r\n try {\r\n var geocoder = powerbi.visuals[\"BI\"].Services.GeocodingManager.geocode;\r\n } catch (e) {\r\n geocoder = services.geocode;\r\n }\r\n\r\n if (geocoder) {\r\n geocoder(place, locationType).always((latlng: any) => {\r\n // we use always because we want to cache unknown values. \r\n // No point asking bing again and again when it tells us it doesn't know about a location\r\n this.globeMapLocationCache[placeKey] = latlng;\r\n this.locationsLoaded++;\r\n //console.log(place, latlng);\r\n\r\n if (latlng.latitude && latlng.longitude) {\r\n renderDatum.lat = latlng.latitude;\r\n renderDatum.lng = latlng.longitude;\r\n\r\n this.defferedRender();\r\n }\r\n });\r\n }\r\n }\r\n\r\n private defferedRender() {\r\n if (!this.deferredRenderTimerId) {\r\n this.deferredRenderTimerId = setTimeout(() => {\r\n this.deferredRenderTimerId = null;\r\n this.composeRenderData();\r\n }, 500);\r\n }\r\n }\r\n\r\n private initRayCaster() {\r\n this.rayCaster = new THREE.Raycaster();\r\n var settings = this.settings;\r\n var mousePosNormalized = this.mousePosNormalized = new THREE.Vector2();\r\n var mousePos = this.mousePos = new THREE.Vector2();\r\n var element = this.container.get(0);\r\n var mouseDownTime;\r\n\r\n $(this.domElement).on(\"mousemove\", (event) => {\r\n // get coordinates in -1 to +1 space\r\n var rect = element.getBoundingClientRect();\r\n mousePos.x = event.clientX;\r\n mousePos.y = event.clientY;\r\n mousePosNormalized.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;\r\n mousePosNormalized.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;\r\n this.needsRender = true;\r\n }).on(\"mousedown\", (event) => {\r\n mouseDownTime = Date.now();\r\n }).on(\"mouseup\", (event) => {\r\n // Debounce slow clicks\r\n if ((Date.now() - mouseDownTime) > settings.clickInterval) return;\r\n if (this.hoveredBar && event.shiftKey) {\r\n this.selectedBar = this.hoveredBar;\r\n this.animateCamera(this.selectedBar.position, () => {\r\n if (!this.selectedBar) return;\r\n this.orbitControls.center.copy(this.selectedBar.position.clone().normalize().multiplyScalar(settings.earthRadius));\r\n this.orbitControls.minDistance = 1;\r\n });\r\n } else {\r\n if (this.selectedBar) {\r\n this.animateCamera(this.selectedBar.position, () => {\r\n this.orbitControls.center.set(0, 0, 0);\r\n this.orbitControls.minDistance = settings.earthRadius + 1;\r\n });\r\n this.selectedBar = null;\r\n }\r\n }\r\n }).on(\"mousewheel DOMMouseScroll\", (e: any) => {\r\n this.needsRender = true;\r\n if (this.orbitControls.enabled && this.orbitControls.enableZoom) {\r\n this.heatTexture.needsUpdate = true;\r\n e = e.originalEvent;\r\n var delta = e.wheelDelta > 0 || e.detail < 0 ? 1 : -1;\r\n var scale = delta > 0 ? this.settings.heatmapScaleOnZoom : (1 / this.settings.heatmapScaleOnZoom);\r\n this.heatmap.multiply(scale);\r\n this.heatmap.update();\r\n }\r\n });\r\n }\r\n\r\n private intersectBars() {\r\n if (!this.rayCaster || !this.barsGroup) return;\r\n var rayCaster = this.rayCaster;\r\n rayCaster.setFromCamera(this.mousePosNormalized, this.camera);\r\n var intersects = rayCaster.intersectObjects(this.barsGroup.children);\r\n\r\n if (intersects && intersects.length > 0) {\r\n //console.log(intersects[0], this.mousePos.x, this.mousePos.y);\r\n var object = intersects[0].object;\r\n if (!object || !object.toolTipData) return;\r\n var toolTipData = object.toolTipData;\r\n var toolTipItems: TooltipDataItem[] = [];\r\n if (toolTipData.location.displayName) toolTipItems.push(toolTipData.location);\r\n if (toolTipData.series) toolTipItems.push(toolTipData.series);\r\n if (toolTipData.height.displayName) toolTipItems.push(toolTipData.height);\r\n if (toolTipData.heat.displayName) toolTipItems.push(toolTipData.heat);\r\n this.hoveredBar = object;\r\n TooltipManager.ToolTipInstance.show(toolTipItems, <TouchRect>{ x: this.mousePos.x, y: this.mousePos.y, width: 0, height: 0 });\r\n } else {\r\n this.hoveredBar = null;\r\n TooltipManager.ToolTipInstance.hide();\r\n }\r\n }\r\n\r\n private animateCamera(to: any, done?: Function) {\r\n if (!this.camera) return;\r\n var startTime = Date.now();\r\n var duration = this.settings.cameraAnimDuration;\r\n var endTime = startTime + duration;\r\n var startPos = this.camera.position.clone().normalize();\r\n var endPos = to.clone().normalize();\r\n var length = this.camera.position.length();\r\n\r\n var easeInOut = function (t) {\r\n t *= 2;\r\n if (t < 1) return (t * t * t) / 2;\r\n t -= 2;\r\n return (t * t * t + 2) / 2;\r\n };\r\n\r\n var onUpdate = () => {\r\n var now = Date.now();\r\n var t = (now - startTime) / duration;\r\n if (t > 1) t = 1;\r\n t = easeInOut(t);\r\n\r\n var pos = new THREE.Vector3()\r\n .add(startPos.clone().multiplyScalar(1 - t))\r\n .add(endPos.clone().multiplyScalar(t))\r\n .normalize()\r\n .multiplyScalar(length);\r\n\r\n this.camera.position = pos;\r\n\r\n if (now < endTime) {\r\n requestAnimationFrame(onUpdate);\r\n } else if (done) {\r\n done();\r\n }\r\n\r\n this.needsRender = true;\r\n };\r\n requestAnimationFrame(onUpdate);\r\n }\r\n\r\n public destroy() {\r\n clearTimeout(this.deferredRenderTimerId);\r\n this.renderLoopEnabled = false;\r\n this.scene = null;\r\n this.heatmap = null;\r\n this.heatTexture = null;\r\n this.camera = null;\r\n if (this.renderer) {\r\n if (this.renderer.context) {\r\n var extension = this.renderer.context.getExtension('WEBGL_lose_context');\r\n if (extension)\r\n extension.loseContext();\r\n this.renderer.context = null;\r\n }\r\n this.renderer.domElement = null;\r\n }\r\n this.renderer = null;\r\n this.data = null;\r\n this.barsGroup = null;\r\n if (this.orbitControls) this.orbitControls.dispose();\r\n this.orbitControls = null;\r\n if (this.domElement) $(this.domElement)\r\n .off(\"mousemove mouseup mousedown mousewheel DOMMouseScroll\");\r\n this.domElement = null;\r\n if (this.container) this.container.empty();\r\n }\r\n\r\n private initZoomControl() {\r\n var radius = 17;\r\n var zoomControlWidth = radius * 8.5;\r\n var zoomControlHeight = radius * 8.5;\r\n var startX = radius * 3;\r\n var startY = radius + 3;\r\n var gap = radius * 2;\r\n\r\n var zoomCss = {\r\n 'position': 'absolute',\r\n 'left': 'calc(100% - ' + zoomControlWidth + 'px)',\r\n 'top': 'calc(100% - ' + zoomControlHeight + 'px)',\r\n 'zIndex': '1000',\r\n };\r\n\r\n var zoomContainer = d3.select(this.container[0])\r\n .append('div')\r\n .style(zoomCss);\r\n\r\n this.zoomControl = zoomContainer.append(\"svg\").attr({ \"width\": zoomControlWidth, \"height\": zoomControlHeight });\r\n\r\n var bottom = this.zoomControl.append(\"g\").on(\"click\", () => this.rotateCam(0, -5));\r\n bottom.append(\"circle\").attr({ cx: startX + gap, cy: startY + (2 * gap), r: radius, fill: \"white\", opacity: 0.5, stroke: 'gray' });\r\n bottom.append(\"path\").attr({ d: \"M\" + (startX + (2 * radius)) + \" \" + (startY + (radius * 4.7)) + \" l12 -20 a40,70 0 0,1 -24,0z\", fill: \"gray\" });\r\n\r\n var left = this.zoomControl.append(\"g\").on(\"click\", () => this.rotateCam(5, 0));\r\n left.append(\"circle\").attr({ cx: startX, cy: startY + gap, r: radius, fill: \"white\", stroke: \"gray\", opacity: 0.5 });\r\n left.append(\"path\").attr({ d: \"M\" + (startX - radius / 1.5) + \" \" + (startY + (radius * 2)) + \" l20 -12 a70,40 0 0,0 0,24z\", fill: \"gray\" });\r\n\r\n var top = this.zoomControl.append(\"g\").on(\"click\", () => this.rotateCam(0, 5));\r\n top.append(\"circle\").attr({ cx: startX + gap, cy: startY, r: radius, fill: \"white\", stroke: \"gray\", opacity: 0.5 });\r\n top.append(\"path\").attr({ d: \"M\" + (startX + (2 * radius)) + \" \" + (startY - (radius / 1.5)) + \" l12 20 a40,70 0 0,0 -24,0z\", fill: \"gray\" });\r\n\r\n var right = this.zoomControl.append(\"g\").on(\"click\", () => this.rotateCam(-5, 0));\r\n right.append(\"circle\").attr({ cx: startX + (2 * gap), cy: startY + gap, r: radius, fill: \"white\", stroke: \"gray\", opacity: 0.5 });\r\n right.append(\"path\").attr({ d: \"M\" + (startX + (4.7 * radius)) + \" \" + (startY + (radius * 2)) + \" l-20 -12 a70,40 0 0,1 0,24z\", fill: \"gray\" });\r\n\r\n var zoomIn = this.zoomControl.append(\"g\").on(\"click\", () => this.zoomClicked(-1));\r\n zoomIn.append(\"circle\").attr({ cx: startX + 4 * radius, cy: startY + 6 * radius, r: radius, fill: \"white\", stroke: \"gray\", opacity: 0.5 });\r\n zoomIn.append(\"rect\").attr({ x: startX + 3.5 * radius, y: startY + 5.9 * radius, width: radius, height: radius / 3, fill: \"gray\" });\r\n zoomIn.append(\"rect\").attr({ x: startX + (4 * radius) - radius / 6, y: startY + 5.55 * radius, width: radius / 3, height: radius, fill: \"gray\" });\r\n\r\n var zoomOut = this.zoomControl.append(\"g\").on(\"click\", () => this.zoomClicked(1));\r\n zoomOut.append(\"circle\").attr({ cx: startX, cy: startY + 6 * radius, r: radius, fill: \"white\", stroke: \"gray\", opacity: \"0.50\" });\r\n zoomOut.append(\"rect\").attr({ x: startX - (radius / 2), y: startY + 5.9 * radius, width: radius, height: radius / 3, fill: \"gray\" });\r\n }\r\n\r\n private initMercartorSphere() {\r\n if (GlobeMap.MercartorSphere) return;\r\n\r\n var MercartorSphere = function (radius, widthSegments, heightSegments) {\r\n THREE.Geometry.call(this);\r\n\r\n this.radius = radius;\r\n this.widthSegments = widthSegments;\r\n this.heightSegments = heightSegments;\r\n\r\n this.t = 0;\r\n\r\n var x, y, vertices = [], uvs = [];\r\n\r\n function interplolate(a, b, t) {\r\n return (1 - t) * a + t * b;\r\n }\r\n\r\n // interpolates between sphere and plane\r\n function interpolateVertex(u, v, t) {\r\n var maxLng = Math.PI * 2;\r\n var maxLat = Math.PI;\r\n var radius = this.radius;\r\n\r\n var sphereX = - radius * Math.cos(u * maxLng) * Math.sin(v * maxLat);\r\n var sphereY = - radius * Math.cos(v * maxLat);\r\n var sphereZ = radius * Math.sin(u * maxLng) * Math.sin(v * maxLat);\r\n\r\n var planeX = u * radius * 2 - radius;\r\n var planeY = v * radius * 2 - radius;\r\n var planeZ = 0;\r\n\r\n var x = interplolate(sphereX, planeX, t);\r\n var y = interplolate(sphereY, planeY, t);\r\n var z = interplolate(sphereZ, planeZ, t);\r\n\r\n return new THREE.Vector3(x, y, z);\r\n }\r\n\r\n // http://mathworld.wolfram.com/MercatorProjection.html\r\n // Mercator projection goes form +85.05 to -85.05 degrees\r\n function interpolateUV(u, v, t) {\r\n var lat = (v - 0.5) * 90 * 2 / 180 * Math.PI; //turn from 0-1 into lat in radians\r\n var sin = Math.sin(lat);\r\n var normalizedV = 0.5 + 0.25 * Math.log((1 + sin) / (1 - sin)) / Math.PI;\r\n return new THREE.Vector2(u, normalizedV);//interplolate(normalizedV1, v, t))\r\n }\r\n\r\n for (y = 0; y <= heightSegments; y++) {\r\n\r\n var verticesRow = [];\r\n var uvsRow = [];\r\n\r\n for (x = 0; x <= widthSegments; x++) {\r\n\r\n var u = x / widthSegments;\r\n var v = y / heightSegments;\r\n\r\n this.vertices.push(interpolateVertex.call(this, u, v, this.t));\r\n uvsRow.push(interpolateUV.call(this, u, v, this.t));\r\n verticesRow.push(this.vertices.length - 1);\r\n }\r\n\r\n vertices.push(verticesRow);\r\n uvs.push(uvsRow);\r\n\r\n }\r\n\r\n //console.log(vertices, uvs);\r\n\r\n for (y = 0; y < this.heightSegments; y++) {\r\n\r\n for (x = 0; x < this.widthSegments; x++) {\r\n\r\n var v1 = vertices[y][x + 1];\r\n var v2 = vertices[y][x];\r\n var v3 = vertices[y + 1][x];\r\n var v4 = vertices[y + 1][x + 1];\r\n\r\n var n1 = this.vertices[v1].clone().normalize();\r\n var n2 = this.vertices[v2].clone().normalize();\r\n var n3 = this.vertices[v3].clone().normalize();\r\n var n4 = this.vertices[v4].clone().normalize();\r\n\r\n var uv1 = uvs[y][x + 1].clone();\r\n var uv2 = uvs[y][x].clone();\r\n var uv3 = uvs[y + 1][x].clone();\r\n var uv4 = uvs[y + 1][x + 1].clone();\r\n\r\n var normals = [n1, n2, n3, n4];\r\n\r\n this.faces.push(new THREE.Face4(v1, v2, v3, v4, normals));\r\n this.faceVertexUvs[0].push([uv1, uv2, uv3, uv4]);\r\n }\r\n\r\n }\r\n\r\n this.computeCentroids();\r\n this.computeFaceNormals();\r\n\r\n this.boundingSphere = new THREE.Sphere(new THREE.Vector3(), radius);\r\n };\r\n\r\n MercartorSphere.prototype = Object.create(THREE.Geometry.prototype);\r\n GlobeMap.MercartorSphere = MercartorSphere;\r\n }\r\n\r\n private getBingMapCanvas(resolution): JQuery {\r\n var tileSize = 256;\r\n var numSegments = Math.pow(2, resolution);\r\n var numTiles = numSegments * numSegments;\r\n var tilesLoaded = 0;\r\n var canvasSize = tileSize * numSegments;\r\n var canvas: JQuery = $('<canvas/>').attr({ width: canvasSize, height: canvasSize });\r\n var canvasElem: any = canvas.get(0);\r\n var canvasContext = canvasElem.getContext(\"2d\");\r\n\r\n function generateQuads(res, quad) {\r\n if (res <= resolution) {\r\n if (res === resolution) {\r\n loadTile(quad);\r\n //console.log(res, maxResolution, quad);\r\n }\r\n\r\n generateQuads(res + 1, quad + \"0\");\r\n generateQuads(res + 1, quad + \"1\");\r\n generateQuads(res + 1, quad + \"2\");\r\n generateQuads(res + 1, quad + \"3\");\r\n }\r\n }\r\n\r\n function loadTile(quad) {\r\n var template: any = \"https://t{server}.tiles.virtualearth.net/tiles/r{quad}.jpeg?g=0&mkt={language}\";\r\n var numServers = 7;\r\n var server = Math.round(Math.random() * numServers);\r\n var language = (navigator[\"languages\"] && navigator[\"languages\"].length) ? navigator[\"languages\"][0] : navigator.language;\r\n var url = template.replace(\"{server}\", server)\r\n .replace(\"{quad}\", quad)\r\n .replace(\"{language}\", language);\r\n var coords = getCoords(quad);\r\n //console.log(quad, coords.x, coords.y)\r\n\r\n var tile = new Image();\r\n tile.onload = function () {\r\n tilesLoaded++;\r\n canvasContext.drawImage(tile, coords.x * tileSize, coords.y * tileSize, tileSize, tileSize);\r\n if (tilesLoaded === numTiles) {\r\n canvas.trigger(\"ready\", resolution);\r\n }\r\n };\r\n\r\n // So the canvas doesn't get tainted\r\n tile.crossOrigin = '';\r\n tile.src = url;\r\n }\r\n\r\n function getCoords(quad) {\r\n var x = 0;\r\n var y = 0;\r\n var last = quad.length - 1;\r\n\r\n for (var i = last; i >= 0; i--) {\r\n var chr = quad.charAt(i);\r\n var pow = Math.pow(2, last - i);\r\n\r\n if (chr === \"1\") {\r\n x += pow;\r\n } else if (chr === \"2\") {\r\n y += pow;\r\n } else if (chr === \"3\") {\r\n x += pow;\r\n y += pow;\r\n }\r\n }\r\n\r\n return { x: x, y: y };\r\n }\r\n\r\n generateQuads(0, \"\");\r\n return canvas;\r\n }\r\n }\r\n}\r\n\r\nfunction loadGlobeMapLibs() {\r\n // include GlobeMapLibs.js\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/CustomVisuals/visuals/globeMap/visual/globeMap.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 ClassAndSelector = jsCommon.CssConstants.ClassAndSelector;\r\n import CreateClassAndSelector = jsCommon.CssConstants.createClassAndSelector;\r\n import PixelConverter = jsCommon.PixelConverter;\r\n\r\n export interface RadarChartConstructorOptions {\r\n animator?: IGenericAnimator;\r\n svg?: D3.Selection;\r\n margin?: IMargin;\r\n }\r\n\r\n export interface RadarChartDatapoint extends SelectableDataPoint {\r\n x: number;\r\n y: number;\r\n y0?: number;\r\n color?: string;\r\n value?: number;\r\n tooltipInfo?: TooltipDataItem[];\r\n labelFormatString?: string;\r\n labelFontSize?: string;\r\n }\r\n\r\n export interface RadarChartData {\r\n legendData: LegendData;\r\n series: RadarChartSeries[];\r\n settings: RadarChartSettings;\r\n dataLabelsSettings: PointDataLabelsSettings;\r\n }\r\n\r\n export interface RadarChartSeries {\r\n fill: string;\r\n name: string;\r\n data: RadarChartDatapoint[];\r\n identity: SelectionId;\r\n }\r\n\r\n export interface RadarChartSettings {\r\n showLegend?: boolean;\r\n }\r\n\r\n export interface RadarChartBehaviorOptions {\r\n selection: D3.Selection;\r\n clearCatcher: D3.Selection;\r\n }\r\n\r\n /**\r\n * RadarChartBehavior\r\n */\r\n export class RadarChartWebBehavior implements IInteractiveBehavior {\r\n private selection: D3.Selection;\r\n\r\n public bindEvents(options: RadarChartBehaviorOptions, selectionHandler: ISelectionHandler): void {\r\n var selection = this.selection = options.selection;\r\n var clearCatcher = options.clearCatcher;\r\n\r\n selection.on('click', function (d: SelectableDataPoint) {\r\n selectionHandler.handleSelection(d, d3.event.ctrlKey);\r\n d3.event.stopPropagation();\r\n });\r\n\r\n clearCatcher.on('click', function () {\r\n selectionHandler.handleClearSelection();\r\n });\r\n }\r\n\r\n public renderSelection(hasSelection: boolean): void {\r\n this.selection.style(\"opacity\", (d: SelectableDataPoint) => (hasSelection && !d.selected) ? RadarChart.DimmedAreaFillOpacity : RadarChart.NodeFillOpacity);\r\n }\r\n }\r\n\r\n export class RadarChart 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: 'Y Axis',\r\n name: 'Y',\r\n kind: powerbi.VisualDataRoleKind.Measure,\r\n },\r\n ],\r\n dataViewMappings: [{\r\n conditions: [{ 'Category': { min: 1, max: 1 } }],\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 objects: {\r\n general: {\r\n displayName: data.createDisplayNameGetter('Visual_General'),\r\n properties: {\r\n formatString: {\r\n type: { formatting: { formatString: 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: {\r\n displayName: data.createDisplayNameGetter('Visual_Show'),\r\n type: { bool: true }\r\n },\r\n position: {\r\n displayName: data.createDisplayNameGetter('Visual_LegendPosition'),\r\n description: data.createDisplayNameGetter('Visual_LegendPositionDescription'),\r\n type: { enumeration: legendPosition.type }\r\n },\r\n showTitle: {\r\n displayName: data.createDisplayNameGetter('Visual_LegendShowTitle'),\r\n description: data.createDisplayNameGetter('Visual_LegendShowTitleDescription'),\r\n type: { bool: true }\r\n },\r\n titleText: {\r\n displayName: data.createDisplayNameGetter('Visual_LegendName'),\r\n description: data.createDisplayNameGetter('Visual_LegendNameDescription'),\r\n type: { text: true },\r\n suppressFormatPainterCopy: true\r\n },\r\n labelColor: {\r\n displayName: data.createDisplayNameGetter('Visual_LegendTitleColor'),\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 }\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 displayName: data.createDisplayNameGetter('Visual_Fill'),\r\n type: { fill: { solid: { color: 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: {\r\n displayName: data.createDisplayNameGetter('Visual_Show'),\r\n type: { bool: true }\r\n },\r\n color: {\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 labelDisplayUnits: {\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 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 suppressFormatPainterCopy: true,\r\n },\r\n fontSize: {\r\n displayName: data.createDisplayNameGetter('Visual_TextSize'),\r\n type: { formatting: { fontSize: true } }\r\n },\r\n }\r\n }\r\n }\r\n };\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 static Properties: any = {\r\n legend: {\r\n show: <DataViewObjectPropertyIdentifier>{ objectName: 'legend', propertyName: 'show' }\r\n },\r\n dataPoint: {\r\n fill: <DataViewObjectPropertyIdentifier>{ objectName: 'dataPoint', propertyName: 'fill' }\r\n },\r\n labels: {\r\n show: <DataViewObjectPropertyIdentifier>{ objectName: 'labels', propertyName: 'show' },\r\n color: <DataViewObjectPropertyIdentifier>{ objectName: 'labels', propertyName: 'color' },\r\n displayUnits: <DataViewObjectPropertyIdentifier>{ objectName: 'labels', propertyName: 'labelDisplayUnits' },\r\n precision: <DataViewObjectPropertyIdentifier>{ objectName: 'labels', propertyName: 'labelPrecision' },\r\n fontSize: <DataViewObjectPropertyIdentifier>{ objectName: 'labels', propertyName: 'fontSize' },\r\n }\r\n };\r\n\r\n private static VisualClassName = 'radarChart';\r\n private static Segments: ClassAndSelector = CreateClassAndSelector('segments');\r\n private static SegmentNode: ClassAndSelector = CreateClassAndSelector('segmentNode');\r\n private static ZeroSegment: ClassAndSelector = CreateClassAndSelector('zeroSegment');\r\n private static ZeroSegmentNode: ClassAndSelector = CreateClassAndSelector('zeroSegmentNode');\r\n private static ZeroLabel: ClassAndSelector = CreateClassAndSelector('zeroLabel');\r\n private static Axis: ClassAndSelector = CreateClassAndSelector('axis');\r\n private static AxisNode: ClassAndSelector = CreateClassAndSelector('axisNode');\r\n private static AxisLabel: ClassAndSelector = CreateClassAndSelector('axisLabel');\r\n private static Chart: ClassAndSelector = CreateClassAndSelector('chart');\r\n private static ChartNode: ClassAndSelector = CreateClassAndSelector('chartNode');\r\n private static ChartArea: ClassAndSelector = CreateClassAndSelector('chartArea');\r\n private static ChartPolygon: ClassAndSelector = CreateClassAndSelector('chartPolygon');\r\n private static ChartDot: ClassAndSelector = CreateClassAndSelector('chartDot');\r\n private static MaxPrecision: number = 17;\r\n private static MinPrecision: number = 0;\r\n\r\n private svg: D3.Selection;\r\n private segments: D3.Selection;\r\n private zeroSegment: D3.Selection;\r\n private axis: D3.Selection;\r\n private chart: D3.Selection;\r\n\r\n private mainGroupElement: D3.Selection;\r\n private colors: IDataColorPalette;\r\n private viewport: IViewport;\r\n private interactivityService: IInteractivityService;\r\n\r\n private animator: IGenericAnimator;\r\n private margin: IMargin;\r\n private legend: ILegend;\r\n private legendObjectProperties: DataViewObject;\r\n private radarChartData: RadarChartData;\r\n private isInteractiveChart: boolean;\r\n private zeroPointRadius: number;\r\n\r\n private static DefaultMargin: IMargin = {\r\n top: 50,\r\n bottom: 50,\r\n right: 100,\r\n left: 100\r\n };\r\n\r\n private static SegmentLevels: number = 6;\r\n private static SegmentFactor: number = 1;\r\n private static Radians: number = 2 * Math.PI;\r\n private static Scale: number = 1;\r\n public static NodeFillOpacity = 1;\r\n public static AreaFillOpacity = 0.6;\r\n public static DimmedAreaFillOpacity = 0.4;\r\n private angle: number;\r\n private radius: number;\r\n\r\n public static AxesLabelsFontFamily: string = \"sans-serif\";\r\n public static AxesLabelsfontSize: string = \"11px\";\r\n public static AxesLabelsMaxWidth: number = 200;\r\n\r\n public static converter(dataView: DataView, colors: IDataColorPalette): RadarChartData {\r\n if (!dataView ||\r\n !dataView.categorical ||\r\n !dataView.categorical.categories ||\r\n !(dataView.categorical.categories.length > 0) ||\r\n !dataView.categorical.categories[0] ||\r\n !dataView.categorical.values ||\r\n !(dataView.categorical.values.length > 0) ||\r\n !colors) {\r\n return {\r\n legendData: {\r\n dataPoints: []\r\n },\r\n settings: {\r\n showLegend: true\r\n },\r\n series: [],\r\n dataLabelsSettings: dataLabelUtils.getDefaultPointLabelSettings(),\r\n };\r\n }\r\n\r\n var catDv: DataViewCategorical = dataView.categorical,\r\n values: DataViewValueColumns = catDv.values,\r\n grouped: DataViewValueColumnGroup[] = catDv && catDv.values ? catDv.values.grouped() : null,\r\n series: RadarChartSeries[] = [],\r\n colorHelper = new ColorHelper(colors, RadarChart.Properties.dataPoint.fill);\r\n\r\n var legendData: LegendData = {\r\n fontSize: 8.25,\r\n dataPoints: [],\r\n title: \"\"\r\n };\r\n\r\n //Parse legend settings \r\n var legendSettings: RadarChartSettings = RadarChart.parseSettings(dataView);\r\n var dataLabelsSettings: PointDataLabelsSettings = RadarChart.parseLabelSettings(dataView);\r\n\r\n for (var i = 0, iLen = values.length; i < iLen; i++) {\r\n var color = colors.getColorByIndex(i).value,\r\n serieIdentity: SelectionId,\r\n queryName: string,\r\n displayName: string,\r\n dataPoints: RadarChartDatapoint[] = [];\r\n\r\n var columnGroup: DataViewValueColumnGroup = grouped\r\n && grouped.length > i && grouped[i].values? grouped[i] : null;\r\n\r\n if (values[i].source) {\r\n var source = values[i].source;\r\n\r\n if (source.queryName) {\r\n queryName = source.queryName;\r\n serieIdentity = SelectionId.createWithMeasure(queryName);\r\n }\r\n\r\n if (source.displayName)\r\n displayName = source.displayName;\r\n\r\n if (source.objects) {\r\n var objects: any = source.objects;\r\n color = colorHelper.getColorForMeasure(objects, queryName);\r\n }\r\n }\r\n\r\n legendData.dataPoints.push({\r\n label: displayName,\r\n color: color,\r\n icon: LegendIcon.Box,\r\n selected: false,\r\n identity: serieIdentity\r\n });\r\n\r\n for (var k = 0, kLen = values[i].values.length; k < kLen; k++) {\r\n var dataPointIdentity: SelectionId = SelectionIdBuilder\r\n .builder()\r\n .withMeasure(queryName)\r\n .withCategory(catDv.categories[0], k)\r\n .withSeries(dataView.categorical.values, columnGroup)\r\n .createSelectionId();\r\n\r\n var tooltipInfo: TooltipDataItem[] = TooltipBuilder.createTooltipInfo(RadarChart.formatStringProp,\r\n catDv,\r\n catDv.categories[0].values[k],\r\n values[i].values[k],\r\n null,\r\n null,\r\n i);\r\n\r\n var labelFormatString = valueFormatter.getFormatString(catDv.values[i].source, RadarChart.formatStringProp);\r\n var fontSizeInPx = jsCommon.PixelConverter.fromPoint(dataLabelsSettings.fontSize);\r\n\r\n dataPoints.push({\r\n x: k,\r\n y: values[i].values[k],\r\n color: color,\r\n identity: dataPointIdentity,\r\n selected: false,\r\n tooltipInfo: tooltipInfo,\r\n value: values[i].values[k],\r\n labelFormatString: labelFormatString,\r\n labelFontSize: fontSizeInPx,\r\n });\r\n }\r\n\r\n if (dataPoints.length > 0)\r\n series.push({\r\n fill: color,\r\n name: displayName,\r\n data: dataPoints,\r\n identity: serieIdentity,\r\n });\r\n }\r\n\r\n return {\r\n legendData: legendData,\r\n settings: legendSettings,\r\n series: series,\r\n dataLabelsSettings: dataLabelsSettings,\r\n };\r\n }\r\n\r\n public constructor(options?: RadarChartConstructorOptions) {\r\n if (options) {\r\n if (options.svg)\r\n this.svg = options.svg;\r\n\r\n if (options.animator)\r\n this.animator = options.animator;\r\n\r\n if (options.margin)\r\n this.margin = options.margin;\r\n }\r\n }\r\n\r\n public init(options: VisualInitOptions): void {\r\n var element = options.element;\r\n\r\n if (!this.svg) {\r\n this.svg = d3.select(element.get(0)).append('svg');\r\n this.svg.style('position', 'absolute');\r\n }\r\n\r\n if (!this.margin)\r\n this.margin = RadarChart.DefaultMargin;\r\n\r\n this.svg.classed(RadarChart.VisualClassName, true);\r\n this.interactivityService = visuals.createInteractivityService(options.host);\r\n this.isInteractiveChart = options.interactivity && options.interactivity.isInteractiveLegend;\r\n this.legend = createLegend(element,\r\n this.isInteractiveChart,\r\n this.interactivityService,\r\n true,\r\n LegendPosition.Top);\r\n this.colors = options.style.colorPalette.dataColors;\r\n this.mainGroupElement = this.svg.append('g');\r\n\r\n this.segments = this.mainGroupElement\r\n .append('g')\r\n .classed(RadarChart.Segments.class, true);\r\n\r\n this.zeroSegment = this.mainGroupElement\r\n .append('g')\r\n .classed(RadarChart.ZeroSegment.class, true);\r\n\r\n this.axis = this.mainGroupElement\r\n .append('g')\r\n .classed(RadarChart.Axis.class, true);\r\n\r\n this.chart = this.mainGroupElement\r\n .append('g')\r\n .classed(RadarChart.Chart.class, true);\r\n }\r\n\r\n public update(options: VisualUpdateOptions): void {\r\n if (!options.dataViews || !options.dataViews[0])\r\n return;\r\n\r\n var dataView = options.dataViews[0];\r\n this.radarChartData = RadarChart.converter(dataView, this.colors);\r\n var categories: any[] = [],\r\n series = this.radarChartData.series,\r\n dataViewMetadataColumn: DataViewMetadataColumn,\r\n duration = AnimatorCommon.GetAnimationDuration(this.animator, options.suppressAnimations);\r\n\r\n if (dataView.categorical &&\r\n dataView.categorical.categories &&\r\n dataView.categorical.categories[0] &&\r\n dataView.categorical.categories[0].values)\r\n categories = dataView.categorical.categories[0].values;\r\n\r\n if (dataView.metadata && dataView.metadata.columns && dataView.metadata.columns.length > 0)\r\n dataViewMetadataColumn = dataView.metadata.columns[0];\r\n\r\n this.viewport = {\r\n height: options.viewport.height > 0 ? options.viewport.height : 0,\r\n width: options.viewport.width > 0 ? options.viewport.width : 0\r\n };\r\n\r\n this.parseLegendProperties(dataView);\r\n this.renderLegend(this.radarChartData);\r\n this.updateViewport();\r\n\r\n this.svg\r\n .attr({\r\n 'height': this.viewport.height,\r\n 'width': this.viewport.width\r\n });\r\n\r\n var mainGroup = this.mainGroupElement;\r\n mainGroup.attr('transform', SVGUtil.translate(this.viewport.width / 2, this.viewport.height / 2));\r\n\r\n var width: number = this.viewport.width - this.margin.left - this.margin.right;\r\n var height: number = this.viewport.height - this.margin.top - this.margin.bottom;\r\n\r\n this.angle = RadarChart.Radians / categories.length;\r\n this.radius = RadarChart.SegmentFactor * RadarChart.Scale * Math.min(width, height) / 2;\r\n\r\n this.drawCircularSegments(categories);\r\n this.drawAxes(categories);\r\n this.drawAxesLabels(categories, dataViewMetadataColumn);\r\n this.drawChart(series, duration);\r\n this.drawDataLabels(series);\r\n this.drawZeroCircularSegment(categories);\r\n\r\n if (this.zeroPointRadius !== 0)\r\n this.drawZeroLabel();\r\n else\r\n this.mainGroupElement.selectAll(RadarChart.ZeroLabel.selector).remove();\r\n }\r\n\r\n private getRadarChartLabelLayout(labelSettings: PointDataLabelsSettings, allDataPoints: RadarChartDatapoint[]): ILabelLayout {\r\n var formattersCache = dataLabelUtils.createColumnFormatterCacheManager();\r\n var angle: number = this.angle;\r\n var viewport = this.viewport;\r\n var halfHeight = this.viewport.height / 2;\r\n var halfWidth = this.viewport.width / 2;\r\n var y: any = this.calculateChartDomain(this.radarChartData.series);\r\n\r\n return {\r\n labelText: (d: RadarChartDatapoint) => {\r\n\r\n var formmater = formattersCache.getOrCreate(d.labelFormatString, labelSettings);\r\n\r\n if (labelSettings.displayUnits === 0) {\r\n var maxDataPoint: RadarChartDatapoint = _.max(allDataPoints, d => d.value);\r\n var maxValue = maxDataPoint.value > 0 ? maxDataPoint.value : 0;\r\n\r\n formmater = formattersCache.getOrCreate(d.labelFormatString, labelSettings, maxValue);\r\n }\r\n return dataLabelUtils.getLabelFormattedText({ label: formmater.format(d.value), maxWidth: viewport.width, fontSize: labelSettings.fontSize });\r\n },\r\n labelLayout: {\r\n x: (d: RadarChartDatapoint) => -1 * y(d.y) * Math.sin(d.x * angle) + halfWidth,\r\n y: (d: RadarChartDatapoint) => -1 * y(d.y) * Math.cos(d.x * angle) + halfHeight - 7,\r\n },\r\n filter: (d: RadarChartDatapoint) => {\r\n return (d != null && d.value != null);\r\n },\r\n style: {\r\n 'fill': labelSettings.labelColor,\r\n 'font-size': (d: RadarChartDatapoint) => PixelConverter.fromPoint(labelSettings.fontSize),\r\n },\r\n };\r\n }\r\n\r\n private drawCircularSegments(values: string[]): void {\r\n var data = [];\r\n var angle: number = this.angle,\r\n factor: number = RadarChart.SegmentFactor,\r\n levels: number = RadarChart.SegmentLevels,\r\n radius: number = this.radius;\r\n\r\n for (var level = 0; level < levels - 1; level++) {\r\n var levelFactor: number = radius * ((level + 1) / levels);\r\n var transform: number = -1 * levelFactor;\r\n\r\n for (var i = 0; i < values.length; i++)\r\n data.push({\r\n x1: levelFactor * (1 - factor * Math.sin(i * angle)),\r\n y1: levelFactor * (1 - factor * Math.cos(i * angle)),\r\n x2: levelFactor * (1 - factor * Math.sin((i + 1) * angle)),\r\n y2: levelFactor * (1 - factor * Math.cos((i + 1) * angle)),\r\n translate: SVGUtil.translate(transform, transform)\r\n });\r\n }\r\n\r\n var selection = this.mainGroupElement\r\n .select(RadarChart.Segments.selector)\r\n .selectAll(RadarChart.SegmentNode.selector)\r\n .data(data);\r\n\r\n selection\r\n .enter()\r\n .append('svg:line')\r\n .classed(RadarChart.SegmentNode.class, true);\r\n selection\r\n .attr({\r\n 'x1': item => item.x1,\r\n 'y1': item => item.y1,\r\n 'x2': item => item.x2,\r\n 'y2': item => item.y2,\r\n 'transform': item => item.translate\r\n });\r\n\r\n selection.exit().remove();\r\n }\r\n\r\n private drawDataLabels(series: RadarChartSeries[]): void {\r\n var allDataPoints: RadarChartDatapoint[] = this.getAllDataPointsList(series);\r\n\r\n if (this.radarChartData.dataLabelsSettings.show) {\r\n var layout = this.getRadarChartLabelLayout(this.radarChartData.dataLabelsSettings, allDataPoints);\r\n var viewport = this.viewport;\r\n\r\n var labels = dataLabelUtils.drawDefaultLabelsForDataPointChart(allDataPoints, this.mainGroupElement, layout, viewport);\r\n labels.attr('transform', SVGUtil.translate(-(viewport.width / 2), -(viewport.height / 2)));\r\n }\r\n else\r\n dataLabelUtils.cleanDataLabels(this.mainGroupElement);\r\n }\r\n\r\n private drawAxes(values: string[]): void {\r\n var angle: number = this.angle,\r\n radius: number = -1 * this.radius;\r\n\r\n var selection: D3.Selection = this.mainGroupElement\r\n .select(RadarChart.Axis.selector)\r\n .selectAll(RadarChart.AxisNode.selector);\r\n\r\n var axis = selection.data(values);\r\n\r\n axis\r\n .enter()\r\n .append('svg:line');\r\n axis\r\n .attr({\r\n 'x1': 0,\r\n 'y1': 0,\r\n 'x2': (name, i) => radius * Math.sin(i * angle),\r\n 'y2': (name, i) => radius * Math.cos(i * angle)\r\n })\r\n .classed(RadarChart.AxisNode.class, true);\r\n\r\n axis.exit().remove();\r\n }\r\n\r\n private drawAxesLabels(values: string[], dataViewMetadataColumn?: DataViewMetadataColumn): void {\r\n var angle: number = this.angle,\r\n radius: number = -1 * this.radius,\r\n length: number = values.length;\r\n\r\n var formatter = valueFormatter.create({\r\n format: valueFormatter.getFormatString(dataViewMetadataColumn, RadarChart.formatStringProp, true),\r\n value: values[0],\r\n value2: values[length - 1],\r\n });\r\n\r\n var selection: D3.Selection = this.mainGroupElement\r\n .select(RadarChart.Axis.selector)\r\n .selectAll(RadarChart.AxisLabel.selector);\r\n\r\n var labels = selection.data(values);\r\n\r\n labels\r\n .enter()\r\n .append('svg:text');\r\n\r\n labels\r\n .attr({\r\n 'text-anchor': 'middle',\r\n 'dy': '1.5em',\r\n 'transform': SVGUtil.translate(0, -10),\r\n 'x': (name, i) => { return (radius - 30) * Math.sin(i * angle); },\r\n 'y': (name, i) => { return (radius - 20) * Math.cos(i * angle); }\r\n })\r\n .text(item => {\r\n var properties: TextProperties = {\r\n fontFamily: RadarChart.AxesLabelsFontFamily,\r\n fontSize: RadarChart.AxesLabelsfontSize,\r\n text: formatter.format(item)\r\n };\r\n return TextMeasurementService.getTailoredTextOrDefault(properties, Math.min(RadarChart.AxesLabelsMaxWidth, this.viewport.width));\r\n })\r\n .classed(RadarChart.AxisLabel.class, true);\r\n\r\n labels.exit().remove();\r\n }\r\n\r\n private drawChart(series: RadarChartSeries[], duration: number): void {\r\n var angle: number = this.angle,\r\n dotRadius: number = 5,\r\n dataPoints: RadarChartDatapoint[][] = this.getDataPoints(series);\r\n\r\n var stack = d3.layout.stack();\r\n var layers = stack(dataPoints);\r\n var y: any = this.calculateChartDomain(series);\r\n\r\n var calculatePoints = (points) => {\r\n return points.map((value) => {\r\n var x1 = -1 * y(value.y) * Math.sin(value.x * angle);\r\n var y1 = -1 * y(value.y) * Math.cos(value.x * angle);\r\n return `${x1},${y1}`;\r\n }).join(' ');\r\n };\r\n\r\n var areas = this.chart.selectAll(RadarChart.ChartArea.selector).data(layers);\r\n\r\n areas\r\n .enter()\r\n .append('g')\r\n .classed(RadarChart.ChartArea.class, true);\r\n\r\n var polygon = areas.selectAll(RadarChart.ChartPolygon.selector).data(d => {\r\n if (d && d.length > 0) {\r\n return [d];\r\n }\r\n\r\n return [];\r\n });\r\n polygon\r\n .enter()\r\n .append('polygon')\r\n .classed(RadarChart.ChartPolygon.class, true);\r\n polygon\r\n .style('fill', d => d[0].color)\r\n .style('opacity', RadarChart.DimmedAreaFillOpacity)\r\n .on('mouseover', function (d) {\r\n d3.select(this).transition()\r\n .duration(duration)\r\n .style('opacity', RadarChart.AreaFillOpacity);\r\n })\r\n .on('mouseout', function (d) {\r\n d3.select(this).transition()\r\n .duration(duration)\r\n .style('opacity', RadarChart.DimmedAreaFillOpacity);\r\n })\r\n .attr('points', calculatePoints);\r\n polygon.exit().remove();\r\n\r\n areas.exit().remove();\r\n var selection = this.chart.selectAll(RadarChart.ChartNode.selector).data(layers);\r\n\r\n selection\r\n .enter()\r\n .append('g')\r\n .classed(RadarChart.ChartNode.class, true);\r\n\r\n var dots = selection.selectAll(RadarChart.ChartDot.selector)\r\n .data((d: RadarChartDatapoint[]) => { return d.filter(d => d.y != null); });\r\n\r\n dots.enter()\r\n .append('svg:circle')\r\n .classed(RadarChart.ChartDot.class, true);\r\n dots.attr('r', dotRadius)\r\n .attr({\r\n 'cx': (value) => -1 * y(value.y) * Math.sin(value.x * angle),\r\n 'cy': (value) => -1 * y(value.y) * Math.cos(value.x * angle)\r\n })\r\n .style('fill', d => d.color);\r\n\r\n dots.exit().remove();\r\n TooltipManager.addTooltip(dots, (tooltipEvent: TooltipEvent) => tooltipEvent.data.tooltipInfo, true);\r\n\r\n selection.exit().remove();\r\n var behaviorOptions: RadarChartBehaviorOptions = undefined;\r\n\r\n if (this.interactivityService) {\r\n // Register interactivity\r\n var dataPointsToBind = this.getAllDataPointsList(series);\r\n\r\n behaviorOptions = { selection: dots, clearCatcher: this.svg };\r\n this.interactivityService.bind(dataPointsToBind, new RadarChartWebBehavior(), behaviorOptions);\r\n }\r\n }\r\n\r\n private calculateChartDomain(series: RadarChartSeries[]): any {\r\n var radius: number = this.radius,\r\n dataPointsList: RadarChartDatapoint[] = this.getAllDataPointsList(series);\r\n\r\n var minValue: number = d3.min(dataPointsList, (d) => { return d.y; });\r\n var maxValue: number = d3.max(dataPointsList, (d) => { return d.y; });\r\n\r\n if (this.isPercentChart(dataPointsList)) {\r\n minValue = minValue >= 0 ? 0 : -1;\r\n maxValue = maxValue <= 0 ? 0 : 1;\r\n }\r\n\r\n var y = d3.scale.linear()\r\n .domain([minValue, maxValue]).range([0, radius]);\r\n\r\n // Calculate zero ring radius\r\n this.zeroPointRadius = ((minValue < 0) && (maxValue > 0)) ? y(0) : 0;\r\n\r\n return y;\r\n }\r\n\r\n private renderLegend(radarChartData: RadarChartData): void {\r\n if (!radarChartData.legendData)\r\n return;\r\n\r\n var legendData: LegendData = radarChartData.legendData;\r\n\r\n if (this.legendObjectProperties) {\r\n LegendData.update(legendData, this.legendObjectProperties);\r\n var position = <string>this.legendObjectProperties[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 var viewport = this.viewport;\r\n this.legend.drawLegend(legendData, { height: viewport.height, width: viewport.width });\r\n Legend.positionChartArea(this.svg, this.legend);\r\n }\r\n\r\n private drawZeroCircularSegment(values: string[]): void {\r\n var data = [];\r\n var angle: number = this.angle,\r\n factor: number = RadarChart.SegmentFactor,\r\n radius: number = this.zeroPointRadius,\r\n transform: number = -1 * radius;\r\n\r\n for (var i = 0; i < values.length; i++)\r\n data.push({\r\n x1: radius * (1 - factor * Math.sin(i * angle)),\r\n y1: radius * (1 - factor * Math.cos(i * angle)),\r\n x2: radius * (1 - factor * Math.sin((i + 1) * angle)),\r\n y2: radius * (1 - factor * Math.cos((i + 1) * angle)),\r\n translate: SVGUtil.translate(transform, transform)\r\n });\r\n\r\n var selection = this.mainGroupElement\r\n .select(RadarChart.ZeroSegment.selector)\r\n .selectAll(RadarChart.ZeroSegmentNode.selector)\r\n .data(data);\r\n\r\n selection\r\n .enter()\r\n .append('svg:line')\r\n .classed(RadarChart.ZeroSegmentNode.class, true);\r\n selection\r\n .attr({\r\n 'x1': item => item.x1,\r\n 'y1': item => item.y1,\r\n 'x2': item => item.x2,\r\n 'y2': item => item.y2,\r\n 'transform': item => item.translate\r\n });\r\n\r\n selection.exit().remove();\r\n }\r\n\r\n private drawZeroLabel() {\r\n var data = [];\r\n data.push({\r\n 'x': this.zeroPointRadius * (1 - RadarChart.SegmentFactor) + 5,\r\n 'y': -1 * this.zeroPointRadius\r\n });\r\n\r\n var zeroLabel = this.mainGroupElement\r\n .select(RadarChart.ZeroSegment.selector)\r\n .selectAll(RadarChart.ZeroLabel.selector).data(data);\r\n\r\n zeroLabel\r\n .enter()\r\n .append('text')\r\n .classed(RadarChart.ZeroLabel.class, true).text(\"0\");\r\n zeroLabel\r\n .attr({\r\n 'x': item => item.x,\r\n 'y': item => item.y\r\n });\r\n }\r\n\r\n private getDataPoints(series: RadarChartSeries[]): RadarChartDatapoint[][] {\r\n var dataPoints: RadarChartDatapoint[][] = [];\r\n\r\n for (var i: number = 0; i < series.length; i++) {\r\n dataPoints.push(series[i].data);\r\n }\r\n\r\n return dataPoints;\r\n }\r\n\r\n private getAllDataPointsList(series: RadarChartSeries[]): RadarChartDatapoint[] {\r\n var dataPoints: RadarChartDatapoint[] = [];\r\n\r\n for (var i: number = 0; i < series.length; i++) {\r\n dataPoints = dataPoints.concat(series[i].data);\r\n }\r\n\r\n return dataPoints;\r\n }\r\n\r\n private isPercentChart(dataPointsList: RadarChartDatapoint[]): boolean {\r\n for (var i: number = 0; i < dataPointsList.length; i++) {\r\n if (dataPointsList[i].labelFormatString.indexOf(\"%\") === -1) {\r\n return false;\r\n }\r\n }\r\n\r\n return true;\r\n }\r\n\r\n private parseLegendProperties(dataView: DataView): void {\r\n if (!dataView || !dataView.metadata) {\r\n this.legendObjectProperties = {};\r\n return;\r\n }\r\n\r\n this.legendObjectProperties = DataViewObjects.getObject(dataView.metadata.objects, \"legend\", {});\r\n }\r\n\r\n private static parseSettings(dataView: DataView): RadarChartSettings {\r\n var objects: DataViewObjects;\r\n\r\n if (!dataView || !dataView.metadata || !dataView.metadata.columns || !dataView.metadata.objects)\r\n objects = null;\r\n else\r\n objects = dataView.metadata.objects;\r\n\r\n return {\r\n showLegend: DataViewObjects.getValue(objects, RadarChart.Properties.legend.show, true)\r\n };\r\n }\r\n\r\n private static getPrecision(value: number): number {\r\n return Math.max(RadarChart.MinPrecision, Math.min(RadarChart.MaxPrecision, value));\r\n }\r\n\r\n private static parseLabelSettings(dataView: DataView): PointDataLabelsSettings {\r\n var objects: DataViewObjects;\r\n\r\n if (!dataView || !dataView.metadata || !dataView.metadata.objects)\r\n objects = null;\r\n else\r\n objects = dataView.metadata.objects;\r\n\r\n var dataLabelsSettings: PointDataLabelsSettings = dataLabelUtils.getDefaultPointLabelSettings();\r\n\r\n var labelsObj: PointDataLabelsSettings = {\r\n show: DataViewObjects.getValue(objects, RadarChart.Properties.labels.show, dataLabelsSettings.show),\r\n labelColor: DataViewObjects.getFillColor(objects, RadarChart.Properties.labels.color, dataLabelsSettings.labelColor),\r\n displayUnits: DataViewObjects.getValue(objects, RadarChart.Properties.labels.displayUnits, dataLabelsSettings.displayUnits),\r\n precision: RadarChart.getPrecision(DataViewObjects.getValue(objects, RadarChart.Properties.labels.precision, dataLabelsSettings.precision)),\r\n fontSize: DataViewObjects.getValue(objects, RadarChart.Properties.labels.fontSize, dataLabelsSettings.fontSize),\r\n position: dataLabelsSettings.position\r\n };\r\n\r\n return labelsObj;\r\n }\r\n\r\n // This function returns the values to be displayed in the property pane for each object.\r\n // Usually it is a bind pass of what the property pane gave you, but sometimes you may want to do\r\n // validation and return other values/defaults\r\n public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstanceEnumeration {\r\n var enumeration = new ObjectEnumerationBuilder();\r\n var settings: RadarChartSettings;\r\n\r\n if (!this.radarChartData || !this.radarChartData.settings)\r\n return [];\r\n\r\n settings = this.radarChartData.settings;\r\n\r\n switch (options.objectName) {\r\n case \"legend\":\r\n enumeration.pushInstance(this.enumerateLegend(settings));\r\n break;\r\n case \"dataPoint\":\r\n this.enumerateDataPoint(enumeration);\r\n break;\r\n case 'labels':\r\n this.enumerateDataLabels(enumeration);\r\n break;\r\n }\r\n\r\n return enumeration.complete();\r\n }\r\n\r\n private getLabelSettingsOptions(enumeration: ObjectEnumerationBuilder, labelSettings: PointDataLabelsSettings): 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 fontSize: true,\r\n };\r\n }\r\n\r\n private enumerateDataLabels(enumeration: ObjectEnumerationBuilder): void {\r\n var labelSettings = this.radarChartData.dataLabelsSettings;\r\n\r\n //Draw default settings\r\n dataLabelUtils.enumerateDataLabels(this.getLabelSettingsOptions(enumeration, labelSettings));\r\n }\r\n\r\n private enumerateLegend(settings: RadarChartSettings): VisualObjectInstance {\r\n var showTitle: boolean = true,\r\n titleText: string = \"\",\r\n legend: VisualObjectInstance,\r\n labelColor: DataColorPalette,\r\n fontSize: number = 8;\r\n\r\n showTitle = DataViewObject.getValue(this.legendObjectProperties, legendProps.showTitle, showTitle);\r\n titleText = DataViewObject.getValue(this.legendObjectProperties, legendProps.titleText, titleText);\r\n labelColor = DataViewObject.getValue(this.legendObjectProperties, legendProps.labelColor, labelColor);\r\n fontSize = DataViewObject.getValue(this.legendObjectProperties, legendProps.fontSize, fontSize);\r\n legend = {\r\n objectName: \"legend\",\r\n displayName: \"legend\",\r\n selector: null,\r\n properties: {\r\n show: settings.showLegend,\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 };\r\n\r\n return legend;\r\n }\r\n\r\n private enumerateDataPoint(enumeration: ObjectEnumerationBuilder): void {\r\n if (!this.radarChartData || !this.radarChartData.series)\r\n return;\r\n\r\n var series: RadarChartSeries[] = this.radarChartData.series;\r\n\r\n for (var i: number = 0; i < series.length; i++) {\r\n var serie = series[i];\r\n\r\n enumeration.pushInstance({\r\n objectName: \"dataPoint\",\r\n displayName: serie.name,\r\n selector: ColorHelper.normalizeSelector(serie.identity.getSelector(), false),\r\n properties: {\r\n fill: { solid: { color: serie.fill } }\r\n }\r\n });\r\n }\r\n }\r\n\r\n private updateViewport(): void {\r\n var legendMargins: IViewport = this.legend.getMargins(),\r\n legendPosition: LegendPosition;\r\n\r\n legendPosition = LegendPosition[<string>this.legendObjectProperties[legendProps.position]];\r\n\r\n switch (legendPosition) {\r\n case LegendPosition.Top:\r\n case LegendPosition.TopCenter:\r\n case LegendPosition.Bottom:\r\n case LegendPosition.BottomCenter:\r\n this.viewport.height -= legendMargins.height;\r\n break;\r\n\r\n case LegendPosition.Left:\r\n case LegendPosition.LeftCenter:\r\n case LegendPosition.Right:\r\n case LegendPosition.RightCenter:\r\n this.viewport.width -= legendMargins.width;\r\n break;\r\n }\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/CustomVisuals/visuals/radarChart/visual/radarChart.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 import ValueFormatter = powerbi.visuals.valueFormatter;\r\n import ClassAndSelector = jsCommon.CssConstants.ClassAndSelector;\r\n import getAnimationDuration = AnimatorCommon.GetAnimationDuration;\r\n import createClassAndSelector = jsCommon.CssConstants.createClassAndSelector;\r\n import PixelConverter = jsCommon.PixelConverter;\r\n\r\n type D3Element =\r\n D3.UpdateSelection |\r\n D3.Selection |\r\n D3.Selectors |\r\n D3.Transition.Transition;\r\n\r\n export interface HistogramConstructorOptions {\r\n svg?: D3.Selection;\r\n animator?: IGenericAnimator;\r\n margin?: IMargin;\r\n }\r\n\r\n export interface HistogramAxisSettings {\r\n axisColor?: string;\r\n displayUnits?: number;\r\n precision?: number;\r\n title?: boolean;\r\n show?: boolean;\r\n style?: string;\r\n }\r\n\r\n export interface HistogramXAxisSettings extends HistogramAxisSettings {\r\n }\r\n\r\n export interface HistogramYAxisSettings extends HistogramAxisSettings {\r\n start?: number;\r\n end?: number;\r\n position?: string;\r\n }\r\n\r\n export interface HistogramLabelSettings {\r\n show?: boolean;\r\n color?: string;\r\n displayUnits?: number;\r\n precision?: number;\r\n fontSize?: number;\r\n }\r\n\r\n export interface HistogramSettings {\r\n displayName?: string;\r\n fillColor?: string;\r\n frequency: boolean;\r\n bins?: number;\r\n precision: number;\r\n maxX?: number;\r\n\r\n xAxisSettings: HistogramXAxisSettings;\r\n yAxisSettings: HistogramYAxisSettings;\r\n labelSettings: HistogramLabelSettings;\r\n }\r\n\r\n export interface HistogramData extends D3.Layout.Bin, TooltipEnabledDataPoint {\r\n range: number[];\r\n selectionIds: SelectionId[];\r\n }\r\n\r\n export interface HistogramDataView {\r\n data: HistogramData[];\r\n xScale?: D3.Scale.LinearScale;\r\n yScale?: D3.Scale.LinearScale;\r\n settings: HistogramSettings;\r\n formatter: IValueFormatter;\r\n xLabelFormatter?: IValueFormatter;\r\n yLabelFormatter?: IValueFormatter;\r\n }\r\n\r\n interface HistogramValue {\r\n value: number;\r\n selectionId: SelectionId;\r\n frequency: number;\r\n }\r\n\r\n interface Legend {\r\n text: string;\r\n transform?: string;\r\n dx?: string;\r\n dy?: string;\r\n }\r\n\r\n interface Brackets {\r\n left: string;\r\n right: string;\r\n }\r\n\r\n interface HistogramProperty {\r\n [propertyName: string]: DataViewObjectPropertyIdentifier;\r\n }\r\n\r\n interface HistogramProperties {\r\n [objectName: string]: HistogramProperty;\r\n }\r\n\r\n export class HistogramChartWarning implements IVisualWarning {\r\n public static ErrorInvalidDataValues: string = \"Some data values are invalid or too big\";\r\n\r\n private message: string;\r\n constructor(message: string) {\r\n this.message = message;\r\n }\r\n\r\n public get code(): string {\r\n return \"BulletChartWarning\";\r\n }\r\n\r\n public getMessages(resourceProvider: jsCommon.IStringResourceProvider): IVisualErrorMessage {\r\n return {\r\n message: this.message,\r\n title: resourceProvider.get(\"\"),\r\n detail: resourceProvider.get(\"\")\r\n };\r\n }\r\n }\r\n\r\n export class Histogram implements IVisual {\r\n private static ClassName: string = \"histogram\";\r\n private static FrequencyText: string = \"Frequency\";\r\n private static DensityText: string = \"Density\";\r\n\r\n private static Properties: HistogramProperties = {\r\n general: {\r\n bins: {\r\n objectName: \"general\",\r\n propertyName: \"bins\"\r\n },\r\n frequency: {\r\n objectName: \"general\",\r\n propertyName: \"frequency\"\r\n },\r\n formatString: {\r\n objectName: \"general\",\r\n propertyName: \"formatString\"\r\n }\r\n },\r\n dataPoint: {\r\n fill: {\r\n objectName: \"dataPoint\",\r\n propertyName: \"fill\"\r\n }\r\n },\r\n labels: {\r\n show: {\r\n objectName: \"labels\",\r\n propertyName: \"show\"\r\n },\r\n color: {\r\n objectName: \"labels\",\r\n propertyName: \"color\"\r\n },\r\n displayUnits: {\r\n objectName: \"labels\",\r\n propertyName: \"displayUnits\"\r\n },\r\n precision: {\r\n objectName: \"labels\",\r\n propertyName: \"precision\"\r\n },\r\n fontSize: {\r\n objectName: \"labels\",\r\n propertyName: \"fontSize\"\r\n }\r\n },\r\n xAxis: {\r\n show: {\r\n objectName: \"xAxis\",\r\n propertyName: \"show\"\r\n },\r\n axisColor: {\r\n objectName: \"xAxis\",\r\n propertyName: \"axisColor\"\r\n },\r\n title: {\r\n objectName: \"xAxis\",\r\n propertyName: \"title\"\r\n },\r\n displayUnits: {\r\n objectName: \"xAxis\",\r\n propertyName: \"displayUnits\"\r\n },\r\n precision: {\r\n objectName: \"xAxis\",\r\n propertyName: \"precision\"\r\n },\r\n style: {\r\n objectName: \"xAxis\",\r\n propertyName: \"style\"\r\n }\r\n },\r\n yAxis: {\r\n show: {\r\n objectName: \"yAxis\",\r\n propertyName: \"show\"\r\n },\r\n axisColor: {\r\n objectName: \"yAxis\",\r\n propertyName: \"axisColor\"\r\n },\r\n title: {\r\n objectName: \"yAxis\",\r\n propertyName: \"title\"\r\n },\r\n displayUnits: {\r\n objectName: \"yAxis\",\r\n propertyName: \"displayUnits\"\r\n },\r\n precision: {\r\n objectName: \"yAxis\",\r\n propertyName: \"precision\"\r\n },\r\n style: {\r\n objectName: \"yAxis\",\r\n propertyName: \"style\"\r\n },\r\n start: {\r\n objectName: \"yAxis\",\r\n propertyName: \"start\"\r\n },\r\n end: {\r\n objectName: \"yAxis\",\r\n propertyName: \"end\"\r\n },\r\n position: {\r\n objectName: \"yAxis\",\r\n propertyName: \"position\"\r\n }\r\n }\r\n };\r\n\r\n private static DefaultHistogramSettings: HistogramSettings = {\r\n frequency: true,\r\n displayName: \"Histogram\",\r\n bins: null,\r\n fillColor: \"#5f9ea0\",\r\n precision: 2,\r\n xAxisSettings: {\r\n show: true,\r\n axisColor: \"#5f9ea0\",\r\n title: true,\r\n displayUnits: 0,\r\n precision: 2,\r\n style: axisStyle.showTitleOnly,\r\n },\r\n yAxisSettings: {\r\n show: true,\r\n axisColor: \"#5f9ea0\",\r\n title: true,\r\n displayUnits: 0,\r\n precision: 2,\r\n style: axisStyle.showTitleOnly,\r\n start: 0,\r\n position: yAxisPosition.left,\r\n },\r\n labelSettings: {\r\n show: false,\r\n color: \"#5f9ea0\",\r\n displayUnits: 0,\r\n precision: 2,\r\n fontSize: 9\r\n },\r\n };\r\n\r\n private static Axes: ClassAndSelector = createClassAndSelector('axes');\r\n private static Axis: ClassAndSelector = createClassAndSelector('axis');\r\n private static Labels: ClassAndSelector = createClassAndSelector('labels');\r\n private static Columns: ClassAndSelector = createClassAndSelector('columns');\r\n private static Column: ClassAndSelector = createClassAndSelector('column');\r\n private static Legends: ClassAndSelector = createClassAndSelector('legends');\r\n private static Legend: ClassAndSelector = createClassAndSelector('legend');\r\n\r\n public static capabilities: VisualCapabilities = {\r\n dataRoles: [{\r\n name: \"Values\",\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: data.createDisplayNameGetter(\"Role_DisplayName_Values\")\r\n }, {\r\n name: \"Frequency\",\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: \"Frequency\"\r\n }],\r\n dataViewMappings: [{\r\n conditions: [{ \"Values\": { min: 1, max: 1 }, \"Frequency\": { min: 0, max: 1 } }],\r\n categorical: {\r\n categories: {\r\n bind: { to: \"Values\" },\r\n dataReductionAlgorithm: { top: {} }\r\n },\r\n values: { for: { in: \"Frequency\" } }\r\n }\r\n }],\r\n sorting: {\r\n implicit: {\r\n clauses: [{ role: \"Values\", direction: 1 /*SortDirection.Ascending*/ }] //Constant SortDirection.Ascending currently is not supported on the msit\r\n }\r\n },\r\n objects: {\r\n general: {\r\n displayName: data.createDisplayNameGetter(\"Visual_General\"),\r\n properties: {\r\n formatString: { type: { formatting: { formatString: true } } },\r\n bins: {\r\n displayName: \"Bins\",\r\n type: { numeric: true }\r\n },\r\n frequency: {\r\n displayName: \"Frequency\",\r\n type: { bool: true }\r\n }\r\n },\r\n },\r\n dataPoint: {\r\n displayName: data.createDisplayNameGetter(\"Visual_DataPoint\"),\r\n properties: {\r\n fill: {\r\n displayName: data.createDisplayNameGetter('Visual_Fill'),\r\n type: { fill: { solid: { color: true } } }\r\n }\r\n }\r\n },\r\n xAxis: {\r\n displayName: 'X-Axis',\r\n properties: {\r\n show: {\r\n displayName: \"Show\",\r\n type: { bool: true },\r\n },\r\n axis: {\r\n displayName: 'Axis',\r\n type: { bool: true }\r\n },\r\n axisColor: {\r\n displayName: \"Color\",\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n title: {\r\n displayName: \"Title\",\r\n type: { bool: true }\r\n },\r\n displayUnits: {\r\n displayName: \"Display Units\",\r\n type: { formatting: { labelDisplayUnits: true } }\r\n },\r\n precision: {\r\n displayName: \"Decimal Places\",\r\n type: { numeric: true },\r\n },\r\n style: {\r\n displayName: \"Style\",\r\n type: { enumeration: axisStyle.type }\r\n },\r\n }\r\n },\r\n yAxis: {\r\n displayName: 'Y-Axis',\r\n properties: {\r\n show: {\r\n displayName: \"Show\",\r\n type: { bool: true },\r\n },\r\n axis: {\r\n displayName: 'yAxis',\r\n type: { bool: true }\r\n },\r\n axisColor: {\r\n displayName: \"Color\",\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n title: {\r\n displayName: \"Title\",\r\n type: { bool: true }\r\n },\r\n displayUnits: {\r\n displayName: \"Display Units\",\r\n type: { formatting: { labelDisplayUnits: true } }\r\n },\r\n precision: {\r\n displayName: \"Decimal Places\",\r\n type: { numeric: true },\r\n },\r\n style: {\r\n displayName: \"Style\",\r\n type: { enumeration: axisStyle.type }\r\n },\r\n start: {\r\n displayName: \"Start\",\r\n type: { numeric: true },\r\n placeHolderText: \"Start\",\r\n suppressFormatPainterCopy: true,\r\n },\r\n end: {\r\n displayName: \"End\",\r\n type: { numeric: true },\r\n placeHolderText: \"End\",\r\n suppressFormatPainterCopy: true,\r\n },\r\n position: {\r\n displayName: \"Position\",\r\n type: { enumeration: yAxisPosition.type },\r\n },\r\n }\r\n },\r\n labels: {\r\n displayName: \"Data Labels\",\r\n properties: {\r\n show: {\r\n displayName: \"Show\",\r\n type: { bool: true }\r\n },\r\n color: {\r\n displayName: \"Color\",\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n displayUnits: {\r\n displayName: \"Display Units\",\r\n type: { formatting: { labelDisplayUnits: true } },\r\n suppressFormatPainterCopy: true\r\n },\r\n precision: {\r\n displayName: \"Decimal Places\",\r\n type: { numeric: true },\r\n suppressFormatPainterCopy: true\r\n },\r\n fontSize: {\r\n displayName: \"Text Size\",\r\n type: { formatting: { fontSize: true } }\r\n },\r\n },\r\n },\r\n }\r\n };\r\n\r\n private ColumnPadding: number = 1;\r\n private MinColumnHeight: number = 1;\r\n private MinOpacity: number = 0.3;\r\n private MaxOpacity: number = 1;\r\n private static MinNumberOfBins: number = 0;\r\n private static MaxNumberOfBins: number = 100;\r\n private static MinPrecision: number = 0;\r\n private static MaxPrecision: number = 17; // max number of decimals in float\r\n private TooltipDisplayName: string = \"Range\";\r\n private SeparatorNumbers: string = \", \";\r\n private LegendSize: number = 50;\r\n private YLegendSize: number = 50;\r\n private XLegendSize: number = 50;\r\n private AxisSize: number = 30;\r\n private DataLabelMargin: number = 0;\r\n private widthOfColumn: number = 0;\r\n private yTitleMargin: number = 0;\r\n private outerPadding: number = 5;\r\n private xAxisProperties: IAxisProperties;\r\n private yAxisProperties: IAxisProperties;\r\n\r\n private ExcludeBrackets: Brackets = {\r\n left: \"(\",\r\n right: \")\"\r\n };\r\n\r\n private IncludeBrackets: Brackets = {\r\n left: \"[\",\r\n right: \"]\"\r\n };\r\n\r\n private margin: IMargin = {\r\n top: 10,\r\n right: 10,\r\n bottom: 10,\r\n left: 10\r\n };\r\n\r\n private durationAnimations: number = 200;\r\n private oldInnerPaddingRatio: number = CartesianChart.InnerPaddingRatio;\r\n private oldMinOrdinalRectThickness: number = CartesianChart.MinOrdinalRectThickness;\r\n\r\n private viewport: IViewport;\r\n private hostService: IVisualHostServices;\r\n private selectionManager: SelectionManager;\r\n private colors: IDataColorPalette;\r\n\r\n private root: D3.Selection;\r\n private svg: D3.Selection;\r\n private main: D3.Selection;\r\n private axes: D3.Selection;\r\n private axisX: D3.Selection;\r\n private axisY: D3.Selection;\r\n private legend: D3.Selection;\r\n private columns: D3.Selection;\r\n private labels: D3.Selection;\r\n\r\n private histogramDataView: HistogramDataView;\r\n\r\n private animator: IGenericAnimator;\r\n\r\n private get columnsSelection(): D3.Selection {\r\n return this.main.select(Histogram.Columns.selector)\r\n .selectAll(Histogram.Column.selector);\r\n }\r\n\r\n private textProperties: TextProperties = {\r\n fontFamily: 'wf_segoe-ui_normal',\r\n fontSize: PixelConverter.toString(9),\r\n };\r\n\r\n constructor(histogramConstructorOptions?: HistogramConstructorOptions) {\r\n\r\n if (histogramConstructorOptions) {\r\n if (histogramConstructorOptions.svg) {\r\n this.svg = histogramConstructorOptions.svg;\r\n }\r\n\r\n if (histogramConstructorOptions.animator) {\r\n this.animator = histogramConstructorOptions.animator;\r\n }\r\n\r\n this.margin = histogramConstructorOptions.margin || this.margin;\r\n }\r\n }\r\n\r\n public init(visualsOptions: VisualInitOptions): void {\r\n this.hostService = visualsOptions.host;\r\n\r\n if (this.svg) {\r\n this.root = this.svg;\r\n } else {\r\n this.root = d3.select(visualsOptions.element.get(0))\r\n .append(\"svg\");\r\n }\r\n\r\n var style: IVisualStyle = visualsOptions.style;\r\n\r\n this.colors = style && style.colorPalette\r\n ? style.colorPalette.dataColors\r\n : new DataColorPalette();\r\n\r\n this.root.classed(Histogram.ClassName, true);\r\n\r\n this.main = this.root.append(\"g\");\r\n\r\n this.axes = this.main\r\n .append(\"g\")\r\n .classed(Histogram.Axes.class, true);\r\n\r\n this.axisX = this.axes\r\n .append(\"g\")\r\n .classed(Histogram.Axis.class, true);\r\n\r\n this.axisY = this.axes\r\n .append(\"g\")\r\n .classed(Histogram.Axis.class, true);\r\n\r\n this.legend = this.main\r\n .append(\"g\")\r\n .classed(Histogram.Legends.class, true);\r\n\r\n this.columns = this.main\r\n .append(\"g\")\r\n .classed(Histogram.Columns.class, true);\r\n\r\n this.labels = this.main\r\n .append(\"g\")\r\n .classed(Histogram.Labels.class, true);\r\n this.selectionManager = new SelectionManager({ hostServices: visualsOptions.host });\r\n }\r\n\r\n public converter(dataView: DataView): HistogramDataView {\r\n if (!dataView ||\r\n !dataView.categorical ||\r\n !dataView.categorical.categories ||\r\n !dataView.categorical.categories[0] ||\r\n !dataView.categorical.categories[0].values ||\r\n !(dataView.categorical.categories[0].values.length > 0)) {\r\n return null;\r\n }\r\n\r\n var settings: HistogramSettings,\r\n histogramLayout: D3.Layout.HistogramLayout,\r\n values: HistogramValue[],\r\n numericalValues: number[] = [],\r\n data: D3.Layout.Bin[],\r\n xScale: D3.Scale.LinearScale,\r\n yScale: D3.Scale.LinearScale,\r\n valueFormatter: IValueFormatter,\r\n frequencies: number[] = [],\r\n identities: DataViewScopeIdentity[] = [],\r\n shiftByValues: number = 0,\r\n sumFrequency: number = 0,\r\n xLabelFormatter: IValueFormatter,\r\n yLabelFormatter: IValueFormatter;\r\n\r\n if (dataView.categorical.values &&\r\n dataView.categorical.values[0] &&\r\n dataView.categorical.values[0].values) {\r\n frequencies = dataView.categorical.values[0].values;\r\n }\r\n\r\n if (dataView.categorical.categories[0].identity\r\n && dataView.categorical.categories[0].identity.length > 0) {\r\n identities = dataView.categorical.categories[0].identity;\r\n }\r\n\r\n settings = this.parseSettings(dataView);\r\n\r\n if (!settings) {\r\n return null;\r\n }\r\n\r\n values = Histogram.getValuesByFrequencies(\r\n dataView.categorical.categories[0].values,\r\n frequencies,\r\n identities);\r\n values.forEach((value: HistogramValue) => {\r\n numericalValues.push(value.value);\r\n sumFrequency += value.frequency;\r\n });\r\n\r\n histogramLayout = d3.layout.histogram();\r\n\r\n if (settings.bins && settings.bins > Histogram.MinNumberOfBins) {\r\n histogramLayout = histogramLayout.bins(settings.bins);\r\n }\r\n\r\n data = histogramLayout.frequency(settings.frequency)(numericalValues);\r\n\r\n data.forEach((bin: D3.Layout.Bin, index: number) => {\r\n var filteredValues: HistogramValue[],\r\n frequency: number;\r\n\r\n filteredValues = values.filter((value: HistogramValue) => {\r\n return Histogram.isValueContainedInRange(value, bin, index);\r\n });\r\n\r\n frequency = filteredValues.reduce((previousValue: number, currentValue: HistogramValue): number => {\r\n return previousValue + currentValue.frequency;\r\n }, 0);\r\n\r\n bin.y = settings.frequency\r\n ? frequency\r\n : frequency / sumFrequency;\r\n\r\n shiftByValues += bin.length;\r\n });\r\n\r\n var yAxisSettings: HistogramYAxisSettings = settings.yAxisSettings;\r\n\r\n var maxYvalue = (yAxisSettings.end !== null) && (yAxisSettings.end > yAxisSettings.start) ?\r\n yAxisSettings.end : d3.max(data, (item: D3.Layout.Bin) => item.y);\r\n\r\n var minYValue = (yAxisSettings.start < maxYvalue) ? yAxisSettings.start : 0;\r\n settings.yAxisSettings.end = maxYvalue;\r\n settings.yAxisSettings.start = minYValue;\r\n settings.maxX = d3.max(data, (item: D3.Layout.Bin) => d3.max(item));\r\n\r\n xScale = d3.scale.linear()\r\n .domain([\r\n d3.min(data, (item: D3.Layout.Bin) => d3.min(item)),\r\n d3.max(data, (item: D3.Layout.Bin) => d3.max(item))\r\n ])\r\n .range([0, this.viewport.width - this.YLegendSize - this.AxisSize]);\r\n\r\n yScale = d3.scale.linear()\r\n .domain([\r\n minYValue,\r\n maxYvalue\r\n ])\r\n .range([this.viewport.height - this.LegendSize, this.outerPadding]);\r\n\r\n valueFormatter = ValueFormatter.create({\r\n format: ValueFormatter.getFormatString(\r\n dataView.categorical.categories[0].source, Histogram.Properties[\"general\"][\"formatString\"]),\r\n value: values[0].value,\r\n value2: values[values.length - 1].value,\r\n precision: settings.precision\r\n });\r\n\r\n xLabelFormatter = ValueFormatter.create({\r\n value: settings.xAxisSettings.displayUnits === 0 ? values[values.length - 1].value : settings.xAxisSettings.displayUnits,\r\n precision: settings.xAxisSettings.precision\r\n });\r\n\r\n yLabelFormatter = ValueFormatter.create({\r\n value: settings.yAxisSettings.displayUnits,\r\n precision: settings.yAxisSettings.precision\r\n });\r\n\r\n return {\r\n xScale: xScale,\r\n yScale: yScale,\r\n settings: settings,\r\n data: this.getData(values, numericalValues, data, settings, yLabelFormatter, xLabelFormatter),\r\n formatter: valueFormatter,\r\n xLabelFormatter: xLabelFormatter,\r\n yLabelFormatter: yLabelFormatter\r\n };\r\n }\r\n\r\n private static getValuesByFrequencies(sourceValues: number[], frequencies: number[], identities: DataViewScopeIdentity[]): HistogramValue[] {\r\n var values: HistogramValue[] = [];\r\n\r\n sourceValues.forEach((item: number, index: number) => {\r\n var frequency: number = 1,\r\n value: number = Number(item);\r\n\r\n value = isNaN(value) ? 0 : value;\r\n\r\n if (frequencies\r\n && frequencies[index]\r\n && !isNaN(frequencies[index])\r\n && frequencies[index] > 1) {\r\n frequency = frequencies[index];\r\n }\r\n\r\n values.push({\r\n value: value,\r\n frequency: frequency,\r\n selectionId: SelectionId.createWithId(identities[index])\r\n });\r\n });\r\n\r\n return values;\r\n }\r\n\r\n private getData(\r\n values: HistogramValue[],\r\n numericalValues: number[],\r\n data: D3.Layout.Bin[],\r\n settings: HistogramSettings,\r\n yValueFormatter: IValueFormatter,\r\n xValueFormatter: IValueFormatter): HistogramData[] {\r\n var minValue: number = d3.min(numericalValues),\r\n maxValue: number = d3.max(numericalValues);\r\n var fontSizeInPx = PixelConverter.fromPoint(settings.labelSettings.fontSize);\r\n\r\n return data.map((bin: any, index: number): HistogramData => {\r\n bin.range = Histogram.getRange(minValue, maxValue, bin.dx, index);\r\n bin.tooltipInfo = this.getTooltipData(bin.y, bin.range, settings, index === 0, yValueFormatter, xValueFormatter);\r\n bin.selectionIds = Histogram.getSelectionIds(values, bin, index);\r\n bin.labelFontSize = fontSizeInPx;\r\n return bin;\r\n });\r\n }\r\n\r\n private static getRange(minValue: number, maxValue: number, step: number, index: number): number[] {\r\n var leftBorder: number = minValue + index * step,\r\n rightBorder: number = leftBorder + step;\r\n\r\n return [leftBorder, rightBorder];\r\n }\r\n\r\n private getTooltipData(\r\n value: number,\r\n range: number[],\r\n settings: HistogramSettings,\r\n includeLeftBorder: boolean,\r\n yValueFormatter: IValueFormatter,\r\n xValueFormatter: IValueFormatter): TooltipDataItem[] {\r\n\r\n return [{\r\n displayName: Histogram.getLegendText(settings),\r\n value: yValueFormatter.format(value)\r\n }, {\r\n displayName: this.TooltipDisplayName,\r\n value: this.rangeToString(range, includeLeftBorder, xValueFormatter)\r\n }];\r\n }\r\n\r\n private static getSelectionIds(values: HistogramValue[], bin: HistogramData, index: number): SelectionId[] {\r\n var selectionIds: SelectionId[] = [];\r\n\r\n values.forEach((value: HistogramValue) => {\r\n if (Histogram.isValueContainedInRange(value, bin, index)) {\r\n selectionIds.push(value.selectionId);\r\n }\r\n });\r\n\r\n return selectionIds;\r\n }\r\n\r\n private static isValueContainedInRange(value: HistogramValue, bin: D3.Layout.Bin, index: number): boolean {\r\n return ((index === 0 && value.value >= bin.x) || (value.value > bin.x)) && value.value <= bin.x + bin.dx;\r\n }\r\n\r\n private parseSettings(dataView: DataView): HistogramSettings {\r\n if (!dataView ||\r\n !dataView.metadata ||\r\n !dataView.metadata.columns ||\r\n !dataView.metadata.columns[0]) {\r\n return null;\r\n }\r\n\r\n var histogramSettings: HistogramSettings = <HistogramSettings>{},\r\n objects: DataViewObjects,\r\n colorHelper: ColorHelper;\r\n\r\n colorHelper = new ColorHelper(\r\n this.colors,\r\n Histogram.Properties[\"dataPoint\"][\"fill\"],\r\n Histogram.DefaultHistogramSettings.fillColor);\r\n\r\n histogramSettings.displayName =\r\n dataView.metadata.columns[0].displayName || Histogram.DefaultHistogramSettings.displayName;\r\n\r\n objects = Histogram.getObjectsFromDataView(dataView);\r\n\r\n var xAxisSettings: HistogramXAxisSettings = {\r\n axisColor: Histogram.getXAxisColor(objects).solid.color,\r\n title: Histogram.getXTitle(objects),\r\n precision: Histogram.getXPrecision(objects),\r\n style: Histogram.getXStyle(objects),\r\n displayUnits: Histogram.getXDisplayUnit(objects),\r\n show: Histogram.getXAxisShow(objects),\r\n };\r\n\r\n var yAxisSettings: HistogramYAxisSettings = {\r\n axisColor: Histogram.getYAxisColor(objects).solid.color,\r\n title: Histogram.getYTitle(objects),\r\n precision: Histogram.getYPrecision(objects),\r\n style: Histogram.getYStyle(objects),\r\n displayUnits: Histogram.getYDisplayUnit(objects),\r\n show: Histogram.getYAxisShow(objects),\r\n\r\n start: Histogram.getYStart(objects),\r\n end: Histogram.getYEnd(objects),\r\n position: Histogram.getYPosition(objects),\r\n };\r\n\r\n var labelSettings: HistogramLabelSettings = {\r\n show: Histogram.getLabelShow(objects),\r\n color: Histogram.getLabelColor(objects).solid.color,\r\n displayUnits: Histogram.getLabelDisplayUnits(objects),\r\n precision: Histogram.getLabelPrecision(objects),\r\n fontSize: Histogram.getLabelFontSize(objects),\r\n };\r\n\r\n histogramSettings.fillColor = colorHelper.getColorForMeasure(objects, \"\");\r\n histogramSettings.bins = Histogram.getBins(objects);\r\n histogramSettings.frequency = Histogram.getFrequency(objects);\r\n histogramSettings.precision = Histogram.getPrecision(objects);\r\n histogramSettings.displayName = Histogram.getLegend(histogramSettings.displayName, xAxisSettings.style, xAxisSettings.displayUnits);\r\n\r\n histogramSettings.xAxisSettings = xAxisSettings;\r\n histogramSettings.yAxisSettings = yAxisSettings;\r\n histogramSettings.labelSettings = labelSettings;\r\n\r\n return histogramSettings;\r\n }\r\n\r\n private static getLegend(title: string, style: string, displayUnit: number): string {\r\n var retValue: string;\r\n var formatter: IValueFormatter = ValueFormatter.create({\r\n value: displayUnit\r\n });\r\n\r\n switch (style) {\r\n case axisStyle.showTitleOnly:\r\n retValue = title;\r\n break;\r\n case axisStyle.showUnitOnly:\r\n retValue = displayUnit === 0 || displayUnit === 1 ? title : formatter.displayUnit.title;\r\n break;\r\n case axisStyle.showBoth:\r\n retValue = displayUnit === 0 || displayUnit === 1 ? title : title + \" (\" + formatter.displayUnit.title + \")\";\r\n break;\r\n }\r\n return retValue;\r\n }\r\n\r\n private static getLabelFontSize(objects: DataViewObjects): number {\r\n return DataViewObjects.getValue<number>(\r\n objects,\r\n Histogram.Properties[\"labels\"][\"fontSize\"],\r\n Histogram.DefaultHistogramSettings.labelSettings.fontSize\r\n );\r\n }\r\n\r\n private static getLabelShow(objects: DataViewObjects): boolean {\r\n return DataViewObjects.getValue<boolean>(\r\n objects,\r\n Histogram.Properties[\"labels\"][\"show\"],\r\n Histogram.DefaultHistogramSettings.labelSettings.show\r\n );\r\n }\r\n\r\n private static getLabelColor(objects: DataViewObjects): Fill {\r\n return DataViewObjects.getValue<Fill>(\r\n objects,\r\n Histogram.Properties[\"labels\"][\"color\"],\r\n {\r\n solid: {\r\n color: Histogram.DefaultHistogramSettings.labelSettings.color\r\n }\r\n }\r\n );\r\n }\r\n\r\n private static getLabelDisplayUnits(objects: DataViewObjects): number {\r\n return DataViewObjects.getValue<number>(\r\n objects,\r\n Histogram.Properties[\"labels\"][\"displayUnits\"],\r\n Histogram.DefaultHistogramSettings.labelSettings.displayUnits\r\n );\r\n }\r\n\r\n private static getLabelPrecision(objects: DataViewObjects): number {\r\n var precision: number = DataViewObjects.getValue(\r\n objects,\r\n Histogram.Properties[\"labels\"][\"precision\"],\r\n Histogram.DefaultHistogramSettings.labelSettings.precision);\r\n\r\n if (precision <= Histogram.MinPrecision) {\r\n return Histogram.MinPrecision;\r\n } else if (precision >= Histogram.MaxPrecision) {\r\n return Histogram.MaxPrecision;\r\n }\r\n\r\n return precision;\r\n }\r\n\r\n private static getXStyle(objects: DataViewObjects): string {\r\n return DataViewObjects.getValue<string>(\r\n objects,\r\n Histogram.Properties[\"xAxis\"][\"style\"],\r\n Histogram.DefaultHistogramSettings.xAxisSettings.style\r\n );\r\n }\r\n\r\n private static getXDisplayUnit(objects: DataViewObjects): number {\r\n return DataViewObjects.getValue<number>(\r\n objects,\r\n Histogram.Properties[\"xAxis\"][\"displayUnits\"],\r\n Histogram.DefaultHistogramSettings.xAxisSettings.displayUnits\r\n );\r\n }\r\n\r\n private static getXPrecision(objects: DataViewObjects): number {\r\n var precision: number = DataViewObjects.getValue(\r\n objects,\r\n Histogram.Properties[\"xAxis\"][\"precision\"],\r\n Histogram.DefaultHistogramSettings.xAxisSettings.precision);\r\n\r\n if (precision <= Histogram.MinPrecision) {\r\n return Histogram.MinPrecision;\r\n } else if (precision >= Histogram.MaxPrecision) {\r\n return Histogram.MaxPrecision;\r\n }\r\n\r\n return precision;\r\n }\r\n\r\n private static getXAxisShow(objects: DataViewObjects): boolean {\r\n return DataViewObjects.getValue<boolean>(\r\n objects,\r\n Histogram.Properties[\"xAxis\"][\"show\"],\r\n Histogram.DefaultHistogramSettings.xAxisSettings.show\r\n );\r\n }\r\n\r\n private static getXAxisColor(objects: DataViewObjects): Fill {\r\n return DataViewObjects.getValue<Fill>(\r\n objects,\r\n Histogram.Properties[\"xAxis\"][\"axisColor\"],\r\n {\r\n solid: {\r\n color: Histogram.DefaultHistogramSettings.xAxisSettings.axisColor\r\n }\r\n }\r\n );\r\n }\r\n\r\n private static getXTitle(objects: DataViewObjects): boolean {\r\n return DataViewObjects.getValue<boolean>(\r\n objects,\r\n Histogram.Properties[\"xAxis\"][\"title\"],\r\n Histogram.DefaultHistogramSettings.xAxisSettings.title);\r\n }\r\n\r\n private static getYStyle(objects: DataViewObjects): string {\r\n return DataViewObjects.getValue<string>(\r\n objects,\r\n Histogram.Properties[\"yAxis\"][\"style\"],\r\n Histogram.DefaultHistogramSettings.yAxisSettings.style\r\n );\r\n }\r\n\r\n private static getYPosition(objects: DataViewObjects): string {\r\n return DataViewObjects.getValue<string>(\r\n objects,\r\n Histogram.Properties[\"yAxis\"][\"position\"],\r\n Histogram.DefaultHistogramSettings.yAxisSettings.position\r\n );\r\n }\r\n\r\n private static getYAxisShow(objects: DataViewObjects): boolean {\r\n return DataViewObjects.getValue<boolean>(\r\n objects,\r\n Histogram.Properties[\"yAxis\"][\"show\"],\r\n Histogram.DefaultHistogramSettings.yAxisSettings.show\r\n );\r\n }\r\n\r\n private static getYAxisColor(objects: DataViewObjects): Fill {\r\n return DataViewObjects.getValue<Fill>(\r\n objects,\r\n Histogram.Properties[\"yAxis\"][\"axisColor\"],\r\n {\r\n solid: {\r\n color: Histogram.DefaultHistogramSettings.yAxisSettings.axisColor\r\n }\r\n }\r\n );\r\n }\r\n\r\n private static getYStart(objects: DataViewObjects): number {\r\n return DataViewObjects.getValue<number>(\r\n objects,\r\n Histogram.Properties[\"yAxis\"][\"start\"],\r\n Histogram.DefaultHistogramSettings.yAxisSettings.start\r\n );\r\n }\r\n\r\n private static getYEnd(objects: DataViewObjects): number {\r\n return DataViewObjects.getValue<number>(\r\n objects,\r\n Histogram.Properties[\"yAxis\"][\"end\"],\r\n Histogram.DefaultHistogramSettings.yAxisSettings.end\r\n );\r\n }\r\n\r\n private static getYDisplayUnit(objects: DataViewObjects): number {\r\n return DataViewObjects.getValue<number>(\r\n objects,\r\n Histogram.Properties[\"yAxis\"][\"displayUnits\"],\r\n Histogram.DefaultHistogramSettings.yAxisSettings.displayUnits\r\n );\r\n }\r\n\r\n private static getYPrecision(objects: DataViewObjects): number {\r\n var precision: number = DataViewObjects.getValue(\r\n objects,\r\n Histogram.Properties[\"yAxis\"][\"precision\"],\r\n Histogram.DefaultHistogramSettings.yAxisSettings.precision\r\n );\r\n\r\n if (precision <= Histogram.MinPrecision) {\r\n return Histogram.MinPrecision;\r\n } else if (precision >= Histogram.MaxPrecision) {\r\n return Histogram.MaxPrecision;\r\n }\r\n\r\n return precision;\r\n }\r\n\r\n private static getYTitle(objects: DataViewObjects): boolean {\r\n return DataViewObjects.getValue<boolean>(\r\n objects,\r\n Histogram.Properties[\"yAxis\"][\"title\"],\r\n Histogram.DefaultHistogramSettings.yAxisSettings.title);\r\n }\r\n\r\n private static getBins(objects: DataViewObjects): number {\r\n var binsNumber: number = Number(DataViewObjects.getValue<number>(\r\n objects,\r\n Histogram.Properties[\"general\"][\"bins\"],\r\n Histogram.DefaultHistogramSettings.bins)\r\n );\r\n\r\n if (!binsNumber || isNaN(binsNumber) || (binsNumber <= Histogram.MinNumberOfBins)) {\r\n return Histogram.DefaultHistogramSettings.bins;\r\n }\r\n\r\n if (binsNumber > Histogram.MaxNumberOfBins) {\r\n return Histogram.MaxNumberOfBins;\r\n }\r\n\r\n return binsNumber;\r\n }\r\n\r\n private static getFrequency(objects: DataViewObjects): boolean {\r\n return DataViewObjects.getValue<boolean>(\r\n objects,\r\n Histogram.Properties[\"general\"][\"frequency\"],\r\n Histogram.DefaultHistogramSettings.frequency\r\n );\r\n }\r\n\r\n private static getPrecision(objects: DataViewObjects): number {\r\n var precision: number = DataViewObjects.getValue(\r\n objects,\r\n Histogram.Properties[\"labels\"][\"precision\"],\r\n Histogram.DefaultHistogramSettings.precision\r\n );\r\n\r\n if (precision <= Histogram.MinPrecision) {\r\n return Histogram.MinPrecision;\r\n }\r\n\r\n if (precision >= Histogram.MaxPrecision) {\r\n return Histogram.MaxPrecision;\r\n }\r\n\r\n return precision;\r\n }\r\n\r\n public validateData(data: HistogramDataView): boolean {\r\n if (data && data.data.some(x=> x.range.some(x => isNaN(x) || x === Infinity || x === -Infinity))) {\r\n this.hostService.setWarnings([new HistogramChartWarning(HistogramChartWarning.ErrorInvalidDataValues)]);\r\n return false;\r\n }\r\n return true;\r\n }\r\n\r\n public update(visualUpdateOptions: VisualUpdateOptions): void {\r\n if (!visualUpdateOptions ||\r\n !visualUpdateOptions.dataViews ||\r\n !visualUpdateOptions.dataViews[0]) {\r\n return;\r\n }\r\n CartesianChart.InnerPaddingRatio = 1;\r\n\r\n var dataView: DataView = visualUpdateOptions.dataViews[0];\r\n\r\n this.durationAnimations = getAnimationDuration(\r\n this.animator,\r\n visualUpdateOptions.suppressAnimations);\r\n\r\n this.setSize(visualUpdateOptions.viewport);\r\n\r\n this.histogramDataView = this.converter(dataView);\r\n if (!this.validateData(this.histogramDataView)) {\r\n this.histogramDataView.data = [];\r\n }\r\n\r\n if (!this.histogramDataView) {\r\n return;\r\n }\r\n\r\n this.YLegendSize = this.histogramDataView.settings.yAxisSettings.title ? 50 : 25;\r\n this.XLegendSize = this.histogramDataView.settings.xAxisSettings.title ? 50 : 25;\r\n\r\n this.fixXTicSize();\r\n\r\n this.xAxisProperties = this.calculateXAxes(dataView.categorical.categories[0].source, this.textProperties, false);\r\n\r\n var ySource = dataView.categorical.values &&\r\n dataView.categorical.values[0] &&\r\n dataView.categorical.values[0].values ? dataView.categorical.values[0].source : dataView.categorical.categories[0].source;\r\n\r\n this.yAxisProperties = this.calculateYAxes(ySource, this.textProperties, false);\r\n\r\n this.render();\r\n\r\n CartesianChart.InnerPaddingRatio = this.oldInnerPaddingRatio;\r\n CartesianChart.MinOrdinalRectThickness = this.oldMinOrdinalRectThickness;\r\n }\r\n\r\n private fixXTicSize(): void {\r\n if (!this.histogramDataView || !this.histogramDataView.settings) {\r\n return;\r\n }\r\n\r\n var ticLabel = this.histogramDataView.xLabelFormatter.format(this.histogramDataView.settings.maxX);\r\n\r\n var textProperties: powerbi.TextProperties = {\r\n text: ticLabel,\r\n fontFamily: this.textProperties.fontFamily,\r\n fontSize: this.textProperties.fontSize\r\n };\r\n var widthOfLabel: number = powerbi.TextMeasurementService.measureSvgTextWidth(textProperties);\r\n\r\n CartesianChart.MinOrdinalRectThickness = widthOfLabel + 3;\r\n }\r\n\r\n private setSize(viewport: IViewport): void {\r\n var height: number,\r\n width: number;\r\n\r\n height = viewport.height -\r\n this.margin.top -\r\n this.margin.bottom;\r\n\r\n width = viewport.width -\r\n this.margin.left -\r\n this.margin.right;\r\n\r\n this.viewport = {\r\n height: height,\r\n width: width\r\n };\r\n\r\n this.updateElements(viewport.height, viewport.width);\r\n }\r\n\r\n private updateElements(height: number, width: number): void {\r\n\r\n this.root.attr({\r\n \"height\": height,\r\n \"width\": width\r\n });\r\n\r\n this.main.attr(\"transform\", SVGUtil.translate(this.margin.left, this.margin.top));\r\n this.legend.attr(\"transform\", SVGUtil.translate(this.margin.left, this.margin.top));\r\n\r\n this.axisX.attr(\r\n \"transform\",\r\n SVGUtil.translate(0, this.viewport.height - this.XLegendSize)\r\n );\r\n }\r\n\r\n public shouldShowYOnRight(): boolean {\r\n return this.histogramDataView.settings.yAxisSettings.position === yAxisPosition.right;\r\n }\r\n\r\n private columsAndAxesTransform(labelWidth: number): void {\r\n var constMargin = 20;\r\n var shiftToRight: number = this.shouldShowYOnRight() ? 10 :\r\n this.histogramDataView.settings.yAxisSettings.title ? this.margin.left + labelWidth + constMargin : this.margin.left + labelWidth;\r\n\r\n this.DataLabelMargin = shiftToRight;\r\n\r\n this.columns.attr(\"transform\", SVGUtil.translate(shiftToRight, 0));\r\n this.axes.attr(\"transform\", SVGUtil.translate(shiftToRight, 0));\r\n\r\n this.axisY.attr('transform', SVGUtil.translate(\r\n this.shouldShowYOnRight() ? this.viewport.width - this.AxisSize - this.YLegendSize + 0.01 : 0, 0));\r\n\r\n this.axisX.attr(\r\n \"transform\",\r\n SVGUtil.translate(0, this.viewport.height - this.XLegendSize));\r\n\r\n }\r\n\r\n private render(): void {\r\n if (!this.histogramDataView || !this.histogramDataView.settings) {\r\n return;\r\n }\r\n\r\n this.renderAxes();\r\n var columnsSelection: D3.UpdateSelection = this.renderColumns();\r\n\r\n this.adjustTransformToAxisLabels();\r\n\r\n this.renderLegend();\r\n\r\n if (this.histogramDataView.settings.labelSettings.show) {\r\n this.renderLabels();\r\n } else {\r\n this.main.selectAll('.labels').selectAll('*').remove();\r\n }\r\n\r\n this.bindSelectionHandler(columnsSelection);\r\n }\r\n\r\n private adjustTransformToAxisLabels(): void {\r\n var maxWidthOfLabael = 0;\r\n this.main.selectAll('g.axis').filter((d, index) => index === 1).selectAll('g.tick text')\r\n .each(function (d, i) {\r\n var p = powerbi.TextMeasurementService.getSvgMeasurementProperties(this);\r\n var textProperties: powerbi.TextProperties = {\r\n text: p.text,\r\n fontFamily: p.fontFamily,\r\n fontSize: p.fontSize\r\n };\r\n var widthOfLabel = powerbi.TextMeasurementService.measureSvgTextWidth(textProperties);\r\n if (widthOfLabel > maxWidthOfLabael)\r\n maxWidthOfLabael = widthOfLabel;\r\n });\r\n var constMargin = 70;\r\n this.yTitleMargin = this.shouldShowYOnRight() ? this.viewport.width - this.AxisSize - constMargin + this.YLegendSize + maxWidthOfLabael : 0;\r\n this.columsAndAxesTransform(maxWidthOfLabael);\r\n }\r\n\r\n private renderColumns(): D3.UpdateSelection {\r\n var data: HistogramData[] = this.histogramDataView.data,\r\n yScale: D3.Scale.LinearScale = this.histogramDataView.yScale,\r\n countOfValues: number = data.length,\r\n widthOfColumn: number,\r\n updateColumnsSelection: D3.UpdateSelection;\r\n\r\n widthOfColumn = countOfValues && ((this.viewport.width - this.AxisSize - this.YLegendSize) / countOfValues - this.ColumnPadding);\r\n\r\n if (widthOfColumn < 0) {\r\n widthOfColumn = 0;\r\n }\r\n\r\n this.widthOfColumn = widthOfColumn;\r\n updateColumnsSelection = this.columnsSelection.data(data);\r\n\r\n updateColumnsSelection\r\n .enter()\r\n .append(\"svg:rect\");\r\n\r\n updateColumnsSelection\r\n .attr(\"x\", this.ColumnPadding / 2)\r\n .attr(\"width\", widthOfColumn)\r\n .attr(\"height\", (item: HistogramData) => this.getColumnHeight(item, yScale))\r\n .style(\"fill\", this.histogramDataView.settings.fillColor)\r\n .attr(\"class\", Histogram.Column.class)\r\n .attr(\"transform\", (item: HistogramData, index: number) => SVGUtil.translate(\r\n widthOfColumn * index + this.ColumnPadding * index,\r\n yScale(item.y) - this.ColumnPadding / 2.5));\r\n\r\n if (countOfValues) {\r\n //if data is empty, it throws for some reason\r\n updateColumnsSelection.classed(Histogram.Column.class);\r\n }\r\n\r\n updateColumnsSelection.exit().remove();\r\n\r\n Histogram.renderTooltip(updateColumnsSelection);\r\n\r\n return updateColumnsSelection;\r\n }\r\n\r\n private static renderTooltip(selection: D3.UpdateSelection): void {\r\n TooltipManager.addTooltip(selection, (tooltipEvent: TooltipEvent) => {\r\n return (<HistogramData>tooltipEvent.data).tooltipInfo;\r\n });\r\n }\r\n\r\n private getColumnHeight(column: D3.Layout.Bin, y: D3.Scale.LinearScale): number {\r\n var height: number = this.viewport.height - this.XLegendSize - y(column.y);\r\n\r\n return height > 0 ? height : this.MinColumnHeight;\r\n }\r\n\r\n private renderAxes(): void {\r\n var xAxis: D3.Svg.Axis,\r\n yAxis: D3.Svg.Axis;\r\n\r\n xAxis = this.xAxisProperties.axis\r\n .tickFormat((item: number) => this.histogramDataView.xLabelFormatter.format(item))\r\n .orient('bottom');\r\n\r\n yAxis = this.yAxisProperties.axis\r\n .orient(this.histogramDataView.settings.yAxisSettings.position.toLowerCase())\r\n .tickFormat((item: number) => this.histogramDataView.yLabelFormatter.format(item));\r\n\r\n var xShow: boolean = this.histogramDataView.settings.xAxisSettings.show;\r\n var yShow: boolean = this.histogramDataView.settings.yAxisSettings.show;\r\n\r\n if (xShow) {\r\n this.axisX\r\n .transition()\r\n .duration(1)\r\n .call(xAxis);\r\n } else {\r\n this.axisX.selectAll('*').remove();\r\n }\r\n\r\n if (yShow) {\r\n this.axisY\r\n .call(yAxis);\r\n } else {\r\n this.axisY.selectAll('*').remove();\r\n }\r\n\r\n this.main.selectAll('g.axis').filter((d, index) => index === 0).selectAll('g.tick text').style({\r\n 'fill': this.histogramDataView.settings.xAxisSettings.axisColor,\r\n });\r\n\r\n this.main.selectAll('g.axis').filter((d, index) => index === 1).selectAll('g.tick text').style({\r\n 'fill': this.histogramDataView.settings.yAxisSettings.axisColor,\r\n });\r\n }\r\n\r\n private getLabaelLayout(): ILabelLayout {\r\n var labelSettings: HistogramLabelSettings = this.histogramDataView.settings.labelSettings;\r\n\r\n var fontSizeInPx: string = PixelConverter.fromPoint(labelSettings.fontSize);\r\n var dataLabelFormatter = ValueFormatter.create({\r\n value: labelSettings.displayUnits,\r\n precision: labelSettings.precision\r\n });\r\n\r\n return {\r\n labelText: (b: D3.Layout.Bin) => {\r\n return dataLabelFormatter.format(b.y).toString();\r\n },\r\n labelLayout: {\r\n x: (b: D3.Layout.Bin) => this.DataLabelMargin + this.histogramDataView.xScale(b.x) + this.widthOfColumn / 2,\r\n y: (b: D3.Layout.Bin) => this.histogramDataView.yScale(b.y) - 5\r\n },\r\n filter: (b: D3.Layout.Bin) => {\r\n return (b != null);\r\n },\r\n style: {\r\n 'fill': labelSettings.color,\r\n 'font-size': fontSizeInPx,\r\n },\r\n };\r\n }\r\n\r\n private renderLabels(): void {\r\n var layout = this.getLabaelLayout();\r\n var dataPointsArray = this.histogramDataView.data;\r\n dataLabelUtils.drawDefaultLabelsForDataPointChart(dataPointsArray, this.main, layout, this.viewport);\r\n }\r\n\r\n private static rangesToArray(data: HistogramData[]): number[] {\r\n return data.reduce((previousValue: number[], currentValue: HistogramData, index: number) => {\r\n var range: number[];\r\n\r\n range = (index === 0)\r\n ? currentValue.range\r\n : currentValue.range.slice(1);\r\n\r\n return previousValue.concat(range);\r\n }, []);\r\n }\r\n\r\n private rangeToString(range: number[], includeLeftBorder: boolean, valueFormatter: IValueFormatter): string {\r\n var leftBracket: string,\r\n rightBracket: string = this.IncludeBrackets.right,\r\n leftBorder: string = valueFormatter.format(range[0]),\r\n rightBorder: string = valueFormatter.format(range[1]);\r\n\r\n leftBracket = includeLeftBorder\r\n ? this.IncludeBrackets.left\r\n : this.ExcludeBrackets.left;\r\n\r\n return `${leftBracket}${leftBorder}${this.SeparatorNumbers}${rightBorder}${rightBracket}`;\r\n }\r\n\r\n private renderLegend(): void {\r\n var legendElements: D3.Selection,\r\n legendSelection: D3.UpdateSelection,\r\n datalegends: Legend[] = this.getDataLegends(this.histogramDataView.settings);\r\n\r\n legendElements = this.main\r\n .select(Histogram.Legends.selector)\r\n .selectAll(Histogram.Legend.selector);\r\n\r\n legendSelection = legendElements.data(datalegends);\r\n\r\n legendSelection\r\n .enter()\r\n .append(\"svg:text\");\r\n\r\n legendSelection\r\n .attr(\"x\", 0)\r\n .attr(\"y\", 0)\r\n .attr(\"dx\", (item: Legend) => item.dx)\r\n .attr(\"dy\", (item: Legend) => item.dy)\r\n .attr(\"transform\", (item: Legend) => item.transform)\r\n .attr(\"class\", Histogram.Legend.class)\r\n .text((item: Legend) => item.text)\r\n .classed(Histogram.Legend.class, true);\r\n\r\n legendSelection\r\n .exit()\r\n .remove();\r\n\r\n this.legend.select('text').style({\r\n 'display': this.histogramDataView.settings.xAxisSettings.title === true ? 'block' : 'none',\r\n });\r\n\r\n this.legend.selectAll('text').filter((d, index) => index === 1).style({\r\n 'display': this.histogramDataView.settings.yAxisSettings.title === true ? 'block' : 'none',\r\n });\r\n }\r\n\r\n private getDataLegends(settings: HistogramSettings): Legend[] {\r\n var bottomLegendText: string = Histogram.getLegendText(settings);\r\n bottomLegendText = Histogram.getLegend(bottomLegendText, settings.yAxisSettings.style, settings.yAxisSettings.displayUnits);\r\n\r\n return [{\r\n transform: SVGUtil.translate(\r\n this.viewport.width / 2,\r\n this.viewport.height),\r\n text: settings.displayName,\r\n dx: \"1em\",\r\n dy: \"-1em\"\r\n }, {\r\n transform: SVGUtil.translateAndRotate(\r\n this.shouldShowYOnRight() ? this.yTitleMargin : 0,\r\n this.viewport.height / 2,\r\n 0,\r\n 0,\r\n 270),\r\n text: bottomLegendText,\r\n dx: \"3em\"\r\n }];\r\n }\r\n\r\n private static getLegendText(settings: HistogramSettings): string {\r\n return settings.frequency\r\n ? Histogram.FrequencyText\r\n : Histogram.DensityText;\r\n }\r\n\r\n private bindSelectionHandler(columnsSelection: D3.UpdateSelection): void {\r\n this.setSelection(columnsSelection);\r\n\r\n columnsSelection.on(\"click\", (data: HistogramData) => {\r\n this.selectionManager.clear();\r\n\r\n data.selectionIds.forEach((selectionId: SelectionId) => {\r\n this.selectionManager.select(selectionId, true).then((selectionIds: SelectionId[]) => {\r\n if (selectionIds.length > 0) {\r\n this.setSelection(columnsSelection, data);\r\n } else {\r\n this.setSelection(columnsSelection);\r\n }\r\n });\r\n });\r\n\r\n d3.event.stopPropagation();\r\n });\r\n\r\n this.root.on(\"click\", () => {\r\n this.selectionManager.clear();\r\n this.setSelection(columnsSelection);\r\n });\r\n }\r\n\r\n private setSelection(columnsSelection: D3.UpdateSelection, data?: HistogramData): void {\r\n columnsSelection.transition()\r\n .duration(this.durationAnimations)\r\n .style(\"fill-opacity\", this.MaxOpacity);\r\n\r\n if (!data) {\r\n return;\r\n }\r\n\r\n columnsSelection\r\n .filter((columnSelection: HistogramData) => {\r\n return columnSelection !== data;\r\n })\r\n .transition()\r\n .duration(this.durationAnimations)\r\n .style(\"fill-opacity\", this.MinOpacity);\r\n }\r\n\r\n public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstance[] {\r\n var instances: VisualObjectInstance[] = [],\r\n settings: HistogramSettings;\r\n\r\n if (!this.histogramDataView ||\r\n !this.histogramDataView.settings) {\r\n return instances;\r\n }\r\n\r\n settings = this.histogramDataView.settings;\r\n\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 bins: settings.bins,\r\n frequency: settings.frequency\r\n }\r\n };\r\n\r\n instances.push(general);\r\n break;\r\n }\r\n case \"dataPoint\": {\r\n var dataPoint: VisualObjectInstance = {\r\n objectName: \"dataPoint\",\r\n displayName: \"dataPoint\",\r\n selector: null,\r\n properties: {\r\n fill: settings.fillColor\r\n }\r\n };\r\n\r\n instances.push(dataPoint);\r\n break;\r\n }\r\n case \"labels\": {\r\n var labelsSettings: HistogramLabelSettings = settings.labelSettings;\r\n var labels: VisualObjectInstance = {\r\n objectName: \"labels\",\r\n displayName: \"labels\",\r\n selector: null,\r\n properties: {\r\n show: labelsSettings.show,\r\n color: labelsSettings.color,\r\n displayUnits: labelsSettings.displayUnits,\r\n precision: labelsSettings.precision,\r\n fontSize: labelsSettings.fontSize\r\n }\r\n };\r\n instances.push(labels);\r\n break;\r\n }\r\n case \"xAxis\": {\r\n var xAxisSettings: HistogramXAxisSettings = settings.xAxisSettings;\r\n var xAxis: VisualObjectInstance = {\r\n objectName: \"xAxis\",\r\n displayName: \"X-Axis\",\r\n selector: null,\r\n properties: {\r\n show: xAxisSettings.show,\r\n title: xAxisSettings.title,\r\n style: xAxisSettings.style,\r\n axisColor: xAxisSettings.axisColor,\r\n displayUnits: xAxisSettings.displayUnits,\r\n precision: xAxisSettings.precision,\r\n }\r\n };\r\n instances.push(xAxis);\r\n break;\r\n }\r\n case \"yAxis\": {\r\n var yAxisSettings: HistogramYAxisSettings = settings.yAxisSettings;\r\n var yAxis: VisualObjectInstance = {\r\n objectName: \"yAxis\",\r\n displayName: \"Y-Axis\",\r\n selector: null,\r\n properties: {\r\n show: yAxisSettings.show,\r\n position: yAxisSettings.position,\r\n start: yAxisSettings.start,\r\n end: yAxisSettings.end,\r\n title: yAxisSettings.title,\r\n style: yAxisSettings.style,\r\n axisColor: yAxisSettings.axisColor,\r\n displayUnits: yAxisSettings.displayUnits,\r\n precision: yAxisSettings.precision,\r\n }\r\n };\r\n instances.push(yAxis);\r\n break;\r\n }\r\n }\r\n return instances;\r\n }\r\n\r\n private static getObjectsFromDataView(dataView: DataView): DataViewObjects {\r\n if (!dataView ||\r\n !dataView.metadata ||\r\n !dataView.metadata.columns ||\r\n !dataView.metadata.objects) {\r\n return null;\r\n }\r\n\r\n return dataView.metadata.objects;\r\n }\r\n\r\n public destroy(): void {\r\n this.root = null;\r\n }\r\n\r\n private calculateXAxes(\r\n source: DataViewMetadataColumn,\r\n textProperties: TextProperties,\r\n scrollbarVisible: boolean): IAxisProperties {\r\n\r\n var visualOptions: CalculateScaleAndDomainOptions = {\r\n viewport: this.viewport,\r\n margin: this.margin,\r\n forcedXDomain: Histogram.rangesToArray(this.histogramDataView.data),\r\n forceMerge: true,\r\n showCategoryAxisLabel: false,\r\n showValueAxisLabel: false,\r\n categoryAxisScaleType: axisScale.linear,\r\n valueAxisScaleType: null,\r\n trimOrdinalDataOnOverflow: false\r\n };\r\n\r\n var width = this.viewport.width;\r\n var axes = this.calculateXAxesProperties(visualOptions, source);\r\n\r\n axes.willLabelsFit = AxisHelper.LabelLayoutStrategy.willLabelsFit(\r\n axes,\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 axes.willLabelsWordBreak = (!axes.willLabelsFit && !scrollbarVisible) && AxisHelper.LabelLayoutStrategy.willLabelsWordBreak(\r\n axes, this.margin, width, TextMeasurementService.measureSvgTextWidth,\r\n TextMeasurementService.estimateSvgTextHeight, TextMeasurementService.getTailoredTextOrDefault,\r\n textProperties);\r\n\r\n return axes;\r\n }\r\n\r\n private calculateXAxesProperties(options: CalculateScaleAndDomainOptions, metaDataColumn: DataViewMetadataColumn): IAxisProperties {\r\n var xAxisProperties = AxisHelper.createAxis({\r\n pixelSpan: this.viewport.width - this.YLegendSize - this.AxisSize,\r\n dataDomain: options.forcedXDomain,\r\n metaDataColumn: metaDataColumn,\r\n formatString: valueFormatter.getFormatString(metaDataColumn, Histogram.Properties[\"general\"][\"formatString\"]),\r\n outerPadding: 0,\r\n isScalar: false,\r\n isVertical: false,\r\n useTickIntervalForDisplayUnits: true,\r\n isCategoryAxis: true,\r\n getValueFn: (index, type) => index,\r\n scaleType: options.categoryAxisScaleType\r\n });\r\n\r\n xAxisProperties.axisLabel = this.histogramDataView.settings.displayName;\r\n return xAxisProperties;\r\n }\r\n\r\n private calculateYAxes(\r\n source: DataViewMetadataColumn,\r\n textProperties: TextProperties,\r\n scrollbarVisible: boolean): IAxisProperties {\r\n\r\n var visualOptions: CalculateScaleAndDomainOptions = {\r\n viewport: this.viewport,\r\n margin: this.margin,\r\n forceMerge: true,\r\n showCategoryAxisLabel: true,\r\n showValueAxisLabel: false,\r\n categoryAxisScaleType: axisScale.linear,\r\n valueAxisScaleType: null,\r\n trimOrdinalDataOnOverflow: false\r\n };\r\n var yAxisSettings: HistogramYAxisSettings = this.histogramDataView.settings.yAxisSettings;\r\n visualOptions.forcedYDomain = AxisHelper.applyCustomizedDomain([yAxisSettings.start, yAxisSettings.end], visualOptions.forcedYDomain);\r\n\r\n var axes = this.calculateYAxesProperties(visualOptions, source);\r\n return axes;\r\n }\r\n\r\n private calculateYAxesProperties(options: CalculateScaleAndDomainOptions, metaDataColumn: DataViewMetadataColumn): IAxisProperties {\r\n var yAxisSettings: HistogramYAxisSettings = this.histogramDataView.settings.yAxisSettings;\r\n var yAxisProperties = AxisHelper.createAxis({\r\n pixelSpan: this.viewport.height - this.XLegendSize + 5,\r\n dataDomain: AxisHelper.combineDomain(options.forcedYDomain, [yAxisSettings.start, yAxisSettings.end]),\r\n metaDataColumn: metaDataColumn,\r\n formatString: valueFormatter.getFormatString(metaDataColumn, Histogram.Properties[\"general\"][\"formatString\"]),\r\n outerPadding: this.outerPadding,\r\n isScalar: true,\r\n isVertical: true,\r\n useTickIntervalForDisplayUnits: true,\r\n isCategoryAxis: false,\r\n getValueFn: (index, type) => index,\r\n scaleType: options.categoryAxisScaleType\r\n });\r\n\r\n return yAxisProperties;\r\n }\r\n\r\n }\r\n}\r\n\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/CustomVisuals/visuals/histogram/visual/histogram.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\nmodule powerbi.visuals.samples {\r\n import ClassAndSelector = jsCommon.CssConstants.ClassAndSelector;\r\n import getAnimationDuration = AnimatorCommon.GetAnimationDuration;\r\n import CreateClassAndSelector = jsCommon.CssConstants.createClassAndSelector;\r\n import AxisScale = powerbi.visuals.axisScale;\r\n import PixelConverter = jsCommon.PixelConverter;\r\n\r\n var MaxXAxisHeight: number = 40;\r\n var LabelMargin: number = 15;\r\n var DefaultRadius: number = 5;\r\n var DefaultStrokeWidth: number = 1;\r\n var DefaultDataPointColor = \"#00B8AA\";\r\n var MinPrecision: number = 0;\r\n var MaxPrecision: number = 17;\r\n export module DotPlotLabelsOrientation {\r\n export enum Orientation {\r\n Horizontal,\r\n Vertical,\r\n };\r\n export var type: IEnumType = createEnumType([\r\n { value: Orientation[0], displayName: \"Horizontal\" },\r\n { value: Orientation[1], displayName: \"Vertical\" }\r\n ]);\r\n }\r\n export var DotPlotProperties: any = {\r\n general: {\r\n formatString: <DataViewObjectPropertyIdentifier>{\r\n objectName: \"general\",\r\n propertyName: \"formatString\"\r\n }\r\n },\r\n labels: {\r\n show: <DataViewObjectPropertyIdentifier>{\r\n objectName: \"labels\",\r\n propertyName: \"show\"\r\n },\r\n fontSize: <DataViewObjectPropertyIdentifier>{\r\n objectName: \"labels\",\r\n propertyName: \"fontSize\"\r\n },\r\n labelPrecision: <DataViewObjectPropertyIdentifier>{\r\n objectName: \"labels\",\r\n propertyName: \"labelPrecision\"\r\n },\r\n labelDisplayUnits: <DataViewObjectPropertyIdentifier>{\r\n objectName: \"labels\",\r\n propertyName: \"labelDisplayUnits\"\r\n },\r\n labelColor: <DataViewObjectPropertyIdentifier>{\r\n objectName: \"labels\",\r\n propertyName: \"color\"\r\n },\r\n orientation: <DataViewObjectPropertyIdentifier>{\r\n objectName: \"labels\",\r\n propertyName: \"orientation\"\r\n }\r\n },\r\n dataPoint: {\r\n fill: <DataViewObjectPropertyIdentifier>{\r\n objectName: \"dataPoint\",\r\n propertyName: \"fill\"\r\n }\r\n },\r\n categories: {\r\n show: <DataViewObjectPropertyIdentifier>{\r\n objectName: \"categories\",\r\n propertyName: \"show\"\r\n },\r\n fontColor: <DataViewObjectPropertyIdentifier>{\r\n objectName: \"categories\",\r\n propertyName: \"fontColor\"\r\n },\r\n fontSize: <DataViewObjectPropertyIdentifier>{\r\n objectName: \"categories\",\r\n propertyName: \"fontSize\"\r\n }\r\n },\r\n categoryAxis: {\r\n show: <DataViewObjectPropertyIdentifier>{\r\n objectName: \"categoryAxis\",\r\n propertyName: \"show\"\r\n },\r\n showAxisTitle: <DataViewObjectPropertyIdentifier>{\r\n objectName: \"categoryAxis\",\r\n propertyName: \"showAxisTitle\"\r\n },\r\n labelColor: <DataViewObjectPropertyIdentifier>{\r\n objectName: \"categoryAxis\",\r\n propertyName: \"labelColor\"\r\n }\r\n }\r\n };\r\n\r\n export interface DotPlotSelectors {\r\n svgPlotSelector: ClassAndSelector;\r\n plotSelector: ClassAndSelector;\r\n plotGroupSelector: ClassAndSelector;\r\n axisSelector: ClassAndSelector;\r\n xAxisSelector: ClassAndSelector;\r\n circleSeletor: ClassAndSelector;\r\n }\r\n\r\n export interface DotPlotChartCategory {\r\n value: string;\r\n selectionId: SelectionId;\r\n }\r\n\r\n export interface DotPlotConstructorOptions {\r\n animator?: IGenericAnimator;\r\n svg?: D3.Selection;\r\n margin?: IMargin;\r\n radius?: number;\r\n strokeWidth?: number;\r\n }\r\n\r\n export interface DotPlotDataPoint {\r\n x: number;\r\n y: number;\r\n tooltipInfo: TooltipDataItem[];\r\n }\r\n\r\n export interface DotPlotSettings {\r\n labelSettings?: VisualDataLabelsSettings;\r\n formatter?: IValueFormatter;\r\n tooltipFormatter?: IValueFormatter;\r\n categorySettings?: DotPlotCategorySettings;\r\n defaultDataPointColor?: string;\r\n categoryAxisSettings?: DotPlotCategoryAxisSettings;\r\n labelOrientation?:DotPlotLabelsOrientation.Orientation;\r\n labelTextMaxSize:number;\r\n }\r\n\r\n export interface DotPlotCategoryAxisSettings {\r\n show?: boolean;\r\n showAxisTitle?: boolean;\r\n labelColor?: Fill;\r\n }\r\n\r\n export interface DotPlotCategorySettings {\r\n show?: boolean;\r\n fontColor?: string;\r\n fontSize?: number;\r\n }\r\n\r\n export interface DotPlotDataGroup extends SelectableDataPoint {\r\n label?: string;\r\n value?: number;\r\n color?: string;\r\n tooltipInfo?: TooltipDataItem[];\r\n dataPoints: DotPlotDataPoint[];\r\n labelFontSize: string;\r\n highlight?: boolean;\r\n }\r\n\r\n export interface DotPlotDataView {\r\n displayName: string;\r\n dataPoints: DotPlotDataGroup[];\r\n values: any[];\r\n settings: DotPlotSettings;\r\n categories: DotPlotChartCategory[];\r\n }\r\n\r\n export class DotPlot implements IVisual {\r\n private viewportIn: IViewport;\r\n\r\n public static capabilities: VisualCapabilities = {\r\n dataRoles: [{\r\n name: 'Category',\r\n kind: powerbi.VisualDataRoleKind.Grouping,\r\n displayName: 'Category'\r\n },\r\n {\r\n name: \"Values\",\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: 'Values'\r\n }],\r\n dataViewMappings: [{\r\n conditions: [\r\n { 'Category': { max: 1 }, 'Values': { 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: \"Values\" } }],\r\n dataReductionAlgorithm: { top: {} }\r\n }\r\n }\r\n },\r\n }],\r\n objects: {\r\n general: {\r\n displayName: 'General',\r\n properties: {\r\n formatString: {\r\n type: { formatting: { formatString: true } },\r\n },\r\n },\r\n },\r\n categoryAxis: {\r\n displayName: 'X-Axis',\r\n properties: {\r\n show: {\r\n displayName: 'Show',\r\n type: { bool: true },\r\n },\r\n showAxisTitle: {\r\n displayName: 'Title',\r\n description: 'Title options',\r\n type: { bool: true }\r\n },\r\n labelColor: {\r\n displayName: 'Label color',\r\n type: { fill: { solid: { color: true } } }\r\n }\r\n }\r\n },\r\n dataPoint: {\r\n displayName: 'Data colors',\r\n properties: {\r\n fill: {\r\n displayName: 'Fill',\r\n type: { fill: { solid: { color: true } } }\r\n }\r\n }\r\n },\r\n labels: {\r\n displayName: \"Data labels\",\r\n description: 'Display data label options',\r\n properties: {\r\n show: {\r\n displayName: 'Show',\r\n type: { bool: true }\r\n },\r\n showSeries: {\r\n displayName: 'Show',\r\n type: { bool: true }\r\n },\r\n color: {\r\n displayName: 'Color',\r\n description: 'Select color for data labels',\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n labelDisplayUnits: {\r\n displayName: 'Display units',\r\n description: 'Select the units (millions, billions, etc.)',\r\n type: { formatting: { labelDisplayUnits: true } },\r\n suppressFormatPainterCopy: true\r\n },\r\n labelPrecision: {\r\n displayName: 'Decimal places',\r\n description: 'Select the number of decimal places to display',\r\n placeHolderText: 'Auto',\r\n type: { numeric: true },\r\n suppressFormatPainterCopy: true\r\n },\r\n showAll: {\r\n displayName: 'Customize series',\r\n type: { bool: true }\r\n },\r\n fontSize: {\r\n displayName: 'Text Size',\r\n type: { formatting: { fontSize: true } }\r\n },\r\n orientation: {\r\n displayName: \"Orientation\",\r\n type: { enumeration: DotPlotLabelsOrientation.type }\r\n }\r\n }\r\n }\r\n }\r\n };\r\n\r\n private DefaultMargin: IMargin = {\r\n top: 10,\r\n bottom: 10,\r\n right: 20,\r\n left: 20\r\n };\r\n\r\n private svg: D3.Selection;\r\n private xAxis: D3.Selection;\r\n private dotPlot: D3.Selection;\r\n private clearCatcher: D3.Selection;\r\n private behavior: IInteractiveBehavior;\r\n\r\n private colors: IDataColorPalette;\r\n private dataView: DataView;\r\n private animator: IGenericAnimator;\r\n private durationAnimations: number = 200;\r\n private dotPlotDataView: DotPlotDataView;\r\n\r\n private radius: number;\r\n private strokeWidth: number;\r\n private interactivityService: IInteractivityService;\r\n private scaleType: string = AxisScale.linear;\r\n private textProperties: TextProperties = {\r\n fontFamily: 'wf_segoe-ui_normal',\r\n fontSize: jsCommon.PixelConverter.toString(9),\r\n };\r\n\r\n private dotPlotSelectors: DotPlotSelectors =\r\n {\r\n svgPlotSelector: CreateClassAndSelector('dotplot'),\r\n plotSelector: CreateClassAndSelector('dotplotSelector'),\r\n plotGroupSelector: CreateClassAndSelector('dotplotGroup'),\r\n axisSelector: CreateClassAndSelector('axisGraphicsContext'),\r\n xAxisSelector: CreateClassAndSelector('x axis'),\r\n circleSeletor: CreateClassAndSelector('circleSelector'),\r\n };\r\n\r\n private DefaultDotPlotSettings: DotPlotSettings = {\r\n labelSettings: {\r\n show: true,\r\n precision: 2,\r\n fontSize: dataLabelUtils.DefaultFontSizeInPt,\r\n displayUnits: 0,\r\n labelColor: dataLabelUtils.defaultLabelColor,\r\n },\r\n categorySettings: {\r\n show: true,\r\n fontColor: LegendData.DefaultLegendLabelFillColor\r\n },\r\n defaultDataPointColor: DefaultDataPointColor,\r\n categoryAxisSettings: {\r\n show: true,\r\n showAxisTitle: true,\r\n labelColor: { solid: { color: dataLabelUtils.defaultLabelColor } }\r\n },\r\n labelOrientation: DotPlotLabelsOrientation.Orientation.Horizontal,\r\n labelTextMaxSize:0\r\n };\r\n\r\n private static getTooltipData(value: number): TooltipDataItem[] {\r\n return [{\r\n displayName: \"Value\",\r\n value: value.toString()\r\n }];\r\n }\r\n\r\n public static converter(dataView: DataView, objects: DataViewObjects, scale: D3.Scale.OrdinalScale, defaultMargin: IMargin, defaultSetting: DotPlotSettings, colors: IDataColorPalette, viewport: IViewport, radius: number): DotPlotDataView {\r\n var values: DataViewValueColumns = dataView.categorical.values,\r\n dataPointsGroup: DotPlotDataGroup[] = [],\r\n displayName: string = dataView.categorical.categories[0].source.displayName,\r\n settings: DotPlotSettings,\r\n defaultColor = DataViewObjects.getFillColor(objects, DotPlotProperties.dataPoint.fill, colors.getColorByIndex(0).value);\r\n\r\n var categories: DotPlotChartCategory[] = dataView.categorical.categories[0].values.map((x, i) => <DotPlotChartCategory>{\r\n value: x,\r\n selectionId: SelectionId.createWithId(dataView.categorical.categories[0].identity[i])\r\n });\r\n \r\n var maxValue = 0; \r\n for (var valueId in values) {\r\n var value = values[valueId];\r\n var max = _.max(value.values);\r\n maxValue = max > maxValue? max: maxValue;\r\n }\r\n \r\n settings = {\r\n categorySettings: this.getCategorySettings(objects, defaultSetting),\r\n defaultDataPointColor: defaultColor,\r\n labelSettings: this.parseSettings(objects, defaultSetting),\r\n categoryAxisSettings: this.parseCategoryAxisSettings(objects, defaultSetting),\r\n labelOrientation: ((DataViewObjects.getValue<DotPlotLabelsOrientation.Orientation>(objects, DotPlotProperties.labels.orientation , DotPlotLabelsOrientation.Orientation.Horizontal)+'') === 'Vertical' ?DotPlotLabelsOrientation.Orientation.Vertical:DotPlotLabelsOrientation.Orientation.Horizontal),\r\n labelTextMaxSize: 0 \r\n };\r\n var textPropertiesCat: powerbi.TextProperties = {\r\n text: \"W\",\r\n fontFamily: \"Segoe UI\" ,\r\n fontSize: settings.labelSettings.fontSize + \"px\"\r\n };\r\n \r\n settings.labelTextMaxSize = powerbi.TextMeasurementService.measureSvgTextWidth(textPropertiesCat) * (maxValue + ' ').length;\r\n \r\n var categoryColumn = dataView.categorical.categories[0];\r\n var diameter: number = 2 * radius + 1;\r\n var dotsTotalHeight: number = viewport.height - radius - MaxXAxisHeight - ( settings.labelOrientation === DotPlotLabelsOrientation.Orientation.Vertical? settings.labelTextMaxSize:0);\r\n var maxDots: number = Math.floor((dotsTotalHeight - defaultMargin.top) / diameter) - 1;\r\n var fontSizeInPx: string = PixelConverter.fromPoint(settings.labelSettings.fontSize);\r\n\r\n var yScale: D3.Scale.LinearScale = d3.scale.linear()\r\n .domain([0, maxDots])\r\n .range([dotsTotalHeight - defaultMargin.bottom, defaultMargin.top + defaultMargin.bottom]);\r\n \r\n \r\n for (var valueId in values) {\r\n var value = values[valueId];\r\n \r\n var min = _.min(value.values);\r\n var max = _.max(value.values);\r\n var color = DataViewObjects.getFillColor(objects, DotPlotProperties.dataPoint.fill, colors.getColorByIndex(0).value);\r\n var length = value && value.values ? value.values.length : 0;\r\n var minDots = min / (max / maxDots);\r\n var dotsScale = d3.scale.log().domain([min < 0 ? 1 : min, max]).range([minDots <= 0 ? 1 : minDots, maxDots]).clamp(true);\r\n\r\n for (var k = 0; k < length; k++) {\r\n var y = dotsScale(value.values[k]);\r\n var dataPoints: DotPlotDataPoint[] = [];\r\n\r\n for (var level = 0; level < y; level++) {\r\n dataPoints.push({\r\n x: scale(categories[k].value) + scale.rangeBand() / 2,\r\n y: yScale(level) + ( settings.labelOrientation === DotPlotLabelsOrientation.Orientation.Vertical? settings.labelTextMaxSize:0),\r\n tooltipInfo: DotPlot.getTooltipData(value.values[k].toFixed(settings.labelSettings.precision))\r\n });\r\n }\r\n\r\n var categorySelectionId = SelectionIdBuilder.builder().withCategory(categoryColumn, k).createSelectionId();\r\n var tooltipInfo = DotPlot.getTooltipData(value.values[k].toFixed(settings.labelSettings.precision));\r\n\r\n dataPointsGroup.push({\r\n selected: false,\r\n value: value.values[k],\r\n label: value.values[k],\r\n color: color,\r\n identity: categorySelectionId,\r\n tooltipInfo: tooltipInfo,\r\n dataPoints: dataPoints,\r\n labelFontSize: fontSizeInPx\r\n });\r\n }\r\n }\r\n\r\n return {\r\n dataPoints: dataPointsGroup,\r\n values: dataView.categorical.categories[0].values,\r\n displayName: displayName,\r\n categories: categories,\r\n settings: settings\r\n };\r\n }\r\n\r\n public constructor(options?: DotPlotConstructorOptions) {\r\n if (options) {\r\n if (options.svg) {\r\n this.svg = options.svg;\r\n }\r\n if (options.animator) {\r\n this.animator = options.animator;\r\n }\r\n\r\n this.radius = options.radius || DefaultRadius;\r\n this.strokeWidth = options.strokeWidth || DefaultStrokeWidth;\r\n }\r\n }\r\n\r\n public init(options: VisualInitOptions): void {\r\n var element = options.element;\r\n this.behavior = new DotplotBehavior();\r\n\r\n this.interactivityService = createInteractivityService(options.host);\r\n this.radius = DefaultRadius;\r\n this.strokeWidth = DefaultStrokeWidth;\r\n this.colors = options.style.colorPalette.dataColors;\r\n\r\n this.svg = d3.select(element.get(0)).append('svg').classed(this.dotPlotSelectors.svgPlotSelector.class, true).style('position', 'absolute').style('left', '5px');\r\n this.clearCatcher = appendClearCatcher(this.svg);\r\n\r\n var axisGraphicsContext = this.svg.append('g').classed(this.dotPlotSelectors.axisSelector.class, true);\r\n this.dotPlot = this.svg.append('g').classed(this.dotPlotSelectors.plotSelector.class, true);\r\n this.xAxis = axisGraphicsContext.append(\"g\").classed(this.dotPlotSelectors.xAxisSelector.class, true);\r\n }\r\n\r\n public update(options: VisualUpdateOptions): void {\r\n if (!options.dataViews || !options.dataViews[0]) return;\r\n\r\n this.durationAnimations = getAnimationDuration(this.animator, options.suppressAnimations);\r\n var dataView = this.dataView = options.dataViews[0];\r\n var viewport = options.viewport;\r\n\r\n if (!dataView ||\r\n !dataView.categorical ||\r\n !dataView.categorical.values ||\r\n dataView.categorical.values.length < 1 ||\r\n !dataView.categorical ||\r\n !dataView.categorical.categories ||\r\n !dataView.categorical.categories[0]) {\r\n this.clearData();\r\n return;\r\n }\r\n\r\n var viewportIn: IViewport =\r\n {\r\n height: (viewport.height - this.DefaultMargin.top),\r\n width: (viewport.width - this.DefaultMargin.left)\r\n };\r\n this.viewportIn = viewportIn;\r\n\r\n this.svg.style({\r\n height: PixelConverter.toString(viewport.height),\r\n width: PixelConverter.toString(viewport.width)\r\n });\r\n\r\n var objects = DotPlot.getObjectsFromDataView(dataView);\r\n var categoryAxisSettings = DotPlot.parseCategoryAxisSettings(objects, this.DefaultDotPlotSettings);\r\n\r\n var xAxisProperties = this.calculateAxes(viewportIn, categoryAxisSettings, this.textProperties, objects, false);\r\n var data = DotPlot.converter(dataView, objects, <D3.Scale.OrdinalScale>xAxisProperties.scale, this.DefaultMargin, this.DefaultDotPlotSettings, this.colors, viewport, this.radius);\r\n\r\n this.dotPlotDataView = data;\r\n var dataPoints = data.dataPoints;\r\n\r\n if (this.interactivityService)\r\n this.interactivityService.applySelectionStateToData(dataPoints);\r\n\r\n this.renderAxis(viewportIn.height - MaxXAxisHeight, viewportIn, xAxisProperties, categoryAxisSettings, data, this.durationAnimations);\r\n this.drawDotPlot(dataPoints, data.settings);\r\n\r\n var dataLabelsSettings = data.settings.labelSettings;\r\n if (dataLabelsSettings.show) {\r\n var layout = this.getEnhanchedDotplotLayout(dataLabelsSettings, viewportIn);\r\n var labels: D3.UpdateSelection = dataLabelUtils.drawDefaultLabelsForDataPointChart(dataPoints, this.svg, layout, viewportIn, !options.suppressAnimations, this.durationAnimations);\r\n if (labels)\r\n labels.attr('transform', (d) => {\r\n if(data.settings.labelOrientation === DotPlotLabelsOrientation.Orientation.Vertical) \r\n return SVGUtil.translateAndRotate(d.size.height/2, 0 - d.size.width/2, d.anchorPoint.x, d.anchorPoint.y, -90);\r\n else\r\n return SVGUtil.translate(0, 0);\r\n });\r\n }\r\n else {\r\n dataLabelUtils.cleanDataLabels(this.svg);\r\n }\r\n }\r\n\r\n public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstanceEnumeration {\r\n var enumeration = new ObjectEnumerationBuilder();\r\n\r\n switch (options.objectName) {\r\n case 'dataPoint':\r\n this.enumerateDataPoints(enumeration, this.dataView);\r\n break;\r\n case 'labels':\r\n this.enumerateDataLabels(enumeration, this.dataView);\r\n break;\r\n case 'categories':\r\n this.enumerateCategories(enumeration, this.dataView);\r\n break;\r\n case 'categoryAxis':\r\n this.enumerateCategoryAxisValues(enumeration, this.dataView);\r\n break;\r\n }\r\n\r\n return enumeration.complete();\r\n }\r\n\r\n private enumerateCategoryAxisValues(enumeration: ObjectEnumerationBuilder, dataView: DataView): void {\r\n var objects = dataView && dataView.metadata ? dataView.metadata.objects : undefined;\r\n enumeration.pushInstance({\r\n objectName: \"categoryAxis\",\r\n displayName: \"Category Axis\",\r\n selector: null,\r\n properties: {\r\n show: DataViewObjects.getValue<boolean>(objects, DotPlotProperties.categoryAxis.show, this.DefaultDotPlotSettings.categoryAxisSettings.show),\r\n showAxisTitle: DataViewObjects.getValue<boolean>(objects, DotPlotProperties.categoryAxis.showAxisTitle, this.DefaultDotPlotSettings.categoryAxisSettings.showAxisTitle),\r\n labelColor: objects && objects['categoryAxis'] && objects['categoryAxis']['labelColor'] ?\r\n objects['categoryAxis']['labelColor'] :\r\n this.DefaultDotPlotSettings.categoryAxisSettings.labelColor\r\n }\r\n });\r\n }\r\n\r\n private static getObjectsFromDataView(dataView: DataView): DataViewObjects {\r\n if (!dataView ||\r\n !dataView.metadata ||\r\n !dataView.metadata.columns ||\r\n !dataView.metadata.objects) {\r\n return null;\r\n }\r\n\r\n return dataView.metadata.objects;\r\n }\r\n\r\n private static parseSettings(objects: DataViewObjects, defaultDotPlotSettings: DotPlotSettings): VisualDataLabelsSettings {\r\n var precision = this.getPrecision(objects, defaultDotPlotSettings);\r\n\r\n return {\r\n show: DataViewObjects.getValue(objects, DotPlotProperties.labels.show, defaultDotPlotSettings.labelSettings.show),\r\n precision: precision,\r\n fontSize: DataViewObjects.getValue(objects, DotPlotProperties.labels.fontSize, defaultDotPlotSettings.labelSettings.fontSize),\r\n displayUnits: DataViewObjects.getValue<number>(objects, DotPlotProperties.labels.labelDisplayUnits, defaultDotPlotSettings.labelSettings.displayUnits),\r\n labelColor: DataViewObjects.getFillColor(objects, DotPlotProperties.labels.labelColor, defaultDotPlotSettings.labelSettings.labelColor),\r\n };\r\n }\r\n\r\n private static parseCategoryAxisSettings(objects: DataViewObjects, defaultDotPlotSettings: DotPlotSettings): DotPlotCategoryAxisSettings {\r\n return {\r\n show: DataViewObjects.getValue(objects, DotPlotProperties.categoryAxis.show, defaultDotPlotSettings.categoryAxisSettings.show),\r\n showAxisTitle: DataViewObjects.getValue(objects, DotPlotProperties.categoryAxis.showAxisTitle, defaultDotPlotSettings.categoryAxisSettings.showAxisTitle),\r\n labelColor: objects && objects['categoryAxis'] && objects['categoryAxis']['labelColor'] ?\r\n objects['categoryAxis']['labelColor'] :\r\n defaultDotPlotSettings.categoryAxisSettings.labelColor\r\n };\r\n }\r\n\r\n private static getCategorySettings(objects: DataViewObjects, defaultDotPlotSettings: DotPlotSettings): DotPlotCategorySettings {\r\n return {\r\n show: DataViewObject.getValue<boolean>(objects, DotPlotProperties.categories.show, defaultDotPlotSettings.categorySettings.show),\r\n fontColor: DataViewObjects.getFillColor(objects, DotPlotProperties.categories.fontColor, defaultDotPlotSettings.categorySettings.fontColor)\r\n };\r\n }\r\n\r\n private static getPrecision(objects: DataViewObjects, defaultDotPlotSettings: DotPlotSettings): number {\r\n var precision: number = DataViewObjects.getValue<number>(objects, DotPlotProperties.labels.labelPrecision, defaultDotPlotSettings.labelSettings.precision);\r\n\r\n if (precision <= MinPrecision)\r\n return MinPrecision;\r\n\r\n if (precision >= MaxPrecision)\r\n return MaxPrecision;\r\n\r\n return precision;\r\n }\r\n\r\n private drawDotPlot(data: DotPlotDataGroup[], setting: DotPlotSettings): void {\r\n var selection: D3.UpdateSelection = this.dotPlot.selectAll(this.dotPlotSelectors.plotGroupSelector.selector).data(data);\r\n var hasSelection = this.interactivityService && this.interactivityService.hasSelection();\r\n\r\n selection\r\n .enter()\r\n .append('g')\r\n .attr(\r\n {\r\n stroke: \"black\",\r\n \"stroke-width\": this.strokeWidth\r\n }).\r\n style(\"fill-opacity\", (item: DotPlotDataGroup) => ColumnUtil.getFillOpacity(item.selected, item.highlight, hasSelection, false)).\r\n classed(this.dotPlotSelectors.plotGroupSelector.class, true);\r\n\r\n var circleSelection = selection.selectAll(this.dotPlotSelectors.circleSeletor.selector).data((d: DotPlotDataGroup) => { return d.dataPoints; });\r\n circleSelection.enter().append('circle')\r\n .classed(this.dotPlotSelectors.circleSeletor.class, true);\r\n\r\n circleSelection.attr(\r\n {\r\n cx: (point: DotPlotDataPoint) => { return point.x; },\r\n cy: (point: DotPlotDataPoint) => { return point.y; },\r\n r: this.radius,\r\n fill: setting.defaultDataPointColor\r\n });\r\n\r\n this.renderTooltip(selection);\r\n circleSelection.exit().remove();\r\n selection.exit().remove();\r\n\r\n var interactivityService = this.interactivityService;\r\n if (interactivityService) {\r\n interactivityService.applySelectionStateToData(data);\r\n\r\n var behaviorOptions: DotplotBehaviorOptions = {\r\n columns: selection,\r\n clearCatcher: this.clearCatcher,\r\n interactivityService: this.interactivityService,\r\n };\r\n interactivityService.bind(data, this.behavior, behaviorOptions);\r\n }\r\n }\r\n\r\n private getEnhanchedDotplotLayout(labelSettings: VisualDataLabelsSettings, viewport: IViewport): ILabelLayout {\r\n var fontSizeInPx = jsCommon.PixelConverter.fromPoint(labelSettings.fontSize);\r\n\r\n var formatter: IValueFormatter = valueFormatter.create({\r\n format: valueFormatter.getFormatString(this.dataView.categorical.categories[0].source, DotPlotProperties.general.formatString),\r\n precision: labelSettings.precision,\r\n value: labelSettings.displayUnits\r\n });\r\n\r\n return {\r\n labelText: function(d) {\r\n return dataLabelUtils.getLabelFormattedText({\r\n label: formatter.format(d.label),\r\n fontSize: labelSettings.fontSize,\r\n maxWidth: viewport.width,\r\n });\r\n },\r\n labelLayout: {\r\n x: (d: DotPlotDataGroup) => d.dataPoints[d.dataPoints.length - 1].x - 5,\r\n y: (d: DotPlotDataGroup) => d.dataPoints[d.dataPoints.length - 1].y - LabelMargin - 5\r\n },\r\n filter: function (d) {\r\n return (d && d.dataPoints && d.dataPoints[d.dataPoints.length - 1]);\r\n },\r\n style: {\r\n 'fill': labelSettings.labelColor,\r\n 'font-size': fontSizeInPx\r\n },\r\n };\r\n }\r\n\r\n private enumerateDataLabels(enumeration: ObjectEnumerationBuilder, dataView: DataView): void {\r\n var objects = dataView && dataView.metadata ? dataView.metadata.objects : undefined;\r\n enumeration.pushInstance({\r\n objectName: \"labels\",\r\n displayName: \"Labels\",\r\n selector: null,\r\n properties: {\r\n show: DataViewObjects.getValue<boolean>(objects, DotPlotProperties.labels.show, this.DefaultDotPlotSettings.labelSettings.show),\r\n fontSize: DataViewObjects.getValue<number>(objects, DotPlotProperties.labels.fontSize, this.DefaultDotPlotSettings.labelSettings.fontSize),\r\n labelPrecision: DataViewObjects.getValue<number>(objects, DotPlotProperties.labels.labelPrecision, this.DefaultDotPlotSettings.labelSettings.precision),\r\n labelDisplayUnits: DataViewObjects.getValue<number>(objects, DotPlotProperties.labels.labelDisplayUnits, this.DefaultDotPlotSettings.labelSettings.displayUnits),\r\n color: DataViewObjects.getFillColor(objects, DotPlotProperties.labels.labelColor, this.DefaultDotPlotSettings.labelSettings.labelColor),\r\n orientation: DataViewObjects.getValue<DotPlotLabelsOrientation.Orientation>(objects, DotPlotProperties.labels.orientation , DotPlotLabelsOrientation.Orientation.Horizontal)\r\n }\r\n });\r\n }\r\n\r\n private enumerateDataPoints(enumeration: ObjectEnumerationBuilder, dataView: DataView): void {\r\n var objects = dataView && dataView.metadata ? dataView.metadata.objects : undefined;\r\n var dataPointColor = DataViewObjects.getFillColor(objects, DotPlotProperties.dataPoint.fill, this.DefaultDotPlotSettings.defaultDataPointColor);\r\n enumeration.pushInstance({\r\n objectName: \"dataPoint\",\r\n displayName: \"Data Points\",\r\n selector: null,\r\n properties: {\r\n fill: { solid: { color: dataPointColor } }\r\n }\r\n });\r\n }\r\n\r\n private enumerateCategories(enumeration: ObjectEnumerationBuilder, dataView: DataView): void {\r\n var objects = dataView && dataView.metadata ? dataView.metadata.objects : undefined;\r\n var categoriesSettings = DotPlot.getCategorySettings(objects, this.DefaultDotPlotSettings);\r\n enumeration.pushInstance({\r\n objectName: \"categories\",\r\n displayName: \"Categories\",\r\n selector: null,\r\n properties: {\r\n show: categoriesSettings.show,\r\n fontSize: categoriesSettings.fontSize,\r\n fontColor: categoriesSettings.fontColor\r\n }\r\n });\r\n }\r\n\r\n private clearData(): void {\r\n this.dotPlot.selectAll(\"*\").remove();\r\n this.xAxis.selectAll(\"*\").remove();\r\n dataLabelUtils.cleanDataLabels(this.svg);\r\n }\r\n\r\n private renderTooltip(selection: D3.UpdateSelection): void {\r\n TooltipManager.addTooltip(selection, (tooltipEvent: TooltipEvent) =>\r\n (<DotPlotDataGroup>tooltipEvent.data).tooltipInfo);\r\n }\r\n\r\n private calculateAxes(\r\n viewportIn: IViewport,\r\n categoryAxisSettings: DotPlotCategoryAxisSettings,\r\n textProperties: TextProperties,\r\n objects: DataViewObjects,\r\n scrollbarVisible: boolean): IAxisProperties {\r\n\r\n var category = this.dataView.categorical.categories && this.dataView.categorical.categories.length > 0\r\n ? this.dataView.categorical.categories[0]\r\n : {\r\n source: undefined,\r\n values: [valueFormatter.format(null)],\r\n identity: undefined,\r\n };\r\n\r\n var visualOptions: CalculateScaleAndDomainOptions = {\r\n viewport: viewportIn,\r\n margin: this.DefaultMargin,\r\n forcedXDomain: this.dataView.categorical.categories[0].values,\r\n forceMerge: false,\r\n showCategoryAxisLabel: false,\r\n showValueAxisLabel: false,\r\n categoryAxisScaleType: this.scaleType,\r\n valueAxisScaleType: null,\r\n valueAxisDisplayUnits: 0,\r\n categoryAxisDisplayUnits: 0,\r\n trimOrdinalDataOnOverflow: false,\r\n };\r\n\r\n var width = viewportIn.width;\r\n var axes = this.calculateAxesProperties(viewportIn, categoryAxisSettings, visualOptions, category.source, objects);\r\n axes.willLabelsFit = AxisHelper.LabelLayoutStrategy.willLabelsFit(\r\n axes,\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 axes.willLabelsWordBreak = (!axes.willLabelsFit && !scrollbarVisible) && AxisHelper.LabelLayoutStrategy.willLabelsWordBreak(\r\n axes, this.DefaultMargin, width, TextMeasurementService.measureSvgTextWidth,\r\n TextMeasurementService.estimateSvgTextHeight, TextMeasurementService.getTailoredTextOrDefault,\r\n textProperties);\r\n\r\n return axes;\r\n }\r\n\r\n private calculateAxesProperties(viewportIn: IViewport, categoryAxisSettings: DotPlotCategoryAxisSettings, options: CalculateScaleAndDomainOptions, metaDataColumn: DataViewMetadataColumn, objects: DataViewObjects): IAxisProperties {\r\n var xAxisProperties = AxisHelper.createAxis({\r\n pixelSpan: viewportIn.width,\r\n dataDomain: options.forcedXDomain,\r\n metaDataColumn: metaDataColumn,\r\n formatString: valueFormatter.getFormatString(metaDataColumn, DotPlotProperties.general.formatString),\r\n outerPadding: 0,\r\n isScalar: false,\r\n isVertical: false,\r\n forcedTickCount: options.forcedTickCount,\r\n useTickIntervalForDisplayUnits: true,\r\n isCategoryAxis: true,\r\n getValueFn: (index, type) => categoryAxisSettings.show ? index : '',\r\n scaleType: options.categoryAxisScaleType,\r\n axisDisplayUnits: options.categoryAxisDisplayUnits,\r\n });\r\n\r\n if (categoryAxisSettings.show)\r\n // Should handle the label, units of the label and the axis style\r\n xAxisProperties.axisLabel = AxisHelper.createAxisLabel(objects, '', ''); //axes.x.axisLabel);\r\n return xAxisProperties;\r\n }\r\n\r\n private renderAxis(height: number, viewportIn: IViewport, xAxisProperties: IAxisProperties, categoryAxisSettings: DotPlotCategoryAxisSettings, data: DotPlotDataView, duration: number): void {\r\n this.xAxis.attr(\r\n {\r\n transform: SVGUtil.translate(0, height)\r\n });\r\n\r\n var xAxis = xAxisProperties.axis;\r\n xAxis.orient('bottom');\r\n\r\n this.xAxis\r\n .transition()\r\n .duration(duration)\r\n .call(xAxis)\r\n .call(DotPlot.setAxisLabelColor, categoryAxisSettings.labelColor);\r\n\r\n var xAxisTicks: D3.Selection = this.xAxis.selectAll('.tick text');\r\n xAxisTicks.data(xAxisProperties.values);\r\n xAxisTicks.call(AxisHelper.LabelLayoutStrategy.clip,\r\n xAxisProperties.xLabelMaxWidth,\r\n TextMeasurementService.svgEllipsis);\r\n\r\n xAxisTicks.append('title').text((d) => d);\r\n\r\n this.xAxis.selectAll('line').style('opacity', data.settings.categoryAxisSettings.show ? 1 : 0);\r\n\r\n this.xAxis.selectAll('.xAxisLabel').remove();\r\n if (data.settings.categoryAxisSettings.showAxisTitle) {\r\n this.xAxis.append(\"text\")\r\n .text(this.dataView.categorical.categories[0].source.displayName)\r\n .style(\"text-anchor\", \"middle\")\r\n .attr('class', 'xAxisLabel')\r\n .style('fill', categoryAxisSettings.labelColor.solid.color)\r\n .attr('transform', 'translate(' + (viewportIn.width / 2) + ',40)');\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 ? fill.solid.color : null);\r\n }\r\n }\r\n\r\n export interface DotplotBehaviorOptions {\r\n columns: D3.Selection;\r\n clearCatcher: D3.Selection;\r\n interactivityService: IInteractivityService;\r\n }\r\n\r\n export class DotplotBehavior implements IInteractiveBehavior {\r\n private columns: D3.Selection;\r\n private clearCatcher: D3.Selection;\r\n private interactivityService: IInteractivityService;\r\n\r\n public bindEvents(options: DotplotBehaviorOptions, selectionHandler: ISelectionHandler): void {\r\n this.columns = options.columns;\r\n this.clearCatcher = options.clearCatcher;\r\n this.interactivityService = options.interactivityService;\r\n\r\n this.columns.on('click', (d: SelectableDataPoint, i: number) => {\r\n selectionHandler.handleSelection(d, d3.event.ctrlKey);\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 var hasHighlights = this.interactivityService.hasSelection();\r\n this.columns.style(\"fill-opacity\", (d: DotPlotDataGroup) => ColumnUtil.getFillOpacity(d.selected, d.highlight, !d.highlight && hasSelection, !d.selected && hasHighlights));\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/CustomVisuals/visuals/dotPlot/visual/dotPlot.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 * This file is based on or incorporates material from the projects listed below (Third Party IP). \r\n * The original copyright notice and the license under which Microsoft received such Third Party IP, \r\n * are set forth below. Such licenses and notices are provided for informational purposes only. \r\n * Microsoft licenses the Third Party IP to you under the licensing terms for the Microsoft product. \r\n * Microsoft reserves all other rights not expressly granted under this agreement, whether by \r\n * implication, estoppel or otherwise.\r\n * \r\n * d3 Force Layout\r\n * Copyright (c) 2010-2015, Michael Bostock\r\n * All rights reserved.\r\n * \r\n * Redistribution and use in source and binary forms, with or without\r\n * modification, are permitted provided that the following conditions are met:\r\n * \r\n * * Redistributions of source code must retain the above copyright notice, this\r\n * list of conditions and the following disclaimer.\r\n * \r\n * * Redistributions in binary form must reproduce the above copyright notice,\r\n * this list of conditions and the following disclaimer in the documentation\r\n * and/or other materials provided with the distribution.\r\n * \r\n * * The name Michael Bostock may not be used to endorse or promote products\r\n * derived from this software without specific prior written permission.\r\n * \r\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\r\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\r\n * DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT,\r\n * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\r\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\r\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\r\n * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\r\n * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,\r\n * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r\n */\r\n\r\n/// <reference path=\"../../../_references.ts\"/>\r\n\r\nmodule powerbi.visuals.samples {\r\n import PixelConverter = jsCommon.PixelConverter;\r\n\r\n export enum LinkColorType {\r\n ByWeight = <any>\"By Weight\",\r\n ByLinkType = <any>\"By Link Type\",\r\n Interactive = <any>\"Interactive\"\r\n }\r\n\r\n export class ForceGraphSettings {\r\n public static get Default() { \r\n return new this();\r\n }\r\n\r\n public static parse(dataView: DataView, capabilities: VisualCapabilities) {\r\n var settings = new this();\r\n if(!dataView || !dataView.metadata || !dataView.metadata.objects) {\r\n return settings;\r\n }\r\n\r\n var properties = this.getProperties(capabilities);\r\n for(var objectKey in capabilities.objects) {\r\n for(var propKey in capabilities.objects[objectKey].properties) {\r\n if(!settings[objectKey] || !_.has(settings[objectKey], propKey)) {\r\n continue;\r\n }\r\n\r\n var type = capabilities.objects[objectKey].properties[propKey].type;\r\n var getValueFn = this.getValueFnByType(type);\r\n settings[objectKey][propKey] = getValueFn(\r\n dataView.metadata.objects,\r\n properties[objectKey][propKey],\r\n settings[objectKey][propKey]);\r\n }\r\n }\r\n\r\n return settings;\r\n }\r\n\r\n public static getProperties(capabilities: VisualCapabilities)\r\n : { [i: string]: { [i: string]: DataViewObjectPropertyIdentifier } } {\r\n var properties = <any>{};\r\n for(var objectKey in capabilities.objects) {\r\n properties[objectKey] = {};\r\n for(var propKey in capabilities.objects[objectKey].properties) {\r\n properties[objectKey][propKey] = <DataViewObjectPropertyIdentifier> {\r\n objectName: objectKey,\r\n propertyName: propKey\r\n };\r\n }\r\n }\r\n\r\n return properties;\r\n }\r\n\r\n public static createEnumTypeFromEnum(type: any): IEnumType {\r\n var even: any = false;\r\n return createEnumType(Object.keys(type)\r\n .filter((key,i) => ((!!(i % 2)) === even && type[key] === key\r\n && !void(even = !even)) || (!!(i % 2)) !== even)\r\n .map(x => <IEnumMember>{ value: x, displayName: x }));\r\n }\r\n\r\n private static getValueFnByType(type: powerbi.data.DataViewObjectPropertyTypeDescriptor) {\r\n switch(_.keys(type)[0]) {\r\n case 'fill': \r\n return DataViewObjects.getFillColor;\r\n default:\r\n return DataViewObjects.getValue;\r\n }\r\n }\r\n\r\n public static enumerateObjectInstances(\r\n settings: any,\r\n options: EnumerateVisualObjectInstancesOptions,\r\n capabilities: VisualCapabilities): VisualObjectInstanceEnumeration {\r\n\r\n var enumeration = new ObjectEnumerationBuilder();\r\n var object = settings && settings[options.objectName];\r\n if(!object) {\r\n return enumeration.complete();\r\n }\r\n\r\n var instance = <VisualObjectInstance>{\r\n objectName: options.objectName,\r\n selector: null,\r\n properties: {}\r\n };\r\n\r\n for(var key in object) {\r\n if(_.has(object,key)) {\r\n instance.properties[key] = object[key];\r\n }\r\n }\r\n\r\n enumeration.pushInstance(instance);\r\n return enumeration.complete();\r\n }\r\n\r\n public labels = {\r\n show: true,\r\n color: dataLabelUtils.defaultLabelColor,\r\n fontSize: dataLabelUtils.DefaultFontSizeInPt\r\n };\r\n public links = {\r\n showArrow: false,\r\n showLabel: false,\r\n colorLink: LinkColorType.Interactive,\r\n thickenLink: true,\r\n displayUnits: 0,\r\n decimalPlaces: <number>null\r\n };\r\n public nodes = {\r\n displayImage: false,\r\n defaultImage: \"Home\",\r\n imageUrl: \"\",\r\n imageExt: \".png\",\r\n nameMaxLength: 10,\r\n highlightReachableLinks: false,\r\n };\r\n public size = {\r\n charge: -15\r\n };\r\n }\r\n\r\n export class ForceGraphColumns<T> {\r\n public static Roles = Object.freeze(\r\n _.mapValues(new ForceGraphColumns<string>(), (x, i) => i));\r\n\r\n public static getMetadataColumns(dataView: DataView): ForceGraphColumns<DataViewMetadataColumn> {\r\n var columns = dataView && dataView.metadata && dataView.metadata.columns;\r\n return columns && _.mapValues(\r\n new ForceGraphColumns<DataViewMetadataColumn>(),\r\n (n, i) => columns.filter(x => x.roles && x.roles[i])[0]);\r\n }\r\n\r\n public static getTableValues(dataView: DataView): ForceGraphColumns<any[]> {\r\n var table = dataView && dataView.table;\r\n var columns = this.getMetadataColumns(dataView);\r\n return columns && table && <any>_.mapValues(\r\n columns, (n: DataViewMetadataColumn, i) => n && table.rows.map(row => row[n.index]));\r\n }\r\n\r\n public static getTableRows(dataView: DataView): ForceGraphColumns<any>[] {\r\n var table = dataView && dataView.table;\r\n var columns = this.getMetadataColumns(dataView);\r\n return columns && table && table.rows.map(row =>\r\n _.mapValues(columns, (n: DataViewMetadataColumn, i) => n && row[n.index]));\r\n }\r\n\r\n public Source: T = null;\r\n public Target: T = null;\r\n public Weight: T = null;\r\n public LinkType: T = null;\r\n public SourceType: T = null;\r\n public TargetType: T = null;\r\n }\r\n\r\n export interface ForceGraphLink {\r\n source: ForceGraphNode;\r\n target: ForceGraphNode;\r\n weight: number;\r\n formattedWeight: string;\r\n type: string;\r\n tooltipInfo: TooltipDataItem[];\r\n }\r\n\r\n export interface ForceGraphNode {\r\n name: string;\r\n image: string;\r\n adj: { [i: string]: number };\r\n\r\n x?: number;\r\n y?: number;\r\n isDrag?: boolean;\r\n isOver?: boolean;\r\n }\r\n\r\n export interface ForceGraphNodes { \r\n [i: string]: ForceGraphNode;\r\n }\r\n\r\n export interface ForceGraphData {\r\n nodes: ForceGraphNodes;\r\n links: ForceGraphLink[];\r\n minFiles: number;\r\n maxFiles: number;\r\n linkedByName: {};\r\n linkTypes: {};\r\n settings: ForceGraphSettings;\r\n }\r\n\r\n export class ForceGraph implements IVisual {\r\n public static VisualClassName = 'forceGraph';\r\n private static Count: number = 0;\r\n\r\n private static DefaultValues = {\r\n defaultLinkColor: \"#bbb\",\r\n defaultLinkHighlightColor: \"#f00\",\r\n defaultLinkThickness: \"1.5px\",\r\n };\r\n private static get Href(): string {\r\n return window.location.href.replace(window.location.hash, \"\");\r\n }\r\n\r\n private data: ForceGraphData;\r\n private get settings(): ForceGraphSettings {\r\n return this.data && this.data.settings;\r\n }\r\n private root: D3.Selection;\r\n private paths: D3.Selection;\r\n private nodes: D3.Selection;\r\n private forceLayout: D3.Layout.ForceLayout;\r\n private dataView: DataView;\r\n private colors: IDataColorPalette;\r\n private uniqieId: string = \"_\" + (ForceGraph.Count++) + \"_\";\r\n\r\n private marginValue: IMargin;\r\n private get margin(): IMargin {\r\n return this.marginValue || { left: 0, right: 0, top: 0, bottom: 0 };\r\n }\r\n private set margin(value: IMargin) {\r\n this.marginValue = $.extend({}, value);\r\n this.viewportInValue = ForceGraph.substractMargin(this.viewport, this.margin);\r\n }\r\n\r\n private viewportValue: IViewport;\r\n private get viewport(): IViewport {\r\n return this.viewportValue || { width: 0, height: 0 };\r\n }\r\n private set viewport(value: IViewport) {\r\n this.viewportValue = $.extend({}, value);\r\n this.viewportInValue = ForceGraph.substractMargin(this.viewport, this.margin);\r\n }\r\n\r\n private viewportInValue: IViewport;\r\n private get viewportIn(): IViewport {\r\n return this.viewportInValue || this.viewport;\r\n }\r\n\r\n private static substractMargin(viewport: IViewport, margin: IMargin): IViewport {\r\n return {\r\n width: Math.max(viewport.width - (margin.left + margin.right), 0),\r\n height: Math.max(viewport.height - (margin.top + margin.bottom), 0)\r\n };\r\n }\r\n\r\n private scale1to10(d) {\r\n var scale = d3.scale.linear().domain([this.data.minFiles, this.data.maxFiles]).rangeRound([1, 10]).clamp(true);\r\n return scale(d);\r\n }\r\n\r\n private getLinkColor(d: ForceGraphLink): string {\r\n switch (this.settings.links.colorLink) {\r\n case LinkColorType.ByWeight:\r\n return this.colors.getColorByIndex(this.scale1to10(d.weight)).value;\r\n case LinkColorType.ByLinkType:\r\n return d.type && this.data.linkTypes[d.type] ? this.data.linkTypes[d.type].color : ForceGraph.DefaultValues.defaultLinkColor;\r\n };\r\n return ForceGraph.DefaultValues.defaultLinkColor;\r\n }\r\n\r\n public static capabilities: VisualCapabilities = {\r\n dataRoles: [\r\n {\r\n name: ForceGraphColumns.Roles.Source,\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: \"Source\",\r\n },\r\n {\r\n name: ForceGraphColumns.Roles.Target,\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: \"Target\",\r\n },\r\n {\r\n name: ForceGraphColumns.Roles.Weight,\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: \"Weight\",\r\n },\r\n {\r\n name: ForceGraphColumns.Roles.LinkType,\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: \"Link Type\",\r\n description: \"Links can be colored by link types\",\r\n },\r\n {\r\n name: ForceGraphColumns.Roles.SourceType,\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: \"Source Type\",\r\n description: \"Source type represents the image name for source entities\",\r\n },\r\n {\r\n name: ForceGraphColumns.Roles.TargetType,\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: \"Target Type\",\r\n description: \"Target type represents the image name for target entities\",\r\n },\r\n ],\r\n objects: {\r\n general: {\r\n properties: {\r\n formatString: {\r\n type: { formatting: { formatString: true } },\r\n }\r\n }\r\n },\r\n labels: {\r\n displayName: 'Data labels',\r\n properties: {\r\n show: {\r\n displayName: 'Show',\r\n type: { bool: true }\r\n },\r\n color: {\r\n displayName: 'Fill',\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n fontSize: {\r\n displayName: 'Text Size',\r\n type: { formatting: { fontSize: true } }\r\n }\r\n }\r\n },\r\n links: {\r\n displayName: 'Links',\r\n properties: {\r\n showArrow: {\r\n type: { bool: true },\r\n displayName: 'Arrow'\r\n },\r\n showLabel: {\r\n type: { bool: true },\r\n displayName: 'Label',\r\n description: 'Displays weight on links',\r\n },\r\n colorLink: {\r\n type: { enumeration: ForceGraphSettings.createEnumTypeFromEnum(LinkColorType) },\r\n displayName: 'Color',\r\n },\r\n thickenLink: {\r\n type: { bool: true },\r\n displayName: 'Thickness',\r\n description: 'Thickenss of links represents weight',\r\n },\r\n displayUnits: {\r\n displayName: 'Display Units',\r\n type: { formatting: { labelDisplayUnits: true } },\r\n },\r\n decimalPlaces: {\r\n displayName: 'Decimal Places',\r\n placeHolderText: 'Auto',\r\n type: { numeric: true },\r\n },\r\n }\r\n },\r\n nodes: {\r\n displayName: 'Nodes',\r\n properties: {\r\n displayImage: {\r\n type: { bool: true },\r\n displayName: 'Image',\r\n description: 'Images are loaded from image url + source or target type + image extension',\r\n },\r\n defaultImage: {\r\n type: { text: true },\r\n displayName: 'Default image'\r\n },\r\n imageUrl: {\r\n type: { text: true },\r\n displayName: 'Image url'\r\n },\r\n imageExt: {\r\n type: { text: true },\r\n displayName: 'Image extension'\r\n },\r\n nameMaxLength: {\r\n type: { numeric: true },\r\n displayName: 'Max name length',\r\n description: 'Max length of the name of entities displayed',\r\n },\r\n highlightReachableLinks: {\r\n type: { bool: true },\r\n displayName: 'Highlight all reachable links',\r\n description: \"In interactive mode, whether a node's all reachable links will be highlighted\",\r\n },\r\n }\r\n },\r\n size: {\r\n displayName: 'Size',\r\n properties: {\r\n charge: {\r\n type: { numeric: true },\r\n displayName: 'Charge',\r\n description: 'The larger the negative charge the more apart the entities, must be negative but greater than -100',\r\n },\r\n }\r\n },\r\n },\r\n dataViewMappings: [{\r\n conditions: [\r\n { \r\n 'Source': { max: 1 },\r\n 'Target': { max: 1 },\r\n 'Weight': { max: 1 },\r\n 'LinkType': { max: 1 },\r\n 'SourceType': { max: 1 },\r\n 'TargetType': { max: 1 } \r\n },\r\n ],\r\n categorical: {\r\n categories: {\r\n for: { in: 'Source' },\r\n dataReductionAlgorithm: { top: {} }\r\n },\r\n values: {\r\n select: [\r\n { bind: { to: 'Target' } },\r\n { bind: { to: 'Weight' } },\r\n { bind: { to: 'LinkType' } },\r\n { bind: { to: 'SourceType' } },\r\n { bind: { to: 'TargetType' } },\r\n ],\r\n },\r\n rowCount: { preferred: { min: 1 } }\r\n },\r\n }],\r\n suppressDefaultTitle: true,\r\n };\r\n\r\n public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstanceEnumeration {\r\n return ForceGraphSettings.enumerateObjectInstances(this.settings, options, ForceGraph.capabilities);\r\n }\r\n\r\n public static converter(dataView: DataView, colors: IDataColorPalette): ForceGraphData {\r\n var categorical: DataViewCategorical = dataView.categorical;\r\n var settings = ForceGraph.parseSettings(dataView);\r\n var nodes: ForceGraphNodes = {};\r\n var minFiles = Number.MAX_VALUE;\r\n var maxFiles = 0;\r\n var linkedByName = {};\r\n var links: ForceGraphLink[] = [];\r\n var linkDataPoints = {};\r\n var linkTypeCount = 0;\r\n var tooltipInfo: TooltipDataItem[] = [];\r\n\r\n var metadata = ForceGraphColumns.getMetadataColumns(dataView);\r\n if(!metadata || !metadata.Source || !metadata.Target) {\r\n return null;\r\n }\r\n\r\n var tableRows = ForceGraphColumns.getTableRows(dataView);\r\n\r\n var formatStringProp = ForceGraphSettings.getProperties(ForceGraph.capabilities)['general']['formatString'];\r\n var categorySourceFormatString = valueFormatter.getFormatString(categorical.categories[0].source, formatStringProp);\r\n var categoryTargetFormatString = valueFormatter.getFormatString(categorical.categories[1].source, formatStringProp);\r\n var weightFormatter: IValueFormatter = metadata.Weight && valueFormatter.create({\r\n format: valueFormatter.getFormatString(metadata.Weight, formatStringProp, true),\r\n precision: settings.links.decimalPlaces,\r\n value: settings.links.displayUnits || _.max(tableRows, x => x.Weight).Weight\r\n });\r\n\r\n tableRows.forEach(x => {\r\n linkedByName[x.Source + \",\" + x.Target] = 1;\r\n var source = nodes[x.Source] || (nodes[x.Source] = { name: x.Source, image: x.SourceType || \"\", adj: {} });\r\n var target = nodes[x.Target] || (nodes[x.Target] = { name: x.Target, image: x.TargetType || \"\", adj: {} });\r\n source.adj[target.name] = 1;\r\n target.adj[source.name] = 1;\r\n\r\n tooltipInfo = [{\r\n displayName: dataView.metadata.columns[0].displayName,\r\n value: valueFormatter.format(source.name, categorySourceFormatString)\r\n }, {\r\n displayName: dataView.metadata.columns[1].displayName,\r\n value: valueFormatter.format(target.name, categoryTargetFormatString)\r\n }];\r\n\r\n if (metadata.Weight) {\r\n tooltipInfo.push({\r\n displayName: dataView.metadata.columns[2].displayName,\r\n value: x.Weight\r\n });\r\n }\r\n\r\n var link = <ForceGraphLink>{\r\n source: source,\r\n target: target,\r\n weight: Math.max(x.Weight, 0),\r\n formattedWeight: x.Weight && weightFormatter.format(x.Weight),\r\n type: x.LinkType || \"\",\r\n tooltipInfo: tooltipInfo,\r\n };\r\n\r\n if (metadata.LinkType) {\r\n if (!linkDataPoints[x.LinkType]) {\r\n linkDataPoints[x.LinkType] = {\r\n label: x.LinkType,\r\n color: colors.getColorByIndex(linkTypeCount++).value,\r\n };\r\n };\r\n };\r\n if (link.weight < minFiles) { minFiles = link.weight; };\r\n if (link.weight > maxFiles) { maxFiles = link.weight; };\r\n links.push(link);\r\n });\r\n\r\n return {\r\n nodes: nodes,\r\n links: links,\r\n minFiles: minFiles,\r\n maxFiles: maxFiles,\r\n linkedByName: linkedByName,\r\n linkTypes: linkDataPoints,\r\n settings\r\n };\r\n }\r\n\r\n private static parseSettings(dataView: DataView): ForceGraphSettings {\r\n var settings = ForceGraphSettings.parse(dataView, ForceGraph.capabilities);\r\n settings.size.charge = Math.min(Math.max(settings.size.charge, -100), -0.1);\r\n settings.links.decimalPlaces = settings.links.decimalPlaces && Math.min(Math.max(settings.links.decimalPlaces, 0), 5);\r\n return settings;\r\n }\r\n\r\n public init(options: VisualInitOptions): void {\r\n this.root = d3.select(options.element.get(0));\r\n this.forceLayout = d3.layout.force();\r\n this.forceLayout.drag()\r\n .on(\"dragstart\", <any>((d: ForceGraphNode) => { d.isDrag = true; this.fadeNode(d); }))\r\n .on(\"dragend\", <any>((d: ForceGraphNode) => { d.isDrag = false; this.fadeNode(d); }))\r\n .on(\"drag\", <any>((d: ForceGraphNode) => this.fadeNode(d)));\r\n this.colors = options.style.colorPalette.dataColors;\r\n }\r\n\r\n public update(options: VisualUpdateOptions) {\r\n if (!options.dataViews || (options.dataViews.length < 1)) return;\r\n this.data = ForceGraph.converter(this.dataView = options.dataViews[0], this.colors);\r\n if (!this.data) return;\r\n\r\n this.viewport = options.viewport;\r\n var k = Math.sqrt(Object.keys(this.data.nodes).length / (this.viewport.width * this.viewport.height));\r\n\r\n this.root.selectAll(\"svg\").remove();\r\n\r\n var svg = this.root\r\n .append(\"svg\")\r\n .attr(\"width\", this.viewport.width)\r\n .attr(\"height\", this.viewport.height)\r\n .classed(ForceGraph.VisualClassName, true);\r\n\r\n this.forceLayout\r\n .gravity(100 * k)\r\n .links(this.data.links)\r\n .size([this.viewport.width, this.viewport.height])\r\n .linkDistance(100)\r\n .charge(this.settings.size.charge / k)\r\n .on(\"tick\", this.tick());\r\n this.updateNodes();\r\n this.forceLayout.start();\r\n\r\n // uncomment if we don't need the marker-end workaround\r\n //if (this.settings.showArrow) {\r\n // build the arrow.\r\n //function marker(d, i) {\r\n // var val = \"mid_\" + i;\r\n // svg.append(\"defs\").selectAll(\"marker\")\r\n // .data([val]) // Different link/path types can be defined here\r\n // .enter().append(\"marker\") // This section adds in the arrows\r\n // .attr(\"id\", String)\r\n // .attr(\"viewBox\", \"0 -5 10 10\")\r\n // .attr(\"refX\", 10)\r\n // .attr(\"refY\", 0)\r\n // .attr(\"markerWidth\", 6)\r\n // .attr(\"markerHeight\", 6)\r\n // .attr(\"orient\", \"auto\")\r\n // .attr(\"markerUnits\", \"userSpaceOnUse\")\r\n // .append(\"path\")\r\n // .attr(\"d\", \"M0,-5L10,0L0,5\")\r\n // //below works if no marker-end workaround needed\r\n // .style(\"fill\", d => this.getLinkColor(d))\r\n // ;\r\n // return \"url(#\" + val + \")\";\r\n //}\r\n //}\r\n this.paths = svg.selectAll(\".link\")\r\n .data(this.forceLayout.links())\r\n .enter().append(\"path\")\r\n .attr(\"class\", \"link\")\r\n .attr(\"id\", (d, i) => \"linkid_\" + this.uniqieId + i)\r\n // uncomment if we don't need the marker-end workaround\r\n //.attr(\"marker-end\", function (d, i) { return marker(d, i); })\r\n .attr(\"stroke-width\", (d: ForceGraphLink) => this.settings.links.thickenLink ? this.scale1to10(d.weight) : ForceGraph.DefaultValues.defaultLinkThickness)\r\n .style(\"stroke\", (d: ForceGraphLink) => this.getLinkColor(d))\r\n // no need for \"fill\" if we don't need the marker-end workaround\r\n .style(\"fill\", (d: ForceGraphLink) => { if (this.settings.links.showArrow) return this.getLinkColor(d); })\r\n .on(\"mouseover\", this.fadePath(.3, ForceGraph.DefaultValues.defaultLinkHighlightColor))\r\n .on(\"mouseout\", this.fadePath(1, ForceGraph.DefaultValues.defaultLinkColor));\r\n\r\n TooltipManager.addTooltip(this.paths, (tooltipEvent: TooltipEvent) => {\r\n return tooltipEvent.data.tooltipInfo;\r\n });\r\n\r\n if (this.settings.links.showLabel) {\r\n var linklabelholderUpdate = svg.selectAll(\".linklabelholder\").data(this.forceLayout.links());\r\n linklabelholderUpdate.enter()\r\n .append(\"g\")\r\n .attr(\"class\", \"linklabelholder\")\r\n .append(\"text\")\r\n .attr(\"class\", \"linklabel\")\r\n .attr(\"y\", \"-12\")\r\n .attr(\"text-anchor\", \"middle\")\r\n .style(\"fill\", \"#000\")\r\n .append(\"textPath\")\r\n .attr(\"xlink:href\", (d, i) => ForceGraph.Href + \"#linkid_\" + this.uniqieId + i)\r\n .attr(\"startOffset\", \"25%\") //use \"50%\" if we don't need the marker-end workaround\r\n .text((d: ForceGraphLink) => this.settings.links.colorLink === LinkColorType.ByLinkType ? d.type : d.formattedWeight);\r\n\r\n linklabelholderUpdate.exit().remove();\r\n }\r\n\r\n // define the nodes\r\n this.nodes = svg.selectAll(\".node\")\r\n .data(this.forceLayout.nodes())\r\n .enter().append(\"g\")\r\n .attr(\"class\", \"node\")\r\n .call(this.forceLayout.drag)\r\n .on(\"mouseover\", (d: ForceGraphNode) => { d.isOver = true; this.fadeNode(d); })\r\n .on(\"mouseout\", (d: ForceGraphNode) => { d.isOver = false; this.fadeNode(d); })\r\n .on(\"mousedown\", () => d3.event.stopPropagation())\r\n .attr(\"drag-resize-disabled\", true);\r\n\r\n // add the nodes\r\n if (this.settings.nodes.displayImage) {\r\n this.nodes.append(\"image\")\r\n .attr(\"xlink:href\", (d: ForceGraphNode) =>\r\n d.image && d.image !== '' ?\r\n this.settings.nodes.imageUrl + d.image + this.settings.nodes.imageExt :\r\n (\r\n this.settings.nodes.defaultImage && this.settings.nodes.defaultImage !== '' ?\r\n this.settings.nodes.imageUrl + this.settings.nodes.defaultImage + this.settings.nodes.imageExt :\r\n 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABsAAAAbCAMAAAHNDTTxAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAACuUExURQAAAMbGxvLy8sfHx/Hx8fLy8vHx8cnJycrKyvHx8fHx8cvLy/Ly8szMzM3NzfHx8dDQ0PHx8fLy8vHx8e/v79LS0tPT0/Ly8tTU1NXV1dbW1vHx8fHx8fDw8NjY2PT09PLy8vLy8vHx8fLy8vHx8fHx8enp6fDw8PLy8uPj4+Tk5OXl5fHx8b+/v/Pz8+bm5vHx8ejo6PLy8vHx8fLy8sTExPLy8vLy8sXFxfHx8YCtMbUAAAA6dFJOUwD/k/+b7/f///+r/////0z/w1RcEP//ZP///4fj/v8Yj3yXn/unDEhQ////YP9Y/8//aIMU/9+L/+fzC4s1AAAACXBIWXMAABcRAAAXEQHKJvM/AAABQElEQVQoU5WS61LCMBCFFymlwSPKVdACIgWkuNyL+P4v5ibZ0jKjP/xm0uw5ySa7mRItAhnMoIC5TwQZdCZiZjcoC8WU6EVsmZgzoqGdxafgvJAvjUXCb2M+0cXNsd/GDarZqSf7av3M2P1E3xhfLkPUvLD5joEYwVVJQXM6+9McWUwLf4nDTCQZAy96UoDjNI/jhl3xPLbQamu8xD7iaIsPKw7GJ7KZEnWLY3Gi8EFj5nqibXnwD5VEGjJXk5sbpLppfvvo1RazQVrhSopPK4TODrtnjS3dY4ic8KurruWQYF+UG60BacexTMyT2jlNg41dOmKvTpkUd/Jevy7ZxQ61ULRUpoododx8GeDPvIrktbFVdUsK6f8Na5VlVpjZJtowTXVy7kfXF5wCaV1tqXAFuIdWJu+JviaQzNzfQvQDGKRXXEmy83cAAAAASUVORK5CYII='\r\n )\r\n )\r\n .attr(\"x\", \"-12px\")\r\n .attr(\"y\", \"-12px\")\r\n .attr(\"width\", \"24px\")\r\n .attr(\"height\", \"24px\");\r\n } else {\r\n this.nodes.append(\"circle\")\r\n .attr(\"r\", (d: ForceGraphLink) => d.weight < 5 ? 5 : d.weight);\r\n }\r\n\r\n // add the text\r\n if (this.settings.labels.show) {\r\n this.nodes.append(\"text\")\r\n .attr({\r\n x: 12,\r\n dy: \".35em\"\r\n })\r\n .style({\r\n fill: this.settings.labels.color,\r\n 'font-size': PixelConverter.fromPoint(this.settings.labels.fontSize)\r\n })\r\n .text((d: ForceGraphNode) => d.name ? (d.name.length > this.settings.nodes.nameMaxLength\r\n ? d.name.substr(0, this.settings.nodes.nameMaxLength)\r\n : d.name) : '');\r\n }\r\n }\r\n\r\n private updateNodes() {\r\n var oldNodes = this.forceLayout.nodes();\r\n this.forceLayout.nodes(d3.values(this.data.nodes));\r\n this.forceLayout.nodes().forEach((node, i) => {\r\n if (!oldNodes[i]) {\r\n return;\r\n }\r\n node.x = oldNodes[i].x;\r\n node.y = oldNodes[i].y;\r\n node.px = oldNodes[i].px;\r\n node.py = oldNodes[i].py;\r\n node.weight = oldNodes[i].weight;\r\n });\r\n }\r\n\r\n private tick() {\r\n var viewport = this.viewportIn;\r\n // limitX and limitY is necessary when you minimize the graph and then resize it to normal.\r\n //\"width/height * 20\" seems enough to move nodes freely by force layout.\r\n var maxWidth = viewport.width * 20;\r\n var maxHeight = viewport.height * 20;\r\n var limitX = x => Math.max((viewport.width - maxWidth) / 2, Math.min((viewport.width + maxWidth) / 2, x));\r\n var limitY = y => Math.max((viewport.height - maxHeight) / 2, Math.min((viewport.height + maxHeight) / 2, y));\r\n //use this if we don't need the marker-end workaround\r\n //path.attr(\"d\", function (d) {\r\n // var dx = d.target.x - d.source.x,\r\n // dy = d.target.y - d.source.y,\r\n // dr = Math.sqrt(dx * dx + dy * dy);\r\n // // x and y distances from center to outside edge of target node\r\n // var offsetX = (dx * d.target.radius) / dr;\r\n // var offsetY = (dy * d.target.radius) / dr;\r\n // return \"M\" +\r\n // d.source.x + \",\" +\r\n // d.source.y + \"A\" +\r\n // dr + \",\" + dr + \" 0 0,1 \" +\r\n // (d.target.x - offsetX) + \",\" +\r\n // (d.target.y - offsetY);\r\n //});\r\n\r\n var getPath = this.settings.links.showArrow ?\r\n //this is for marker-end workaround, build the marker with the path\r\n (d: ForceGraphLink) => {\r\n d.source.x = limitX(d.source.x);\r\n d.source.y = limitY(d.source.y);\r\n d.target.x = limitX(d.target.x);\r\n d.target.y = limitY(d.target.y);\r\n var dx = d.target.x - d.source.x,\r\n dy = d.target.y - d.source.y,\r\n dr = Math.sqrt(dx * dx + dy * dy),\r\n theta = Math.atan2(dy, dx) + Math.PI / 7.85,\r\n d90 = Math.PI / 2,\r\n dtxs = d.target.x - 6 * Math.cos(theta),\r\n dtys = d.target.y - 6 * Math.sin(theta);\r\n return \"M\" +\r\n d.source.x + \",\" +\r\n d.source.y + \"A\" +\r\n dr + \",\" + dr + \" 0 0 1,\" +\r\n d.target.x + \",\" +\r\n d.target.y +\r\n \"A\" + dr + \",\" + dr + \" 0 0 0,\" + d.source.x + \",\" + d.source.y + \"M\" + dtxs + \",\" + dtys + \"l\" + (3.5 * Math.cos(d90 - theta) - 10 * Math.cos(theta)) + \",\" + (-3.5 * Math.sin(d90 - theta) - 10 * Math.sin(theta)) + \"L\" + (dtxs - 3.5 * Math.cos(d90 - theta) - 10 * Math.cos(theta)) + \",\" + (dtys + 3.5 * Math.sin(d90 - theta) - 10 * Math.sin(theta)) + \"z\";\r\n } :\r\n (d: ForceGraphLink) => {\r\n d.source.x = limitX(d.source.x);\r\n d.source.y = limitY(d.source.y);\r\n d.target.x = limitX(d.target.x);\r\n d.target.y = limitY(d.target.y);\r\n var dx = d.target.x - d.source.x,\r\n dy = d.target.y - d.source.y,\r\n dr = Math.sqrt(dx * dx + dy * dy);\r\n return \"M\" +\r\n d.source.x + \",\" +\r\n d.source.y + \"A\" +\r\n dr + \",\" + dr + \" 0 0,1 \" +\r\n d.target.x + \",\" +\r\n d.target.y;\r\n };\r\n\r\n return () => {\r\n this.paths.each(function() { this.parentNode.insertBefore(this, this); });\r\n this.paths.attr(\"d\", getPath);\r\n this.nodes.attr(\"transform\", d => \"translate(\" + limitX(d.x) + \",\" + limitY(d.y) + \")\");\r\n };\r\n }\r\n\r\n private fadePath(opacity: number, highlight: string) {\r\n if (this.settings.links.colorLink !== LinkColorType.Interactive) return;\r\n return (d: ForceGraphLink) => {\r\n this.paths.style(\"stroke-opacity\", (o: ForceGraphLink) => o.source === d.source && o.target === d.target ? 1 : opacity);\r\n this.paths.style(\"stroke\", (o: ForceGraphLink) => o.source === d.source && o.target === d.target ? highlight : ForceGraph.DefaultValues.defaultLinkColor);\r\n };\r\n }\r\n\r\n private isReachable(a: ForceGraphNode, b: ForceGraphNode): boolean {\r\n if (a.name === b.name) return true;\r\n if (this.data.linkedByName[a.name + \",\" + b.name]) return true;\r\n var visited = {};\r\n for (var name in this.data.nodes) {\r\n visited[name] = false;\r\n };\r\n visited[a.name] = true;\r\n\r\n var stack = [];\r\n stack.push(a.name);\r\n while (stack.length > 0) {\r\n var cur = stack.pop();\r\n var node = this.data.nodes[cur];\r\n for (var nb in node.adj) {\r\n if (nb === b.name) return true;\r\n\r\n if (!visited[nb]) {\r\n visited[nb] = true;\r\n stack.push(nb);\r\n }\r\n }\r\n };\r\n return false;\r\n }\r\n\r\n private fadeNode(node: ForceGraphNode) {\r\n if (this.settings.links.colorLink !== LinkColorType.Interactive) {\r\n return;\r\n }\r\n\r\n var isConnected = (a: ForceGraphNode, b: ForceGraphNode) => this.data.linkedByName[a.name + \",\" + b.name]\r\n || this.data.linkedByName[b.name + \",\" + a.name] || a.name === b.name;\r\n\r\n var isHighlight = node.isOver || node.isDrag;\r\n var opacity: number = isHighlight ? 0.3 : 1;\r\n var highlight: string = isHighlight\r\n ? ForceGraph.DefaultValues.defaultLinkHighlightColor\r\n : ForceGraph.DefaultValues.defaultLinkColor;\r\n\r\n var that = this;\r\n this.nodes.style(\"stroke-opacity\", function(o: ForceGraphNode) {\r\n var thisOpacity = (that.settings.nodes.highlightReachableLinks ? that.isReachable(node, o) : isConnected(node, o)) ? 1 : opacity;\r\n this.setAttribute('fill-opacity', thisOpacity);\r\n return thisOpacity;\r\n });\r\n\r\n this.paths.style(\"stroke-opacity\", (o: ForceGraphLink) =>\r\n (this.settings.nodes.highlightReachableLinks ? this.isReachable(node, o.source) :\r\n (o.source === node || o.target === node)) ? 1 : opacity);\r\n this.paths.style(\"stroke\", (o: ForceGraphLink) =>\r\n (this.settings.nodes.highlightReachableLinks ? this.isReachable(node, o.source) :\r\n (o.source === node || o.target === node)) ? highlight : ForceGraph.DefaultValues.defaultLinkColor);\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/CustomVisuals/visuals/forceGraph/visual/forceGraph.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 import PixelConverter = jsCommon.PixelConverter;\r\n import IStringResourceProvider = jsCommon.IStringResourceProvider;\r\n\r\n var PercentFormat: string = \"0.00 %;-0.00 %;0.00 %\";\r\n var MillisecondsInADay: number = 86400000;\r\n var MillisecondsInWeek: number = 604800000;\r\n var MillisecondsInAMonth: number = 2629746000;\r\n var MillisecondsInAYear: number = 31556952000;\r\n var ChartLineHeight: number = 40;\r\n var PaddingTasks: number = 5;\r\n\r\n export enum GanttDateType {\r\n Day = <any>\"Day\",\r\n Week = <any>\"Week\",\r\n Month = <any>\"Month\",\r\n Year = <any>\"Year\"\r\n }\r\n\r\n function createEnumTypeFromEnum(type: any): IEnumType {\r\n var even: any = false;\r\n return createEnumType(Object.keys(type)\r\n .filter((key,i) => ((!!(i % 2)) === even && type[key] === key && !void(even === !even)) || (!!(i % 2)) !== even)\r\n .map(x => <IEnumMember>{ value: x, displayName: x }));\r\n }\r\n\r\n export interface Task extends SelectableDataPoint {\r\n id: number;\r\n name: string;\r\n start: Date;\r\n duration: number;\r\n completion: number;\r\n resource: string;\r\n end: Date;\r\n taskType: string;\r\n description: string;\r\n color: string;\r\n tooltipInfo: TooltipDataItem[];\r\n }\r\n\r\n export interface GroupedTask {\r\n id: number;\r\n name: string;\r\n tasks: Task[];\r\n }\r\n\r\n export interface GanttChartFormatters {\r\n startDateFormatter: IValueFormatter;\r\n completionFormatter: IValueFormatter;\r\n durationFormatter: IValueFormatter;\r\n }\r\n\r\n export interface GanttViewModel {\r\n dataView: DataView;\r\n settings: GanttSettings<any>;\r\n tasks: Task[];\r\n series: GanttSeries[];\r\n legendData: LegendData;\r\n taskTypes: TaskTypes;\r\n }\r\n\r\n export interface GanttDataPoint extends SelectableDataPoint {\r\n color: string;\r\n value: any;\r\n }\r\n\r\n export interface GanttSeries extends SelectableDataPoint {\r\n tasks: Task[];\r\n fill: string;\r\n name: string;\r\n }\r\n\r\n export interface TaskTypes { /*TODO: change to more proper name*/\r\n types: string[];\r\n typeName: string;\r\n };\r\n\r\n interface Line {\r\n x1: number;\r\n y1: number;\r\n x2: number;\r\n y2: number;\r\n tooltipInfo: TooltipDataItem[];\r\n }\r\n\r\n module Selectors {\r\n\r\n import ClassAndSelector = jsCommon.CssConstants.ClassAndSelector;\r\n import CreateClassAndSelector = jsCommon.CssConstants.createClassAndSelector;\r\n\r\n export var ClassName: ClassAndSelector = CreateClassAndSelector(\"gantt\");\r\n export var Chart: ClassAndSelector = CreateClassAndSelector(\"chart\");\r\n export var ChartLine: ClassAndSelector = CreateClassAndSelector(\"chart-line\");\r\n export var Body: ClassAndSelector = CreateClassAndSelector(\"gantt-body\");\r\n export var AxisGroup: ClassAndSelector = CreateClassAndSelector(\"axis\");\r\n export var Domain: ClassAndSelector = CreateClassAndSelector(\"domain\");\r\n export var AxisTick: ClassAndSelector = CreateClassAndSelector(\"tick\");\r\n\r\n export var Tasks: ClassAndSelector = CreateClassAndSelector(\"tasks\");\r\n export var TaskGroup: ClassAndSelector = CreateClassAndSelector(\"task-group\");\r\n export var SingleTask: ClassAndSelector = CreateClassAndSelector(\"task\");\r\n export var TaskRect: ClassAndSelector = CreateClassAndSelector(\"task-rect\");\r\n export var TaskProgress: ClassAndSelector = CreateClassAndSelector(\"task-progress\");\r\n export var TaskResource: ClassAndSelector = CreateClassAndSelector(\"task-resource\");\r\n export var SingleMilestone: ClassAndSelector = CreateClassAndSelector(\"milestone\");\r\n\r\n export var TaskLabels: ClassAndSelector = CreateClassAndSelector(\"task-labels\");\r\n export var TaskLines: ClassAndSelector = CreateClassAndSelector(\"task-lines\");\r\n export var SingleTaskLine: ClassAndSelector = CreateClassAndSelector(\"task-line\");\r\n export var Label: ClassAndSelector = CreateClassAndSelector(\"label\");\r\n export var LegendItems: ClassAndSelector = CreateClassAndSelector(\"legendItem\");\r\n export var LegendTitle: ClassAndSelector = CreateClassAndSelector(\"legendTitle\");\r\n }\r\n\r\n export interface GanttSettings<T> {\r\n general: { \r\n groupTasks: T\r\n };\r\n legend: {\r\n show: T,\r\n position: T,\r\n showTitle: T,\r\n titleText: T,\r\n labelColor: T,\r\n fontSize: T,\r\n };\r\n taskLabels: {\r\n show: T,\r\n fill: T,\r\n fontSize: T,\r\n width: T,\r\n };\r\n taskCompletion: {\r\n show: T,\r\n fill: T,\r\n };\r\n taskResource: {\r\n show: T,\r\n fill: T,\r\n fontSize: T,\r\n };\r\n dateType: {\r\n type: T\r\n };\r\n }\r\n\r\n export class Gantt implements IVisual {\r\n private viewport: IViewport;\r\n private colors: IDataColorPalette;\r\n private legend: ILegend;\r\n\r\n private textProperties: TextProperties = {\r\n fontFamily: 'wf_segoe-ui_normal',\r\n fontSize: jsCommon.PixelConverter.toString(9),\r\n };\r\n\r\n public static DefaultSettings: GanttSettings<any> = {\r\n general: { \r\n groupTasks: false\r\n },\r\n legend: {\r\n show: true,\r\n position: legendPosition.right,\r\n showTitle: true,\r\n titleText: \"\",\r\n labelColor: \"#000000\",\r\n fontSize: 8,\r\n },\r\n taskLabels: {\r\n show: true,\r\n fill: \"#000000\",\r\n fontSize: 9,\r\n width: 110,\r\n },\r\n taskCompletion: {\r\n show: true,\r\n fill: \"#000000\",\r\n },\r\n taskResource: {\r\n show: true,\r\n fill: \"#000000\",\r\n fontSize: 9,\r\n },\r\n dateType: {\r\n type: GanttDateType.Week\r\n }\r\n };\r\n\r\n public static DefaultValues = {\r\n AxisTickSize: 6,\r\n MaxTaskOpacity: 1,\r\n MinTaskOpacity: 0.4,\r\n ProgressBarHeight: 4,\r\n ResourceWidth: 100,\r\n TaskColor: \"#00B099\",\r\n TaskLineWidth: 15,\r\n DefaultDateType: <string>(<any>GanttDateType.Week),\r\n DateFormatStrings: {\r\n Day: \"MMM dd\",\r\n Week: \"MMM dd\",\r\n Month: \"MMM yyyy\",\r\n Year: \"yyyy\"\r\n } \r\n };\r\n\r\n public static capabilities: VisualCapabilities = {\r\n dataRoles: [\r\n {\r\n name: \"Legend\",\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: \"Legend\",\r\n }, {\r\n name: \"Task\",\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: \"Task\"\r\n }, {\r\n name: \"StartDate\",\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: \"Start Date\",\r\n }, {\r\n name: \"Duration\",\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: \"Duration\",\r\n requiredTypes: [{ numeric: true }, { integer: true }]\r\n }, {\r\n name: \"Completion\",\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: \"% Completion\",\r\n requiredTypes: [{ numeric: true }, { integer: true }]\r\n }, {\r\n name: \"Resource\",\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: \"Resource\"\r\n }\r\n ],\r\n dataViewMappings: [{\r\n conditions: [\r\n {\r\n \"Legend\": { min: 0, max: 1 },\r\n \"Task\": { min: 1, max: 1 },\r\n \"StartDate\": { min: 0, max: 0 },\r\n \"Duration\": { min: 0, max: 0 },\r\n \"Completion\": { min: 0, max: 0 },\r\n \"Resource\": { min: 0, max: 0 }\r\n }, {\r\n \"Legend\": { min: 0, max: 1 },\r\n \"Task\": { min: 1, max: 1 },\r\n \"StartDate\": { min: 0, max: 1 },\r\n \"Duration\": { min: 0, max: 0 },\r\n \"Completion\": { min: 0, max: 0 },\r\n \"Resource\": { min: 0, max: 0 }\r\n }, {\r\n \"Legend\": { min: 0, max: 1 },\r\n \"Task\": { min: 0, max: 1 },\r\n \"StartDate\": { min: 0, max: 1 },\r\n \"Duration\": { min: 0, max: 1 },\r\n \"Completion\": { min: 0, max: 1 },\r\n \"Resource\": { min: 0, max: 1 },\r\n }\r\n ],\r\n table: {\r\n rows: {\r\n select:\r\n [\r\n { for: { in: \"Legend\" } },\r\n { for: { in: \"Task\" } },\r\n { for: { in: \"StartDate\" } },\r\n { for: { in: \"Duration\" } },\r\n { for: { in: \"Completion\" } },\r\n { for: { in: \"Resource\" } },\r\n ]\r\n },\r\n },\r\n }],\r\n objects: {\r\n general: {\r\n displayName: data.createDisplayNameGetter(\"Visual_General\"),\r\n properties: {\r\n groupTasks: {\r\n displayName: \"Group Tasks\",\r\n type: { bool: true }\r\n }\r\n },\r\n },\r\n legend: {\r\n displayName: \"Legend\",\r\n description: \"Display legend options\",\r\n properties: {\r\n show: {\r\n displayName: \"Show\",\r\n type: { bool: true }\r\n },\r\n position: {\r\n displayName: \"Position\",\r\n description: \"Select the location for the legend\",\r\n type: { enumeration: legendPosition.type }\r\n },\r\n showTitle: {\r\n displayName: \"Title\",\r\n description: \"Display a title for legend symbols\",\r\n type: { bool: true }\r\n },\r\n titleText: {\r\n displayName: \"Legend Name\",\r\n description: \"Title text\",\r\n type: { text: true },\r\n suppressFormatPainterCopy: true\r\n },\r\n labelColor: {\r\n displayName: \"Color\",\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n fontSize: {\r\n displayName: \"Text Size\",\r\n type: { formatting: { fontSize: true } }\r\n }\r\n }\r\n },\r\n //dataPoint: {\r\n // displayName: \"Data colors\",\r\n // properties: {\r\n // fill: {\r\n // displayName: \"Fill\",\r\n // type: { fill: { solid: { color: true } } }\r\n // }\r\n // }\r\n //},\r\n taskLabels: {\r\n displayName: 'Category Labels',\r\n properties: {\r\n show: {\r\n displayName: \"Show\",\r\n type: { bool: true }\r\n },\r\n fill: {\r\n displayName: 'Fill',\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n fontSize: {\r\n displayName: 'Font Size',\r\n type: { formatting: { fontSize: true } }\r\n },\r\n width: {\r\n displayName: 'Width',\r\n type: { numeric: true }\r\n }\r\n }\r\n },\r\n taskCompletion: {\r\n displayName: 'Task Completion',\r\n properties: {\r\n show: {\r\n type: { bool: true }\r\n },\r\n fill: {\r\n displayName: 'Completion Color',\r\n type: { fill: { solid: { color: true } } }\r\n }\r\n }\r\n },\r\n taskResource: {\r\n displayName: 'Data Labels',\r\n properties: {\r\n show: {\r\n displayName: \"Show\",\r\n type: { bool: true }\r\n },\r\n fill: {\r\n displayName: 'Color',\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n fontSize: {\r\n displayName: 'Font Size',\r\n type: { formatting: { fontSize: true } }\r\n }\r\n }\r\n },\r\n dateType: {\r\n displayName: 'Gantt Date Type',\r\n properties: {\r\n type: {\r\n displayName: \"Type\",\r\n type: { enumeration: createEnumTypeFromEnum(GanttDateType) }\r\n },\r\n }\r\n },\r\n },\r\n sorting: {\r\n default: {},\r\n },\r\n };\r\n\r\n private static Properties: GanttSettings<DataViewObjectPropertyIdentifier> = Gantt.getProperties(Gantt.capabilities);\r\n private static getProperties(capabilities: VisualCapabilities): any {\r\n var result = {};\r\n for(var objectKey in capabilities.objects) {\r\n result[objectKey] = {};\r\n for(var propKey in capabilities.objects[objectKey].properties) {\r\n result[objectKey][propKey] = <DataViewObjectPropertyIdentifier> {\r\n objectName: objectKey,\r\n propertyName: propKey\r\n };\r\n }\r\n }\r\n\r\n return result;\r\n }\r\n\r\n private static get DefaultMargin(): IMargin {\r\n return {\r\n top: 50,\r\n right: 40,\r\n bottom: 40,\r\n left: 10\r\n };\r\n }\r\n\r\n private margin: IMargin = Gantt.DefaultMargin;\r\n\r\n private style: IVisualStyle;\r\n private body: D3.Selection;\r\n private ganttSvg: D3.Selection;\r\n private viewModel: GanttViewModel;\r\n private timeScale: D3.Scale.TimeScale;\r\n private axisGroup: D3.Selection;\r\n\r\n private chartGroup: D3.Selection;\r\n private taskGroup: D3.Selection;\r\n private lineGroup: D3.Selection;\r\n\r\n private clearCatcher: D3.Selection;\r\n private ganttDiv: D3.Selection;\r\n private selectionManager: SelectionManager;\r\n private behavior: GanttChartBehavior;\r\n private interactivityService: IInteractivityService;\r\n private hostServices: IVisualHostServices;\r\n private isInteractiveChart: boolean;\r\n\r\n public init(options: VisualInitOptions): void {\r\n this.viewport = _.clone(options.viewport);\r\n this.style = options.style;\r\n this.body = d3.select(options.element.get(0));\r\n\r\n this.hostServices = options.host;\r\n this.selectionManager = new SelectionManager({ hostServices: options.host });\r\n\r\n this.isInteractiveChart = options.interactivity && options.interactivity.isInteractiveLegend;\r\n this.interactivityService = createInteractivityService(this.hostServices);\r\n this.createViewport(options.element);\r\n this.updateChartSize();\r\n this.behavior = new GanttChartBehavior();\r\n this.colors = options.style.colorPalette.dataColors;\r\n }\r\n\r\n /**\r\n * Create the vieport area of the gantt chart\r\n */\r\n private createViewport(element: JQuery): void {\r\n //create div container to the whole viewport area\r\n this.ganttDiv = this.body.append(\"div\")\r\n .classed(Selectors.Body.class, true);\r\n\r\n //create container to the svg area\r\n this.ganttSvg = this.ganttDiv\r\n .append(\"svg\")\r\n .classed(Selectors.ClassName.class, true);\r\n\r\n //create clear catcher\r\n this.clearCatcher = appendClearCatcher(this.ganttSvg);\r\n\r\n //create axis container\r\n this.axisGroup = this.ganttSvg\r\n .append(\"g\")\r\n .classed(Selectors.AxisGroup.class, true);\r\n\r\n //create task lines container\r\n this.lineGroup = this.ganttSvg\r\n .append(\"g\")\r\n .classed(Selectors.TaskLines.class, true);\r\n\r\n //create chart container\r\n this.chartGroup = this.ganttSvg\r\n .append(\"g\")\r\n .classed(Selectors.Chart.class, true);\r\n\r\n //create tasks container\r\n this.taskGroup = this.chartGroup\r\n .append(\"g\")\r\n .classed(Selectors.Tasks.class, true);\r\n\r\n //create legend container\r\n this.legend = createLegend(element,\r\n this.isInteractiveChart,\r\n this.interactivityService,\r\n true,\r\n LegendPosition.Top);\r\n }\r\n\r\n /**\r\n * Clear the viewport area\r\n */\r\n private clearViewport(): void {\r\n this.body.selectAll(Selectors.LegendItems.selector).remove();\r\n this.body.selectAll(Selectors.LegendTitle.selector).remove();\r\n this.axisGroup.selectAll(Selectors.AxisTick.selector).remove();\r\n this.axisGroup.selectAll(Selectors.Domain.selector).remove();\r\n this.lineGroup.selectAll(\"*\").remove();\r\n this.chartGroup.selectAll(Selectors.ChartLine.selector).remove();\r\n this.chartGroup.selectAll(Selectors.TaskGroup.selector).remove();\r\n this.chartGroup.selectAll(Selectors.SingleTask.selector).remove();\r\n }\r\n\r\n /**\r\n * Update div container size to the whole viewport area\r\n * @param viewport The vieport to change it size \r\n */\r\n private updateChartSize(): void {\r\n this.ganttDiv.style({\r\n height: PixelConverter.toString(this.viewport.height),\r\n width: PixelConverter.toString(this.viewport.width)\r\n });\r\n }\r\n\r\n /**\r\n * Get task property from the data view\r\n * @param columnSource\r\n * @param child\r\n * @param propertyName The property to get\r\n */\r\n private static getTaskProperty<T>(columnSource: DataViewMetadataColumn[], child: DataViewTableRow, propertyName: string): T {\r\n if (!child ||\r\n !columnSource ||\r\n !(columnSource.length > 0) ||\r\n !columnSource[0].roles)\r\n return null;\r\n\r\n var index = columnSource.indexOf(columnSource.filter(x=> x.roles[propertyName])[0]);\r\n return index !== -1 ? <T>child[index] : null;\r\n }\r\n\r\n /**\r\n * Check if dataView has a given role\r\n * @param column The dataView headers\r\n * @param name The role to find\r\n */\r\n private static hasRole(column: DataViewMetadataColumn, name: string) {\r\n var roles = column.roles;\r\n return roles && roles[name];\r\n }\r\n\r\n /**\r\n * Get the tooltip info (data display names & formated values)\r\n * @param task All task attributes.\r\n * @param formatters Formatting options for gantt attributes.\r\n */\r\n private static getTooltipInfo(task: Task, formatters: GanttChartFormatters, timeInterval: string = \"Days\") {\r\n var tooltipDataArray: TooltipDataItem[] = [];\r\n\r\n if (task.taskType)\r\n tooltipDataArray.push({ displayName: Gantt.capabilities.dataRoles[0].name, value: task.taskType });\r\n\r\n tooltipDataArray.push({ displayName: Gantt.capabilities.dataRoles[1].name, value: task.name });\r\n if (!isNaN(task.start.getDate()))\r\n tooltipDataArray.push({ displayName: Gantt.capabilities.dataRoles[2].name, value: formatters.startDateFormatter.format(task.start.toLocaleDateString()) });\r\n\r\n tooltipDataArray.push({ displayName: Gantt.capabilities.dataRoles[3].name, value: formatters.durationFormatter.format(task.duration) + \" \" + timeInterval });\r\n tooltipDataArray.push({ displayName: Gantt.capabilities.dataRoles[4].name, value: formatters.completionFormatter.format(task.completion) });\r\n\r\n if (task.resource)\r\n tooltipDataArray.push({ displayName: Gantt.capabilities.dataRoles[5].name, value: task.resource });\r\n\r\n return tooltipDataArray;\r\n }\r\n\r\n /**\r\n * Check if task has data for task\r\n * @param dataView\r\n */\r\n private static isChartHasTask(dataView: DataView): boolean {\r\n if (dataView.table &&\r\n dataView.table.columns) {\r\n for (var column of dataView.table.columns) {\r\n if (Gantt.hasRole(column, \"Task\")) {\r\n return true;\r\n }\r\n }\r\n }\r\n return false;\r\n }\r\n\r\n /**\r\n * Returns the chart formatters\r\n * @param dataView The data Model\r\n */\r\n private static getFormatters(dataView: DataView): GanttChartFormatters {\r\n if (!dataView ||\r\n !dataView.metadata ||\r\n !dataView.metadata.columns)\r\n return null;\r\n\r\n var dateFormat = \"d\";\r\n var numberFormat = \"#\";\r\n\r\n for (var dvColumn of dataView.metadata.columns) {\r\n if (!!dataView.categorical.categories) {\r\n for (var dvCategory of dataView.categorical.categories) {\r\n if (Gantt.hasRole(dvCategory.source, \"StartDate\"))\r\n dateFormat = dvColumn.format;\r\n }\r\n }\r\n }\r\n\r\n return <GanttChartFormatters>{\r\n startDateFormatter: valueFormatter.create({ format: dateFormat }),\r\n durationFormatter: valueFormatter.create({ format: numberFormat }),\r\n completionFormatter: valueFormatter.create({ format: PercentFormat, value: 1, allowFormatBeautification: true })\r\n };\r\n }\r\n\r\n /**\r\n * Create task objects dataView\r\n * @param dataView The data Model.\r\n * @param formatters task attributes represented format.\r\n * @param series An array that holds the color data of different task groups.\r\n */\r\n private static createTasks(dataView: DataView, formatters: GanttChartFormatters, colors: IDataColorPalette): Task[] {\r\n var columnSource = dataView.table.columns;\r\n var data = dataView.table.rows;\r\n var categories = dataView.categorical.categories[0];\r\n var colorHelper = new ColorHelper(colors, undefined/* Gantt.Properties.dataPoint.fill*/);\r\n\r\n return data.map((child: DataViewTableRow, index: number) => {\r\n var dateString = Gantt.getTaskProperty<Date>(columnSource, child, \"StartDate\");\r\n //var startDate = new Date(dateString);\r\n dateString = Gantt.isValidDate(dateString) ? dateString : new Date(Date.now());\r\n\r\n var duration = Gantt.getTaskProperty<number>(columnSource, child, \"Duration\");\r\n\r\n var completionValue = Gantt.getTaskProperty<number>(columnSource, child, \"Completion\");\r\n var completion = Gantt.convertToDecimal(completionValue);\r\n completion = completion <= 1 ? completion : 1;\r\n\r\n var taskType = Gantt.getTaskProperty<string>(columnSource, child, \"Legend\");\r\n var tasksTypeColor: string = colorHelper.getColorForMeasure(dataView.metadata.objects, taskType);\r\n\r\n var task: Task = {\r\n id: index,\r\n name: Gantt.getTaskProperty<string>(columnSource, child, \"Task\"),\r\n start: dateString ? dateString : new Date(Date.now()),\r\n duration: duration > 0 ? duration : 1,\r\n end: null,\r\n completion: completion > 0 ? completion : 0,\r\n resource: Gantt.getTaskProperty<string>(columnSource, child, \"Resource\"),\r\n taskType: taskType,\r\n color: tasksTypeColor ? tasksTypeColor : Gantt.DefaultValues.TaskColor, /* get color by task type */\r\n tooltipInfo: null,\r\n description: \"\",\r\n identity: SelectionId.createWithIdAndMeasure(categories.identity[index], taskType),\r\n selected: false\r\n };\r\n\r\n task.end = d3.time.day.offset(task.start, task.duration);\r\n task.tooltipInfo = Gantt.getTooltipInfo(task, formatters);\r\n return task;\r\n });\r\n }\r\n\r\n /**\r\n * Create the gantt tasks series based on all task types\r\n * @param taskTypes All unique types from the tasks array.\r\n */\r\n private static createSeries(objects: DataViewObjects, tasks: Task[], dataView: DataView, colors: IDataColorPalette): GanttSeries[] {\r\n var colorHelper = new ColorHelper(colors, undefined /*Gantt.Properties.dataPoint.fill*/);\r\n var taskGroup: _.Dictionary<Task[]> = _.groupBy(tasks, t=> t.taskType);\r\n var taskTypes = Gantt.getAllTasksTypes(dataView);\r\n\r\n var series: GanttSeries[] = _.map(taskTypes.types, type => {\r\n return {\r\n tasks: taskGroup[type],\r\n fill: colorHelper.getColorForMeasure(objects, type),\r\n name: type,\r\n identity: SelectionId.createWithMeasure(type),\r\n selected: false\r\n };\r\n });\r\n\r\n return series;\r\n }\r\n\r\n /**\r\n * Convert the dataView to view model\r\n * @param dataView The data Model\r\n */\r\n public static converter(dataView: DataView, colors: IDataColorPalette): GanttViewModel {\r\n if(!dataView \r\n || !dataView.categorical\r\n || !Gantt.isChartHasTask(dataView)\r\n || dataView.table.rows.length === 0) {\r\n return null;\r\n }\r\n\r\n var settings = Gantt.parseSettings(dataView, colors);\r\n var taskTypes = Gantt.getAllTasksTypes(dataView);\r\n\r\n var legendData: LegendData = {\r\n fontSize: settings.legend.fontSize,\r\n dataPoints: [],\r\n title: taskTypes.typeName\r\n };\r\n\r\n var colorHelper = new ColorHelper(colors, undefined /*Gantt.Properties.dataPoint.fill*/);\r\n legendData.dataPoints = _.map(taskTypes.types, type => {\r\n return {\r\n label: type,\r\n color: colorHelper.getColorForMeasure(dataView.metadata.objects, type),\r\n icon: LegendIcon.Circle,\r\n selected: false,\r\n identity: SelectionId.createWithMeasure(type)\r\n };\r\n });\r\n\r\n var formatters: GanttChartFormatters = this.getFormatters(dataView);\r\n\r\n var tasks: Task[] = Gantt.createTasks(dataView, formatters, colors);\r\n var series = Gantt.createSeries(dataView.metadata.objects, tasks, dataView, colors);\r\n\r\n var viewModel: GanttViewModel = {\r\n dataView: dataView,\r\n settings: settings,\r\n tasks: tasks,\r\n series: series,\r\n legendData: legendData,\r\n taskTypes: taskTypes,\r\n };\r\n\r\n return viewModel;\r\n }\r\n\r\n private static parseSettings(dataView: DataView, colors: IDataColorPalette): GanttSettings<any> {\r\n var result: GanttSettings<any> = _.cloneDeep(Gantt.DefaultSettings);\r\n if(!dataView || !dataView.metadata || !dataView.metadata.objects) {\r\n return result;\r\n }\r\n\r\n var objects = dataView.metadata.objects;\r\n\r\n result.general.groupTasks = DataViewObjects.getValue<boolean>(objects, Gantt.Properties.general.groupTasks, Gantt.DefaultSettings.general.groupTasks);\r\n\r\n result.taskLabels.show = DataViewObjects.getValue<boolean>(objects, Gantt.Properties.taskLabels.show, Gantt.DefaultSettings.taskLabels.show); \r\n result.taskLabels.fill = DataViewObjects.getFillColor(objects, Gantt.Properties.taskLabels.fill, Gantt.DefaultSettings.taskLabels.fill);\r\n result.taskLabels.fontSize = DataViewObjects.getValue<number>(objects, Gantt.Properties.taskLabels.fontSize, Gantt.DefaultSettings.taskLabels.fontSize);\r\n result.taskLabels.width = DataViewObjects.getValue<number>(objects, Gantt.Properties.taskLabels.width, result.taskLabels.show ? Gantt.DefaultSettings.taskLabels.width : 0);\r\n\r\n result.taskCompletion.show = DataViewObjects.getValue<boolean>(objects, Gantt.Properties.taskCompletion.show, Gantt.DefaultSettings.taskCompletion.show);\r\n delete result.taskCompletion.show;\r\n\r\n result.taskCompletion.fill = DataViewObjects.getFillColor(objects, Gantt.Properties.taskCompletion.fill, Gantt.DefaultSettings.taskCompletion.fill);\r\n\r\n result.taskResource.show = DataViewObjects.getValue<boolean>(objects, Gantt.Properties.taskResource.show, Gantt.DefaultSettings.taskResource.show);\r\n result.taskResource.fontSize = DataViewObjects.getValue<number>(objects, Gantt.Properties.taskResource.fontSize, Gantt.DefaultSettings.taskResource.fontSize);\r\n result.taskResource.fill = DataViewObjects.getFillColor(objects, Gantt.Properties.taskResource.fill, Gantt.DefaultSettings.taskResource.fill);\r\n\r\n result.dateType.type = DataViewObjects.getValue<GanttDateType>(objects, Gantt.Properties.dateType.type, Gantt.DefaultSettings.dateType.type);\r\n\r\n result.legend.show = DataViewObjects.getValue<boolean>(objects, Gantt.Properties.legend.show, Gantt.DefaultSettings.legend.show);\r\n result.legend.fontSize = DataViewObjects.getValue<number>(objects, Gantt.Properties.legend.fontSize, Gantt.DefaultSettings.legend.fontSize);\r\n result.legend.labelColor = DataViewObjects.getFillColor(objects, Gantt.Properties.legend.labelColor, Gantt.DefaultSettings.legend.labelColor);\r\n result.legend.position = DataViewObjects.getValue<string>(objects, Gantt.Properties.legend.position, Gantt.DefaultSettings.legend.position);\r\n result.legend.showTitle = DataViewObjects.getValue<boolean>(objects, Gantt.Properties.legend.showTitle, Gantt.DefaultSettings.legend.showTitle);\r\n result.legend.titleText = DataViewObjects.getValue<string>(objects, Gantt.Properties.legend.titleText, Gantt.DefaultSettings.legend.titleText);\r\n\r\n return result;\r\n }\r\n\r\n private static isValidDate(date: Date) {\r\n if (Object.prototype.toString.call(date) !== \"[object Date]\")\r\n return false;\r\n return !isNaN(date.getTime());\r\n }\r\n\r\n private static convertToDecimal(number) {\r\n if (!(number >= 0 && number <= 1))\r\n return (number / 100);\r\n return number;\r\n }\r\n\r\n /**\r\n * Gets all unique types from the tasks array\r\n * @param dataView The data model.\r\n */\r\n private static getAllTasksTypes(dataView: DataView): TaskTypes {\r\n var types: string[] = [];\r\n var groupName: string = \"\";\r\n var taskTypes: TaskTypes;\r\n var data = dataView.table.rows;\r\n var index = _.findIndex(dataView.table.columns, col => col.roles.hasOwnProperty(\"Legend\"));\r\n\r\n if (index !== -1) {\r\n groupName = dataView.table.columns[index].displayName;\r\n types = _.unique(data, (d) => d[index]).map((d) => d[index]);\r\n }\r\n\r\n taskTypes = {\r\n typeName: groupName,\r\n types: types\r\n };\r\n\r\n return taskTypes;\r\n }\r\n\r\n /**\r\n * Get legend data, calculate position and draw it\r\n */\r\n private renderLegend(): void {\r\n if (!this.viewModel.legendData) {\r\n return;\r\n }\r\n\r\n LegendData.update(this.viewModel.legendData,\r\n DataViewObjects.getObject(this.viewModel.dataView.metadata.objects, \"legend\", {}));\r\n\r\n var position = this.viewModel.settings.legend.show\r\n ? LegendPosition[<string>this.viewModel.settings.legend.position]\r\n : LegendPosition.None;\r\n\r\n this.legend.changeOrientation(position);\r\n\r\n this.legend.drawLegend(this.viewModel.legendData, _.clone(this.viewport));\r\n Legend.positionChartArea(this.ganttDiv, this.legend);\r\n\r\n switch(this.legend.getOrientation()) {\r\n case LegendPosition.Left:\r\n case LegendPosition.LeftCenter:\r\n case LegendPosition.Right:\r\n case LegendPosition.RightCenter:\r\n this.viewport.width -= this.legend.getMargins().width;\r\n break;\r\n case LegendPosition.Top:\r\n case LegendPosition.TopCenter:\r\n case LegendPosition.Bottom:\r\n case LegendPosition.BottomCenter:\r\n this.viewport.height -= this.legend.getMargins().height;\r\n break;\r\n }\r\n }\r\n\r\n /**\r\n * Called on data change or resizing\r\n * @param options The visual option that contains the dataview and the viewport\r\n */\r\n public update(options: VisualUpdateOptions) {\r\n if (!options.dataViews || !options.dataViews[0]) {\r\n return;\r\n }\r\n\r\n this.viewModel = Gantt.converter(options.dataViews[0], this.colors);\r\n if(!this.viewModel) {\r\n this.clearViewport();\r\n return;\r\n }\r\n\r\n this.viewport = _.clone(options.viewport);\r\n this.margin = Gantt.DefaultMargin;\r\n\r\n this.renderLegend();\r\n this.updateChartSize();\r\n\r\n var tasks = this.viewModel.tasks;\r\n\r\n if (this.interactivityService) {\r\n this.interactivityService.applySelectionStateToData(tasks);\r\n this.interactivityService.applySelectionStateToData(this.viewModel.series);\r\n }\r\n\r\n if (tasks.length > 0) {\r\n var tasksSortedByStartDate: Task[] = _.sortBy(tasks, (t) => t.start);\r\n var tasksSortedByEndDate: Task[] = _.sortBy(tasks, (t) => t.end);\r\n var dateTypeMilliseconds = this.getDateType();\r\n\r\n var startDate: Date = tasksSortedByStartDate[0].start,\r\n endDate: Date = tasksSortedByEndDate[tasks.length - 1].end,\r\n ticks = Math.ceil(Math.round(endDate.valueOf() - startDate.valueOf()) / dateTypeMilliseconds);\r\n\r\n var groupedTasks: GroupedTask[] = this.groupTasks(tasks);\r\n\r\n ticks = ticks === 0 || ticks === 1 ? 2 : ticks;\r\n var axisLength = ticks * 50;\r\n this.ganttSvg\r\n .attr({\r\n height: PixelConverter.toString(groupedTasks.length * ChartLineHeight + this.margin.top),\r\n width: PixelConverter.toString(this.margin.left + this.viewModel.settings.taskLabels.width + axisLength + Gantt.DefaultValues.ResourceWidth)\r\n });\r\n\r\n var viewportIn: IViewport = {\r\n height: this.viewport.height,\r\n width: axisLength\r\n };\r\n\r\n var xAxisProperties = this.calculateAxes(viewportIn, this.textProperties, startDate, endDate, axisLength, ticks, false);\r\n this.timeScale = <D3.Scale.TimeScale>xAxisProperties.scale;\r\n\r\n this.renderAxis(xAxisProperties, 200);\r\n this.renderTasks(groupedTasks);\r\n\r\n this.createMilestoneLine(groupedTasks);\r\n this.updateTaskLabels(groupedTasks, this.viewModel.settings.taskLabels.width);\r\n this.updateElementsPositions(this.viewport, this.margin);\r\n\r\n if (this.interactivityService) {\r\n var behaviorOptions: GanttBehaviorOptions = {\r\n clearCatcher: this.clearCatcher,\r\n taskSelection: this.taskGroup.selectAll(Selectors.SingleTask.selector),\r\n legendSelection: this.body.selectAll(Selectors.LegendItems.selector),\r\n interactivityService: this.interactivityService\r\n };\r\n this.interactivityService.bind(tasks, this.behavior, behaviorOptions);\r\n }\r\n }\r\n }\r\n\r\n private getDateType(): number {\r\n var milliSeconds: number = MillisecondsInWeek;\r\n\r\n switch (this.viewModel.settings.dateType.type) {\r\n case \"Day\":\r\n milliSeconds = MillisecondsInADay;\r\n break;\r\n\r\n case \"Week\":\r\n milliSeconds = MillisecondsInWeek;\r\n break;\r\n\r\n case \"Month\":\r\n milliSeconds = MillisecondsInAMonth;\r\n break;\r\n\r\n case \"Year\":\r\n milliSeconds = MillisecondsInAYear;\r\n break;\r\n }\r\n\r\n return milliSeconds;\r\n }\r\n\r\n private calculateAxes(\r\n viewportIn: IViewport,\r\n textProperties: TextProperties,\r\n startDate: Date,\r\n endDate: Date,\r\n axisLength: number,\r\n ticksCount: number,\r\n scrollbarVisible: boolean): IAxisProperties {\r\n\r\n var dataTypeDatetime = ValueType.fromPrimitiveTypeAndCategory(PrimitiveType.Date);\r\n var category: DataViewMetadataColumn = { displayName: \"StartDate\", queryName: \"StartDate\", type: dataTypeDatetime, index: 0 };\r\n var visualOptions: CalculateScaleAndDomainOptions = {\r\n viewport: viewportIn,\r\n margin: this.margin,\r\n forcedXDomain: [startDate, endDate],\r\n forceMerge: false,\r\n showCategoryAxisLabel: false,\r\n showValueAxisLabel: false,\r\n categoryAxisScaleType: powerbi.visuals.axisScale.linear,\r\n valueAxisScaleType: null,\r\n valueAxisDisplayUnits: 0,\r\n categoryAxisDisplayUnits: 0,\r\n trimOrdinalDataOnOverflow: false,\r\n forcedTickCount: ticksCount\r\n };\r\n\r\n var width = viewportIn.width;\r\n var axes = this.calculateAxesProperties(viewportIn, visualOptions, axisLength, category);\r\n axes.willLabelsFit = AxisHelper.LabelLayoutStrategy.willLabelsFit(\r\n axes,\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 axes.willLabelsWordBreak = (!axes.willLabelsFit && !scrollbarVisible) && AxisHelper.LabelLayoutStrategy.willLabelsWordBreak(\r\n axes, this.margin, width, TextMeasurementService.measureSvgTextWidth,\r\n TextMeasurementService.estimateSvgTextHeight, TextMeasurementService.getTailoredTextOrDefault,\r\n textProperties);\r\n\r\n return axes;\r\n }\r\n\r\n private calculateAxesProperties(viewportIn: IViewport, options: CalculateScaleAndDomainOptions, axisLength: number, metaDataColumn: DataViewMetadataColumn): IAxisProperties {\r\n var xAxisProperties = AxisHelper.createAxis({\r\n pixelSpan: viewportIn.width,\r\n dataDomain: options.forcedXDomain,\r\n metaDataColumn: metaDataColumn,\r\n formatString: Gantt.DefaultValues.DateFormatStrings[this.viewModel.settings.dateType.type],\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,\r\n getValueFn: (index, type) => {\r\n return valueFormatter.format(new Date(index),\r\n Gantt.DefaultValues.DateFormatStrings[this.viewModel.settings.dateType.type]);\r\n },\r\n scaleType: options.categoryAxisScaleType,\r\n axisDisplayUnits: options.categoryAxisDisplayUnits,\r\n });\r\n\r\n xAxisProperties.axisLabel = metaDataColumn.displayName;\r\n return xAxisProperties;\r\n }\r\n\r\n private groupTasks(tasks: Task[]): GroupedTask[] {\r\n if(this.viewModel.settings.general.groupTasks) {\r\n var groupedTasks = _.groupBy(tasks, x => x.name);\r\n var result: GroupedTask[] = _.map(groupedTasks, (x,i) => <GroupedTask>{ \r\n name: i,\r\n tasks: groupedTasks[i] \r\n });\r\n\r\n result.forEach((x,i) => { \r\n x.tasks.forEach(t => t.id = i);\r\n x.id = i;\r\n });\r\n\r\n return result;\r\n }\r\n\r\n return tasks.map(x => <GroupedTask>{ \r\n name: x.name,\r\n id: x.id,\r\n tasks: [x] \r\n });\r\n }\r\n\r\n private renderAxis(xAxisProperties: IAxisProperties, duration: number): void {\r\n var xAxis = xAxisProperties.axis;\r\n xAxis.orient('bottom');\r\n\r\n this.axisGroup.transition().duration(duration).call(xAxis);\r\n }\r\n /**\r\n * Update task labels and add its tooltips \r\n * @param tasks All tasks array\r\n * @param width The task label width\r\n */\r\n private updateTaskLabels(tasks: GroupedTask[], width: number): void {\r\n var axisLabel: D3.UpdateSelection;\r\n var taskLineCoordinateX: number = 15;\r\n var taskLabelsShow = this.viewModel ? this.viewModel.settings.taskLabels.show : true;\r\n var taskLabelsColor = this.viewModel ? this.viewModel.settings.taskLabels.fill : Gantt.DefaultSettings.taskLabels.fill;\r\n var taskLabelsFontSize = this.viewModel ? this.viewModel.settings.taskLabels.fontSize : Gantt.DefaultSettings.taskLabels.fontSize;\r\n\r\n if (taskLabelsShow) {\r\n axisLabel = this.lineGroup.selectAll(Selectors.Label.selector).data(tasks);\r\n axisLabel.enter().append(\"text\").classed(Selectors.Label.class, true);\r\n axisLabel.attr({\r\n x: taskLineCoordinateX,\r\n y: (task: GroupedTask, i: number) => this.getTaskLabelCoordinateY(task.id),\r\n fill: taskLabelsColor,\r\n \"stroke-width\": 1\r\n })\r\n .style(\"font-size\", PixelConverter.fromPoint(taskLabelsFontSize))\r\n .text((task: GroupedTask) => { return task.name; });\r\n\r\n axisLabel.call(AxisHelper.LabelLayoutStrategy.clip, width - 20, TextMeasurementService.svgEllipsis);\r\n axisLabel.append(\"title\").text((task: GroupedTask) => { return task.name; });\r\n axisLabel.exit().remove();\r\n }\r\n else {\r\n this.lineGroup.selectAll(Selectors.Label.selector).remove();\r\n }\r\n }\r\n\r\n private renderTasks(groupedTasks: GroupedTask[]) {\r\n var taskGroupSelection: D3.UpdateSelection = this.taskGroup.selectAll(Selectors.TaskGroup.selector).data(groupedTasks);\r\n var taskProgressColor = this.viewModel ? this.viewModel.settings.taskCompletion.fill : Gantt.DefaultSettings.taskCompletion.fill;\r\n var taskResourceShow = this.viewModel ? this.viewModel.settings.taskResource.show : true;\r\n var padding: number = 4;\r\n var taskResourceColor = this.viewModel ? this.viewModel.settings.taskResource.fill : Gantt.DefaultSettings.taskResource.fill;\r\n var taskResourceFontSize: number = this.viewModel ? this.viewModel.settings.taskResource.fontSize : Gantt.DefaultSettings.taskResource.fontSize;\r\n\r\n //render task group container \r\n taskGroupSelection.enter().append(\"g\").classed(Selectors.TaskGroup.class, true);\r\n\r\n var taskSelection = taskGroupSelection.selectAll(Selectors.SingleTask.selector).data((d: GroupedTask) => d.tasks);\r\n taskSelection.enter().append(\"g\").classed(Selectors.SingleTask.class, true);\r\n\r\n //render task main rect\r\n var taskRect = taskSelection.selectAll(Selectors.TaskRect.selector).data((d: Task) => [d]);\r\n taskRect.enter().append(\"rect\").classed(Selectors.TaskRect.class, true);\r\n taskRect.classed(Selectors.TaskRect.class, true).attr({\r\n x: (task: Task) => this.timeScale(task.start),\r\n y: (task: Task) => this.getBarYCoordinate(task.id),\r\n width: (task: Task) => this.taskDurationToWidth(task),\r\n height: () => this.getBarHeight()\r\n }).style(\"fill\", (task: Task) => task.color);\r\n taskRect.exit().remove();\r\n\r\n //render task progress rect \r\n var taskProgress = taskSelection.selectAll(Selectors.TaskProgress.selector).data((d: Task) => [d]);\r\n taskProgress.enter().append(\"rect\").classed(Selectors.TaskProgress.class, true);\r\n taskProgress.attr({\r\n x: (task: Task) => this.timeScale(task.start),\r\n y: (task: Task) => this.getBarYCoordinate(task.id) + this.getBarHeight() / 2 - Gantt.DefaultValues.ProgressBarHeight / 2,\r\n width: (task: Task) => this.setTaskProgress(task),\r\n height: Gantt.DefaultValues.ProgressBarHeight\r\n }).style(\"fill\", taskProgressColor);\r\n taskProgress.exit().remove();\r\n\r\n if (taskResourceShow) {\r\n //render task resource labels\r\n var taskResource = taskSelection.selectAll(Selectors.TaskResource.selector).data((d: Task) => [d]);\r\n taskResource.enter().append(\"text\").classed(Selectors.TaskResource.class, true);\r\n taskResource.attr({\r\n x: (task: Task) => this.timeScale(task.end) + padding,\r\n y: (task: Task) => (this.getBarYCoordinate(task.id) + (this.getBarHeight() / 2) + padding)\r\n })\r\n .text((task: Task) => task.resource)\r\n .style({\r\n fill: taskResourceColor,\r\n \"font-size\": PixelConverter.fromPoint(taskResourceFontSize)\r\n }).call(AxisHelper.LabelLayoutStrategy.clip,\r\n Gantt.DefaultValues.ResourceWidth - 10,\r\n TextMeasurementService.svgEllipsis);\r\n\r\n taskResource.exit().remove();\r\n }\r\n else {\r\n taskSelection.selectAll(Selectors.TaskResource.selector).remove();\r\n }\r\n\r\n TooltipManager.addTooltip(taskSelection, (tooltipEvent: TooltipEvent) => (<Task>tooltipEvent.data).tooltipInfo);\r\n taskSelection.exit().remove();\r\n taskGroupSelection.exit().remove();\r\n }\r\n\r\n public onClearSelection() {\r\n this.selectionManager.clear();\r\n }\r\n\r\n /**\r\n * Returns the matching Y coordinate for a given task index \r\n * @param taskIndex Task Number\r\n */\r\n private getTaskLabelCoordinateY(taskIndex: number): number {\r\n var fontSize: number = + this.viewModel.settings.taskLabels.fontSize;\r\n return (ChartLineHeight * taskIndex) + (this.getBarHeight() + 5 - (40 - fontSize) / 4);\r\n }\r\n\r\n /**\r\n * Set the task progress bar in the gantt\r\n * @param task All task attributes\r\n */\r\n private setTaskProgress(task: Task): number {\r\n var fraction = task.completion / 1.0,\r\n y = this.timeScale,\r\n progress = (y(task.end) - y(task.start)) * fraction;\r\n\r\n return progress;\r\n }\r\n\r\n /**\r\n * Set the task progress bar in the gantt\r\n * @param lineNumber Line number that represents the task number\r\n */\r\n private getBarYCoordinate(lineNumber: number): number {\r\n return (ChartLineHeight * lineNumber) + (PaddingTasks);\r\n }\r\n\r\n private getBarHeight(): number {\r\n return ChartLineHeight / 1.5;\r\n }\r\n\r\n /**\r\n * convert task duration to width in the time scale\r\n * @param task The task to convert\r\n */\r\n private taskDurationToWidth(task: Task): number {\r\n return this.timeScale(task.end) - this.timeScale(task.start);\r\n }\r\n\r\n private getTooltipForMilstoneLine(timestamp: number, milestoneTitle: string): TooltipDataItem[] {\r\n var stringDate = new Date(timestamp).toDateString();\r\n var tooltip: TooltipDataItem[] = [{ displayName: milestoneTitle, value: stringDate }];\r\n return tooltip;\r\n }\r\n\r\n /**\r\n * Create vertical dotted line that represent milestone in the time axis (by default it shows not time)\r\n * @param tasks All tasks array\r\n * @param timestamp the milestone to be shown in the time axis (default Date.now())\r\n */\r\n private createMilestoneLine(tasks: GroupedTask[], milestoneTitle: string = \"Today\", timestamp: number = Date.now()): void {\r\n var line: Line[] = [{\r\n x1: this.timeScale(timestamp),\r\n y1: 0,\r\n x2: this.timeScale(timestamp),\r\n y2: this.getMilestoneLineLength(tasks.length),\r\n tooltipInfo: this.getTooltipForMilstoneLine(timestamp, milestoneTitle)\r\n }];\r\n\r\n var chartLineSelection: D3.UpdateSelection = this.chartGroup.selectAll(Selectors.ChartLine.selector).data(line);\r\n chartLineSelection.enter().append(\"line\").classed(Selectors.ChartLine.class, true);\r\n chartLineSelection.attr({\r\n x1: (line: Line) => line.x1,\r\n y1: (line: Line) => line.y1,\r\n x2: (line: Line) => line.x2,\r\n y2: (line: Line) => line.y2,\r\n tooltipInfo: (line: Line) => line.tooltipInfo\r\n });\r\n\r\n TooltipManager.addTooltip(chartLineSelection, (tooltipEvent: TooltipEvent) => (<Line>tooltipEvent.data).tooltipInfo);\r\n chartLineSelection.exit().remove();\r\n }\r\n\r\n private updateElementsPositions(viewport: IViewport, margin: IMargin): void {\r\n this.axisGroup.attr(\"transform\", SVGUtil.translate(this.viewModel.settings.taskLabels.width + margin.left, 15));\r\n this.chartGroup.attr(\"transform\", SVGUtil.translate(this.viewModel.settings.taskLabels.width + margin.left, margin.top));\r\n this.lineGroup.attr(\"transform\", SVGUtil.translate(0, margin.top));\r\n }\r\n\r\n private getMilestoneLineLength(numOfTasks: number): number {\r\n return numOfTasks * ChartLineHeight;\r\n }\r\n\r\n private enumerateGeneral(settings: GanttSettings<any>): VisualObjectInstance[] {\r\n return [/*{\r\n selector: null,\r\n properties: <any>settings.general,\r\n objectName: Gantt.Properties.general.groupTasks.objectName\r\n }*/];\r\n }\r\n\r\n private enumerateLegend(settings: GanttSettings<any>): VisualObjectInstance[] {\r\n return [{\r\n displayName: Gantt.Properties.legend.show.objectName,\r\n selector: null,\r\n properties: <any>settings.legend,\r\n objectName: Gantt.Properties.legend.show.objectName\r\n }];\r\n }\r\n\r\n private enumerateDataPoints(settings: GanttSettings<any>): VisualObjectInstance[] {\r\n return this.viewModel.series.map((item: GanttSeries) => <VisualObjectInstance>{\r\n objectName: 'dataPoint',\r\n displayName: item.name,\r\n selector: ColorHelper.normalizeSelector(item.identity.getSelector(), false),\r\n properties: { fill: { solid: { color: item.fill } } }\r\n });\r\n }\r\n\r\n private enumerateTaskCompletion(settings: GanttSettings<any>): VisualObjectInstance[] {\r\n return [{\r\n selector: null,\r\n properties: <any>settings.taskCompletion,\r\n objectName: Gantt.Properties.taskCompletion.show.objectName\r\n }];\r\n }\r\n\r\n private enumerateTaskLabels(settings: GanttSettings<any>): VisualObjectInstance[] {\r\n return [{\r\n selector: null,\r\n properties: <any>settings.taskLabels,\r\n objectName: Gantt.Properties.taskLabels.show.objectName\r\n }];\r\n }\r\n\r\n private enumerateTaskResources(settings: GanttSettings<any>): VisualObjectInstance[] {\r\n return [{\r\n selector: null,\r\n properties: <any>settings.taskResource,\r\n objectName: Gantt.Properties.taskResource.show.objectName\r\n }];\r\n }\r\n\r\n private enumerateDateType(settings: GanttSettings<any>): VisualObjectInstance[] {\r\n return [{\r\n selector: null,\r\n properties: <any>settings.dateType,\r\n objectName: Gantt.Properties.dateType.type.objectName\r\n }];\r\n }\r\n\r\n public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstanceEnumeration {\r\n var settings = this.viewModel && this.viewModel.settings || Gantt.DefaultSettings;\r\n var enumeration = new ObjectEnumerationBuilder();\r\n var push = (instances: VisualObjectInstance[]) => instances.forEach(x => enumeration.pushInstance(x));\r\n switch (options.objectName) {\r\n case 'general':\r\n push(this.enumerateGeneral(settings));\r\n break;\r\n case 'legend':\r\n push(this.enumerateLegend(settings));\r\n break;\r\n case 'dataPoint':\r\n push(this.enumerateDataPoints(settings));\r\n break;\r\n case 'taskLabels':\r\n push(this.enumerateTaskLabels(settings));\r\n break;\r\n case 'taskCompletion':\r\n push(this.enumerateTaskCompletion(settings));\r\n break;\r\n case 'taskResource':\r\n push(this.enumerateTaskResources(settings));\r\n break;\r\n case 'dateType':\r\n push(this.enumerateDateType(settings));\r\n break;\r\n }\r\n\r\n return enumeration.complete();\r\n }\r\n }\r\n\r\n export interface GanttBehaviorOptions {\r\n clearCatcher: D3.Selection;\r\n taskSelection: D3.Selection;\r\n legendSelection: D3.Selection;\r\n interactivityService: IInteractivityService;\r\n }\r\n\r\n export class GanttChartBehavior implements IInteractiveBehavior {\r\n private options: GanttBehaviorOptions;\r\n\r\n public bindEvents(options: GanttBehaviorOptions, selectionHandler: ISelectionHandler) {\r\n this.options = options;\r\n var clearCatcher = options.clearCatcher;\r\n\r\n options.taskSelection.on('click', (d: SelectableDataPoint) => {\r\n selectionHandler.handleSelection(d, d3.event.ctrlKey);\r\n d3.event.stopPropagation();\r\n });\r\n\r\n clearCatcher.on('click', () => {\r\n selectionHandler.handleClearSelection();\r\n });\r\n }\r\n\r\n public renderSelection(hasSelection: boolean) {\r\n this.options.taskSelection.style(\"opacity\", (d: SelectableDataPoint) => {\r\n return (hasSelection && !d.selected) ? Gantt.DefaultValues.MinTaskOpacity : Gantt.DefaultValues.MaxTaskOpacity;\r\n });\r\n }\r\n }\r\n\r\n export class GanttChartWarning implements IVisualWarning {\r\n public get code(): string {\r\n return \"GanttChartWarning\";\r\n }\r\n\r\n public getMessages(resourceProvider: IStringResourceProvider): IVisualErrorMessage {\r\n var message: string = \"This visual requires task value\",\r\n titleKey: string = \"\",\r\n detailKey: string = \"\",\r\n visualMessage: IVisualErrorMessage;\r\n\r\n visualMessage = {\r\n message: message,\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}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/CustomVisuals/visuals/gantt/visual/gantt.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\timport ClassAndSelector = jsCommon.CssConstants.ClassAndSelector;\r\n\timport createClassAndSelector = jsCommon.CssConstants.createClassAndSelector;\r\n\timport SelectionManager = utility.SelectionManager;\r\n\timport px = jsCommon.PixelConverter.toString;\r\n\timport pt = jsCommon.PixelConverter.fromPoint;\r\n\timport fromPointToPixel = jsCommon.PixelConverter.fromPointToPixel;\r\n\r\n\texport const Months: IEnumType = createEnumType([\r\n\t\t{ value: 1, displayName: 'January' },\r\n\t\t{ value: 2, displayName: 'February' },\r\n\t\t{ value: 3, displayName: 'March' },\r\n\t\t{ value: 4, displayName: 'April' },\r\n\t\t{ value: 5, displayName: 'May' },\r\n\t\t{ value: 6, displayName: 'June' },\r\n\t\t{ value: 7, displayName: 'July' },\r\n\t\t{ value: 8, displayName: 'August' },\r\n\t\t{ value: 9, displayName: 'September' },\r\n\t\t{ value: 10, displayName: 'October' },\r\n\t\t{ value: 11, displayName: 'November' },\r\n\t\t{ value: 12, displayName: 'December' }\r\n\t]);\r\n\r\n\texport const WeekDays: IEnumType = createEnumType([\r\n\t\t{ value: 0, displayName: 'Sunday' },\r\n\t\t{ value: 1, displayName: 'Monday' },\r\n\t\t{ value: 2, displayName: 'Tuesday' },\r\n\t\t{ value: 3, displayName: 'Wednesday' },\r\n\t\t{ value: 4, displayName: 'Thursday' },\r\n\t\t{ value: 5, displayName: 'Friday' },\r\n\t\t{ value: 6, displayName: 'Saturday' }\r\n\t]);\r\n\r\n\texport enum GranularityType {\r\n\t\tyear,\r\n\t\tquarter,\r\n\t\tmonth,\r\n\t\tweek,\r\n\t\tday\r\n\t}\r\n\r\n\texport interface GranularityName {\r\n\t\tgranularityType: GranularityType;\r\n\t\tname: string;\r\n\t}\r\n\r\n\texport interface TimelineMargins {\r\n\t\tLeftMargin: number;\r\n\t\tRightMargin: number;\r\n\t\tTopMargin: number;\r\n\t\tBottomMargin: number;\r\n\t\tCellWidth: number;\r\n\t\tCellHeight: number;\r\n\t\tStartXpoint: number;\r\n\t\tStartYpoint: number;\r\n\t\tElementWidth: number;\r\n\t\tMinCellWidth: number;\r\n\t\tMaxCellHeight: number;\r\n\t\tPeriodSlicerRectWidth: number;\r\n\t\tPeriodSlicerRectHeight: number;\r\n\t}\r\n\r\n\texport interface DefaultTimelineProperties {\r\n\t\tDefaultLabelsShow: boolean;\r\n\t\tTimelineDefaultTextSize: number;\r\n\t\tTimelineDefaultCellColor: string;\r\n\t\tTimelineDefaultCellColorOut: string;\r\n\t\tTimelineDefaultTimeRangeShow: boolean;\r\n\t\tDefaultTimeRangeColor: string;\r\n\t\tDefaultLabelColor: string;\r\n\t\tDefaultScaleColor: string;\r\n\t\tDefaultSliderColor: string;\r\n\t\tDefaultGranularity: GranularityType;\r\n\t\tDefaultFirstMonth: number;\r\n\t\tDefaultFirstDay: number;\r\n\t\tDefaultFirstWeekDay: number;\r\n\t}\r\n\r\n\texport interface TimelineSelectors {\r\n\t\tTimelineVisual: ClassAndSelector;\r\n\t\tSelectionRangeContainer: ClassAndSelector;\r\n\t\ttextLabel: ClassAndSelector;\r\n\t\tLowerTextCell: ClassAndSelector;\r\n\t\tUpperTextCell: ClassAndSelector;\r\n\t\tUpperTextArea: ClassAndSelector;\r\n\t\tLowerTextArea: ClassAndSelector;\r\n\t\tRangeTextArea: ClassAndSelector;\r\n\t\tCellsArea: ClassAndSelector;\r\n\t\tCursorsArea: ClassAndSelector;\r\n\t\tMainArea: ClassAndSelector;\r\n\t\tSelectionCursor: ClassAndSelector;\r\n\t\tCell: ClassAndSelector;\r\n\t\tCellRect: ClassAndSelector;\r\n\t\tVertLine: ClassAndSelector;\r\n\t\tTimelineSlicer: ClassAndSelector;\r\n\t\tPeriodSlicerGranularities: ClassAndSelector;\r\n\t\tPeriodSlicerSelection: ClassAndSelector;\r\n\t\tPeriodSlicerSelectionRect: ClassAndSelector;\r\n\t\tPeriodSlicerRect: ClassAndSelector;\r\n\t}\r\n\r\n\texport interface TimelineLabel {\r\n\t\ttitle: string;\r\n\t\ttext: string;\r\n\t\tid: number;\r\n\t}\r\n\r\n\texport interface ExtendedLabel {\r\n\t\tyearLabels?: TimelineLabel[];\r\n\t\tquarterLabels?: TimelineLabel[];\r\n\t\tmonthLabels?: TimelineLabel[];\r\n\t\tweekLabels?: TimelineLabel[];\r\n\t\tdayLabels?: TimelineLabel[];\r\n\t}\r\n\r\n\tconst SelectedCellColorProp: DataViewObjectPropertyIdentifier = { objectName: 'cells', propertyName: 'fillSelected' };\r\n\tconst UnselectedCellColorProp: DataViewObjectPropertyIdentifier = { objectName: 'cells', propertyName: 'fillUnselected' };\r\n\tconst ScaleColorProp: DataViewObjectPropertyIdentifier = { objectName: 'granularity', propertyName: 'scaleColor' };\r\n\tconst SliderColorProp: DataViewObjectPropertyIdentifier = { objectName: 'granularity', propertyName: 'sliderColor' };\r\n\tconst TimeRangeColorProp: DataViewObjectPropertyIdentifier = { objectName: 'rangeHeader', propertyName: 'fontColor' };\r\n\tconst TimeRangeSizeProp: DataViewObjectPropertyIdentifier = { objectName: 'rangeHeader', propertyName: 'textSize' };\r\n\tconst TimeRangeShowProp: DataViewObjectPropertyIdentifier = { objectName: 'rangeHeader', propertyName: 'show' };\r\n\tconst LabelsColorProp: DataViewObjectPropertyIdentifier = { objectName: 'labels', propertyName: 'fontColor' };\r\n\tconst LabelsSizeProp: DataViewObjectPropertyIdentifier = { objectName: 'labels', propertyName: 'textSize' };\r\n\tconst LabelsShowProp: DataViewObjectPropertyIdentifier = { objectName: 'labels', propertyName: 'show' };\r\n\tconst CalendarMonthProp: DataViewObjectPropertyIdentifier = { objectName: 'calendar', propertyName: 'month' };\r\n\tconst CalendarDayProp: DataViewObjectPropertyIdentifier = { objectName: 'calendar', propertyName: 'day' };\r\n\tconst WeekDayProp: DataViewObjectPropertyIdentifier = { objectName: 'weekDay', propertyName: 'day' };\r\n\tconst GranularityNames: GranularityName[] = [\r\n\t\t{\r\n\t\t\tgranularityType: GranularityType.year,\r\n\t\t\tname: \"year\"\r\n\t\t}, {\r\n\t\t\tgranularityType: GranularityType.quarter,\r\n\t\t\tname: \"quarter\"\r\n\t\t}, {\r\n\t\t\tgranularityType: GranularityType.month,\r\n\t\t\tname: \"month\"\r\n\t\t}, {\r\n\t\t\tgranularityType: GranularityType.week,\r\n\t\t\tname: \"week\"\r\n\t\t}, {\r\n\t\t\tgranularityType: GranularityType.day,\r\n\t\t\tname: \"day\"\r\n\t\t}];\r\n\r\n\texport interface DatePeriod {\r\n\t\tidentifierArray: (string | number)[];\r\n\t\tstartDate: Date;\r\n\t\tendDate: Date;\r\n\t\tyear: number;\r\n\t\tweek: number[];\r\n\t\tfraction: number;\r\n\t\tindex: number;\r\n\t}\r\n\r\n\texport interface Granularity {\r\n\t\tgetType(): GranularityType;\r\n\t\tsplitDate(date: Date): (string | number)[];\r\n\t\tgetDatePeriods(): DatePeriod[];\r\n\t\tresetDatePeriods(): void;\r\n\t\tgetExtendedLabel(): ExtendedLabel;\r\n\t\tsetExtendedLabel(extendedLabel: ExtendedLabel): void;\r\n\t\tcreateLabels(granularity: Granularity): TimelineLabel[];\r\n\t\tsameLabel(firstDatePeriod: DatePeriod, secondDatePeriod: DatePeriod): boolean;\r\n\t\tgenerateLabel(datePeriod: DatePeriod): TimelineLabel;\r\n\t\taddDate(date: Date, identifierArray: (string | number)[]);\r\n\t\tsetNewEndDate(date: Date): void;\r\n\t\tsplitPeriod(index: number, newFraction: number, newDate: Date): void;\r\n\t}\r\n\r\n\texport interface TimelineCursorOverElement {\r\n\t\tindex: number;\r\n\t\tdatapoint: TimelineDatapoint;\r\n\t}\r\n\r\n\texport class TimelineGranularity {\r\n\t\tprivate datePeriods: DatePeriod[] = [];\r\n\t\tprivate extendedLabel: ExtendedLabel;\r\n\r\n\t\t/**\r\n\t\t* Returns the short month name of the given date (e.g. Jan, Feb, Mar)\r\n\t\t*/\r\n\t\tpublic shortMonthName(date: Date): string {\r\n\t\t\treturn date.toString().split(' ')[1];\r\n\t\t}\r\n\r\n\t\tpublic resetDatePeriods(): void {\r\n\t\t\tthis.datePeriods = [];\r\n\t\t}\r\n\r\n\t\tpublic getDatePeriods() {\r\n\t\t\treturn this.datePeriods;\r\n\t\t}\r\n\r\n\t\tpublic getExtendedLabel(): ExtendedLabel {\r\n\t\t\treturn this.extendedLabel;\r\n\t\t}\r\n\r\n\t\tpublic setExtendedLabel(extendedLabel: ExtendedLabel): void {\r\n\t\t\tthis.extendedLabel = extendedLabel;\r\n\t\t}\r\n\r\n\t\tpublic createLabels(granularity: Granularity): TimelineLabel[] {\r\n\t\t\tlet labels: TimelineLabel[] = [];\r\n\t\t\tlet lastDatePeriod: DatePeriod;\r\n\t\t\t_.map(this.datePeriods, (x) => {\r\n\t\t\t\tif (_.isEmpty(labels) || !granularity.sameLabel(x, lastDatePeriod)) {\r\n\t\t\t\t\tlastDatePeriod = x;\r\n\t\t\t\t\tlabels.push(granularity.generateLabel(x));\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t\treturn labels;\r\n\t\t}\r\n\r\n\t\t/**\r\n\t\t* Adds the new date into the given datePeriods array\r\n\t\t* If the date corresponds to the last date period, given the current granularity,\r\n\t\t* it will be added to that date period. Otherwise, a new date period will be added to the array.\r\n\t\t* i.e. using Month granularity, Feb 2 2015 corresponds to Feb 3 2015.\r\n\t\t* It is assumed that the given date does not correspond to previous date periods, other than the last date period\r\n\t\t*/\r\n\t\tpublic addDate(date: Date, identifierArray: (string | number)[]): void {\r\n\t\t\tlet datePeriods: DatePeriod[] = this.getDatePeriods();\r\n\t\t\tlet lastDatePeriod: DatePeriod = datePeriods[datePeriods.length - 1];\r\n\t\t\tif (datePeriods.length === 0 || !_.isEqual(lastDatePeriod.identifierArray, identifierArray)) {\r\n\t\t\t\tif (datePeriods.length > 0)\r\n\t\t\t\t\tlastDatePeriod.endDate = date;\r\n\t\t\t\tdatePeriods.push({\r\n\t\t\t\t\tidentifierArray: identifierArray,\r\n\t\t\t\t\tstartDate: date,\r\n\t\t\t\t\tendDate: date,\r\n\t\t\t\t\tweek: this.determineWeek(date),\r\n\t\t\t\t\tyear: this.determineYear(date),\r\n\t\t\t\t\tfraction: 1,\r\n\t\t\t\t\tindex: datePeriods.length\r\n\t\t\t\t});\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t\tlastDatePeriod.endDate = date;\r\n\t\t}\r\n\r\n\t\tpublic setNewEndDate(date: Date): void {\r\n\t\t\t_.last(this.datePeriods).endDate = date;\r\n\t\t}\r\n\r\n\t\t/**\r\n\t\t * Splits a given period into two periods.\r\n\t\t * The new period is added after the index of the old one, while the old one is simply updated.\r\n\t\t * @param index The index of the date priod to be split\r\n\t\t * @param newFraction The fraction value of the new date period\r\n\t\t * @param newDate The date in which the date period is split\r\n\t\t */\r\n\t\tpublic splitPeriod(index: number, newFraction: number, newDate: Date): void {\r\n\t\t\tlet oldDatePeriod: DatePeriod = this.datePeriods[index];\r\n\t\t\toldDatePeriod.fraction -= newFraction;\r\n\t\t\tlet newDateObject: DatePeriod = {\r\n\t\t\t\tidentifierArray: oldDatePeriod.identifierArray,\r\n\t\t\t\tstartDate: newDate,\r\n\t\t\t\tendDate: oldDatePeriod.endDate,\r\n\t\t\t\tweek: this.determineWeek(newDate),\r\n\t\t\t\tyear: this.determineYear(newDate),\r\n\t\t\t\tfraction: newFraction,\r\n\t\t\t\tindex: oldDatePeriod.index + oldDatePeriod.fraction\r\n\t\t\t};\r\n\t\t\toldDatePeriod.endDate = newDate;\r\n\t\t\tthis.datePeriods.splice(index + 1, 0, newDateObject);\r\n\t\t}\r\n\r\n\t\tprivate previousMonth(month: number): number {\r\n\t\t\treturn (month > 0) ? month - 1 : 11;\r\n\t\t}\r\n\r\n\t\tprivate nextMonth(month: number): number {\r\n\t\t\treturn (month < 11) ? month + 1 : 0;\r\n\t\t}\r\n\r\n\t\tprivate countWeeks(startDate: Date, endDate: Date): number {\r\n\t\t\tlet totalDays: number;\r\n\t\t\tif (endDate.getFullYear() === startDate.getFullYear() && endDate.getMonth() === startDate.getMonth() && endDate.getDate() >= startDate.getDate())\r\n\t\t\t\ttotalDays = endDate.getDate() - startDate.getDate();\r\n\t\t\telse {\r\n\t\t\t\ttotalDays = endDate.getDate() - 1;\r\n\t\t\t\tlet lastMonth = this.nextMonth(startDate.getMonth());\r\n\t\t\t\tlet month = endDate.getMonth();\r\n\t\t\t\twhile (month !== lastMonth) {\r\n\t\t\t\t\ttotalDays += new Date(endDate.getFullYear(), month, 0).getDate();\r\n\t\t\t\t\tmonth = this.previousMonth(month);\r\n\t\t\t\t}\r\n\t\t\t\ttotalDays += new Date(endDate.getFullYear(), lastMonth, 0).getDate() - startDate.getDate();\r\n\t\t\t}\r\n\t\t\treturn 1 + Math.floor(totalDays / 7);\r\n\t\t}\r\n\r\n\t\tpublic determineWeek(date: Date): number[] {\r\n\t\t\tvar year = date.getFullYear();\r\n\t\t\tif (this.inPreviousYear(date))\r\n\t\t\t\tyear--;\r\n\t\t\tlet dateOfFirstWeek: Date = Timeline.calendar.getDateOfFirstWeek(year);\r\n\t\t\tlet weeks: number = this.countWeeks(dateOfFirstWeek, date);\r\n\t\t\treturn [weeks, year];\r\n\t\t}\r\n\r\n\t\tprivate inPreviousYear(date: Date): boolean {\r\n\t\t\tlet dateOfFirstWeek: Date = Timeline.calendar.getDateOfFirstWeek(date.getFullYear());\r\n\t\t\treturn date < dateOfFirstWeek;\r\n\t\t}\r\n\r\n\t\tpublic determineYear(date: Date): number {\r\n\t\t\tlet firstDay: Date = new Date(date.getFullYear(), Timeline.calendar.getFirstMonthOfYear(), Timeline.calendar.getFirstDayOfYear());\r\n\t\t\treturn date.getFullYear() - ((firstDay <= date) ? 0 : 1);\r\n\t\t}\r\n\t}\r\n\r\n\texport class DayGranularity extends TimelineGranularity implements Granularity {\r\n\t\tpublic getType(): GranularityType {\r\n\t\t\treturn GranularityType.day;\r\n\t\t}\r\n\r\n\t\tpublic splitDate(date: Date): (string | number)[] {\r\n\t\t\treturn [this.shortMonthName(date), date.getDate(), date.getFullYear()];\r\n\t\t}\r\n\r\n\t\tpublic sameLabel(firstDatePeriod: DatePeriod, secondDatePeriod: DatePeriod): boolean {\r\n\t\t\treturn firstDatePeriod.startDate.getTime() === secondDatePeriod.startDate.getTime();\r\n\t\t}\r\n\r\n\t\tpublic generateLabel(datePeriod: DatePeriod): TimelineLabel {\r\n\t\t\treturn {\r\n\t\t\t\ttitle: this.shortMonthName(datePeriod.startDate) + ' ' + datePeriod.startDate.getDate() + ' - ' + datePeriod.year,\r\n\t\t\t\ttext: datePeriod.startDate.getDate().toString(),\r\n\t\t\t\tid: datePeriod.index\r\n\t\t\t};\r\n\t\t}\r\n\t}\r\n\r\n\texport class MonthGranularity extends TimelineGranularity implements Granularity {\r\n\t\tpublic getType(): GranularityType {\r\n\t\t\treturn GranularityType.month;\r\n\t\t}\r\n\r\n\t\tpublic splitDate(date: Date): (string | number)[] {\r\n\t\t\treturn [this.shortMonthName(date), date.getFullYear()];\r\n\t\t}\r\n\r\n\t\tpublic sameLabel(firstDatePeriod: DatePeriod, secondDatePeriod: DatePeriod): boolean {\r\n\t\t\treturn this.shortMonthName(firstDatePeriod.startDate) === this.shortMonthName(secondDatePeriod.startDate);\r\n\t\t}\r\n\r\n\t\tpublic generateLabel(datePeriod: DatePeriod): TimelineLabel {\r\n\t\t\tlet shortMonthName = this.shortMonthName(datePeriod.startDate);\r\n\t\t\treturn {\r\n\t\t\t\ttitle: shortMonthName,\r\n\t\t\t\ttext: shortMonthName,\r\n\t\t\t\tid: datePeriod.index\r\n\t\t\t};\r\n\t\t}\r\n\t}\r\n\r\n\texport class WeekGranularity extends TimelineGranularity implements Granularity {\r\n\t\tpublic getType(): GranularityType {\r\n\t\t\treturn GranularityType.week;\r\n\t\t}\r\n\r\n\t\tpublic splitDate(date: Date): (string | number)[] {\r\n\t\t\treturn this.determineWeek(date);\r\n\t\t}\r\n\r\n\t\tpublic sameLabel(firstDatePeriod: DatePeriod, secondDatePeriod: DatePeriod): boolean {\r\n\t\t\treturn _.isEqual(firstDatePeriod.week, secondDatePeriod.week);\r\n\t\t}\r\n\r\n\t\tpublic generateLabel(datePeriod: DatePeriod): TimelineLabel {\r\n\t\t\treturn {\r\n\t\t\t\ttitle: 'Week ' + datePeriod.week[0] + ' - ' + datePeriod.week[1],\r\n\t\t\t\ttext: 'W' + datePeriod.week[0],\r\n\t\t\t\tid: datePeriod.index\r\n\t\t\t};\r\n\t\t}\r\n\t}\r\n\r\n\texport class QuarterGranularity extends TimelineGranularity implements Granularity {\r\n\t\t/**\r\n\t\t * Returns the date's quarter name (e.g. Q1, Q2, Q3, Q4)\r\n\t\t * @param date A date \r\n\t\t */\r\n\t\tprivate quarterText(date: Date): string {\r\n\t\t\tlet quarter = 3;\r\n\t\t\tlet year = date.getFullYear();\r\n\t\t\twhile (date < Timeline.calendar.getQuarterStartDate(year, quarter))\r\n\t\t\t\tif (quarter > 0)\r\n\t\t\t\t\tquarter--;\r\n\t\t\t\telse {\r\n\t\t\t\t\tquarter = 3;\r\n\t\t\t\t\tyear--;\r\n\t\t\t\t}\r\n\t\t\tquarter++;\r\n\t\t\treturn 'Q' + quarter;\r\n\t\t}\r\n\r\n\t\tpublic getType(): GranularityType {\r\n\t\t\treturn GranularityType.quarter;\r\n\t\t}\r\n\r\n\t\tpublic splitDate(date: Date): (string | number)[] {\r\n\t\t\treturn [this.quarterText(date), date.getFullYear()];\r\n\t\t}\r\n\r\n\t\tpublic sameLabel(firstDatePeriod: DatePeriod, secondDatePeriod: DatePeriod): boolean {\r\n\t\t\treturn this.quarterText(firstDatePeriod.startDate) === this.quarterText(secondDatePeriod.startDate)\r\n\t\t\t\t&& firstDatePeriod.year === secondDatePeriod.year;\r\n\t\t}\r\n\r\n\t\tpublic generateLabel(datePeriod: DatePeriod): TimelineLabel {\r\n\t\t\tlet quarter = this.quarterText(datePeriod.startDate);\r\n\t\t\treturn {\r\n\t\t\t\ttitle: quarter + ' ' + datePeriod.year,\r\n\t\t\t\ttext: quarter,\r\n\t\t\t\tid: datePeriod.index\r\n\t\t\t};\r\n\t\t}\r\n\t}\r\n\r\n\texport class YearGranularity extends TimelineGranularity implements Granularity {\r\n\t\tpublic getType(): GranularityType {\r\n\t\t\treturn GranularityType.year;\r\n\t\t}\r\n\r\n\t\tpublic splitDate(date: Date): (string | number)[] {\r\n\t\t\treturn [date.getFullYear()];\r\n\t\t}\r\n\r\n\t\tpublic sameLabel(firstDatePeriod: DatePeriod, secondDatePeriod: DatePeriod): boolean {\r\n\t\t\treturn firstDatePeriod.year === secondDatePeriod.year;\r\n\t\t}\r\n\r\n\t\tpublic generateLabel(datePeriod: DatePeriod): TimelineLabel {\r\n\t\t\treturn {\r\n\t\t\t\ttitle: 'Year ' + datePeriod.year,\r\n\t\t\t\ttext: datePeriod.year.toString(),\r\n\t\t\t\tid: datePeriod.index\r\n\t\t\t};\r\n\t\t}\r\n\t}\r\n\r\n\texport class TimelineGranularityData {\r\n\t\tprivate dates: Date[];\r\n\t\tprivate granularities: Granularity[];\r\n\t\tprivate endingDate: Date;\r\n\r\n\t\t/**\r\n\t\t * Returns the date of the previos day \r\n\t\t * @param date The following date\r\n\t\t */\r\n\t\tpublic static previousDay(date: Date): Date {\r\n\t\t\tlet prevDay: Date = new Date(date.getTime());\r\n\t\t\tprevDay.setDate(prevDay.getDate() - 1);\r\n\t\t\treturn prevDay;\r\n\t\t}\r\n\r\n\t\t/**\r\n\t\t * Returns the date of the next day \r\n\t\t * @param date The previous date\r\n\t\t */\r\n\t\tpublic static nextDay(date: Date): Date {\r\n\t\t\tlet nextDay: Date = new Date(date.getTime());\r\n\t\t\tnextDay.setDate(nextDay.getDate() + 1);\r\n\t\t\treturn nextDay;\r\n\t\t}\r\n\r\n\t\t/**\r\n\t\t* Returns an array of dates with all the days between the start date and the end date\r\n\t\t*/\r\n\t\tprivate setDatesRange(startDate: Date, endDate: Date): void {\r\n\t\t\tthis.dates = [];\r\n\t\t\tlet date: Date = startDate;\r\n\t\t\twhile (date <= endDate) {\r\n\t\t\t\tthis.dates.push(date);\r\n\t\t\t\tdate = TimelineGranularityData.nextDay(date);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tconstructor(startDate: Date, endDate: Date) {\r\n\t\t\tthis.granularities = [];\r\n\t\t\tthis.setDatesRange(startDate, endDate);\r\n\t\t\tlet lastDate: Date = this.dates[this.dates.length - 1];\r\n\t\t\tthis.endingDate = TimelineGranularityData.nextDay(lastDate);\r\n\t\t}\r\n\r\n\t\t/**\r\n\t\t * Adds a new granularity to the array of granularities.\r\n\t\t * Resets the new granularity, adds all dates to it, and then edits the last date period with the ending date.\r\n\t\t * @param granularity The new granularity to be added\r\n\t\t */\r\n\t\tpublic addGranularity(granularity: Granularity): void {\r\n\t\t\tgranularity.resetDatePeriods();\r\n\t\t\tfor (let date of this.dates) {\r\n\t\t\t\tlet identifierArray: (string | number)[] = granularity.splitDate(date);\r\n\t\t\t\tgranularity.addDate(date, identifierArray);\r\n\t\t\t}\r\n\t\t\tgranularity.setNewEndDate(this.endingDate);\r\n\t\t\tthis.granularities.push(granularity);\r\n\t\t}\r\n\r\n\t\t/**\r\n\t\t * Returns a specific granularity from the array of granularities\r\n\t\t * @param index The index of the requested granularity\r\n\t\t */\r\n\t\tpublic getGranularity(index: number): Granularity {\r\n\t\t\treturn this.granularities[index];\r\n\t\t}\r\n\r\n\t\tpublic createGranularities(): void {\r\n\t\t\tthis.granularities = [];\r\n\t\t\tthis.addGranularity(new YearGranularity());\r\n\t\t\tthis.addGranularity(new QuarterGranularity());\r\n\t\t\tthis.addGranularity(new MonthGranularity());\r\n\t\t\tthis.addGranularity(new WeekGranularity());\r\n\t\t\tthis.addGranularity(new DayGranularity());\r\n\t\t}\r\n\r\n\t\tpublic createLabels(): void {\r\n\t\t\tthis.granularities.forEach((x) => {\r\n\t\t\t\tx.setExtendedLabel({\r\n\t\t\t\t\tdayLabels: x.getType() >= GranularityType.day ? x.createLabels(this.granularities[GranularityType.day]) : [],\r\n\t\t\t\t\tweekLabels: x.getType() >= GranularityType.week ? x.createLabels(this.granularities[GranularityType.week]) : [],\r\n\t\t\t\t\tmonthLabels: x.getType() >= GranularityType.month ? x.createLabels(this.granularities[GranularityType.month]) : [],\r\n\t\t\t\t\tquarterLabels: x.getType() >= GranularityType.quarter ? x.createLabels(this.granularities[GranularityType.quarter]) : [],\r\n\t\t\t\t\tyearLabels: x.getType() >= GranularityType.year ? x.createLabels(this.granularities[GranularityType.year]) : [],\r\n\t\t\t\t});\r\n\t\t\t});\r\n\t\t}\r\n\t}\r\n\r\n\texport class Utils {\r\n\t\t/**\r\n\t\t * Returns the date of the start of the selection\r\n\t\t * @param timelineData The TimelineData which contains all the date periods\r\n\t\t */\r\n\t\tpublic static getStartSelectionDate(timelineData: TimelineData): Date {\r\n\t\t\treturn timelineData.currentGranularity.getDatePeriods()[timelineData.selectionStartIndex].startDate;\r\n\t\t}\r\n\r\n\t\t/**\r\n\t\t * Returns the date of the end of the selection\r\n\t\t * @param timelineData The TimelineData which contains all the date periods\r\n\t\t */\r\n\t\tpublic static getEndSelectionDate(timelineData: TimelineData): Date {\r\n\t\t\treturn timelineData.currentGranularity.getDatePeriods()[timelineData.selectionEndIndex].endDate;\r\n\t\t}\r\n\r\n\t\t/**\r\n\t\t * Returns the date period of the end of the selection\r\n\t\t * @param timelineData The TimelineData which contains all the date periods\r\n\t\t */\r\n\t\tpublic static getEndSelectionPeriod(timelineData: TimelineData): DatePeriod {\r\n\t\t\treturn timelineData.currentGranularity.getDatePeriods()[timelineData.selectionEndIndex];\r\n\t\t}\r\n\r\n\t\t/**\r\n\t\t * Returns the color of a cell, depending on whether its date period is between the selected date periods.\r\n\t\t * CellRects should be transparent filled by default if there isn't any color sets.\r\n\t\t * @param d The TimelineDataPoint of the cell\r\n\t\t * @param timelineData The TimelineData with the selected date periods\r\n\t\t * @param timelineFormat The TimelineFormat with the chosen colors\r\n\t\t */\r\n\t\tpublic static getCellColor(d: TimelineDatapoint, timelineData: TimelineData, cellFormat: CellFormat): string {\r\n\t\t\tlet inSelectedPeriods: boolean = d.datePeriod.startDate >= Utils.getStartSelectionDate(timelineData) && d.datePeriod.endDate <= Utils.getEndSelectionDate(timelineData);\r\n\t\t\treturn inSelectedPeriods ? cellFormat.colorInProperty : (cellFormat.colorOutProperty || 'transparent');\r\n\t\t}\r\n\r\n\t\t/**\r\n\t\t * Returns the granularity type of the given granularity name\r\n\t\t * @param granularityName The name of the granularity\r\n\t\t */\r\n\t\tpublic static getGranularityType(granularityName: string): GranularityType {\r\n\t\t\tlet index: number = _.findIndex(GranularityNames, x => x.name === granularityName);\r\n\t\t\treturn GranularityNames[index].granularityType;\r\n\t\t}\r\n\r\n\t\t/**\r\n\t\t * Returns the name of the granularity type\r\n\t\t * @param granularity The type of granularity\r\n\t\t */\r\n\t\tpublic static getGranularityName(granularity: GranularityType): string {\r\n\t\t\tlet index: number = _.findIndex(GranularityNames, x => x.granularityType === granularity);\r\n\t\t\treturn GranularityNames[index].name;\r\n\t\t}\r\n\r\n\t\t/**\r\n\t\t * Splits the date periods of the current granularity, in case the stard and end of the selection is in between a date period.\r\n\t\t * i.e. for a quarter granularity and a selection between Feb 6 and Dec 23, the date periods for Q1 and Q4 will be split accordingly\r\n\t\t * @param timelineData The TimelineData that contains the date periods\r\n\t\t * @param startDate The starting date of the selection\r\n\t\t * @param endDate The ending date of the selection\r\n\t\t */\r\n\t\tpublic static separateSelection(timelineData: TimelineData, startDate: Date, endDate: Date): void {\r\n\t\t\tlet datePeriods: DatePeriod[] = timelineData.currentGranularity.getDatePeriods();\r\n\t\t\tlet startDateIndex: number = _.findIndex(datePeriods, x => startDate < x.endDate);\r\n\t\t\tlet endDateIndex: number = _.findIndex(datePeriods, x => endDate <= x.endDate);\r\n\t\t\ttimelineData.selectionStartIndex = startDateIndex;\r\n\t\t\ttimelineData.selectionEndIndex = endDateIndex;\r\n\t\t\tlet startRatio: number = Utils.getDateRatio(datePeriods[startDateIndex], startDate, true);\r\n\t\t\tlet endRatio: number = Utils.getDateRatio(datePeriods[endDateIndex], endDate, false);\r\n\t\t\tif (endRatio > 0)\r\n\t\t\t\ttimelineData.currentGranularity.splitPeriod(endDateIndex, endRatio, endDate);\r\n\t\t\tif (startRatio > 0) {\r\n\t\t\t\tlet startFration: number = datePeriods[startDateIndex].fraction - startRatio;\r\n\t\t\t\ttimelineData.currentGranularity.splitPeriod(startDateIndex, startFration, startDate);\r\n\t\t\t\ttimelineData.selectionStartIndex++;\r\n\t\t\t\ttimelineData.selectionEndIndex++;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t/**\r\n\t\t * Returns the ratio of the given date compared to the whole date period.\r\n\t\t * The ratio is calculated either from the start or the end of the date period.\r\n\t\t * i.e. the ratio of Feb 7 2016 compared to the month of Feb 2016,\r\n\t\t * is 0.2142 from the start of the month, or 0.7857 from the end of the month.\r\n\t\t * @param datePeriod The date period that contain the specified date\r\n\t\t * @param date The date\r\n\t\t * @param fromStart Whether to calculater the ratio from the start of the date period.\r\n\t\t */\r\n\t\tpublic static getDateRatio(datePeriod: DatePeriod, date: Date, fromStart: boolean): number {\r\n\t\t\tlet dateDifference: number = fromStart ? date.getTime() - datePeriod.startDate.getTime() : datePeriod.endDate.getTime() - date.getTime();\r\n\t\t\tlet periodDifference: number = datePeriod.endDate.getTime() - datePeriod.startDate.getTime();\r\n\t\t\treturn periodDifference === 0 ? 0 : dateDifference / periodDifference;\r\n\t\t}\r\n\r\n\t\t/**\r\n\t\t* Returns the time range text, depending on the given granularity (e.g. \"Feb 3 2014 - Apr 5 2015\", \"Q1 2014 - Q2 2015\")\r\n\t\t*/\r\n\t\tpublic static timeRangeText(timelineData: TimelineData): string {\r\n\t\t\tlet startSelectionDateArray: (string | number)[] = timelineData.currentGranularity.splitDate(Utils.getStartSelectionDate(timelineData));\r\n\t\t\tlet endSelectionDateArray: (string | number)[] = timelineData.currentGranularity.splitDate(Utils.getEndSelectionPeriod(timelineData).startDate);\r\n\t\t\treturn startSelectionDateArray.join(' ') + ' - ' + endSelectionDateArray.join(' ');\r\n\t\t}\r\n\r\n\t\tpublic static dateRangeText(datePeriod: DatePeriod): string {\r\n\t\t\treturn datePeriod.startDate.toDateString() + ' - ' + TimelineGranularityData.previousDay(datePeriod.endDate).toDateString();\r\n\t\t}\r\n\r\n\t\t/**\r\n\t\t * Combines the first two partial date periods, into a single date period.\r\n\t\t * Returns whether a partial date period was found.\r\n\t\t * i.e. combines \"Feb 1 2016 - Feb 5 2016\" with \"Feb 5 2016 - Feb 29 2016\" into \"Feb 1 2016 - Feb 29 2016\"\r\n\t\t * @param datePeriods The list of date periods\r\n\t\t */\r\n\t\tpublic static unseparateSelection(datePeriods: DatePeriod[]): boolean {\r\n\t\t\tlet separationIndex: number = _.findIndex(datePeriods, x => x.fraction < 1);\r\n\t\t\tif (separationIndex >= 0) {\r\n\t\t\t\tdatePeriods[separationIndex].endDate = datePeriods[separationIndex + 1].endDate;\r\n\t\t\t\tdatePeriods[separationIndex].fraction += datePeriods[separationIndex + 1].fraction;\r\n\t\t\t\tdatePeriods.splice(separationIndex + 1, 1);\r\n\t\t\t\treturn true;\r\n\t\t\t}\r\n\t\t\treturn false;\r\n\t\t}\r\n\t}\r\n\r\n\texport interface TimelineProperties {\r\n\t\tleftMargin: number;\r\n\t\trightMargin: number;\r\n\t\ttopMargin: number;\r\n\t\tbottomMargin: number;\r\n\t\ttextYPosition: number;\r\n\t\tstartXpoint: number;\r\n\t\tstartYpoint: number;\r\n\t\telementWidth: number;\r\n\t\telement: any;\r\n\t\tcellWidth: number;\r\n\t\tcellHeight: number;\r\n\t\tcellsYPosition: number;\r\n\t}\r\n\r\n\texport interface TimelineFormat {\r\n\t\tcellFormat?: CellFormat;\r\n\t\trangeTextFormat?: LabelFormat;\r\n\t\tlabelFormat?: LabelFormat;\r\n\t\tcalendarFormat?: CalendarFormat;\r\n\t\tgranularityFormat?: GranularityFormat;\r\n\t}\r\n\r\n\texport interface LabelFormat {\r\n\t\tshowProperty: boolean;\r\n\t\tsizeProperty: number;\r\n\t\tcolorProperty: string;\r\n\t}\r\n\r\n\texport interface CalendarFormat {\r\n\t\tfirstMonthProperty: number;\r\n\t\tfirstDayProperty: number;\r\n\t\tweekDayProperty: number;\r\n\t}\r\n\r\n\texport interface CellFormat {\r\n\t\tcolorInProperty: string;\r\n\t\tcolorOutProperty: string;\r\n\t}\r\n\r\n\texport interface GranularityFormat {\r\n\t\tscaleColorProperty: string;\r\n\t\tsliderColorProperty: string;\r\n\t}\r\n\r\n\texport interface TimelineData {\r\n\t\tdragging?: boolean;\r\n\t\tcategorySourceName?: string;\r\n\t\tcolumnIdentity?: powerbi.data.SQColumnRefExpr;\r\n\t\ttimelineDatapoints?: TimelineDatapoint[];\r\n\t\telementsCount?: number;\r\n\t\tselectionStartIndex?: number;\r\n\t\tselectionEndIndex?: number;\r\n\t\tcursorDataPoints?: CursorDatapoint[];\r\n\t\tcurrentGranularity?: Granularity;\r\n\t}\r\n\r\n\texport interface CursorDatapoint {\r\n\t\tx: number;\r\n\t\tcursorIndex: number;\r\n\t\tselectionIndex: number;\r\n\t}\r\n\r\n\texport interface TimelineDatapoint {\r\n\t\tindex: number;\r\n\t\tdatePeriod: DatePeriod;\r\n\t}\r\n\r\n\texport interface DateDictionary {\r\n\t\t[year: number]: Date;\r\n\t}\r\n\r\n\texport class Calendar {\r\n\t\tprivate firstDayOfWeek: number;\r\n\t\tprivate firstMonthOfYear: number;\r\n\t\tprivate firstDayOfYear: number;\r\n\t\tprivate dateOfFirstWeek: DateDictionary;\r\n\t\tprivate quarterFirstMonths: number[];\r\n\r\n\t\tpublic getFirstDayOfWeek(): number {\r\n\t\t\treturn this.firstDayOfWeek;\r\n\t\t}\r\n\r\n\t\tpublic getFirstMonthOfYear(): number {\r\n\t\t\treturn this.firstMonthOfYear;\r\n\t\t}\r\n\r\n\t\tpublic getFirstDayOfYear(): number {\r\n\t\t\treturn this.firstDayOfYear;\r\n\t\t}\r\n\r\n\t\tpublic getQuarterStartDate(year: number, quarterIndex: number): Date {\r\n\t\t\treturn new Date(year, this.quarterFirstMonths[quarterIndex], this.firstDayOfYear);\r\n\t\t}\r\n\r\n\t\tpublic isChanged(calendarFormat: CalendarFormat): boolean {\r\n\t\t\treturn this.firstMonthOfYear !== (calendarFormat.firstMonthProperty - 1)\r\n\t\t\t\t|| this.firstDayOfYear !== calendarFormat.firstDayProperty\r\n\t\t\t\t|| this.firstDayOfWeek !== calendarFormat.weekDayProperty;\r\n\t\t}\r\n\r\n\t\tconstructor(calendarFormat: CalendarFormat) {\r\n\t\t\tthis.firstDayOfWeek = calendarFormat.weekDayProperty;\r\n\t\t\tthis.firstMonthOfYear = calendarFormat.firstMonthProperty - 1;\r\n\t\t\tthis.firstDayOfYear = calendarFormat.firstDayProperty;\r\n\t\t\tthis.dateOfFirstWeek = {};\r\n\t\t\tthis.quarterFirstMonths = [0, 3, 6, 9].map((x) => x + this.firstMonthOfYear);\r\n\t\t}\r\n\r\n\t\tprivate calculateDateOfFirstWeek(year: number): Date {\r\n\t\t\tlet date: Date = new Date(year, this.firstMonthOfYear, this.firstDayOfYear);\r\n\t\t\twhile (date.getDay() !== this.firstDayOfWeek)\r\n\t\t\t\tdate = TimelineGranularityData.nextDay(date);\r\n\t\t\treturn date;\r\n\t\t}\r\n\r\n\t\tpublic getDateOfFirstWeek(year: number): Date {\r\n\t\t\tif (!this.dateOfFirstWeek[year])\r\n\t\t\t\tthis.dateOfFirstWeek[year] = this.calculateDateOfFirstWeek(year);\r\n\t\t\treturn this.dateOfFirstWeek[year];\r\n\t\t}\r\n\t}\r\n\r\n\texport class Timeline implements IVisual {\r\n\t\tprivate requiresNoUpdate: boolean = false;\r\n\t\tprivate datasetsChangedState: boolean = false;\r\n\t\tprivate timelineProperties: TimelineProperties;\r\n\t\tprivate timelineFormat: TimelineFormat;\r\n\t\tprivate timelineData: TimelineData;\r\n\t\tprivate timelineGranularityData: TimelineGranularityData;\r\n\t\tprivate hostServices: IVisualHostServices;\r\n\t\tprivate svg: D3.Selection;\r\n\t\tprivate timelineDiv: D3.Selection;\r\n\t\tprivate body: D3.Selection;\r\n\t\tprivate rangeText: D3.Selection;\r\n\t\tprivate mainGroupElement: D3.Selection;\r\n\t\tprivate yearLabelsElement: D3.Selection;\r\n\t\tprivate quarterLabelsElement: D3.Selection;\r\n\t\tprivate monthLabelsElement: D3.Selection;\r\n\t\tprivate weekLabelsElement: D3.Selection;\r\n\t\tprivate dayLabelsElement: D3.Selection;\r\n\t\tprivate cellsElement: D3.Selection;\r\n\t\tprivate cursorGroupElement: D3.Selection;\r\n\t\tprivate selectorContainer: D3.Selection;\r\n\t\tprivate options: VisualUpdateOptions;\r\n\t\tprivate periodSlicerRect: D3.Selection;\r\n\t\tprivate selectedText: D3.Selection;\r\n\t\tprivate vertLine: D3.Selection;\r\n\t\tprivate horizLine: D3.Selection;\r\n\t\tprivate textLabels: D3.Selection;\r\n\t\tprivate selector = ['Y', 'Q', 'M', 'W', 'D'];\r\n\t\tprivate initialized: boolean;\r\n\t\tprivate selectionManager: SelectionManager;\r\n\t\tprivate clearCatcher: D3.Selection;\r\n\t\tprivate dataView: DataView;\r\n\t\tprivate valueType: string;\r\n\t\tprivate values: any[];\r\n\t\tprivate svgWidth: number;\r\n\t\tprivate newGranularity: GranularityType;\r\n\t\tpublic static calendar: Calendar;\r\n\t\tpublic static capabilities: VisualCapabilities = {\r\n\t\t\tdataRoles: [{\r\n\t\t\t\tname: 'Time',\r\n\t\t\t\tkind: powerbi.VisualDataRoleKind.Grouping,\r\n\t\t\t\tdisplayName: 'Time'\r\n\t\t\t}],\r\n\t\t\tdataViewMappings: [{\r\n\t\t\t\tconditions: [\r\n\t\t\t\t\t{ 'Time': { max: 1 } }\r\n\t\t\t\t],\r\n\t\t\t\tcategorical: {\r\n\t\t\t\t\tcategories: {\r\n\t\t\t\t\t\tfor: { in: 'Time' },\r\n\t\t\t\t\t\tdataReductionAlgorithm: { sample: {} }\r\n\t\t\t\t\t},\r\n\t\t\t\t\tvalues: {\r\n\t\t\t\t\t\tselect:\r\n\t\t\t\t\t\t[{\r\n\t\t\t\t\t\t\tbind: { to: 'Time' }\r\n\t\t\t\t\t\t}]\r\n\t\t\t\t\t},\r\n\t\t\t\t}\r\n\t\t\t}],\r\n\t\t\tobjects: {\r\n\t\t\t\tgeneral: {\r\n\t\t\t\t\tdisplayName: 'General',\r\n\t\t\t\t\tproperties: {\r\n\t\t\t\t\t\tformatString: {\r\n\t\t\t\t\t\t\ttype: {\r\n\t\t\t\t\t\t\t\tformatting: {\r\n\t\t\t\t\t\t\t\t\tformatString: true\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t},\r\n\t\t\t\t\t\tselected: {\r\n\t\t\t\t\t\t\ttype: { bool: true }\r\n\t\t\t\t\t\t},\r\n\t\t\t\t\t\tfilter: {\r\n\t\t\t\t\t\t\ttype: { filter: {} },\r\n\t\t\t\t\t\t\trule: {\r\n\t\t\t\t\t\t\t\toutput: {\r\n\t\t\t\t\t\t\t\t\tproperty: 'selected',\r\n\t\t\t\t\t\t\t\t\tselector: ['Time'],\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t},\r\n\t\t\t\t\t},\r\n\t\t\t\t},\r\n\t\t\t\tcalendar: {\r\n\t\t\t\t\tdisplayName: 'Fiscal Year Start',\r\n\t\t\t\t\tproperties: {\r\n\t\t\t\t\t\tmonth: {\r\n\t\t\t\t\t\t\tdisplayName: 'Month',\r\n\t\t\t\t\t\t\ttype: { enumeration: Months }\r\n\t\t\t\t\t\t},\r\n\t\t\t\t\t\tday: {\r\n\t\t\t\t\t\t\tdisplayName: 'Day',\r\n\t\t\t\t\t\t\ttype: { numeric: true }\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t},\r\n\t\t\t\tweekDay: {\r\n\t\t\t\t\tdisplayName: 'First Day of Week',\r\n\t\t\t\t\tproperties: {\r\n\t\t\t\t\t\tday: {\r\n\t\t\t\t\t\t\tdisplayName: 'Day',\r\n\t\t\t\t\t\t\ttype: { enumeration: WeekDays }\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t},\r\n\t\t\t\trangeHeader: {\r\n\t\t\t\t\tdisplayName: 'Range Header',\r\n\t\t\t\t\tproperties: {\r\n\t\t\t\t\t\tshow: {\r\n\t\t\t\t\t\t\tdisplayName: 'Show',\r\n\t\t\t\t\t\t\ttype: { bool: true }\r\n\t\t\t\t\t\t},\r\n\t\t\t\t\t\tfontColor: {\r\n\t\t\t\t\t\t\tdisplayName: 'Font color',\r\n\t\t\t\t\t\t\ttype: { fill: { solid: { color: true } } }\r\n\t\t\t\t\t\t},\r\n\t\t\t\t\t\ttextSize: {\r\n\t\t\t\t\t\t\tdisplayName: 'Text Size',\r\n\t\t\t\t\t\t\ttype: { numeric: true }\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t},\r\n\t\t\t\tcells: {\r\n\t\t\t\t\tdisplayName: 'Cells',\r\n\t\t\t\t\tproperties: {\r\n\t\t\t\t\t\tfillSelected: {\r\n\t\t\t\t\t\t\tdisplayName: 'Selected cell color',\r\n\t\t\t\t\t\t\ttype: { fill: { solid: { color: true } } }\r\n\t\t\t\t\t\t},\r\n\t\t\t\t\t\tfillUnselected: {\r\n\t\t\t\t\t\t\tdisplayName: 'Unselected cell color',\r\n\t\t\t\t\t\t\ttype: { fill: { solid: { color: { nullable: true } } } }\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t},\r\n\t\t\t\tgranularity: {\r\n\t\t\t\t\tdisplayName: 'Granularity',\r\n\t\t\t\t\tproperties: {\r\n\t\t\t\t\t\tscaleColor: {\r\n\t\t\t\t\t\t\tdisplayName: 'Scale color',\r\n\t\t\t\t\t\t\ttype: { fill: { solid: { color: true } } }\r\n\t\t\t\t\t\t},\r\n\t\t\t\t\t\tsliderColor: {\r\n\t\t\t\t\t\t\tdisplayName: 'Slider color',\r\n\t\t\t\t\t\t\ttype: { fill: { solid: { color: true } } }\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t},\r\n\t\t\t\tlabels: {\r\n\t\t\t\t\tdisplayName: 'Labels',\r\n\t\t\t\t\tproperties: {\r\n\t\t\t\t\t\tshow: {\r\n\t\t\t\t\t\t\tdisplayName: 'Show',\r\n\t\t\t\t\t\t\ttype: { bool: true }\r\n\t\t\t\t\t\t},\r\n\t\t\t\t\t\tfontColor: {\r\n\t\t\t\t\t\t\tdisplayName: 'Font color',\r\n\t\t\t\t\t\t\ttype: { fill: { solid: { color: true } } }\r\n\t\t\t\t\t\t},\r\n\t\t\t\t\t\ttextSize: {\r\n\t\t\t\t\t\t\tdisplayName: 'Text Size',\r\n\t\t\t\t\t\t\ttype: { numeric: true }\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t};\r\n\r\n\t\tprivate timelineMargins: TimelineMargins =\r\n\t\t{\r\n\t\t\tLeftMargin: 15,\r\n\t\t\tRightMargin: 15,\r\n\t\t\tTopMargin: 15,\r\n\t\t\tBottomMargin: 10,\r\n\t\t\tCellWidth: 40,\r\n\t\t\tCellHeight: 25,\r\n\t\t\tStartXpoint: 10,\r\n\t\t\tStartYpoint: 20,\r\n\t\t\tElementWidth: 30,\r\n\t\t\tMinCellWidth: 30,\r\n\t\t\tMaxCellHeight: 60,\r\n\t\t\tPeriodSlicerRectWidth: 15,\r\n\t\t\tPeriodSlicerRectHeight: 23\r\n\t\t};\r\n\r\n\t\tprivate defaultTimelineProperties: DefaultTimelineProperties =\r\n\t\t{\r\n\t\t\tDefaultLabelsShow: true,\r\n\t\t\tTimelineDefaultTextSize: 9,\r\n\t\t\tTimelineDefaultCellColor: \"#ADD8E6\",\r\n\t\t\tTimelineDefaultCellColorOut: \"\", // transparent by default\r\n\t\t\tTimelineDefaultTimeRangeShow: true,\r\n\t\t\tDefaultTimeRangeColor: \"#777777\",\r\n\t\t\tDefaultLabelColor: \"#777777\",\r\n\t\t\tDefaultScaleColor: \"#000000\",\r\n\t\t\tDefaultSliderColor: \"#AAAAAA\",\r\n\t\t\tDefaultGranularity: GranularityType.month,\r\n\t\t\tDefaultFirstMonth: 1,\r\n\t\t\tDefaultFirstDay: 1,\r\n\t\t\tDefaultFirstWeekDay: 0\r\n\t\t};\r\n\r\n\t\tprivate timelineSelectors: TimelineSelectors =\r\n\t\t{\r\n\t\t\tTimelineVisual: createClassAndSelector('Timeline'),\r\n\t\t\tSelectionRangeContainer: createClassAndSelector('selectionRangeContainer'),\r\n\t\t\ttextLabel: createClassAndSelector('label'),\r\n\t\t\tLowerTextCell: createClassAndSelector('lowerTextCell'),\r\n\t\t\tUpperTextCell: createClassAndSelector('upperTextCell'),\r\n\t\t\tUpperTextArea: createClassAndSelector('upperTextArea'),\r\n\t\t\tLowerTextArea: createClassAndSelector('lowerTextArea'),\r\n\t\t\tRangeTextArea: createClassAndSelector('rangeTextArea'),\r\n\t\t\tCellsArea: createClassAndSelector('cellsArea'),\r\n\t\t\tCursorsArea: createClassAndSelector('cursorsArea'),\r\n\t\t\tMainArea: createClassAndSelector('mainArea'),\r\n\t\t\tSelectionCursor: createClassAndSelector('selectionCursor'),\r\n\t\t\tCell: createClassAndSelector('cell'),\r\n\t\t\tCellRect: createClassAndSelector('cellRect'),\r\n\t\t\tVertLine: createClassAndSelector('timelineVertLine'),\r\n\t\t\tTimelineSlicer: createClassAndSelector('timelineSlicer'),\r\n\t\t\tPeriodSlicerGranularities: createClassAndSelector('periodSlicerGranularities'),\r\n\t\t\tPeriodSlicerSelection: createClassAndSelector('periodSlicerSelection'),\r\n\t\t\tPeriodSlicerSelectionRect: createClassAndSelector('periodSlicerSelectionRect'),\r\n\t\t\tPeriodSlicerRect: createClassAndSelector('periodSlicerRect')\r\n\t\t};\r\n\r\n\t\t/**\r\n\t\t * Changes the current granularity depending on the given granularity type\r\n\t\t * Separates the new granularity's date periods which contain the start/end selection\r\n\t\t * Unseparates the date periods of the previous granularity.\r\n\t\t * @param granularity The new granularity type\r\n\t\t */\r\n\t\tpublic changeGranularity(granularity: GranularityType, startDate: Date, endDate: Date): void {\r\n\t\t\tif (Utils.unseparateSelection(this.timelineData.currentGranularity.getDatePeriods()))\r\n\t\t\t\tUtils.unseparateSelection(this.timelineData.currentGranularity.getDatePeriods());\r\n\t\t\tthis.timelineData.currentGranularity = this.timelineGranularityData.getGranularity(granularity);\r\n\t\t\tUtils.separateSelection(this.timelineData, startDate, endDate);\r\n\t\t}\r\n\r\n\t\tpublic init(options: VisualInitOptions): void {\r\n\t\t\tthis.hostServices = options.host;\r\n\t\t\tthis.initialized = false;\r\n\t\t\tlet element = options.element;\r\n\t\t\tthis.selectionManager = new SelectionManager({ hostServices: options.host });\r\n\r\n\t\t\tthis.timelineProperties = {\r\n\t\t\t\telement: element,\r\n\t\t\t\ttextYPosition: 50,\r\n\t\t\t\tcellsYPosition: this.timelineMargins.TopMargin * 3 + 65,\r\n\t\t\t\ttopMargin: this.timelineMargins.TopMargin,\r\n\t\t\t\tbottomMargin: this.timelineMargins.BottomMargin,\r\n\t\t\t\tleftMargin: this.timelineMargins.LeftMargin,\r\n\t\t\t\tstartXpoint: this.timelineMargins.StartXpoint,\r\n\t\t\t\tstartYpoint: this.timelineMargins.StartYpoint,\r\n\t\t\t\tcellWidth: this.timelineMargins.CellWidth,\r\n\t\t\t\tcellHeight: this.timelineMargins.CellHeight,\r\n\t\t\t\telementWidth: this.timelineMargins.ElementWidth,\r\n\t\t\t\trightMargin: this.timelineMargins.RightMargin\r\n\t\t\t};\r\n\r\n\t\t\tthis.body = d3.select(element.get(0));\r\n\t\t\tthis.timelineDiv = this.body.append('div');\r\n\t\t\tthis.svg = this.timelineDiv.append('svg').attr('width', px(options.viewport.width)).classed(this.timelineSelectors.TimelineVisual.class, true);\r\n\r\n\t\t\tthis.addWrappElements();\r\n\t\t}\r\n\r\n\t\tprivate addWrappElements(): void {\r\n\t\t\tthis.clearCatcher = appendClearCatcher(this.svg);\r\n\r\n\t\t\tthis.clearCatcher.data([this])\r\n\t\t\t\t.on(\"click\", (timeline: Timeline) => timeline.clear())\r\n\t\t\t\t.on(\"touchstart\", (timeline: Timeline) => timeline.clear());\r\n\r\n\t\t\tthis.rangeText = this.svg.append('g').classed(this.timelineSelectors.RangeTextArea.class, true).append('text');\r\n\t\t\tthis.mainGroupElement = this.svg.append('g').classed(this.timelineSelectors.MainArea.class, true);\r\n\t\t\tthis.yearLabelsElement = this.mainGroupElement.append('g');\r\n\t\t\tthis.quarterLabelsElement = this.mainGroupElement.append('g');\r\n\t\t\tthis.monthLabelsElement = this.mainGroupElement.append('g');\r\n\t\t\tthis.weekLabelsElement = this.mainGroupElement.append('g');\r\n\t\t\tthis.dayLabelsElement = this.mainGroupElement.append('g');\r\n\t\t\tthis.cellsElement = this.mainGroupElement.append('g').classed(this.timelineSelectors.CellsArea.class, true);\r\n\t\t\tthis.cursorGroupElement = this.svg.append('g').classed(this.timelineSelectors.CursorsArea.class, true);\r\n\t\t}\r\n\r\n\t\tprivate clear(): void {\r\n\t\t\tif (this.initialized) {\r\n\t\t\t\tthis.selectionManager.clear();\r\n\r\n\t\t\t\tif (this.timelineData) {\r\n\t\t\t\t\tthis.timelineData.selectionStartIndex = 0;\r\n\t\t\t\t\tthis.timelineData.selectionEndIndex = this.timelineData.currentGranularity.getDatePeriods().length - 1;\r\n\t\t\t\t\tif (_.any(this.timelineData.timelineDatapoints, (x) => x.index % 1 !== 0))\r\n\t\t\t\t\t\tthis.selectPeriod(this.timelineData.currentGranularity.getType());\r\n\t\t\t\t\telse {\r\n\t\t\t\t\t\tTimeline.updateCursors(this.timelineData, this.timelineProperties.cellWidth);\r\n\t\t\t\t\t\tthis.fillCells(this.timelineFormat.cellFormat);\r\n\t\t\t\t\t\tthis.renderCursors(this.timelineData, this.timelineFormat, this.timelineProperties.cellHeight, this.timelineProperties.cellsYPosition);\r\n\t\t\t\t\t\tthis.renderTimeRangeText(this.timelineData, this.timelineFormat.rangeTextFormat);\r\n\t\t\t\t\t\tthis.fillColorGranularity(this.timelineFormat.granularityFormat);\r\n\t\t\t\t\t}\r\n\t\t\t\t\tthis.setSelection(this.timelineData);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tprivate drawGranular(timelineProperties: TimelineProperties): void {\r\n\t\t\tlet dragPeriodRectState: boolean = false;\r\n\t\t\tlet startXpoint = timelineProperties.startXpoint;\r\n\t\t\tlet startYpoint = timelineProperties.startYpoint;\r\n\t\t\tlet elementWidth = timelineProperties.elementWidth;\r\n\r\n\t\t\tthis.selectorContainer = this.svg.append('g').classed(this.timelineSelectors.TimelineSlicer.class, true);\r\n\t\t\tthis.selectorContainer.on('mouseleave', d => dragPeriodRectState = false);\r\n\r\n\t\t\t// create horiz. line\r\n\t\t\tthis.horizLine = this.selectorContainer.append('rect');\r\n\t\t\tlet selectorPeriods = this.selector;\r\n\t\t\tthis.horizLine.attr({\r\n\t\t\t\theight: px(1),\r\n\t\t\t\tx: px(startXpoint),\r\n\t\t\t\ty: px(startYpoint + 2),\r\n\t\t\t\twidth: px((selectorPeriods.length - 1) * elementWidth)\r\n\t\t\t});\r\n\r\n\t\t\t// create vert. lines\r\n\t\t\tthis.vertLine = this.selectorContainer.selectAll(\"vertLines\")\r\n\t\t\t\t.data(selectorPeriods).enter().append('rect');\r\n\t\t\tthis.vertLine\r\n\t\t\t\t.classed(this.timelineSelectors.VertLine.class, true)\r\n\t\t\t\t.attr({\r\n\t\t\t\t\tx: (d, index) => px(startXpoint + index * elementWidth),\r\n\t\t\t\t\ty: px(startYpoint),\r\n\t\t\t\t\twidth: px(2),\r\n\t\t\t\t\theight: px(3)\r\n\t\t\t\t})\r\n\t\t\t\t.style({ 'cursor': 'pointer' });\r\n\r\n\t\t\t// create text lables\r\n\t\t\tlet text = this.selectorContainer.selectAll(this.timelineSelectors.PeriodSlicerGranularities.selector)\r\n\t\t\t\t.data(selectorPeriods)\r\n\t\t\t\t.enter()\r\n\t\t\t\t.append(\"text\")\r\n\t\t\t\t.classed(this.timelineSelectors.PeriodSlicerGranularities.class, true);\r\n\r\n\t\t\tthis.textLabels = text.text((d) => d)\r\n\t\t\t\t.attr({\r\n\t\t\t\t\tx: (d, index) => px(startXpoint - 3 + index * elementWidth),\r\n\t\t\t\t\ty: px(startYpoint - 3)\r\n\t\t\t\t});\r\n\r\n\t\t\t// create selected period text\r\n\t\t\tthis.selectedText = this.selectorContainer.append(\"text\").classed(this.timelineSelectors.PeriodSlicerSelection.class, true);\r\n\t\t\tthis.selectedText.text(Utils.getGranularityName(this.defaultTimelineProperties.DefaultGranularity))\r\n\t\t\t\t.attr({\r\n\t\t\t\t\tx: px(startXpoint + 2 * elementWidth),\r\n\t\t\t\t\ty: px(startYpoint + 17),\r\n\t\t\t\t});\r\n\r\n\t\t\tlet selRects = this.selectorContainer\r\n\t\t\t\t.selectAll(this.timelineSelectors.PeriodSlicerSelectionRect.selector)\r\n\t\t\t\t.data(selectorPeriods)\r\n\t\t\t\t.enter()\r\n\t\t\t\t.append('rect')\r\n\t\t\t\t.classed(this.timelineSelectors.PeriodSlicerSelectionRect.class, true);\r\n\r\n\t\t\tlet clickHandler: (d: any, index: number) => void = (d: any, index: number) => {\r\n\t\t\t\tthis.selectPeriod(index);\r\n\t\t\t\tdragPeriodRectState = true;\r\n\t\t\t};\r\n\r\n\t\t\tselRects.attr({\r\n\t\t\t\t\tx: (d, index) => px(startXpoint - elementWidth / 2 + index * elementWidth),\r\n\t\t\t\t\ty: px(3),\r\n\t\t\t\t\twidth: px(elementWidth),\r\n\t\t\t\t\theight: px(23)\r\n\t\t\t\t})\r\n\t\t\t\t.style({ 'cursor': 'pointer' })\r\n\t\t\t\t.on('mousedown', clickHandler)\r\n\t\t\t\t.on('touchstart', clickHandler)\r\n\t\t\t\t.on('mouseup', () => dragPeriodRectState = false)\r\n\t\t\t\t.on('touchend', () => dragPeriodRectState = false)\r\n\t\t\t\t.on(\"mouseover\", (d, index) => {\r\n\t\t\t\t\tif (dragPeriodRectState) {\r\n\t\t\t\t\t\tthis.selectPeriod(index);\r\n\t\t\t\t\t}\r\n\t\t\t\t});\r\n\r\n\t\t\tlet dragPeriodRect = d3.behavior.drag()\r\n\t\t\t\t.on(\"dragstart\", function (e, b) {\r\n\t\t\t\t\tdragPeriodRectState = true;\r\n\t\t\t\t})\r\n\t\t\t\t.on(\"dragend\", function (e, b) {\r\n\t\t\t\t\tdragPeriodRectState = false;\r\n\t\t\t\t});\r\n\r\n\t\t\tthis.periodSlicerRect = this.selectorContainer\r\n\t\t\t\t.append('rect').classed(this.timelineSelectors.PeriodSlicerRect.class, true)\r\n\t\t\t\t.attr({\r\n\t\t\t\t\tx: px(startXpoint - 6 + this.defaultTimelineProperties.DefaultGranularity * elementWidth),\r\n\t\t\t\t\ty: px(startYpoint - 16),\r\n\t\t\t\t\trx: px(4),\r\n\t\t\t\t\twidth: px(15),\r\n\t\t\t\t\theight: px(23)\r\n\t\t\t\t})\r\n\t\t\t\t.on('mouseup', d => dragPeriodRectState = false);\r\n\t\t\tthis.periodSlicerRect.call(dragPeriodRect);\r\n\t\t}\r\n\r\n\t\tpublic fillColorGranularity(granularityFormat: GranularityFormat): void {\r\n\t\t\tthis.periodSlicerRect.style(\"stroke\", granularityFormat.sliderColorProperty);\r\n\t\t\tthis.selectedText.attr('fill', granularityFormat.scaleColorProperty);\r\n\t\t\tthis.textLabels.attr('fill', granularityFormat.scaleColorProperty);\r\n\t\t\tthis.vertLine.attr('fill', granularityFormat.scaleColorProperty);\r\n\t\t\tthis.horizLine.attr('fill', granularityFormat.scaleColorProperty);\r\n\t\t} \r\n\r\n\t\tpublic redrawPeriod(granularity: GranularityType): void {\r\n\t\t\tlet dx = this.timelineMargins.StartXpoint + granularity * this.timelineMargins.ElementWidth;\r\n\t\t\tthis.periodSlicerRect.transition().attr(\"x\", px(dx - 7));\r\n\t\t\tthis.selectedText.text(Utils.getGranularityName(granularity));\r\n\t\t\tlet startDate: Date = Utils.getStartSelectionDate(this.timelineData);\r\n\t\t\tlet endDate: Date = Utils.getEndSelectionDate(this.timelineData);\r\n\t\t\tthis.changeGranularity(granularity, startDate, endDate);\r\n\t\t}\r\n\r\n\t\tprivate static setMeasures(labelFormat: LabelFormat, granularityType: GranularityType, datePeriodsCount: number, viewport: IViewport, timelineProperties: TimelineProperties, timelineMargins: TimelineMargins) {\r\n\t\t\ttimelineProperties.cellsYPosition = timelineProperties.textYPosition;\r\n\t\t\tlet labelSize = fromPointToPixel(labelFormat.sizeProperty);\r\n\t\t\tif (labelFormat.showProperty)\r\n\t\t\t\ttimelineProperties.cellsYPosition += labelSize * 1.5 * (granularityType + 1);\r\n\t\t\tlet svgHeight = Math.max(0, viewport.height - timelineMargins.TopMargin);\r\n\t\t\tlet maxHeight = viewport.width - timelineMargins.RightMargin - timelineMargins.MinCellWidth * datePeriodsCount;\r\n\t\t\tlet height = Math.max(timelineMargins.MinCellWidth, Math.min(timelineMargins.MaxCellHeight, maxHeight, svgHeight - timelineProperties.cellsYPosition - 20));\r\n\t\t\tlet width = Math.max(timelineMargins.MinCellWidth, (viewport.width - height - timelineMargins.RightMargin) / datePeriodsCount);\r\n\t\t\ttimelineProperties.cellHeight = height;\r\n\t\t\ttimelineProperties.cellWidth = width;\r\n\t\t}\r\n\r\n\t\tprivate visualChangeOnly(options: VisualUpdateOptions): boolean {\r\n\t\t\tif (options && options.dataViews && options.dataViews[0] && options.dataViews[0].metadata &&\r\n\t\t\t\tthis.options && this.options.dataViews && this.options.dataViews[0] && this.options.dataViews[0].metadata) {\r\n\t\t\t\tlet newObjects = options.dataViews[0].metadata.objects;\r\n\t\t\t\tlet oldObjects = this.options.dataViews[0].metadata.objects;\r\n\t\t\t\tlet properties = ['rangeHeader', 'cells', 'labels', 'granularity'];\r\n\t\t\t\tlet metadataChanged = !properties.every((x) => _.isEqual(newObjects ? newObjects[x] : undefined, oldObjects ? oldObjects[x] : undefined));\r\n\t\t\t\treturn options.suppressAnimations || metadataChanged;\r\n\t\t\t}\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\t/**\r\n\t\t * Note: Public for testability.\r\n\t\t */\r\n\t\tpublic datasetsChanged(options: VisualUpdateOptions): boolean {\r\n\t\t\tif (options && options.dataViews && options.dataViews[0] && options.dataViews[0].categorical &&\r\n\t\t\t\toptions.dataViews[0].categorical.categories && options.dataViews[0].categorical.categories[0] &&\r\n\t\t\t\toptions.dataViews[0].categorical.categories[0].source &&\r\n\t\t\t\tthis.options && this.options.dataViews && this.options.dataViews[0] && this.options.dataViews[0].categorical && \r\n\t\t\t\tthis.options.dataViews[0].categorical.categories && this.options.dataViews[0].categorical.categories[0] && \r\n\t\t\t\tthis.options.dataViews[0].categorical.categories[0].source){\r\n\r\n\t\t\t\tvar newObjects = options.dataViews[0].categorical.categories[0].source.displayName;\r\n\t\t\t\tvar oldObjects = this.options.dataViews[0].categorical.categories[0].source.displayName;\r\n\t\t\t\tif (!_.isEqual(newObjects, oldObjects))\r\n\t\t\t\t\treturn true;\r\n\t\t\t}\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tprivate unavailableType(dataViewCategorical: DataViewCategorical): boolean {\r\n\t\t\treturn !dataViewCategorical.categories\r\n\t\t\t\t|| dataViewCategorical.categories.length !== 1\r\n\t\t\t\t|| !dataViewCategorical.categories[0].values\r\n\t\t\t\t|| dataViewCategorical.categories[0].values.length === 0\r\n\t\t\t\t|| !dataViewCategorical.categories[0].source\r\n\t\t\t\t|| !dataViewCategorical.categories[0].source.type;\r\n\t\t}\r\n\r\n\t\tprivate unavailableChildIdentityField(dataViewTree: DataViewTree): boolean {\r\n\t\t\treturn !dataViewTree.root || !dataViewTree.root.childIdentityFields || dataViewTree.root.childIdentityFields.length === 0;\r\n\t\t}\r\n\r\n\t\tprivate createTimelineOptions(dataView: DataView): boolean {\r\n\t\t\tthis.dataView = dataView;\r\n\t\t\tif (!dataView.categorical\r\n\t\t\t\t|| !dataView.metadata\r\n\t\t\t\t|| this.unavailableType(dataView.categorical)\r\n\t\t\t\t|| !dataView.tree\r\n\t\t\t\t|| this.unavailableChildIdentityField(dataView.tree))\r\n\t\t\t\treturn false;\r\n\t\t\tlet columnExp = <powerbi.data.SQColumnRefExpr>dataView.tree.root.childIdentityFields[0];\r\n\t\t\tthis.valueType = columnExp ? columnExp.ref : null;\r\n\t\t\tif (!(dataView.categorical.categories[0].source.type.dateTime ||\r\n\t\t\t\t(dataView.categorical.categories[0].source.type.numeric && (this.valueType === 'Year' || this.valueType === 'Date'))))\r\n\t\t\t\treturn false;\r\n\t\t\tthis.values = this.prepareValues(this.dataView.categorical.categories[0].values);\r\n\t\t\treturn true;\r\n\t\t}\r\n\r\n\t\t//Public for testability.\r\n\t\tpublic prepareValues(values) {\r\n\t\t\t// remove null strings and rebuild string type date \r\n\t\t\t// (BUG #7266283 IN PBI-service)\r\n\t\t\tvalues = values.filter(Boolean);\r\n\t\t\tfor (var i in values) { \r\n\t\t\t\tvar item = values[i];\r\n\t\t\t\tif(typeof(item) === 'String' && (String(new Date(item)) !== 'Invalid Date')){\r\n\t\t\t\t\treturn values[i] = new Date(item);\r\n\t\t\t\t}\r\n\t\t\t};\r\n\t\t\treturn values;\r\n\t\t}\r\n\r\n\t\tprivate createTimelineData() {\r\n\t\t\tlet startDate: Date;\r\n\t\t\tlet endDate: Date;\r\n\t\t\tif (this.valueType === 'Year') {\r\n\t\t\t\tlet years: number[] = this.values;\r\n\t\t\t\tstartDate = new Date(_.min(years), 0);\r\n\t\t\t\tendDate = new Date(_.max(years), 11);\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\tlet dates: Date[] = this.values;\r\n\t\t\t\tstartDate = _.min(dates);\r\n\t\t\t\tendDate = _.max(dates);\r\n\t\t\t}\r\n\r\n\t\t\tthis.timelineFormat = Timeline.fillTimelineFormat(this.options.dataViews[0].metadata.objects, this.defaultTimelineProperties);\r\n\r\n\t\t\tif (!this.initialized){\r\n\t\t\t\tthis.drawGranular(this.timelineProperties);\r\n\t\t\t\tthis.fillColorGranularity(this.timelineFormat.granularityFormat);\r\n\t\t\t}\r\n\t\t\tif (this.initialized) {\r\n\t\t\t\tlet actualEndDate = TimelineGranularityData.nextDay(endDate);\r\n\t\t\t\tlet daysPeriods = this.timelineGranularityData.getGranularity(GranularityType.day).getDatePeriods();\r\n\t\t\t\tlet prevStartDate = daysPeriods[0].startDate;\r\n\t\t\t\tlet prevEndDate = daysPeriods[daysPeriods.length - 1].endDate;\r\n\t\t\t\tlet changedSelection = startDate.getTime() >= prevStartDate.getTime() && actualEndDate.getTime() <= prevEndDate.getTime();\r\n\t\t\t\tthis.newGranularity = this.timelineData.currentGranularity.getType();\r\n\t\t\t\tif (changedSelection) {\r\n\t\t\t\t\tthis.changeGranularity(this.newGranularity, startDate, actualEndDate);\r\n\t\t\t\t}\r\n\t\t\t\telse {\r\n\t\t\t\t\tif (actualEndDate < prevEndDate)\r\n\t\t\t\t\t\tendDate = daysPeriods[daysPeriods.length - 1].startDate;\r\n\t\t\t\t\tif (startDate > prevStartDate)\r\n\t\t\t\t\t\tstartDate = prevStartDate;\r\n\t\t\t\t\tthis.initialized = false;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tif (!this.initialized) {\r\n\t\t\t\tthis.timelineGranularityData = new TimelineGranularityData(startDate, endDate);\r\n\t\t\t\tthis.timelineData = {\r\n\t\t\t\t\telementsCount: 0,\r\n\t\t\t\t\ttimelineDatapoints: [],\r\n\t\t\t\t\tcursorDataPoints: new Array<CursorDatapoint>()\r\n\t\t\t\t};\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tpublic update(options: VisualUpdateOptions): void {\r\n\t\t\tlet visualChange: boolean = this.visualChangeOnly(options);\r\n\t\t\tthis.datasetsChangedState = this.datasetsChanged(options);\r\n\r\n\t\t\tthis.requiresNoUpdate = this.requiresNoUpdate && !this.datasetsChangedState && !visualChange;\r\n\t\t\tif (this.requiresNoUpdate) {\r\n\t\t\t\tthis.requiresNoUpdate = false;\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tthis.options = options;\r\n\t\t\tif (!options.dataViews || !options.dataViews[0])\r\n\t\t\t\treturn;\r\n\r\n\t\t\tlet validOptions: boolean = this.createTimelineOptions(options.dataViews[0]);\r\n\t\t\tif (!validOptions) {\r\n\t\t\t\tthis.clearData();\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tthis.newGranularity = this.defaultTimelineProperties.DefaultGranularity;\r\n\t\t\tif (!visualChange) this.createTimelineData();\r\n\r\n\t\t\tthis.timelineFormat = Timeline.converter(this.timelineData, this.timelineProperties, this.defaultTimelineProperties, this.timelineGranularityData, options.dataViews[0], this.initialized, this.newGranularity, options.viewport, this.timelineMargins);\r\n\t\t\tthis.render(this.timelineData, this.timelineFormat, this.timelineProperties, options);\r\n\t\t\tthis.initialized = true;\r\n\t\t}\r\n\r\n\t\tpublic selectPeriod(periodNameIndex): void {\r\n\t\t\tthis.redrawPeriod(periodNameIndex);\r\n\t\t\tthis.timelineFormat = Timeline.converter(this.timelineData, this.timelineProperties, this.defaultTimelineProperties, this.timelineGranularityData, this.options.dataViews[0], this.initialized, this.timelineData.currentGranularity.getType(), this.options.viewport, this.timelineMargins);\r\n\t\t\tthis.render(this.timelineData, this.timelineFormat, this.timelineProperties, this.options);\r\n\t\t}\r\n\r\n\t\tprivate static isDataNotMatch(dataView): boolean {\r\n\t\t\tif (dataView.categorical.categories.length <= 0 ||\r\n\t\t\t\tdataView.categorical.categories[0] === undefined ||\r\n\t\t\t\tdataView.categorical.categories[0].identityFields === undefined ||\r\n\t\t\t\tdataView.categorical.categories[0].identityFields.length <= 0)\r\n\t\t\t\treturn true;\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tpublic static converter(timelineData: TimelineData, timelineProperties: TimelineProperties, defaultTimelineProperties: DefaultTimelineProperties, timelineGranularityData: TimelineGranularityData, dataView: DataView, initialized: boolean, granularityType: GranularityType, viewport: IViewport, timelineMargins: TimelineMargins): TimelineFormat {\r\n\t\t\tlet timelineFormat = Timeline.fillTimelineFormat(dataView.metadata.objects, defaultTimelineProperties);\r\n\t\t\tif (!initialized) {\r\n\t\t\t\ttimelineData.cursorDataPoints.push({ x: 0, selectionIndex: 0, cursorIndex: 0 });\r\n\t\t\t\ttimelineData.cursorDataPoints.push({ x: 0, selectionIndex: 0, cursorIndex: 1 });\r\n\t\t\t}\r\n\t\t\tif (!initialized || Timeline.calendar.isChanged(timelineFormat.calendarFormat)) {\r\n\t\t\t\tTimeline.calendar = new Calendar(timelineFormat.calendarFormat);\r\n\t\t\t\ttimelineGranularityData.createGranularities();\r\n\t\t\t\ttimelineGranularityData.createLabels();\r\n\t\t\t\ttimelineData.currentGranularity = timelineGranularityData.getGranularity(granularityType);\r\n\t\t\t\ttimelineData.selectionStartIndex = 0;\r\n\t\t\t\ttimelineData.selectionEndIndex = timelineData.currentGranularity.getDatePeriods().length - 1;\r\n\t\t\t}\r\n\t\t\ttimelineData.categorySourceName = dataView.categorical.categories[0].source.displayName;\r\n\t\t\ttimelineData.columnIdentity = <powerbi.data.SQColumnRefExpr>dataView.categorical.categories[0].identityFields[0];\r\n\t\t\tif (dataView.categorical.categories[0].source.type.numeric) {\r\n\t\t\t\ttimelineData.columnIdentity.ref = \"Date\";\r\n\t\t\t}\r\n\t\t\tif (this.isDataNotMatch(dataView))\r\n\t\t\t\treturn;\r\n\t\t\tlet timelineElements: DatePeriod[] = timelineData.currentGranularity.getDatePeriods();\r\n\t\t\ttimelineData.elementsCount = timelineElements.length;\r\n\t\t\ttimelineData.timelineDatapoints = [];\r\n\t\t\tfor (let currentTimePeriod of timelineElements) {\r\n\t\t\t\tlet datapoint: TimelineDatapoint = {\r\n\t\t\t\t\tindex: currentTimePeriod.index,\r\n\t\t\t\t\tdatePeriod: currentTimePeriod\r\n\t\t\t\t};\r\n\t\t\t\ttimelineData.timelineDatapoints.push(datapoint);\r\n\t\t\t}\r\n\t\t\tlet countFullCells = timelineData.currentGranularity.getDatePeriods().filter((x) => x.index % 1 === 0).length;\r\n\t\t\tTimeline.setMeasures(timelineFormat.labelFormat, timelineData.currentGranularity.getType(), countFullCells, viewport, timelineProperties, timelineMargins);\r\n\t\t\tTimeline.updateCursors(timelineData, timelineProperties.cellWidth);\r\n\t\t\treturn timelineFormat;\r\n\t\t}\r\n\r\n\t\tprivate render(timelineData: TimelineData, timelineFormat: TimelineFormat, timelineProperties: TimelineProperties, options: VisualUpdateOptions): void {\r\n\t\t\tlet timelineDatapointsCount = this.timelineData.timelineDatapoints.filter((x) => x.index % 1 === 0).length;\r\n\t\t\tthis.svgWidth = 1 + this.timelineProperties.cellHeight + timelineProperties.cellWidth * timelineDatapointsCount;\r\n\t\t\tthis.renderTimeRangeText(timelineData, timelineFormat.rangeTextFormat);\r\n\t\t\tthis.fillColorGranularity(this.timelineFormat.granularityFormat);\r\n\t\t\tthis.timelineDiv.attr({\r\n\t\t\t\theight: px(options.viewport.height),\r\n\t\t\t\twidth: px(options.viewport.width),\r\n\t\t\t\t'drag-resize-disabled': true\r\n\t\t\t}).style({\r\n\t\t\t\t'overflow-x': 'auto',\r\n\t\t\t\t'overflow-y': 'auto'\r\n\t\t\t});\r\n\t\t\tthis.svg.attr({\r\n\t\t\t\theight: px(Math.max(0, options.viewport.height - this.timelineMargins.TopMargin)),\r\n\t\t\t\twidth: px(Math.max(0, this.svgWidth))\r\n\t\t\t});\r\n\t\t\tlet fixedTranslateString: string = SVGUtil.translate(timelineProperties.leftMargin, timelineProperties.topMargin);\r\n\t\t\tlet translateString: string = SVGUtil.translate(timelineProperties.cellHeight / 2, timelineProperties.topMargin);\r\n\t\t\tthis.mainGroupElement.attr('transform', translateString);\r\n\t\t\tthis.selectorContainer.attr('transform', fixedTranslateString);\r\n\t\t\tthis.cursorGroupElement.attr('transform', translateString);\r\n\r\n\t\t\tlet extendedLabels = this.timelineData.currentGranularity.getExtendedLabel();\r\n\t\t\tlet granularityType = this.timelineData.currentGranularity.getType();\r\n\t\t\tlet yPos = 0, yDiff = 1.50;\r\n\t\t\tthis.renderLabels(extendedLabels.yearLabels, this.yearLabelsElement, yPos, granularityType === 0);\r\n\t\t\tyPos += yDiff;\r\n\t\t\tthis.renderLabels(extendedLabels.quarterLabels, this.quarterLabelsElement, yPos, granularityType === 1);\r\n\t\t\tyPos += yDiff;\r\n\t\t\tthis.renderLabels(extendedLabels.monthLabels, this.monthLabelsElement, yPos, granularityType === 2);\r\n\t\t\tyPos += yDiff;\r\n\t\t\tthis.renderLabels(extendedLabels.weekLabels, this.weekLabelsElement, yPos, granularityType === 3);\r\n\t\t\tyPos += yDiff;\r\n\t\t\tthis.renderLabels(extendedLabels.dayLabels, this.dayLabelsElement, yPos, granularityType === 4);\r\n\t\t\tthis.renderCells(timelineData, timelineFormat, timelineProperties, options.suppressAnimations);\r\n\t\t\tthis.renderCursors(timelineData, timelineFormat, timelineProperties.cellHeight, timelineProperties.cellsYPosition);\r\n\t\t}\r\n\r\n\t\tprivate renderLabels(labels: TimelineLabel[], labelsElement: D3.Selection, index: number, isLast: boolean): void {\r\n\t\t\tlet labelTextSelection = labelsElement.selectAll(this.timelineSelectors.textLabel.selector);\r\n\t\t\tif (!this.timelineFormat.labelFormat.showProperty) {\r\n\t\t\t\tlabelTextSelection.remove();\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t\tlet labelsGroupSelection = labelTextSelection.data(labels);\r\n\t\t\tlabelsGroupSelection.enter().append('text').classed(this.timelineSelectors.textLabel.class, true);\r\n\r\n\t\t\tlabelsGroupSelection.text((x: TimelineLabel, id: number) => {\r\n\t\t\t\tif (!isLast && id === 0 && labels.length > 1) {\r\n\t\t\t\t\tlet fontSize = pt(this.timelineFormat.labelFormat.sizeProperty);\r\n\t\t\t\t\tlet textProperties: powerbi.TextProperties = {\r\n\t\t\t\t\t\ttext: labels[0].text,\r\n\t\t\t\t\t\tfontFamily: 'arial',\r\n\t\t\t\t\t\tfontSize: fontSize\r\n\t\t\t\t\t};\r\n\t\t\t\t\tlet halfFirstTextWidth = TextMeasurementService.measureSvgTextWidth(textProperties) / 2;\r\n\t\t\t\t\ttextProperties = {\r\n\t\t\t\t\t\ttext: labels[1].text,\r\n\t\t\t\t\t\tfontFamily: 'arial',\r\n\t\t\t\t\t\tfontSize: fontSize\r\n\t\t\t\t\t};\r\n\t\t\t\t\tlet halfSecondTextWidth = TextMeasurementService.measureSvgTextWidth(textProperties) / 2;\r\n\t\t\t\t\tlet diff = this.timelineProperties.cellWidth * (labels[1].id - labels[0].id);\r\n\t\t\t\t\tif (diff < halfFirstTextWidth + halfSecondTextWidth)\r\n\t\t\t\t\t\treturn \"\";\r\n\t\t\t\t}\r\n\t\t\t\tlet labelFormattedTextOptions: LabelFormattedTextOptions = {\r\n\t\t\t\t\tlabel: x.text,\r\n\t\t\t\t\tmaxWidth: this.timelineProperties.cellWidth * (isLast ? 0.90 : 3),\r\n\t\t\t\t\tfontSize: this.timelineFormat.labelFormat.sizeProperty\r\n\t\t\t\t};\r\n\t\t\t\treturn dataLabelUtils.getLabelFormattedText(labelFormattedTextOptions);\r\n\t\t\t})\r\n\t\t\t\t.style('font-size', pt(this.timelineFormat.labelFormat.sizeProperty))\r\n\t\t\t\t.attr({\r\n\t\t\t\t\tx: (x: TimelineLabel) => (x.id + 0.5) * this.timelineProperties.cellWidth,\r\n\t\t\t\t\ty: this.timelineProperties.textYPosition + (1 + index) * fromPointToPixel(this.timelineFormat.labelFormat.sizeProperty),\r\n\t\t\t\t\tfill: this.timelineFormat.labelFormat.colorProperty\r\n\t\t\t\t}).append('title').text((x: TimelineLabel) => x.title);\r\n\t\t\tlabelsGroupSelection.exit().remove();\r\n\t\t}\r\n\r\n\t\tprivate clearData(): void {\r\n\t\t\tthis.initialized = false;\r\n\t\t\tthis.mainGroupElement.selectAll(this.timelineSelectors.CellRect.selector).remove();\r\n\t\t\tthis.mainGroupElement.selectAll(this.timelineSelectors.textLabel.selector).remove();\r\n\t\t\tthis.rangeText.text(\"\");\r\n\t\t\tthis.cursorGroupElement.selectAll(this.timelineSelectors.SelectionCursor.selector).remove();\r\n\t\t\tthis.svg.select(this.timelineSelectors.TimelineSlicer.selector).remove();\r\n\t\t\tthis.mainGroupElement.selectAll(this.timelineSelectors.textLabel.selector);\r\n\t\t}\r\n\r\n\t\tprivate static updateCursors(timelineData: TimelineData, cellWidth: number): void {\r\n\t\t\tlet startDate: DatePeriod = timelineData.timelineDatapoints[timelineData.selectionStartIndex].datePeriod;\r\n\t\t\ttimelineData.cursorDataPoints[0].selectionIndex = startDate.index;\r\n\t\t\tlet endDate: DatePeriod = timelineData.timelineDatapoints[timelineData.selectionEndIndex].datePeriod;\r\n\t\t\ttimelineData.cursorDataPoints[1].selectionIndex = (endDate.index + endDate.fraction);\r\n\t\t}\r\n\r\n\t\tprivate static fillTimelineFormat(objects: any, timelineProperties: DefaultTimelineProperties): TimelineFormat {\r\n\t\t\tlet timelineFormat: TimelineFormat =\r\n\t\t\t\t{\r\n\t\t\t\t\trangeTextFormat: {\r\n\t\t\t\t\t\tshowProperty: DataViewObjects.getValue<boolean>(objects, TimeRangeShowProp, timelineProperties.TimelineDefaultTimeRangeShow),\r\n\t\t\t\t\t\tcolorProperty: DataViewObjects.getFillColor(objects, TimeRangeColorProp, timelineProperties.DefaultTimeRangeColor),\r\n\t\t\t\t\t\tsizeProperty: DataViewObjects.getValue<number>(objects, TimeRangeSizeProp, timelineProperties.TimelineDefaultTextSize)\r\n\t\t\t\t\t},\r\n\t\t\t\t\tcellFormat: {\r\n\t\t\t\t\t\tcolorInProperty: DataViewObjects.getFillColor(objects, SelectedCellColorProp, timelineProperties.TimelineDefaultCellColor),\r\n\t\t\t\t\t\tcolorOutProperty: DataViewObjects.getFillColor(objects, UnselectedCellColorProp, timelineProperties.TimelineDefaultCellColorOut)\r\n\t\t\t\t\t},\r\n\t\t\t\t\tgranularityFormat: {\r\n\t\t\t\t\t\tscaleColorProperty: DataViewObjects.getFillColor(objects, ScaleColorProp, timelineProperties.DefaultScaleColor),\r\n\t\t\t\t\t\tsliderColorProperty: DataViewObjects.getFillColor(objects, SliderColorProp, timelineProperties.DefaultSliderColor)\r\n\t\t\t\t\t},\r\n\t\t\t\t\tlabelFormat: {\r\n\t\t\t\t\t\tshowProperty: DataViewObjects.getValue<boolean>(objects, LabelsShowProp, timelineProperties.DefaultLabelsShow),\r\n\t\t\t\t\t\tcolorProperty: DataViewObjects.getFillColor(objects, LabelsColorProp, timelineProperties.DefaultLabelColor),\r\n\t\t\t\t\t\tsizeProperty: DataViewObjects.getValue<number>(objects, LabelsSizeProp, timelineProperties.TimelineDefaultTextSize)\r\n\t\t\t\t\t},\r\n\t\t\t\t\tcalendarFormat: {\r\n\t\t\t\t\t\tfirstMonthProperty: DataViewObjects.getValue<number>(objects, CalendarMonthProp, 1),\r\n\t\t\t\t\t\tfirstDayProperty: Math.max(1, Math.min(31, DataViewObjects.getValue<number>(objects, CalendarDayProp, timelineProperties.DefaultFirstDay))),\r\n\t\t\t\t\t\tweekDayProperty: Math.max(0, Math.min(6, DataViewObjects.getValue<number>(objects, WeekDayProp, timelineProperties.DefaultFirstWeekDay)))\r\n\t\t\t\t\t}\r\n\t\t\t\t};\r\n\t\t\treturn timelineFormat;\r\n\t\t}\r\n\r\n\t\tpublic fillCells(cellFormat: CellFormat): void {\r\n\t\t\tlet dataPoints = this.timelineData.timelineDatapoints;\r\n\t\t\tlet cellSelection = this.mainGroupElement.selectAll(this.timelineSelectors.CellRect.selector).data(dataPoints);\r\n\t\t\tcellSelection.attr('fill', d => Utils.getCellColor(d, this.timelineData, cellFormat));\r\n\t\t}\r\n\r\n\t\tpublic renderCells(timelineData: TimelineData, timelineFormat: TimelineFormat, timelineProperties: TimelineProperties, suppressAnimations: any): void {\r\n\t\t\tlet allDataPoints = timelineData.timelineDatapoints;\r\n\t\t\tlet totalX = 0;\r\n\t\t\tlet cellsSelection = this.cellsElement.selectAll(this.timelineSelectors.CellRect.selector).data(allDataPoints);\r\n\t\t\tcellsSelection.enter().append('rect').classed(this.timelineSelectors.CellRect.class, true);\r\n\t\t\tcellsSelection\r\n\t\t\t\t.attr({\r\n\t\t\t\t\theight: px(timelineProperties.cellHeight),\r\n\t\t\t\t\twidth: (d: TimelineDatapoint) => px(d.datePeriod.fraction * timelineProperties.cellWidth),\r\n\t\t\t\t\tx: (d: TimelineDatapoint) => {\r\n\t\t\t\t\t\tlet value = totalX;\r\n\t\t\t\t\t\ttotalX += d.datePeriod.fraction * timelineProperties.cellWidth;\r\n\t\t\t\t\t\treturn px(value);\r\n\t\t\t\t\t},\r\n\t\t\t\t\ty: px(timelineProperties.cellsYPosition),\r\n\t\t\t\t\tid: (d: TimelineDatapoint) => d.index\r\n\t\t\t\t});\r\n\r\n\t\t\tlet clickHandler: (d: TimelineDatapoint, index: number) => void = (d: TimelineDatapoint, index: number) => {\r\n\t\t\t\td3.event.preventDefault();\r\n\t\t\t\tlet cursorDataPoints = this.timelineData.cursorDataPoints;\r\n\t\t\t\tlet keyEvent: any = d3.event;\r\n\t\t\t\tif (keyEvent.altKey || keyEvent.shiftKey) {\r\n\t\t\t\t\tif (this.timelineData.selectionEndIndex < index) {\r\n\t\t\t\t\t\tcursorDataPoints[1].selectionIndex = (d.datePeriod.index + d.datePeriod.fraction);\r\n\t\t\t\t\t\ttimelineData.selectionEndIndex = index;\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse {\r\n\t\t\t\t\t\tcursorDataPoints[0].selectionIndex = d.datePeriod.index;\r\n\t\t\t\t\t\ttimelineData.selectionStartIndex = index;\r\n\t\t\t\t\t}\r\n\t\t\t\t} else {\r\n\t\t\t\t\ttimelineData.selectionStartIndex = index;\r\n\t\t\t\t\ttimelineData.selectionEndIndex = index;\r\n\t\t\t\t\tcursorDataPoints[0].selectionIndex = d.datePeriod.index;\r\n\t\t\t\t\tcursorDataPoints[1].selectionIndex = (d.datePeriod.index + d.datePeriod.fraction);\r\n\t\t\t\t}\r\n\r\n\t\t\t\tthis.fillCells(timelineFormat.cellFormat);\r\n\t\t\t\tthis.renderCursors(timelineData, timelineFormat, timelineProperties.cellHeight, timelineProperties.cellsYPosition);\r\n\t\t\t\tthis.renderTimeRangeText(timelineData, timelineFormat.rangeTextFormat);\r\n\t\t\t\tthis.fillColorGranularity(this.timelineFormat.granularityFormat);\r\n\t\t\t\tthis.setSelection(timelineData);\r\n\t\t\t};\r\n\r\n\t\t\tcellsSelection\r\n\t\t\t\t.on('click', clickHandler)\r\n\t\t\t\t.on(\"touchstart\", clickHandler);\r\n\r\n\t\t\tthis.fillCells(timelineFormat.cellFormat);\r\n\t\t\tcellsSelection.exit().remove();\r\n\t\t}\r\n\r\n\t\tpublic dragstarted(): void {\r\n\t\t\tthis.timelineData.dragging = true;\r\n\t\t}\r\n\r\n\t\tpublic dragged(currentCursor: CursorDatapoint): void {\r\n\t\t\tif (this.timelineData.dragging === true) {\r\n\t\t\t\tlet xScale = 1;\r\n\t\t\t\tlet container = d3.select(this.timelineSelectors.TimelineVisual.selector);\r\n\r\n\t\t\t\tif (container) {\r\n\t\t\t\t\tlet transform = container.style(\"transform\");\r\n\t\t\t\t\tif (transform !== undefined && transform !== 'none') {\r\n\t\t\t\t\t\tlet str = transform.split(\"(\")[1];\r\n\t\t\t\t\t\txScale = Number(str.split(\", \")[0]);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\tlet cursorOverElement: TimelineCursorOverElement = this.findCursorOverElement(d3.event.x);\r\n\r\n\t\t\t\tif (!cursorOverElement) {\r\n\t\t\t\t\treturn;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tlet currentlyMouseOverElement: TimelineDatapoint = cursorOverElement.datapoint,\r\n\t\t\t\t\tcurrentlyMouseOverElementIndex: number = cursorOverElement.index;\r\n\r\n\t\t\t\tif (currentCursor.cursorIndex === 0 && currentlyMouseOverElementIndex <= this.timelineData.selectionEndIndex) {\r\n\t\t\t\t\tthis.timelineData.selectionStartIndex = currentlyMouseOverElementIndex;\r\n\t\t\t\t\tthis.timelineData.cursorDataPoints[0].selectionIndex = currentlyMouseOverElement.datePeriod.index;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (currentCursor.cursorIndex === 1 && currentlyMouseOverElementIndex >= this.timelineData.selectionStartIndex) {\r\n\t\t\t\t\tthis.timelineData.selectionEndIndex = currentlyMouseOverElementIndex;\r\n\t\t\t\t\tthis.timelineData.cursorDataPoints[1].selectionIndex = (currentlyMouseOverElement.datePeriod.index + currentlyMouseOverElement.datePeriod.fraction);\r\n\t\t\t\t}\r\n\r\n\t\t\t\tthis.fillCells(this.timelineFormat.cellFormat);\r\n\t\t\t\tthis.renderCursors(this.timelineData, this.timelineFormat, this.timelineProperties.cellHeight, this.timelineProperties.cellsYPosition);\r\n\t\t\t\tthis.renderTimeRangeText(this.timelineData, this.timelineFormat.rangeTextFormat);\r\n\t\t\t\tthis.fillColorGranularity(this.timelineFormat.granularityFormat);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t/**\r\n\t\t * Note: Public for testability.\r\n\t\t */\r\n\t\tpublic findCursorOverElement(x: number): TimelineCursorOverElement {\r\n\t\t\tlet timelineDatapoints: TimelineDatapoint[] = this.timelineData.timelineDatapoints || [],\r\n\t\t\t\tlength: number = timelineDatapoints.length,\r\n\t\t\t\tcellWidth: number = this.timelineProperties.cellWidth;\r\n\r\n\t\t\tif (timelineDatapoints[0] && timelineDatapoints[1] && x <= timelineDatapoints[1].index * cellWidth) {\r\n\t\t\t\treturn {\r\n\t\t\t\t\tindex: 0,\r\n\t\t\t\t\tdatapoint: timelineDatapoints[0]\r\n\t\t\t\t};\r\n\t\t\t} else if (timelineDatapoints[length - 1] && x >= timelineDatapoints[length - 1].index * cellWidth) {\r\n\t\t\t\treturn {\r\n\t\t\t\t\tindex: length - 1,\r\n\t\t\t\t\tdatapoint: timelineDatapoints[length - 1]\r\n\t\t\t\t};\r\n\t\t\t}\r\n\r\n\t\t\tfor (let i = 1; i < length; i++) {\r\n\t\t\t\tlet left: number = timelineDatapoints[i].index * cellWidth,\r\n\t\t\t\t\tright: number = timelineDatapoints[i + 1].index * cellWidth;\r\n\r\n\t\t\t\tif (x >= left && x <= right) {\r\n\t\t\t\t\treturn {\r\n\t\t\t\t\t\tindex: i,\r\n\t\t\t\t\t\tdatapoint: timelineDatapoints[i]\r\n\t\t\t\t\t};\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tpublic dragended(): void {\r\n\t\t\tthis.setSelection(this.timelineData);\r\n\t\t}\r\n\r\n\t\tprivate drag = d3.behavior.drag()\r\n\t\t\t.origin((d: CursorDatapoint) => {\r\n\t\t\t\td.x = d.selectionIndex * this.timelineProperties.cellWidth;\r\n\r\n\t\t\t\treturn d;\r\n\t\t\t})\r\n\t\t\t.on(\"dragstart\", () => { this.dragstarted(); })\r\n\t\t\t.on(\"drag\", (d: CursorDatapoint) => { this.dragged(d); })\r\n\t\t\t.on(\"dragend\", () => { this.dragended(); });\r\n\r\n\t\tpublic renderCursors(timelineData: TimelineData, timelineFormat: TimelineFormat, cellHeight: number, cellsYPosition: number): D3.UpdateSelection {\r\n\t\t\tlet cursorSelection = this.cursorGroupElement.selectAll(this.timelineSelectors.SelectionCursor.selector).data(timelineData.cursorDataPoints);\r\n\t\t\tcursorSelection.enter().append('path').classed(this.timelineSelectors.SelectionCursor.class, true);\r\n\r\n\t\t\tcursorSelection.attr(\"transform\", (d: CursorDatapoint) => SVGUtil.translate(d.selectionIndex * this.timelineProperties.cellWidth, cellHeight / 2 + cellsYPosition)).attr({\r\n\t\t\t\td: d3.svg.arc()\r\n\t\t\t\t\t.innerRadius(0)\r\n\t\t\t\t\t.outerRadius(cellHeight / 2)\r\n\t\t\t\t\t.startAngle(d => d.cursorIndex * Math.PI + Math.PI)\r\n\t\t\t\t\t.endAngle(d => d.cursorIndex * Math.PI + 2 * Math.PI)\r\n\t\t\t})\r\n\t\t\t\t.call(this.drag);\r\n\r\n\t\t\tcursorSelection.exit().remove();\r\n\t\t\treturn cursorSelection;\r\n\t\t}\r\n\r\n\t\tpublic renderTimeRangeText(timelineData: TimelineData, timeRangeFormat: LabelFormat): void {\r\n\t\t\tlet leftMargin = (GranularityNames.length + 2) * this.timelineProperties.elementWidth;\r\n\t\t\tlet maxWidth = this.svgWidth - leftMargin - this.timelineProperties.leftMargin;\r\n\r\n\t\t\tif (timeRangeFormat.showProperty && maxWidth > 0) {\r\n\t\t\t\tlet timeRangeText = Utils.timeRangeText(timelineData);\r\n\t\t\t\tlet labelFormattedTextOptions: LabelFormattedTextOptions = {\r\n\t\t\t\t\tlabel: timeRangeText,\r\n\t\t\t\t\tmaxWidth: maxWidth,\r\n\t\t\t\t\tfontSize: timeRangeFormat.sizeProperty\r\n\t\t\t\t};\r\n\t\t\t\tlet actualText = dataLabelUtils.getLabelFormattedText(labelFormattedTextOptions);\r\n\t\t\t\tthis.rangeText.classed(this.timelineSelectors.SelectionRangeContainer.class, true);\r\n\t\t\t\tthis.rangeText.attr({\r\n\t\t\t\t\tx: (GranularityNames.length) * (this.timelineProperties.elementWidth + this.timelineProperties.leftMargin),\r\n\t\t\t\t\ty: 40,\r\n\t\t\t\t\tfill: timeRangeFormat.colorProperty\r\n\t\t\t\t})\r\n\t\t\t\t\t.style({\r\n\t\t\t\t\t\t'font-size': pt(timeRangeFormat.sizeProperty)\r\n\t\t\t\t\t}).text(actualText)\r\n\t\t\t\t\t.append('title').text(timeRangeText);;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t\tthis.rangeText.text(\"\");\r\n\t\t}\r\n\r\n\t\tpublic setSelection(timelineData: TimelineData): void {\r\n\t\t\tthis.requiresNoUpdate = true;\r\n\t\t\tlet lower: data.SQConstantExpr = powerbi.data.SQExprBuilder.dateTime(Utils.getStartSelectionDate(timelineData));\r\n\t\t\tlet upper: data.SQConstantExpr = powerbi.data.SQExprBuilder.dateTime(new Date(Utils.getEndSelectionDate(timelineData).getTime() - 1));\r\n\t\t\tlet filterExpr = powerbi.data.SQExprBuilder.between(timelineData.columnIdentity, lower, upper);\r\n\t\t\tlet filter = powerbi.data.SemanticFilter.fromSQExpr(filterExpr);\r\n\t\t\tlet objects: VisualObjectInstancesToPersist = {\r\n\t\t\t\tmerge: [\r\n\t\t\t\t\t<VisualObjectInstance>{\r\n\t\t\t\t\t\tobjectName: \"general\",\r\n\t\t\t\t\t\tselector: undefined,\r\n\t\t\t\t\t\tproperties: {\r\n\t\t\t\t\t\t\t\"filter\": filter,\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t]\r\n\t\t\t};\r\n\t\t\tthis.hostServices.persistProperties(objects);\r\n\t\t\tthis.hostServices.onSelect({ data: [] });\r\n\t\t}\r\n\r\n\t\t// This function retruns the values to be displayed in the property pane for each object.\r\n\t\t// Usually it is a bind pass of what the property pane gave you, but sometimes you may want to do\r\n\t\t// validation and return other values/defaults \r\n\t\tpublic enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstanceEnumeration {\r\n\t\t\tlet enumeration = new ObjectEnumerationBuilder();\r\n\t\t\tswitch (options.objectName) {\r\n\t\t\t\tcase 'rangeHeader':\r\n\t\t\t\t\tthis.enumerateRangeHeader(enumeration, this.dataView);\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase 'cells':\r\n\t\t\t\t\tthis.enumerateCells(enumeration, this.dataView);\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase 'granularity':\r\n\t\t\t\t\tthis.enumerateGranularity(enumeration, this.dataView);\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase 'labels':\r\n\t\t\t\t\tthis.enumerateLabels(enumeration, this.dataView);\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase 'calendar':\r\n\t\t\t\t\tthis.enumerateCalendar(enumeration, this.dataView);\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase 'weekDay':\r\n\t\t\t\t\tthis.enumerateWeekDay(enumeration, this.dataView);\r\n\t\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t\treturn enumeration.complete();\r\n\t\t}\r\n\r\n\t\tpublic enumerateRangeHeader(enumeration: ObjectEnumerationBuilder, dataview: DataView): void {\r\n\t\t\tlet objects = dataview && dataview.metadata ? dataview.metadata.objects : undefined;\r\n\t\t\tenumeration.pushInstance({\r\n\t\t\t\tobjectName: 'rangeHeader',\r\n\t\t\t\tdisplayName: 'Selection Color',\r\n\t\t\t\tselector: null,\r\n\t\t\t\tproperties: {\r\n\t\t\t\t\tshow: DataViewObjects.getValue<boolean>(objects, TimeRangeShowProp, this.defaultTimelineProperties.TimelineDefaultTimeRangeShow),\r\n\t\t\t\t\tfontColor: DataViewObjects.getFillColor(objects, TimeRangeColorProp, this.defaultTimelineProperties.DefaultTimeRangeColor),\r\n\t\t\t\t\ttextSize: DataViewObjects.getValue<number>(objects, TimeRangeSizeProp, this.defaultTimelineProperties.TimelineDefaultTextSize)\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t}\r\n\r\n\t\tpublic enumerateCells(enumeration: ObjectEnumerationBuilder, dataview: DataView): void {\r\n\t\t\tlet objects = dataview && dataview.metadata ? dataview.metadata.objects : undefined;\r\n\t\t\tenumeration.pushInstance({\r\n\t\t\t\tobjectName: 'cells',\r\n\t\t\t\tselector: null,\r\n\t\t\t\tproperties: {\r\n\t\t\t\t\tfillSelected: DataViewObjects.getFillColor(objects, SelectedCellColorProp, this.defaultTimelineProperties.TimelineDefaultCellColor),\r\n\t\t\t\t\tfillUnselected: DataViewObjects.getFillColor(objects, UnselectedCellColorProp, this.defaultTimelineProperties.TimelineDefaultCellColorOut)\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t}\r\n\r\n\t\tpublic enumerateGranularity(enumeration: ObjectEnumerationBuilder, dataview: DataView): void {\r\n\t\t\tvar objects = dataview && dataview.metadata ? dataview.metadata.objects : undefined;\r\n\t\t\tenumeration.pushInstance({\r\n\t\t\t\tobjectName: 'granularity',\r\n\t\t\t\tselector: null,\r\n\t\t\t\tproperties: {\r\n\t\t\t\t\tscaleColor: DataViewObjects.getFillColor(objects, ScaleColorProp, this.defaultTimelineProperties.DefaultScaleColor),\r\n\t\t\t\t\tsliderColor: DataViewObjects.getFillColor(objects, SliderColorProp, this.defaultTimelineProperties.DefaultSliderColor),\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t}\r\n\r\n\t\tpublic enumerateLabels(enumeration: ObjectEnumerationBuilder, dataview: DataView): void {\r\n\t\t\tlet objects = dataview && dataview.metadata ? dataview.metadata.objects : undefined;\r\n\t\t\tenumeration.pushInstance({\r\n\t\t\t\tobjectName: 'labels',\r\n\t\t\t\tselector: null,\r\n\t\t\t\tproperties: {\r\n\t\t\t\t\tshow: DataViewObjects.getValue<boolean>(objects, LabelsShowProp, this.defaultTimelineProperties.DefaultLabelsShow),\r\n\t\t\t\t\tfontColor: DataViewObjects.getFillColor(objects, LabelsColorProp, this.defaultTimelineProperties.DefaultLabelColor),\r\n\t\t\t\t\ttextSize: DataViewObjects.getValue<number>(objects, LabelsSizeProp, this.defaultTimelineProperties.TimelineDefaultTextSize)\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t}\r\n\r\n\t\tpublic enumerateCalendar(enumeration: ObjectEnumerationBuilder, dataview: DataView): void {\r\n\t\t\tlet objects = dataview && dataview.metadata ? dataview.metadata.objects : undefined;\r\n\t\t\tenumeration.pushInstance({\r\n\t\t\t\tobjectName: 'calendar',\r\n\t\t\t\tselector: null,\r\n\t\t\t\tproperties: {\r\n\t\t\t\t\tmonth: Math.max(1, Math.min(12, DataViewObjects.getValue<number>(objects, CalendarMonthProp, 1))),\r\n\t\t\t\t\tday: Math.max(1, Math.min(31, DataViewObjects.getValue<number>(objects, CalendarDayProp, 1))),\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t}\r\n\r\n\t\tpublic enumerateWeekDay(enumeration: ObjectEnumerationBuilder, dataview: DataView): void {\r\n\t\t\tlet objects = dataview && dataview.metadata ? dataview.metadata.objects : undefined;\r\n\t\t\tenumeration.pushInstance({\r\n\t\t\t\tobjectName: 'weekDay',\r\n\t\t\t\tselector: null,\r\n\t\t\t\tproperties: {\r\n\t\t\t\t\tday: Math.max(0, Math.min(6, DataViewObjects.getValue<number>(objects, WeekDayProp, 0)))\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t}\r\n\t}\r\n}\r\n\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/CustomVisuals/visuals/timeline/visual/timeline.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 import ValueFormatter = powerbi.visuals.valueFormatter;\r\n import ClassAndSelector = jsCommon.CssConstants.ClassAndSelector;\r\n import PixelConverter = jsCommon.PixelConverter;\r\n\r\n export interface StreamData {\r\n series: StreamGraphSeries[];\r\n legendData: LegendData;\r\n valueFormatter: IValueFormatter;\r\n categoryFormatter: IValueFormatter;\r\n streamGraphSettings: StreamGraphSettings;\r\n categoriesText: string[];\r\n }\r\n\r\n export interface StreamDataPoint {\r\n x: number;\r\n y: number;\r\n y0?: number;\r\n text: string;\r\n labelFontSize: string;\r\n }\r\n\r\n export interface StreamGraphSeries extends SelectableDataPoint {\r\n dataPoints: StreamDataPoint[];\r\n tooltipInfo?: TooltipDataItem[];\r\n highlight?: boolean;\r\n }\r\n\r\n export interface StreamGraphSettings {\r\n legendSettings: StreamGraphLegendSettings;\r\n categoryAxisSettings: StreamGraphAxisSettings;\r\n valueAxisSettings: StreamGraphAxisSettings;\r\n dataLabelsSettings: VisualDataLabelsSettings;\r\n }\r\n\r\n export interface StreamGraphLegendSettings {\r\n show: boolean;\r\n showTitle: boolean;\r\n labelColor: string;\r\n titleText: string;\r\n fontSize: number;\r\n }\r\n\r\n export interface StreamGraphAxisSettings {\r\n show: boolean;\r\n labelColor: string;\r\n showAxisTitle: boolean;\r\n }\r\n\r\n export interface StreamProperty {\r\n [propertyName: string]: DataViewObjectPropertyIdentifier;\r\n }\r\n\r\n const StreamGraphAxisGraphicsContextClassName = \"axisGraphicsContext\";\r\n const DataPointsContainer = \"dataPointsContainer\";\r\n const StreamGraphXAxisClassName = \"x axis\";\r\n const StreamGraphYAxisClassName = \"y axis\";\r\n const StreamGraphDefaultColor = \"#777\";\r\n const StreamGraphDefaultFontSizeInPoints: number = 8;\r\n const DefaultDataLabelsOffset: number = 4;\r\n const DefaultLabelTickWidth: number = 10;\r\n const DefaultLegendLabelFillColor: string = \"#666666\";\r\n const StreamGraphDefaultFontFamily: string = \"wf_segoe-ui_normal\";\r\n const StreamGraphDefaultFontWeight: string = \"normal\";\r\n const XAxisOnSize: number = 20;\r\n const XAxisOffSize: number = 10;\r\n const XAxisLabelSize: number = 20;\r\n const YAxisOnSize: number = 45;\r\n const YAxisOffSize: number = 10;\r\n const YAxisLabelSize: number = 20;\r\n const StreamGraphDefaultSettings: StreamGraphSettings = {\r\n legendSettings: {\r\n show: true,\r\n showTitle: true,\r\n labelColor: DefaultLegendLabelFillColor,\r\n titleText: \"\",\r\n fontSize: StreamGraphDefaultFontSizeInPoints\r\n },\r\n categoryAxisSettings: {\r\n show: true,\r\n labelColor: StreamGraphDefaultColor,\r\n showAxisTitle: false,\r\n },\r\n valueAxisSettings: {\r\n show: true,\r\n labelColor: StreamGraphDefaultColor,\r\n showAxisTitle: false,\r\n },\r\n dataLabelsSettings: dataLabelUtils.getDefaultPointLabelSettings(),\r\n };\r\n\r\n export interface StreamGraphBehaviorOptions {\r\n selection: D3.Selection;\r\n clearCatcher: D3.Selection;\r\n interactivityService: IInteractivityService;\r\n }\r\n\r\n class StreamGraphWebBehavior implements IInteractiveBehavior {\r\n private selection: D3.Selection;\r\n private clearCatcher: D3.Selection;\r\n private interactivityService: IInteractivityService;\r\n\r\n public bindEvents(options: StreamGraphBehaviorOptions, selectionHandler: ISelectionHandler) {\r\n this.selection = options.selection;\r\n this.clearCatcher = options.clearCatcher;\r\n this.interactivityService = options.interactivityService;\r\n\r\n this.selection.on(\"click\", (d: StreamGraphSeries, i: number) => {\r\n selectionHandler.handleSelection(d, d3.event.ctrlKey);\r\n });\r\n\r\n this.clearCatcher.on(\"click\", () => {\r\n selectionHandler.handleClearSelection();\r\n });\r\n }\r\n\r\n public renderSelection(hasSelection: boolean) {\r\n let hasHighlights = this.interactivityService.hasSelection();\r\n this.selection.style(\"fill-opacity\", (d: StreamGraphSeries) => {\r\n return ColumnUtil.getFillOpacity(d.selected, d.highlight, !d.highlight && hasSelection, !d.selected && hasHighlights);\r\n });\r\n }\r\n }\r\n\r\n export class StreamGraph implements IVisual {\r\n private static VisualClassName = \"streamGraph\";\r\n\r\n private static Properties: any = {\r\n general: {\r\n formatString: <DataViewObjectPropertyIdentifier>{\r\n objectName: \"general\",\r\n propertyName: \"formatString\"\r\n }\r\n },\r\n legend: {\r\n show: <DataViewObjectPropertyIdentifier>{\r\n objectName: \"legend\",\r\n propertyName: \"show\"\r\n },\r\n showTitle: <DataViewObjectPropertyIdentifier>{\r\n objectName: \"legend\",\r\n propertyName: \"showTitle\"\r\n },\r\n titleText: <DataViewObjectPropertyIdentifier>{\r\n objectName: \"legend\",\r\n propertyName: \"titleText\"\r\n },\r\n labelColor: <DataViewObjectPropertyIdentifier>{\r\n objectName: \"legend\",\r\n propertyName: \"labelColor\"\r\n },\r\n fontSize: <DataViewObjectPropertyIdentifier>{\r\n objectName: \"legend\",\r\n propertyName: \"fontSize\"\r\n }\r\n },\r\n categoryAxis: {\r\n show: <DataViewObjectPropertyIdentifier>{\r\n objectName: \"categoryAxis\",\r\n propertyName: \"show\"\r\n },\r\n labelColor: <DataViewObjectPropertyIdentifier>{\r\n objectName: \"categoryAxis\",\r\n propertyName: \"labelColor\"\r\n },\r\n showAxisTitle: <DataViewObjectPropertyIdentifier>{\r\n objectName: \"categoryAxis\",\r\n propertyName: \"showAxisTitle\"\r\n }\r\n },\r\n valueAxis: {\r\n show: <DataViewObjectPropertyIdentifier>{\r\n objectName: \"valueAxis\",\r\n propertyName: \"show\"\r\n },\r\n labelColor: <DataViewObjectPropertyIdentifier>{\r\n objectName: \"valueAxis\",\r\n propertyName: \"labelColor\"\r\n },\r\n showAxisTitle: <DataViewObjectPropertyIdentifier>{\r\n objectName: \"valueAxis\",\r\n propertyName: \"showAxisTitle\"\r\n }\r\n },\r\n labels: {\r\n show: <DataViewObjectPropertyIdentifier>{\r\n objectName: \"labels\",\r\n propertyName: \"show\"\r\n },\r\n color: <DataViewObjectPropertyIdentifier>{\r\n objectName: \"labels\",\r\n propertyName: \"color\"\r\n },\r\n fontSize: <DataViewObjectPropertyIdentifier>{\r\n objectName: \"labels\",\r\n propertyName: \"fontSize\"\r\n }\r\n }\r\n };\r\n\r\n private static Layer: ClassAndSelector = {\r\n \"class\": \"layer\",\r\n selector: \".layer\"\r\n };\r\n\r\n private static XAxisLabel: ClassAndSelector = {\r\n \"class\": \"xAxisLabel\",\r\n selector: \".xAxisLabel\"\r\n };\r\n\r\n private static YAxisLabel: ClassAndSelector = {\r\n \"class\": \"yAxisLabel\",\r\n selector: \".yAxisLabel\"\r\n };\r\n\r\n private static MaxNumberOfAxisXValues: number = 5;\r\n\r\n public static capabilities: VisualCapabilities = {\r\n dataRoles: [\r\n {\r\n name: \"Category\",\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: \"Category\",\r\n }, {\r\n name: \"Series\",\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: \"Series\",\r\n }, {\r\n name: \"Y\",\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: data.createDisplayNameGetter(\"Role_DisplayName_Values\"),\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: { bottom: {} }\r\n },\r\n values: {\r\n group: {\r\n by: \"Series\",\r\n select: [{ for: { in: \"Y\" } }],\r\n dataReductionAlgorithm: { bottom: {} }\r\n }\r\n },\r\n }\r\n }],\r\n objects: {\r\n general: {\r\n displayName: \"General\",\r\n properties: {\r\n formatString: { type: { formatting: { formatString: true } } },\r\n wiggle: {\r\n type: { bool: true },\r\n displayName: \"Wiggle\"\r\n }\r\n }\r\n },\r\n categoryAxis: {\r\n displayName: \"X-Axis\",\r\n properties: {\r\n show: {\r\n displayName: \"show\",\r\n type: { bool: true }\r\n },\r\n showAxisTitle: {\r\n displayName: \"Title\",\r\n type: { bool: true }\r\n },\r\n labelColor: {\r\n displayName: \"Color\",\r\n type: { fill: { solid: { color: true } } }\r\n }\r\n }\r\n },\r\n valueAxis: {\r\n displayName: \"Y-Axis\",\r\n properties: {\r\n show: {\r\n displayName: \"show\",\r\n type: { bool: true }\r\n },\r\n showAxisTitle: {\r\n displayName: \"Title\",\r\n type: { bool: true }\r\n },\r\n labelColor: {\r\n displayName: \"Color\",\r\n type: { fill: { solid: { color: true } } }\r\n }\r\n }\r\n },\r\n legend: {\r\n displayName: \"Legend\",\r\n properties: {\r\n show: {\r\n displayName: \"show\",\r\n type: { bool: true }\r\n },\r\n position: {\r\n displayName: \"Position\",\r\n type: { enumeration: legendPosition.type }\r\n },\r\n showTitle: {\r\n displayName: \"Title\",\r\n type: { bool: true }\r\n },\r\n titleText: {\r\n displayName: \"Legend Name\",\r\n type: { text: true },\r\n suppressFormatPainterCopy: true\r\n },\r\n labelColor: {\r\n displayName: \"Color\",\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n fontSize: {\r\n displayName: \"Text Size\",\r\n type: { formatting: { fontSize: true } }\r\n }\r\n }\r\n },\r\n labels: {\r\n displayName: \"Data Labels\",\r\n properties: {\r\n show: {\r\n displayName: \"Show\",\r\n type: { bool: true },\r\n },\r\n color: {\r\n displayName: \"Color\",\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n fontSize: {\r\n displayName: \"Text Size\",\r\n type: { formatting: { fontSize: true } },\r\n },\r\n }\r\n }\r\n },\r\n supportsHighlight: true,\r\n };\r\n\r\n private margin: IMargin = { left: YAxisOnSize, right: 15, bottom: XAxisOnSize, top: 10 };\r\n\r\n private viewport: IViewport;\r\n\r\n private svg: D3.Selection;\r\n private dataPointsContainer: D3.Selection;\r\n private axisGraphicsContext: D3.Selection;\r\n private xAxis: D3.Selection;\r\n private yAxis: D3.Selection;\r\n private clearCatcher: D3.Selection;\r\n private interactivityService: IInteractivityService;\r\n private behavior: IInteractiveBehavior;\r\n private colors: IDataColorPalette;\r\n private dataView: DataView;\r\n private legend: ILegend;\r\n private data: StreamData;\r\n\r\n public converter(dataView: DataView, colors: IDataColorPalette, interactivityService: IInteractivityService): StreamData {\r\n if (!dataView ||\r\n !dataView.categorical ||\r\n !dataView.categorical.values ||\r\n !dataView.categorical.categories ||\r\n !colors) {\r\n return null;\r\n }\r\n\r\n let catDv: DataViewCategorical = dataView.categorical,\r\n categories = catDv.categories,\r\n values: DataViewValueColumns = catDv.values,\r\n series: StreamGraphSeries[] = [],\r\n legendData: LegendData = {\r\n dataPoints: [],\r\n title: values.source ? values.source.displayName : \"\",\r\n fontSize: StreamGraphDefaultFontSizeInPoints,\r\n },\r\n value: number = 0,\r\n valueFormatter: IValueFormatter,\r\n categoryFormatter: IValueFormatter;\r\n\r\n let category = categories && categories.length > 0 ? categories[0] : null;\r\n let formatString = StreamGraph.Properties.general.formatString;\r\n let hasHighlights: boolean = !!(values.length > 0 && values[0].highlights);\r\n let streamGraphSettings: StreamGraphSettings = this.parseSettings(dataView);\r\n let fontSizeInPx = PixelConverter.fromPoint(streamGraphSettings.dataLabelsSettings.fontSize);\r\n\r\n for (let i = 0; i < values.length; i++) {\r\n let label = values[i].source.groupName;\r\n let identity: SelectionId = values[i].identity\r\n ? SelectionId.createWithId(values[i].identity)\r\n : SelectionId.createWithMeasure(values[i].source.queryName);\r\n\r\n if (label)\r\n legendData.dataPoints.push({\r\n label: label,\r\n color: colors.getColorByIndex(i).value,\r\n icon: LegendIcon.Box,\r\n selected: false,\r\n identity: identity\r\n });\r\n else\r\n label = values[i].source.displayName;\r\n\r\n let tooltipInfo: TooltipDataItem[] = TooltipBuilder.createTooltipInfo(\r\n formatString,\r\n { categories: null, values: values },\r\n null,\r\n null,\r\n null,\r\n null,\r\n i);\r\n\r\n series[i] = {\r\n dataPoints: [],\r\n tooltipInfo: tooltipInfo,\r\n highlight: hasHighlights,\r\n identity: identity,\r\n selected: false,\r\n };\r\n\r\n let dataPointsValues = values[i].values;\r\n if (dataPointsValues.length === 0)\r\n continue;\r\n\r\n for (let k = 0; k < dataPointsValues.length; k++) {\r\n let y: number = hasHighlights ? values[i].highlights[k] : dataPointsValues[k];\r\n if (y > value)\r\n value = y;\r\n\r\n series[i].dataPoints.push({\r\n x: k,\r\n y: isNaN(y) ? 0 : y,\r\n text: label,\r\n labelFontSize: fontSizeInPx\r\n });\r\n }\r\n }\r\n\r\n if (interactivityService)\r\n interactivityService.applySelectionStateToData(series);\r\n\r\n valueFormatter = ValueFormatter.create({\r\n format: \"g\",\r\n value: value\r\n });\r\n\r\n categoryFormatter = ValueFormatter.create({\r\n format: ValueFormatter.getFormatString(\r\n category.source,\r\n StreamGraph.Properties.general.formatString),\r\n value: category.values\r\n });\r\n\r\n let categoriesText: string[] = [];\r\n let getTextPropertiesFunction = this.getTextPropertiesFunction;\r\n\r\n for (let index = 0; index < category.values.length; index++) {\r\n let formattedValue: string;\r\n if (category.values[index] != null) {\r\n formattedValue = categoryFormatter.format(category.values[index]);\r\n let textLength = TextMeasurementService.measureSvgTextWidth(getTextPropertiesFunction(formattedValue));\r\n if (textLength > StreamGraph.MaxNumberOfAxisXValues)\r\n StreamGraph.MaxNumberOfAxisXValues = textLength;\r\n }\r\n categoriesText.push(formattedValue);\r\n }\r\n\r\n return {\r\n series: series,\r\n legendData: legendData,\r\n valueFormatter: valueFormatter,\r\n categoryFormatter: categoryFormatter,\r\n streamGraphSettings: streamGraphSettings,\r\n categoriesText: categoriesText\r\n };\r\n }\r\n\r\n private parseSettings(dataView: DataView): StreamGraphSettings {\r\n if (!dataView || !dataView.metadata)\r\n return StreamGraphDefaultSettings;\r\n\r\n let objects: DataViewObjects = dataView.metadata.objects;\r\n let streamGraphSettings: StreamGraphSettings = _.cloneDeep(StreamGraphDefaultSettings);\r\n\r\n let categoryAxisSettings: StreamGraphAxisSettings = streamGraphSettings.categoryAxisSettings;\r\n categoryAxisSettings.show = DataViewObjects.getValue<boolean>(objects, StreamGraph.Properties.categoryAxis.show, categoryAxisSettings.show);\r\n categoryAxisSettings.labelColor = <string>DataViewObjects.getFillColor(objects, StreamGraph.Properties.categoryAxis.labelColor, categoryAxisSettings.labelColor);\r\n categoryAxisSettings.showAxisTitle = DataViewObjects.getValue<boolean>(objects, StreamGraph.Properties.categoryAxis.showAxisTitle, categoryAxisSettings.showAxisTitle);\r\n\r\n let valueAxisSettings: StreamGraphAxisSettings = streamGraphSettings.valueAxisSettings;\r\n valueAxisSettings.show = DataViewObjects.getValue<boolean>(objects, StreamGraph.Properties.valueAxis.show, valueAxisSettings.show);\r\n valueAxisSettings.labelColor = <string>DataViewObjects.getFillColor(objects, StreamGraph.Properties.valueAxis.labelColor, valueAxisSettings.labelColor);\r\n valueAxisSettings.showAxisTitle = DataViewObjects.getValue<boolean>(objects, StreamGraph.Properties.valueAxis.showAxisTitle, valueAxisSettings.showAxisTitle);\r\n\r\n let dataLabelsSettings: VisualDataLabelsSettings = streamGraphSettings.dataLabelsSettings;\r\n dataLabelsSettings.show = DataViewObjects.getValue<boolean>(objects, StreamGraph.Properties.labels.show, dataLabelsSettings.show);\r\n dataLabelsSettings.labelColor = <string>DataViewObjects.getFillColor(objects, StreamGraph.Properties.labels.color, dataLabelsSettings.labelColor);\r\n dataLabelsSettings.fontSize = DataViewObjects.getValue<number>(objects, StreamGraph.Properties.labels.fontSize, dataLabelsSettings.fontSize);\r\n\r\n let legendSettings: StreamGraphLegendSettings = streamGraphSettings.legendSettings;\r\n let valuesSource: DataViewMetadataColumn = dataView.categorical.values.source;\r\n let titleTextDefault: string = valuesSource && _.isEmpty(legendSettings.titleText) ? valuesSource.displayName : legendSettings.titleText;\r\n\r\n legendSettings.show = DataViewObjects.getValue<boolean>(objects, StreamGraph.Properties.legend.show, legendSettings.show);\r\n legendSettings.showTitle = DataViewObjects.getValue<boolean>(objects, StreamGraph.Properties.legend.showTitle, legendSettings.showTitle);\r\n legendSettings.titleText = DataViewObjects.getValue<string>(objects, StreamGraph.Properties.legend.titleText, titleTextDefault);\r\n legendSettings.labelColor = DataViewObjects.getValue<string>(objects, StreamGraph.Properties.legend.labelColor, legendSettings.labelColor);\r\n legendSettings.fontSize = DataViewObjects.getValue<number>(objects, StreamGraph.Properties.legend.fontSize, legendSettings.fontSize);\r\n if (_.isEmpty(legendSettings.titleText))\r\n legendSettings.titleText = titleTextDefault; // Force a value (shouldn't be empty with show=true)\r\n\r\n return streamGraphSettings;\r\n }\r\n\r\n public init(options: VisualInitOptions): void {\r\n let element: JQuery = options.element;\r\n\r\n let svg: D3.Selection = this.svg = d3.select(element.get(0))\r\n .append(\"svg\")\r\n .classed(StreamGraph.VisualClassName, true)\r\n .style(\"position\", \"absolute\");\r\n\r\n this.clearCatcher = appendClearCatcher(svg);\r\n this.axisGraphicsContext = svg.append(\"g\").classed(StreamGraphAxisGraphicsContextClassName, true);\r\n this.xAxis = this.axisGraphicsContext.append(\"g\").classed(StreamGraphXAxisClassName, true);\r\n this.yAxis = this.axisGraphicsContext.append(\"g\").classed(StreamGraphYAxisClassName, true);\r\n this.dataPointsContainer = svg.append(\"g\").classed(DataPointsContainer, true);\r\n this.viewport = options.viewport;\r\n this.colors = options.style.colorPalette.dataColors;\r\n this.behavior = new StreamGraphWebBehavior();\r\n let interactivity = options.interactivity;\r\n this.interactivityService = createInteractivityService(options.host);\r\n this.legend = createLegend(element, interactivity && interactivity.isInteractiveLegend, this.interactivityService, true);\r\n }\r\n\r\n public update(options: VisualUpdateOptions): void {\r\n if (!options.dataViews || !options.dataViews[0] || !options.dataViews[0].categorical) {\r\n this.clearData();\r\n return;\r\n };\r\n\r\n this.viewport = {\r\n width: Math.max(0, options.viewport.width),\r\n height: Math.max(0, options.viewport.height)\r\n };\r\n\r\n let duration: number = options.suppressAnimations ? 0 : 250,\r\n dataView: DataView = this.dataView = options.dataViews[0],\r\n data: StreamData = this.data = this.converter(dataView, this.colors, this.interactivityService);\r\n\r\n if (!data || !data.series || !data.series.length) {\r\n this.clearData();\r\n return;\r\n }\r\n\r\n this.renderLegend(data);\r\n this.renderXAxisLabels();\r\n this.renderYAxisLabels();\r\n\r\n this.svg.attr({\r\n \"width\": this.viewport.width + \"px\",\r\n \"height\": this.viewport.height + \"px\"\r\n });\r\n\r\n let selection: D3.UpdateSelection = this.renderChart(data.series, duration);\r\n\r\n TooltipManager.addTooltip(selection, (tooltipEvent: TooltipEvent) => tooltipEvent.data.tooltipInfo);\r\n\r\n let interactivityService = this.interactivityService;\r\n\r\n if (interactivityService) {\r\n let behaviorOptions: StreamGraphBehaviorOptions = {\r\n selection: selection,\r\n clearCatcher: this.clearCatcher,\r\n interactivityService: interactivityService,\r\n };\r\n\r\n interactivityService.bind(data.series, this.behavior, behaviorOptions);\r\n }\r\n }\r\n\r\n private getStreamGraphLabelLayout(xScale: D3.Scale.LinearScale, yScale: D3.Scale.LinearScale): ILabelLayout {\r\n let dataLabelsSettings = this.data.streamGraphSettings.dataLabelsSettings;\r\n let fontSize = PixelConverter.fromPoint(dataLabelsSettings.fontSize);\r\n\r\n return {\r\n labelText: (d: StreamDataPoint) => {\r\n return d.text;\r\n },\r\n labelLayout: {\r\n x: (d: StreamDataPoint) => xScale(d.x),\r\n y: (d: StreamDataPoint) => yScale(d.y0)\r\n },\r\n filter: (d: StreamDataPoint) => {\r\n return (d != null && d.text != null);\r\n },\r\n style: {\r\n \"fill\": dataLabelsSettings.labelColor,\r\n \"font-size\": fontSize,\r\n },\r\n };\r\n }\r\n\r\n private renderChart(series: StreamGraphSeries[], duration: number): D3.UpdateSelection {\r\n let stack: D3.Layout.StackLayout = d3.layout.stack()\r\n .values((d: StreamGraphSeries) => d.dataPoints);\r\n let width: number = this.viewport.width;\r\n let height: number = this.viewport.height;\r\n\r\n if (this.getWiggle(this.dataView))\r\n stack.offset(\"wiggle\");\r\n\r\n let layers: StreamGraphSeries[] = stack(series);\r\n let margin: IMargin = this.margin;\r\n let xScale: D3.Scale.LinearScale = d3.scale.linear()\r\n .domain([0, series[0].dataPoints.length - 1])\r\n .range([margin.left, width - margin.right]);\r\n\r\n let yMax = d3.max(layers, (layer: StreamGraphSeries) => {\r\n return d3.max(layer.dataPoints, (d: StreamDataPoint) => {\r\n return d.y0 + d.y;\r\n });\r\n });\r\n\r\n let yMin = d3.min(layers, (layer: StreamGraphSeries) => {\r\n return d3.min(layer.dataPoints, (d: StreamDataPoint) => {\r\n return d.y0 + d.y;\r\n });\r\n });\r\n\r\n let yScale: D3.Scale.LinearScale = d3.scale.linear()\r\n .domain([Math.min(yMin, 0), yMax])\r\n .range([height - margin.bottom, margin.top])\r\n .nice();\r\n\r\n let area: D3.Svg.Area = d3.svg.area()\r\n .interpolate(\"monotone\")\r\n .x(d => xScale(d.x))\r\n .y0(d => yScale(d.y0))\r\n .y1(d => yScale(d.y0 + d.y))\r\n .defined((d: StreamDataPoint) => !isNaN(d.y0) && !isNaN(d.y));\r\n\r\n let selection: D3.UpdateSelection = this.dataPointsContainer.selectAll(StreamGraph.Layer.selector)\r\n .data(layers);\r\n\r\n selection.enter()\r\n .append(\"path\")\r\n .classed(StreamGraph.Layer.class, true);\r\n\r\n selection\r\n .style(\"fill\", (d: StreamGraphSeries, i) => this.colors.getColorByIndex(i).value)\r\n .style(\"fill-opacity\", ColumnUtil.DefaultOpacity)\r\n .transition()\r\n .duration(duration)\r\n .attr(\"d\", (d: StreamGraphSeries) => area(d.dataPoints));\r\n\r\n selection.selectAll(\"path\").append(\"g\").classed(DataPointsContainer, true);\r\n\r\n selection.exit().remove();\r\n\r\n if (this.data.streamGraphSettings.dataLabelsSettings.show) {\r\n let labelsXScale: D3.Scale.LinearScale = d3.scale.linear()\r\n .domain([0, series[0].dataPoints.length - 1])\r\n .range([0, width - margin.left - margin.right]);\r\n\r\n let layout = this.getStreamGraphLabelLayout(labelsXScale, yScale);\r\n\r\n // Merge all points into a single array\r\n let dataPointsArray: StreamDataPoint[] = [];\r\n\r\n series.forEach((seriesItem: StreamGraphSeries) => {\r\n let filteredDataPoints: StreamDataPoint[];\r\n\r\n filteredDataPoints = seriesItem.dataPoints.filter((dataPoint: StreamDataPoint) => {\r\n return dataPoint && dataPoint.y !== null && dataPoint.y !== undefined;\r\n });\r\n\r\n if (filteredDataPoints.length > 0) {\r\n dataPointsArray = dataPointsArray.concat(filteredDataPoints);\r\n }\r\n });\r\n\r\n let viewport: IViewport = {\r\n height: height - margin.top - margin.bottom,\r\n width: width - margin.right - margin.left,\r\n };\r\n\r\n let labels: D3.UpdateSelection = dataLabelUtils.drawDefaultLabelsForDataPointChart(dataPointsArray, this.svg, layout, viewport);\r\n\r\n if (labels) {\r\n let offset: number = DefaultDataLabelsOffset + margin.left;\r\n labels.attr(\"transform\", (d) => SVGUtil.translate(offset + (d.size.width / 2), d.size.height / 2));\r\n }\r\n }\r\n else {\r\n dataLabelUtils.cleanDataLabels(this.svg);\r\n }\r\n\r\n this.drawAxis(this.data, xScale, yScale);\r\n\r\n return selection;\r\n }\r\n\r\n private drawAxis(data: StreamData, xScale: D3.Scale.LinearScale, yScale: D3.Scale.LinearScale) {\r\n let margin: IMargin = this.margin,\r\n shiftY: number = this.viewport.height - margin.bottom,\r\n shiftX: number = this.viewport.width - margin.left - margin.right,\r\n categoriesText = this.data.categoriesText,\r\n xAxis: D3.Svg.Axis = d3.svg.axis(),\r\n maxNumberOfAxisXValues: number = StreamGraph.MaxNumberOfAxisXValues,\r\n getTextPropertiesFunction = this.getTextPropertiesFunction;\r\n\r\n for (let index = 0; index < categoriesText.length; index++) {\r\n if (categoriesText[index] != null) {\r\n let str = categoriesText[index].toString();\r\n let textLength = TextMeasurementService.measureSvgTextWidth(getTextPropertiesFunction(str));\r\n if (textLength > maxNumberOfAxisXValues)\r\n maxNumberOfAxisXValues = textLength;\r\n }\r\n }\r\n\r\n xAxis.scale(xScale)\r\n .orient(\"bottom\")\r\n .ticks(categoriesText.length)\r\n .tickFormat((index: number): string => {\r\n let item: string = categoriesText[index];\r\n\r\n if (data.categoryFormatter) {\r\n item = data.categoryFormatter.format(item);\r\n }\r\n\r\n if (index !== null && index !== undefined &&\r\n (index === 0 || index === categoriesText.length - 1)) {\r\n item = TextMeasurementService.getTailoredTextOrDefault(\r\n getTextPropertiesFunction(item),\r\n (index ? margin.right : margin.left) * 2);\r\n }\r\n\r\n return item;\r\n });\r\n\r\n let yAxis: D3.Svg.Axis = d3.svg.axis()\r\n .scale(yScale)\r\n .orient(\"left\")\r\n .tickFormat((item: any): any => {\r\n let tempItem = item;\r\n if (data.valueFormatter) {\r\n tempItem = data.valueFormatter.format(tempItem);\r\n }\r\n tempItem = TextMeasurementService.getTailoredTextOrDefault(getTextPropertiesFunction(tempItem.toString()), YAxisOnSize - DefaultLabelTickWidth);\r\n return tempItem;\r\n });\r\n\r\n this.setMaxTicks(xAxis, shiftX, Math.max(2, Math.round(shiftX / maxNumberOfAxisXValues)));\r\n this.setMaxTicks(yAxis, shiftY);\r\n\r\n let valueAxisSettings = this.data.streamGraphSettings.valueAxisSettings;\r\n if (valueAxisSettings.show) {\r\n let axisColor: Fill = valueAxisSettings.labelColor;\r\n this.yAxis\r\n .attr(\"transform\", SVGUtil.translate(margin.left, 0))\r\n .call(yAxis);\r\n this.yAxis.selectAll(\"text\").style(\"fill\", axisColor);\r\n } else\r\n this.yAxis.selectAll(\"*\").remove();\r\n\r\n let categoryAxisSettings = this.data.streamGraphSettings.categoryAxisSettings;\r\n if (categoryAxisSettings.show) {\r\n let axisColor: Fill = categoryAxisSettings.labelColor;\r\n this.xAxis\r\n .attr(\"transform\", SVGUtil.translate(0, shiftY))\r\n .call(xAxis);\r\n this.xAxis.selectAll(\"text\").style(\"fill\", axisColor);\r\n } else\r\n this.xAxis.selectAll(\"*\").remove();\r\n }\r\n\r\n private renderYAxisLabels(): void {\r\n this.axisGraphicsContext.selectAll(StreamGraph.YAxisLabel.selector).remove();\r\n let valueAxisSettings: StreamGraphAxisSettings = this.data.streamGraphSettings.valueAxisSettings;\r\n this.margin.left = valueAxisSettings.show ? YAxisOnSize : YAxisOffSize;\r\n\r\n if (valueAxisSettings.showAxisTitle) {\r\n this.margin.left += YAxisLabelSize;\r\n let categoryAxisSettings: StreamGraphAxisSettings = this.data.streamGraphSettings.categoryAxisSettings;\r\n let isXAxisOn: boolean = categoryAxisSettings.show === true;\r\n let isXTitleOn: boolean = categoryAxisSettings.showAxisTitle === true;\r\n let marginTop: number = this.margin.top;\r\n let height: number = this.viewport.height - marginTop - (isXAxisOn ? XAxisOnSize : XAxisOffSize) - (isXTitleOn ? XAxisLabelSize : 0);\r\n let values = this.dataView.categorical.values;\r\n let yAxisText: string = values.source ? values.source.displayName : this.getYAxisTitleFromValues(values);\r\n let textSettings: TextProperties = this.getTextPropertiesFunction(yAxisText);\r\n yAxisText = TextMeasurementService.getTailoredTextOrDefault(textSettings, height);\r\n let yAxisClass: string = StreamGraph.YAxisLabel.class;\r\n let yAxisLabel: D3.Selection = this.axisGraphicsContext.append(\"text\")\r\n .style(\"text-anchor\", \"middle\")\r\n .style(\"font-family\", textSettings.fontFamily)\r\n .style(\"font-size\", textSettings.fontSize)\r\n .style(\"font-style\", textSettings.fontStyle)\r\n .style(\"font-weight\", textSettings.fontWeight)\r\n .text(yAxisText)\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: yAxisClass,\r\n transform: \"rotate(-90)\",\r\n fill: valueAxisSettings.labelColor,\r\n x: -(marginTop + (height / 2)),\r\n dy: \"1em\"\r\n });\r\n });\r\n });\r\n\r\n yAxisLabel.call(AxisHelper.LabelLayoutStrategy.clip,\r\n height,\r\n TextMeasurementService.svgEllipsis);\r\n }\r\n }\r\n\r\n private getYAxisTitleFromValues(values: DataViewValueColumns): string {\r\n let valuesMetadataArray: powerbi.DataViewMetadataColumn[] = [];\r\n for (let i = 0; i < values.length; i++) {\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 let valuesNames: string[] = valuesMetadataArray.map(v => v ? v.displayName : \"\").filter((value, index, self) => value !== \"\" && self.indexOf(value) === index);\r\n return valueFormatter.formatListAnd(valuesNames);\r\n }\r\n\r\n private renderXAxisLabels(): void {\r\n this.axisGraphicsContext.selectAll(StreamGraph.XAxisLabel.selector).remove();\r\n let categoryAxisSettings = this.data.streamGraphSettings.categoryAxisSettings;\r\n this.margin.bottom = categoryAxisSettings.show ? XAxisOnSize : XAxisOffSize;\r\n\r\n if (categoryAxisSettings.showAxisTitle)\r\n if (this.dataView.categorical.categories[0].source) {\r\n this.margin.bottom += XAxisLabelSize;\r\n let valueAxisSettings: StreamGraphAxisSettings = this.data.streamGraphSettings.valueAxisSettings;\r\n let isYAxisOn: boolean = valueAxisSettings.show === true;\r\n let isYTitleOn: boolean = valueAxisSettings.showAxisTitle === true;\r\n let leftMargin: number = (isYAxisOn ? YAxisOnSize : YAxisOffSize) + (isYTitleOn ? YAxisLabelSize : 0);\r\n let width: number = this.viewport.width - this.margin.right - leftMargin;\r\n let height: number = this.viewport.height;\r\n let xAxisText: string = this.dataView.categorical.categories[0].source.displayName;\r\n let textSettings: TextProperties = this.getTextPropertiesFunction(xAxisText);\r\n xAxisText = TextMeasurementService.getTailoredTextOrDefault(textSettings, width);\r\n let xAxisClass: string = StreamGraph.XAxisLabel.class;\r\n let xAxisLabel: D3.Selection = this.axisGraphicsContext.append(\"text\")\r\n .style(\"text-anchor\", \"middle\")\r\n .style(\"font-family\", textSettings.fontFamily)\r\n .style(\"font-size\", textSettings.fontSize)\r\n .style(\"font-weight\", textSettings.fontWeight)\r\n .text(xAxisText)\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: xAxisClass,\r\n transform: SVGUtil.translate(leftMargin + (width / 2), height),\r\n fill: categoryAxisSettings.labelColor,\r\n dy: \"-0.5em\",\r\n });\r\n });\r\n });\r\n\r\n xAxisLabel.call(AxisHelper.LabelLayoutStrategy.clip,\r\n width,\r\n TextMeasurementService.svgEllipsis);\r\n }\r\n }\r\n\r\n private renderLegend(streamGraphData: StreamData): void {\r\n let legendSettings: StreamGraphLegendSettings = streamGraphData.streamGraphSettings.legendSettings;\r\n let legendData: LegendData = streamGraphData.legendData;\r\n if (!this.dataView || !this.dataView.metadata)\r\n return;\r\n\r\n let legendObjectProperties: DataViewObject = DataViewObjects.getObject(this.dataView.metadata.objects, \"legend\", {});\r\n legendObjectProperties[\"titleText\"] = legendSettings.titleText; // Force legend title when show = true\r\n LegendData.update(legendData, legendObjectProperties);\r\n\r\n let position: string = <string>legendObjectProperties[legendProps.position];\r\n\r\n if (position)\r\n this.legend.changeOrientation(LegendPosition[position]);\r\n\r\n this.legend.drawLegend(legendData, _.clone(this.viewport));\r\n Legend.positionChartArea(this.svg, this.legend);\r\n\r\n this.updateViewPort();\r\n }\r\n\r\n private updateViewPort(): void {\r\n let legendMargins: IViewport = this.legend.getMargins(),\r\n legendPosition: LegendPosition = this.legend.getOrientation();\r\n\r\n switch (legendPosition) {\r\n case LegendPosition.Top:\r\n case LegendPosition.TopCenter:\r\n case LegendPosition.Bottom:\r\n case LegendPosition.BottomCenter: {\r\n this.viewport.height = Math.max(0, this.viewport.height - legendMargins.height);\r\n break;\r\n }\r\n case LegendPosition.Left:\r\n case LegendPosition.LeftCenter:\r\n case LegendPosition.Right:\r\n case LegendPosition.RightCenter: {\r\n this.viewport.width = Math.max(0, this.viewport.width - legendMargins.width);\r\n break;\r\n }\r\n }\r\n }\r\n\r\n private setMaxTicks(axis: D3.Svg.Axis, maxSize: number, maxValue?: number): void {\r\n let maxTicks = maxValue === undefined\r\n ? this.getTicksByAxis(axis).length\r\n : Math.min(maxValue, this.getTicksByAxis(axis).length);\r\n\r\n if (axis.scale().domain.toString() === d3.scale.linear().domain.toString())\r\n axis.ticks(this.getFittedTickLength(axis, maxSize, maxTicks));\r\n else\r\n axis.tickValues(this.getFittedTickValues(axis, maxSize, maxTicks));\r\n }\r\n\r\n private getFittedTickLength(axis: D3.Svg.Axis, maxSize: number, maxTicks: number): number {\r\n for (let ticks: any[] = this.getTicksByAxis(axis), measureTickFunction = this.getMeasureTickFunction(axis, ticks);\r\n maxTicks > 0 && maxSize > 0 && (this.measureTicks(ticks, measureTickFunction) > maxSize || axis.scale().ticks([maxTicks]).length > maxTicks);\r\n maxTicks-- , ticks = this.getTicksByAxis(axis)) {\r\n axis.ticks(maxTicks);\r\n }\r\n return maxTicks;\r\n }\r\n\r\n private getFittedTickValues(axis: D3.Svg.Axis, maxSize: number, maxTicks: number): any[] {\r\n let ticks: any[] = this.getTicksByAxis(axis),\r\n maxWidthOf2Ticks: number,\r\n tickPairsWidths: any[] = [],\r\n measureTickFunction: (any) => number = this.getMeasureTickFunction(axis, ticks);\r\n\r\n for (let currentMaxTicks: number = maxTicks, indexes: number[] = [];\r\n maxTicks > 0 && maxSize > 0;\r\n currentMaxTicks-- , indexes = []) {\r\n switch (currentMaxTicks) {\r\n case 0:\r\n return [];\r\n case 1:\r\n indexes = [0];\r\n break;\r\n case 2:\r\n indexes = [0, ticks.length - 1];\r\n break;\r\n default:\r\n let takeEvery: number = ticks.length / (currentMaxTicks - 1);\r\n\r\n for (let i = 0; i < currentMaxTicks - 1; i++) {\r\n indexes.push(Math.round(takeEvery * i));\r\n }\r\n\r\n indexes.push(ticks.length - 1);\r\n break;\r\n }\r\n\r\n let ticksIndexes: any[][] = indexes.map(x => [ticks[x], x]);\r\n maxWidthOf2Ticks = (maxSize / ticks.length) * 2;\r\n\r\n ticksIndexes.reduce((a, b) => {\r\n tickPairsWidths.push([measureTickFunction(a[0]) + measureTickFunction(b[0]), (b[1] - a[1]) * maxWidthOf2Ticks]);\r\n return b;\r\n });\r\n\r\n if (!tickPairsWidths.some(x => x[0] > x[1])) {\r\n return ticksIndexes.map(x => x[0]);\r\n }\r\n }\r\n return [];\r\n }\r\n\r\n private measureTicks(ticks: any[], measureTickFunction: (number) => any): number {\r\n return ticks.map((x: any) => measureTickFunction(x)).reduce((a: number, b: number) => a + b);\r\n }\r\n\r\n private getTicksByAxis(axis: D3.Svg.Axis): any[] {\r\n let scale = axis.scale();\r\n let result: any = axis.tickValues() === null\r\n ? scale.ticks\r\n ? scale.ticks.apply(scale, axis.ticks())\r\n : scale.domain()\r\n : axis.tickValues();\r\n\r\n return result.length === undefined ? [result] : result;\r\n }\r\n\r\n private getMeasureTickFunction(axis: D3.Svg.Axis, ticks: string[]): (number) => any {\r\n let measureFunction = axis.orient() === \"top\" || axis.orient() === \"bottom\"\r\n ? TextMeasurementService.measureSvgTextWidth\r\n : TextMeasurementService.measureSvgTextHeight;\r\n let getTextPropertiesFunction = this.getTextPropertiesFunction;\r\n\r\n let cache = {};\r\n\r\n return function (x: any): number {\r\n return cache[x]\r\n ? cache[x]\r\n : cache[x] = measureFunction(getTextPropertiesFunction(axis.tickFormat()(x))) + axis.tickPadding();\r\n };\r\n }\r\n\r\n private getTextPropertiesFunction(text: string): TextProperties {\r\n let fontFamily: string = StreamGraphDefaultFontFamily,\r\n fontSize: string = PixelConverter.fromPoint(StreamGraphDefaultFontSizeInPoints),\r\n fontWeight: string = StreamGraphDefaultFontWeight;\r\n\r\n return { text: text, fontFamily: fontFamily, fontSize: fontSize, fontWeight: fontWeight };\r\n }\r\n\r\n private getWiggle(dataView: DataView) {\r\n if (dataView && dataView.metadata) {\r\n let objects = dataView.metadata.objects;\r\n\r\n if (objects) {\r\n let general = DataViewObjects.getObject(objects, \"general\", undefined);\r\n\r\n if (general)\r\n return <boolean>general[\"wiggle\"];\r\n }\r\n }\r\n return true;\r\n }\r\n\r\n private enumerateValueAxisValues(enumeration: ObjectEnumerationBuilder): void {\r\n let valueAxisSettings: StreamGraphAxisSettings = this.data && this.data.streamGraphSettings ? this.data.streamGraphSettings.valueAxisSettings : StreamGraphDefaultSettings.valueAxisSettings;\r\n\r\n enumeration.pushInstance({\r\n selector: null,\r\n objectName: \"valueAxis\",\r\n displayName: \"Y-Axis\",\r\n properties: {\r\n show: valueAxisSettings.show,\r\n showAxisTitle: valueAxisSettings.showAxisTitle,\r\n labelColor: valueAxisSettings.labelColor,\r\n }\r\n });\r\n }\r\n\r\n private enumerateCategoryAxisValues(enumeration: ObjectEnumerationBuilder): void {\r\n let categoryAxisSettings: StreamGraphAxisSettings = this.data && this.data.streamGraphSettings ? this.data.streamGraphSettings.categoryAxisSettings : StreamGraphDefaultSettings.categoryAxisSettings;\r\n\r\n enumeration.pushInstance({\r\n selector: null,\r\n objectName: \"categoryAxis\",\r\n displayName: \"X-Axis\",\r\n properties: {\r\n show: categoryAxisSettings.show,\r\n showAxisTitle: categoryAxisSettings.showAxisTitle,\r\n labelColor: categoryAxisSettings.labelColor,\r\n }\r\n });\r\n }\r\n\r\n private enumerateLegend(enumeration: ObjectEnumerationBuilder): void {\r\n let legendSettings: StreamGraphLegendSettings = this.data && this.data.streamGraphSettings ? this.data.streamGraphSettings.legendSettings : StreamGraphDefaultSettings.legendSettings;\r\n\r\n enumeration.pushInstance({\r\n selector: null,\r\n objectName: \"legend\",\r\n displayName: \"Legend\",\r\n properties: {\r\n show: legendSettings.show,\r\n position: LegendPosition[this.legend.getOrientation()],\r\n showTitle: legendSettings.showTitle,\r\n titleText: legendSettings.titleText,\r\n labelColor: legendSettings.labelColor,\r\n fontSize: legendSettings.fontSize,\r\n }\r\n });\r\n }\r\n\r\n private clearData() {\r\n this.svg.selectAll(StreamGraph.Layer.selector).remove();\r\n this.legend.drawLegend({ dataPoints: [] }, this.viewport);\r\n this.yAxis.selectAll(\"*\").remove();\r\n this.axisGraphicsContext.selectAll(StreamGraph.YAxisLabel.selector).remove();\r\n this.xAxis.selectAll(\"*\").remove();\r\n this.axisGraphicsContext.selectAll(StreamGraph.XAxisLabel.selector).remove();\r\n this.svg.select(\".labels\").remove();\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 enumeration: ObjectEnumerationBuilder = new ObjectEnumerationBuilder(),\r\n dataView = this.dataView;\r\n\r\n let dataLabelsSettings: any;\r\n if (this.data) {\r\n dataLabelsSettings = this.data.streamGraphSettings.dataLabelsSettings\r\n ? this.data.streamGraphSettings.dataLabelsSettings\r\n : StreamGraphDefaultSettings.dataLabelsSettings;\r\n }\r\n\r\n switch (options.objectName) {\r\n case \"legend\": {\r\n if (dataView\r\n && dataView.categorical\r\n && dataView.categorical.values\r\n && dataView.categorical.values.source)\r\n this.enumerateLegend(enumeration);\r\n break;\r\n }\r\n case \"categoryAxis\": {\r\n this.enumerateCategoryAxisValues(enumeration);\r\n break;\r\n }\r\n case \"valueAxis\": {\r\n this.enumerateValueAxisValues(enumeration);\r\n break;\r\n }\r\n case \"labels\": {\r\n let labelSettingOptions: VisualDataLabelsSettingsOptions = {\r\n enumeration: enumeration,\r\n dataLabelsSettings: dataLabelsSettings,\r\n show: true,\r\n fontSize: true,\r\n };\r\n\r\n dataLabelUtils.enumerateDataLabels(labelSettingOptions);\r\n break;\r\n }\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 wiggle: this.getWiggle(dataView)\r\n }\r\n };\r\n\r\n enumeration.pushInstance(general);\r\n break;\r\n }\r\n }\r\n\r\n return enumeration.complete();\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/CustomVisuals/visuals/streamGraph/visual/streamGraph.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 import ValueFormatter = powerbi.visuals.valueFormatter;\r\n import ClassAndSelector = jsCommon.CssConstants.ClassAndSelector;\r\n import createClassAndSelector = jsCommon.CssConstants.createClassAndSelector;\r\n import TextMeasurementService = powerbi.TextMeasurementService;\r\n import defaultLabelPrecision = dataLabelUtils.defaultLabelPrecision;\r\n import defaultLabelColor = dataLabelUtils.defaultLabelColor;\r\n import DefaultFontSizeInPt = dataLabelUtils.DefaultFontSizeInPt;\r\n import defaultLabelDensity = dataLabelUtils.defaultLabelDensity;\r\n\r\n export interface PulseChartConstructorOptions {\r\n animator?: IGenericAnimator;\r\n svg?: D3.Selection;\r\n behavior?: IInteractiveBehavior;\r\n }\r\n export interface PulseBehaviorOptions {\r\n layerOptions?: any[];\r\n clearCatcher: D3.Selection;\r\n }\r\n\r\n export interface TooltipSettings {\r\n dataPointColor: string;\r\n marginTop: number;\r\n timeHeight: number;\r\n }\r\n\r\n export interface PulseChartChartDataLabelsSettings extends PointDataLabelsSettings {\r\n labelDensity: string;\r\n }\r\n\r\n export interface PulseChartSeries extends SelectableDataPoint {\r\n name?: string;\r\n displayName: string;\r\n key: string;\r\n lineIndex: number;\r\n xCol: DataViewMetadataColumn;\r\n yCol: DataViewMetadataColumn;\r\n labelSettings: PulseChartChartDataLabelsSettings;\r\n data: PulseChartDataPoint[];\r\n color: string;\r\n identity: SelectionId;\r\n width: number;\r\n xAxisProperties?: PulseChartXAxisProperties;\r\n widthOfGap: number;\r\n }\r\n\r\n export interface PulseChartTooltipData {\r\n value: string;\r\n title: string;\r\n description: string;\r\n offsetX?: number;\r\n }\r\n\r\n export interface PulseChartAnimationPosition {\r\n series: number;\r\n index: number;\r\n }\r\n\r\n export interface PulseChartPointXY {\r\n x: number;\r\n y: number;\r\n }\r\n\r\n export interface PulseChartPrimitiveDataPoint\r\n extends TooltipEnabledDataPoint, SelectableDataPoint, LabelEnabledDataPoint {\r\n\r\n categoryValue: any;\r\n value: number;\r\n categoryIndex: number;\r\n seriesIndex: number;\r\n highlight?: boolean;\r\n key: string;\r\n labelSettings: PulseChartChartDataLabelsSettings;\r\n pointColor?: string;\r\n }\r\n\r\n export interface PulseChartDataPoint extends PulseChartPrimitiveDataPoint, PulseChartPointXY {\r\n groupIndex: number;\r\n popupInfo?: PulseChartTooltipData;\r\n eventSize: number;\r\n runnerCounterValue: any;\r\n runnerCounterFormatString: any;\r\n }\r\n\r\n export interface PulseChartLegend extends DataViewObject {\r\n show?: boolean;\r\n showTitle?: boolean;\r\n titleText?: string;\r\n position?: LegendPosition;\r\n }\r\n\r\n export interface PulseChartPopupSettings {\r\n show: boolean;\r\n alwaysOnTop: boolean;\r\n width: number;\r\n height: number;\r\n color: string;\r\n fontSize: number;\r\n fontColor: string;\r\n showTime: boolean;\r\n showTitle: boolean;\r\n timeColor: string;\r\n timeFill: string;\r\n }\r\n\r\n export interface PulseChartDotsSettings {\r\n color: string;\r\n size: number;\r\n minSize: number;\r\n maxSize: number;\r\n transparency: number;\r\n }\r\n\r\n export function createEnumTypeFromEnum(type: any): IEnumType {\r\n var even: any = false;\r\n return createEnumType(Object.keys(type)\r\n .filter((key,i) => ((!!(i % 2)) === even && type[key] === key && !void(even === !even)) || (!!(i % 2)) !== even)\r\n .map(x => <IEnumMember>{ value: x, displayName: x }));\r\n }\r\n\r\n export enum PulseChartXAxisDateFormat {\r\n //DateAndTime = <any>'Date and time',\r\n DateOnly = <any>'Date only',\r\n TimeOnly = <any>'Time only'\r\n }\r\n\r\n export enum XAxisPosition {\r\n Center = <any>'Center',\r\n Bottom = <any>'Bottom',\r\n }\r\n\r\n export enum RunnerCounterPosition {\r\n TopLeft = <any>'Top Left',\r\n TopRight = <any>'Top Right'\r\n }\r\n\r\n export interface PulseChartGapsSettings {\r\n show: boolean;\r\n visibleGapsPercentage: number;\r\n }\r\n\r\n export interface PulseChartSeriesSetting {\r\n fill: string;\r\n width: number;\r\n }\r\n\r\n export interface PulseChartPlaybackSettings {\r\n pauseDuration: number;\r\n playSpeed: number;\r\n autoplay: boolean;\r\n autoplayPauseDuration: number;\r\n color: string;\r\n position: PulseChartAnimationPosition;\r\n }\r\n\r\n export interface PulseChartRunnerCounterSettings {\r\n show: boolean;\r\n label: string;\r\n position: RunnerCounterPosition;\r\n fontSize: number;\r\n fontColor: string;\r\n }\r\n\r\n export interface PulseChartAxisSettings {\r\n formatterOptions?: ValueFormatterOptions;\r\n fontColor: string;\r\n color: string;\r\n show: boolean;\r\n }\r\n\r\n export interface PulseChartXAxisSettings extends PulseChartAxisSettings {\r\n position: XAxisPosition;\r\n dateFormat?: PulseChartXAxisDateFormat;\r\n backgroundColor: string;\r\n }\r\n\r\n export interface PulseChartYAxisSettings extends PulseChartAxisSettings {}\r\n\r\n export interface PulseChartSettings {\r\n formatStringProperty: DataViewObjectPropertyIdentifier;\r\n displayName?: string;\r\n dots: PulseChartDotsSettings;\r\n fillColor?: string;\r\n precision: number;\r\n legend?: PulseChartLegend;\r\n colors?: IColorPalette;\r\n series: PulseChartSeriesSetting;\r\n popup: PulseChartPopupSettings;\r\n gaps: PulseChartGapsSettings;\r\n xAxis: PulseChartXAxisSettings;\r\n yAxis: PulseChartYAxisSettings;\r\n runnerCounter: PulseChartRunnerCounterSettings;\r\n playback: PulseChartPlaybackSettings;\r\n }\r\n\r\n export interface PulseChartAxesLabels {\r\n x: string;\r\n y: string;\r\n y2?: string;\r\n }\r\n\r\n export interface PulseChartData {\r\n settings: PulseChartSettings;\r\n columns: PulseChartDataRoles<DataViewCategoricalColumn>;\r\n categoryMetadata: DataViewMetadataColumn;\r\n hasHighlights: boolean;\r\n\r\n series: PulseChartSeries[];\r\n isScalar?: boolean;\r\n dataLabelsSettings: PointDataLabelsSettings;\r\n axesLabels: PulseChartAxesLabels;\r\n hasDynamicSeries?: boolean;\r\n defaultSeriesColor?: string;\r\n categoryData?: PulseChartPrimitiveDataPoint[];\r\n\r\n categories: any[];\r\n legendData?: LegendData;\r\n\r\n grouped: DataViewValueColumnGroup[];\r\n\r\n xScale?: D3.Scale.TimeScale | D3.Scale.LinearScale;\r\n commonYScale?: D3.Scale.LinearScale;\r\n yScales?: D3.Scale.LinearScale[];\r\n yAxis?: D3.Svg.Axis;\r\n\r\n widthOfXAxisLabel: number;\r\n widthOfTooltipValueLabel: number;\r\n heightOfTooltipDescriptionTextLine: number;\r\n runnerCounterHeight: number;\r\n }\r\n\r\n export interface PulseChartProperty {\r\n [propertyName: string]: DataViewObjectPropertyIdentifier;\r\n }\r\n\r\n export interface PulseChartProperties {\r\n [objectName: string]: PulseChartProperty;\r\n }\r\n\r\n export interface PulseChartXAxisProperties {\r\n values: (Date | number)[];\r\n scale: D3.Scale.TimeScale;\r\n axis: D3.Svg.Axis;\r\n rotate: boolean;\r\n }\r\n\r\n export interface PulseChartPoint {\r\n x: number;\r\n value: Date | number;\r\n }\r\n\r\n export interface PulseChartDataRoles<T> {\r\n Timestamp?: T;\r\n Category?: T;\r\n Value?: T;\r\n EventTitle?: T;\r\n EventDescription?: T;\r\n EventSize?: T;\r\n RunnerCounter?: T;\r\n }\r\n\r\n export interface PulseChartElementDimensions {\r\n x: number;\r\n y: number;\r\n width: number;\r\n height: number;\r\n }\r\n\r\n export class PulseChart implements IVisual {\r\n public static RoleDisplayNames = <PulseChartDataRoles<string>> {\r\n Timestamp: \"Timestamp\",\r\n Category: \"Category\",\r\n Value: \"Value\",\r\n EventTitle: \"Event Title\",\r\n EventDescription: \"Event Description\",\r\n EventSize: \"Event Size\",\r\n RunnerCounter: \"Runner Counter\",\r\n };\r\n\r\n public static RoleNames = <PulseChartDataRoles<string>>_.mapValues(PulseChart.RoleDisplayNames, (x, i) => i);\r\n\r\n public static capabilities: VisualCapabilities = {\r\n dataRoles: [\r\n {\r\n displayName: PulseChart.RoleDisplayNames.Timestamp,\r\n name: PulseChart.RoleNames.Timestamp,\r\n kind: powerbi.VisualDataRoleKind.Grouping\r\n },\r\n {\r\n displayName: PulseChart.RoleDisplayNames.Value,\r\n name: PulseChart.RoleNames.Value,\r\n kind: powerbi.VisualDataRoleKind.Measure\r\n },\r\n /* Temporary disabled\r\n {\r\n displayName: PulseChart.RoleDisplayNames.Category,\r\n name: PulseChart.RoleNames.Category,\r\n kind: powerbi.VisualDataRoleKind.Grouping\r\n },*/\r\n {\r\n displayName: PulseChart.RoleDisplayNames.EventTitle,\r\n name: PulseChart.RoleNames.EventTitle,\r\n kind: powerbi.VisualDataRoleKind.GroupingOrMeasure\r\n },\r\n {\r\n displayName: PulseChart.RoleDisplayNames.EventDescription,\r\n name: PulseChart.RoleNames.EventDescription,\r\n kind: powerbi.VisualDataRoleKind.GroupingOrMeasure\r\n },\r\n {\r\n displayName: PulseChart.RoleDisplayNames.EventSize,\r\n name: PulseChart.RoleNames.EventSize,\r\n kind: powerbi.VisualDataRoleKind.GroupingOrMeasure\r\n },\r\n {\r\n displayName: PulseChart.RoleDisplayNames.RunnerCounter,\r\n name: PulseChart.RoleNames.RunnerCounter,\r\n kind: powerbi.VisualDataRoleKind.GroupingOrMeasure\r\n },\r\n ],\r\n dataViewMappings: [{\r\n conditions: <any>[\r\n <PulseChartDataRoles<NumberRange>> {\r\n Timestamp: { max: 1 },\r\n Value: { max: 1 },\r\n Category: { max: 1 },\r\n EventTitle: { max: 1 },\r\n EventDescription: { max: 1 },\r\n EventSize: { max: 1 },\r\n RunnerCounter: { max: 1 },\r\n }\r\n ],\r\n categorical: {\r\n categories: {\r\n for: { in: PulseChart.RoleNames.Timestamp },\r\n dataReductionAlgorithm: { top: { count: 10000 } }\r\n },\r\n values: {\r\n group: {\r\n by: PulseChart.RoleNames.Category,\r\n select: [\r\n { bind: { to: PulseChart.RoleNames.Value } },\r\n { bind: { to: PulseChart.RoleNames.EventTitle } },\r\n { bind: { to: PulseChart.RoleNames.EventDescription } },\r\n { bind: { to: PulseChart.RoleNames.EventSize } },\r\n { bind: { to: PulseChart.RoleNames.RunnerCounter } },\r\n ],\r\n dataReductionAlgorithm: { top: {} }\r\n },\r\n },\r\n },\r\n }],\r\n objects: {\r\n series: {\r\n displayName: \"Series\",\r\n description: \"Series\",\r\n properties: {\r\n fill: {\r\n displayName: data.createDisplayNameGetter('Visual_Fill'),\r\n type: {\r\n fill: {\r\n solid: {\r\n color: true\r\n }\r\n }\r\n }\r\n },\r\n width: {\r\n displayName: 'Width',\r\n type: {\r\n numeric: true\r\n }\r\n },\r\n }\r\n },\r\n gaps: {\r\n displayName: \"Gaps\",\r\n description: \"Gaps\",\r\n properties: {\r\n show: {\r\n displayName: data.createDisplayNameGetter(\"Visual_Show\"),\r\n type: { bool: true }\r\n },\r\n transparency: {//visibleGapsPercentage\r\n displayName: 'Visible gaps',\r\n type: { numeric: true }\r\n },\r\n }\r\n },\r\n general: {\r\n displayName: 'General',\r\n properties: {\r\n formatString: { type: { formatting: { formatString: true } } },\r\n fill: {\r\n displayName: 'Background color',\r\n type: { fill: { solid: { color: true } } }\r\n }\r\n }\r\n },\r\n popup: {\r\n displayName: 'Popup',\r\n properties: {\r\n show: {\r\n displayName: data.createDisplayNameGetter(\"Visual_Show\"),\r\n type: { bool: true }\r\n },\r\n alwaysOnTop: {\r\n displayName: 'Always on top',\r\n type: { bool: true }\r\n },\r\n width: {\r\n displayName: 'Width',\r\n type: {\r\n numeric: true\r\n }\r\n },\r\n height: {\r\n displayName: 'Height',\r\n type: {\r\n numeric: true\r\n }\r\n },\r\n color: {\r\n displayName: data.createDisplayNameGetter('Visual_Fill'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n fontSize: {\r\n displayName: \"Text size\",\r\n type: { formatting: { fontSize: true } }\r\n },\r\n fontColor: {\r\n displayName: \"Text color\",\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n showTime: {\r\n displayName: 'Show time',\r\n type: { bool: true }\r\n },\r\n showTitle: {\r\n displayName: 'Show title',\r\n type: { bool: true }\r\n },\r\n timeColor: {\r\n displayName: \"Time color\",\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n timeFill: {\r\n displayName: \"Time fill\",\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n }\r\n },\r\n dots: {\r\n displayName: 'Dots',\r\n properties: {\r\n color: {\r\n displayName: data.createDisplayNameGetter('Visual_Fill'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n minSize: {\r\n displayName: \"Min Size\",\r\n type: { numeric: true }\r\n },\r\n maxSize: {\r\n displayName: \"Max Size\",\r\n type: { numeric: true }\r\n },\r\n size: {\r\n displayName: \"Default Size\",\r\n type: { numeric: true }\r\n },\r\n transparency: {\r\n displayName: 'Transparency',\r\n type: { numeric: true }\r\n },\r\n }\r\n },\r\n xAxis: {\r\n displayName: data.createDisplayNameGetter('Visual_XAxis'),\r\n properties: {\r\n show: {\r\n displayName: data.createDisplayNameGetter(\"Visual_Show\"),\r\n type: { bool: true }\r\n },\r\n position: {\r\n displayName: \"Position\",\r\n type: { enumeration: createEnumTypeFromEnum(XAxisPosition) }\r\n },\r\n fontColor: {\r\n displayName: \"Font Color\",\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n color: {\r\n displayName: \"Axis Color\",\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n backgroundColor: {\r\n displayName: \"Background Color\",\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n }\r\n },\r\n yAxis: {\r\n displayName: data.createDisplayNameGetter('Visual_YAxis'),\r\n properties: {\r\n show: {\r\n displayName: data.createDisplayNameGetter(\"Visual_Show\"),\r\n type: { bool: true }\r\n },\r\n fontColor: {\r\n displayName: \"Font Color\",\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n color: {\r\n displayName: \"Axis Color\",\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n }\r\n },\r\n playback: {\r\n displayName: 'Playback',\r\n properties: {\r\n autoplay: {\r\n displayName: \"Autoplay\",\r\n type: { bool: true }\r\n },\r\n playSpeed: {\r\n displayName: \"Speed (dots/sec)\",\r\n type: { numeric: true }\r\n },\r\n pauseDuration: {\r\n displayName: \"Pause Duration\",\r\n type: { numeric: true }\r\n },\r\n autoplayPauseDuration: {\r\n displayName: \"Start Delay\",\r\n type: { numeric: true }\r\n },\r\n color: {\r\n displayName: \"Buttons Color\",\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n position: {\r\n displayName: \"Position\",\r\n type: { text: true }\r\n }\r\n }\r\n },\r\n runnerCounter: {\r\n displayName: 'Runner Counter',\r\n properties: {\r\n show: {\r\n displayName: data.createDisplayNameGetter(\"Visual_Show\"),\r\n type: { bool: true }\r\n },\r\n label: {\r\n displayName: \"Label\",\r\n type: { text: true }\r\n },\r\n position: {\r\n displayName: \"Position\",\r\n type: { enumeration: createEnumTypeFromEnum(RunnerCounterPosition) }\r\n },\r\n fontSize: {\r\n displayName: \"Text Size\",\r\n type: { formatting: { fontSize: true } }\r\n },\r\n fontColor: {\r\n displayName: \"Font Color\",\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n }\r\n },\r\n },\r\n sorting: {\r\n implicit: {\r\n clauses: [{\r\n role: PulseChart.RoleNames.Timestamp,\r\n direction: 1//SortDirection.Ascending\r\n }]\r\n }\r\n },\r\n supportsHighlight: true\r\n };\r\n\r\n private static Properties: PulseChartProperties = PulseChart.getProperties(PulseChart.capabilities);\r\n public static getProperties(capabilities: VisualCapabilities): any {\r\n var result = {};\r\n for(var objectKey in capabilities.objects) {\r\n result[objectKey] = {};\r\n for(var propKey in capabilities.objects[objectKey].properties) {\r\n result[objectKey][propKey] = <DataViewObjectPropertyIdentifier> {\r\n objectName: objectKey,\r\n propertyName: propKey\r\n };\r\n }\r\n }\r\n\r\n return result;\r\n }\r\n\r\n private static DefaultMargin: IMargin = {\r\n top: 20,\r\n bottom: 20,\r\n right: 25,\r\n left: 25,\r\n };\r\n\r\n private static DefaultViewport: IViewport = {\r\n width: 50,\r\n height: 50\r\n };\r\n\r\n private static PlaybackButtonsHeight = 26;\r\n private static PopupMinHeight: number = 20;\r\n private static PopupMinWidth: number = 20;\r\n private static PopupMaxHeight: number = 200;\r\n private static PopupMaxWidth: number = 2000;\r\n private static MaxWidthOfYAxis: number = 50;\r\n private static PopupTextPadding: number = 3;\r\n private static XAxisTickSpace: number = 15;\r\n private static XAxisTickHeight: number = 16;\r\n private static MinimumTicksToRotate: number = 3;\r\n private static AxisTickRotateAngle: number = -35;\r\n\r\n private static GetPopupValueTextProperties(text?: string, fontSizeValue = 12): TextProperties {\r\n return {\r\n text: text || \"\",\r\n fontFamily: \"sans-serif\",\r\n fontSize: fontSizeValue + \"px\",\r\n };\r\n }\r\n\r\n private static GetPopupTitleTextProperties(text?: string, fontSizeValue = 12): TextProperties {\r\n return {\r\n text: text || \"\",\r\n fontFamily: \"sans-serif\",\r\n fontWeight: \"bold\",\r\n fontSize: fontSizeValue + \"px\",\r\n };\r\n }\r\n\r\n private static GetPopupDescriptionTextProperties(text?: string, fontSizeValue = 12): TextProperties {\r\n return {\r\n text: text || \"\",\r\n fontFamily: \"sans-serif\",\r\n fontSize: fontSizeValue + \"px\",\r\n };\r\n }\r\n\r\n public static GetRunnerCounterTextProperties(text?: string, fontSizeValue = 12): TextProperties {\r\n return {\r\n text: text || \"\",\r\n fontFamily: \"sans-serif\",\r\n fontSize: fontSizeValue + \"px\",\r\n };\r\n }\r\n\r\n public static ConvertTextPropertiesToStyle(textProperties: TextProperties): Object {\r\n return {\r\n 'font-family': textProperties.fontFamily,\r\n 'font-weight': textProperties.fontWeight,\r\n 'font-size': textProperties.fontSize\r\n };\r\n }\r\n\r\n private static GetDateTimeFormatString(dateFormatType: PulseChartXAxisDateFormat, dateFormat: string): string {\r\n switch(dateFormatType) {\r\n case PulseChartXAxisDateFormat.DateOnly: return dateFormat;\r\n case PulseChartXAxisDateFormat.TimeOnly: return \"H:mm\";\r\n default: return \"\";\r\n };\r\n }\r\n\r\n private static GetFullWidthOfDateFormat(dateFormat: string, textProperties: TextProperties): number {\r\n textProperties.text = valueFormatter.create({ format: dateFormat }).format(new Date(2000,10,20,20,20,20));\r\n return TextMeasurementService.measureSvgTextWidth(textProperties);\r\n }\r\n\r\n public static AddOnTouchClick(selection: D3.Selection, callback: (data: any, index: number) => any): D3.Selection {\r\n var preventDefaultCallback = (data: any, index: number) => { d3.event.preventDefault(); callback(data, index); };\r\n return selection.on(\"click\", preventDefaultCallback).on(\"touchstart\", preventDefaultCallback);\r\n }\r\n\r\n private static DefaultSettings: PulseChartSettings = {\r\n precision: 0,\r\n popup: {\r\n show: true,\r\n alwaysOnTop: false,\r\n width: 100,\r\n height: 80,\r\n color: \"#808181\",\r\n fontSize: 10,\r\n fontColor: 'white',\r\n showTime: true,\r\n showTitle: true,\r\n timeColor: 'white',\r\n timeFill: '#010101',\r\n },\r\n dots: {\r\n color: \"#808181\",\r\n size: 5,\r\n minSize: 5,\r\n maxSize: 20,\r\n transparency: 25,\r\n },\r\n gaps: {\r\n show: false,\r\n visibleGapsPercentage: 1\r\n },\r\n series: {\r\n fill: '#3779B7',\r\n width: 2,\r\n },\r\n xAxis: {\r\n color: \"#777777\",\r\n fontColor: \"#777777\",\r\n position: XAxisPosition.Center,\r\n show: true,\r\n dateFormat: PulseChartXAxisDateFormat.TimeOnly,\r\n backgroundColor: \"#E1F2F7\"\r\n },\r\n yAxis: {\r\n color: \"#777777\",\r\n fontColor: \"#777777\",\r\n show: true\r\n },\r\n playback: {\r\n autoplay: false,\r\n playSpeed: 5,\r\n pauseDuration: 10,\r\n autoplayPauseDuration: 0,\r\n color: \"#777\",\r\n position: null,\r\n },\r\n runnerCounter: {\r\n show: true,\r\n label: \"\",\r\n position: RunnerCounterPosition.TopRight,\r\n fontSize: 13,\r\n fontColor: \"#777777\"\r\n },\r\n formatStringProperty: PulseChart.Properties[\"general\"][\"formatString\"]\r\n };\r\n\r\n private static DefaultTooltipSettings: TooltipSettings = {\r\n dataPointColor: \"#808181\",\r\n marginTop: 20,\r\n timeHeight: 15,\r\n };\r\n\r\n private static MaxGapCount: number = 100;\r\n private static MinGapWidth = <[number]>_.object(<any>[[\r\n PulseChartXAxisDateFormat.DateOnly, 60 * 1000 * 24], [\r\n PulseChartXAxisDateFormat.TimeOnly, 60 * 1000],\r\n ], undefined);\r\n\r\n private static Chart: ClassAndSelector = createClassAndSelector('chart');\r\n private static Line: ClassAndSelector = createClassAndSelector('line');\r\n private static LineContainer: ClassAndSelector = createClassAndSelector('lineContainer');\r\n private static LineNode: ClassAndSelector = createClassAndSelector('lineNode');\r\n private static XAxisNode: ClassAndSelector = createClassAndSelector('xAxisNode');\r\n private static Dot: ClassAndSelector = createClassAndSelector('dot');\r\n private static DotsContainer: ClassAndSelector = createClassAndSelector('dotsContainer');\r\n private static Tooltip: ClassAndSelector = createClassAndSelector('Tooltip');\r\n private static TooltipRect: ClassAndSelector = createClassAndSelector('tooltipRect');\r\n private static TooltipTriangle: ClassAndSelector = createClassAndSelector('tooltipTriangle');\r\n private static Gaps: ClassAndSelector = createClassAndSelector(\"gaps\");\r\n private static Gap: ClassAndSelector = createClassAndSelector(\"gap\");\r\n private static GapNode: ClassAndSelector = createClassAndSelector(\"gapNode\");\r\n private static TooltipLine: ClassAndSelector = createClassAndSelector('tooltipLine');\r\n private static TooltipTime: ClassAndSelector = createClassAndSelector('tooltipTime');\r\n private static TooltipTimeRect: ClassAndSelector = createClassAndSelector('tooltipTimeRect');\r\n private static TooltipTitle: ClassAndSelector = createClassAndSelector('tooltipTitle');\r\n private static TooltipDescription: ClassAndSelector = createClassAndSelector('tooltipDescription');\r\n private static TooltipContainer: ClassAndSelector = createClassAndSelector('tooltipContainer');\r\n private static AnimationDot: ClassAndSelector = createClassAndSelector('animationDot');\r\n\r\n private static getCategoricalColumnOfRole(dataView: DataView, roleName: string): DataViewCategoryColumn | DataViewValueColumn {\r\n var filterFunc = (cols: DataViewCategoricalColumn[]) => cols.filter((x) => x.source && x.source.roles && x.source.roles[roleName])[0];\r\n return filterFunc(dataView.categorical.categories) || filterFunc(dataView.categorical.values);\r\n }\r\n\r\n public static converter(dataView: DataView, colors: IDataColorPalette, interactivityService?: IInteractivityService): PulseChartData {\r\n if (!dataView\r\n || !dataView.categorical\r\n || !dataView.categorical.values\r\n || !dataView.categorical.values[0]\r\n || !dataView.categorical.values[0].values\r\n || !dataView.categorical.categories) {\r\n return null;\r\n }\r\n\r\n var columns: PulseChartDataRoles<DataViewCategoricalColumn> = <any>_.mapValues(PulseChart.RoleNames, (x,i) => PulseChart.getCategoricalColumnOfRole(dataView, i));\r\n var timeStampColumn = <DataViewCategoryColumn>columns.Timestamp;\r\n\r\n if (!timeStampColumn) {\r\n return null;\r\n }\r\n\r\n var isScalar: boolean = !(timeStampColumn.source && timeStampColumn.source.type && timeStampColumn.source.type.dateTime);\r\n var settings: PulseChartSettings = PulseChart.parseSettings(dataView, colors, columns);\r\n\r\n var categoryValues: any[] = timeStampColumn.values;\r\n\r\n if (!categoryValues || _.isEmpty(dataView.categorical.values) || !columns.Value || _.isEmpty(columns.Value.values)) {\r\n return null;\r\n }\r\n\r\n var minValuesValue = Math.min.apply(null, columns.Value.values), maxValuesValue = Math.max.apply(null, columns.Value.values);\r\n var minCategoryValue = Math.min.apply(null, categoryValues), maxCategoryValue = Math.max.apply(null, categoryValues);\r\n settings.xAxis.dateFormat =\r\n (maxCategoryValue - minCategoryValue < (24 * 60 * 60 * 1000)\r\n && new Date(maxCategoryValue).getDate() === new Date(minCategoryValue).getDate())\r\n ? PulseChartXAxisDateFormat.TimeOnly\r\n : PulseChartXAxisDateFormat.DateOnly;\r\n\r\n settings.xAxis.formatterOptions = {\r\n value: isScalar ? minCategoryValue : new Date(minCategoryValue),\r\n value2: isScalar ? maxCategoryValue : new Date(maxCategoryValue)\r\n };\r\n settings.yAxis.formatterOptions = {\r\n value: minValuesValue,\r\n value2: maxValuesValue,\r\n format: ValueFormatter.getFormatString(columns.Value.source, PulseChart.DefaultSettings.formatStringProperty)\r\n };\r\n\r\n if (isScalar) {\r\n settings.xAxis.formatterOptions.format = ValueFormatter.getFormatString(timeStampColumn.source,\r\n PulseChart.DefaultSettings.formatStringProperty);\r\n } else {\r\n settings.xAxis.formatterOptions.format = PulseChart.GetDateTimeFormatString(settings.xAxis.dateFormat, timeStampColumn.source.format);\r\n }\r\n\r\n var widthOfXAxisLabel = 70;\r\n var widthOfTooltipValueLabel = isScalar ? 60 : PulseChart.GetFullWidthOfDateFormat(timeStampColumn.source.format, PulseChart.GetPopupValueTextProperties()) + 5;\r\n var heightOfTooltipDescriptionTextLine = TextMeasurementService.measureSvgTextHeight(PulseChart.GetPopupDescriptionTextProperties(\"lj\", settings.popup.fontSize));\r\n var runnerCounterFormatString = columns.RunnerCounter && visuals.valueFormatter.getFormatString(columns.RunnerCounter.source, settings.formatStringProperty);\r\n settings.popup.width = Math.max(widthOfTooltipValueLabel + 20, settings.popup.width);\r\n\r\n var minSize: number = PulseChart.DefaultSettings.dots.minSize;\r\n var maxSize: number = PulseChart.DefaultSettings.dots.maxSize;\r\n if (settings.dots) {\r\n minSize = settings.dots.minSize;\r\n maxSize = settings.dots.maxSize;\r\n }\r\n\r\n var eventSizeScale: D3.Scale.LinearScale = <D3.Scale.LinearScale> PulseChart.createScale(\r\n true,\r\n columns.EventSize ? [d3.min(columns.EventSize.values), d3.max(columns.EventSize.values)] : [0, 0],\r\n minSize,\r\n maxSize);\r\n\r\n var xAxisCardProperties: DataViewObject = PulseChartAxisPropertiesHelper.getCategoryAxisProperties(dataView.metadata);\r\n\r\n var hasDynamicSeries: boolean = !!(timeStampColumn.values && timeStampColumn.source);\r\n\r\n var dataPointLabelSettings = PulseChartDataLabelUtils.getDefaultPulseChartLabelSettings();\r\n var gapWidths = PulseChart.getGapWidths(categoryValues);\r\n var maxGapWidth = Math.max.apply(null, gapWidths);\r\n\r\n var firstValueMeasureIndex: number = 0, firstGroupIndex: number = 0, secondGroupIndex = 1;\r\n var grouped: DataViewValueColumnGroup[] = dataView.categorical.values && dataView.categorical.values.grouped();\r\n var y_group0Values = grouped[firstGroupIndex]\r\n && grouped[firstGroupIndex].values[firstValueMeasureIndex]\r\n && grouped[firstGroupIndex].values[firstValueMeasureIndex].values;\r\n var y_group1Values = grouped[secondGroupIndex]\r\n && grouped[secondGroupIndex].values[firstValueMeasureIndex]\r\n && grouped[secondGroupIndex].values[firstValueMeasureIndex].values;\r\n\r\n var series: PulseChartSeries[] = [];\r\n var dataPoints: PulseChartDataPoint[] = [];\r\n\r\n for (var categoryIndex = 0, seriesCategoryIndex = 0, len = timeStampColumn.values.length; categoryIndex < len; categoryIndex++ , seriesCategoryIndex++) {\r\n var categoryValue = categoryValues[categoryIndex];\r\n var value = AxisHelper.normalizeNonFiniteNumber(timeStampColumn.values[categoryIndex]);\r\n var runnerCounterValue = columns.RunnerCounter && columns.RunnerCounter.values && columns.RunnerCounter.values[categoryIndex];\r\n var identity = SelectionIdBuilder.builder().withCategory(timeStampColumn, categoryIndex).createSelectionId();\r\n\r\n var minGapWidth: number = Math.max((maxCategoryValue - minCategoryValue) / PulseChart.MaxGapCount, PulseChart.MinGapWidth[settings.xAxis.dateFormat]);\r\n var gapWidth: number = gapWidths[categoryIndex];\r\n var isGap: boolean = settings.gaps.show\r\n && gapWidth > 0\r\n && gapWidth > (minGapWidth + (100 - settings.gaps.visibleGapsPercentage) * (maxGapWidth - minGapWidth) / 100);\r\n\r\n if (isGap && dataPoints.length > 0) {\r\n series.push({\r\n displayName: grouped[firstGroupIndex].name,\r\n key: identity.getKey(),\r\n lineIndex: series.length,\r\n color: settings.series.fill,\r\n xCol: timeStampColumn.source,\r\n yCol: timeStampColumn.source,\r\n data: dataPoints,\r\n identity: identity,\r\n selected: false,\r\n labelSettings: dataPointLabelSettings,\r\n width: settings.series.width,\r\n widthOfGap: gapWidth\r\n });\r\n\r\n seriesCategoryIndex = 0;\r\n dataPoints = [];\r\n }\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\r\n var popupInfo: PulseChartTooltipData = null;\r\n var eventSize = (columns.EventSize && columns.EventSize.values && columns.EventSize.values[categoryIndex]) || 0;\r\n\r\n if ((columns.EventTitle && columns.EventTitle.values && columns.EventTitle.values[categoryIndex]) ||\r\n (columns.EventDescription && columns.EventDescription.values && columns.EventDescription.values[categoryIndex])) {\r\n var formattedValue = categoryValue;\r\n\r\n if (!isScalar && categoryValue) {\r\n formattedValue = valueFormatter.create({ format: timeStampColumn.source.format }).format(categoryValue);\r\n }\r\n\r\n popupInfo = {\r\n value: formattedValue,\r\n title: columns.EventTitle && columns.EventTitle.values && columns.EventTitle.values[categoryIndex],\r\n description: columns.EventDescription && columns.EventDescription.values && columns.EventDescription.values[categoryIndex],\r\n };\r\n }\r\n\r\n var dataPoint: PulseChartDataPoint = {\r\n categoryValue: categoryValue,\r\n value: value,\r\n categoryIndex: categoryIndex,\r\n seriesIndex: series.length,\r\n tooltipInfo: null,//tooltipInfo,\r\n popupInfo: popupInfo,\r\n selected: false,\r\n identity: identity,\r\n key: JSON.stringify({ ser: identity.getKey(), catIdx: categoryIndex }),\r\n labelFill: dataPointLabelSettings.labelColor,\r\n labelSettings: dataPointLabelSettings,\r\n x: categoryValue,\r\n y: (y_group0Values && y_group0Values[categoryIndex]) || (y_group1Values && y_group1Values[categoryIndex]) || 0,\r\n pointColor: settings.series.fill,\r\n groupIndex: PulseChart.getGroupIndex(categoryIndex, grouped),\r\n eventSize: columns.EventSize ? eventSizeScale(eventSize) : 0,\r\n runnerCounterValue: runnerCounterValue,\r\n runnerCounterFormatString: runnerCounterFormatString,\r\n };\r\n\r\n dataPoints.push(dataPoint);\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: grouped[firstGroupIndex].name,\r\n key: identity.getKey(),\r\n lineIndex: series.length,\r\n color: settings.series.fill,\r\n xCol: timeStampColumn.source,\r\n yCol: timeStampColumn.source,\r\n data: dataPoints,\r\n identity: identity,\r\n selected: false,\r\n labelSettings: dataPointLabelSettings,\r\n width: settings.series.width,\r\n widthOfGap: 0\r\n });\r\n }\r\n\r\n xAxisCardProperties = PulseChartAxisPropertiesHelper.getCategoryAxisProperties(dataView.metadata);\r\n var valueAxisProperties = PulseChartAxisPropertiesHelper.getValueAxisProperties(dataView.metadata);\r\n\r\n var values = dataView.categorical.categories;\r\n\r\n // Convert to DataViewMetadataColumn\r\n var valuesMetadataArray: powerbi.DataViewMetadataColumn[] = [];\r\n if (values) {\r\n for (var 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 var axesLabels = converterHelper.createAxesLabels(xAxisCardProperties, valueAxisProperties, timeStampColumn.source, valuesMetadataArray);\r\n if (interactivityService) {\r\n interactivityService.applySelectionStateToData(series);\r\n }\r\n\r\n return {\r\n columns: columns,\r\n series: series,\r\n isScalar: isScalar,\r\n dataLabelsSettings: dataPointLabelSettings,\r\n axesLabels: { x: axesLabels.xAxisLabel, y: axesLabels.yAxisLabel },\r\n hasDynamicSeries: hasDynamicSeries,\r\n categoryMetadata: timeStampColumn.source,\r\n categories: categoryValues,\r\n settings: settings,\r\n grouped: grouped,\r\n hasHighlights: !!(<any>columns.Value).highlights,\r\n widthOfXAxisLabel: widthOfXAxisLabel,\r\n widthOfTooltipValueLabel: widthOfTooltipValueLabel,\r\n heightOfTooltipDescriptionTextLine: heightOfTooltipDescriptionTextLine,\r\n runnerCounterHeight: TextMeasurementService.measureSvgTextHeight(\r\n PulseChart.GetRunnerCounterTextProperties(\"lj\", settings.runnerCounter.fontSize))\r\n };\r\n }\r\n\r\n private static createAxisY(\r\n commonYScale: D3.Scale.LinearScale,\r\n height: number,\r\n formatterOptions: ValueFormatterOptions,\r\n show: boolean = true): D3.Svg.Axis {\r\n\r\n var formatter = valueFormatter.create(formatterOptions);\r\n var ticks: number = Math.max(2, Math.round(height / 40));\r\n var yAxis: D3.Svg.Axis = d3.svg.axis()\r\n .scale(commonYScale)\r\n .ticks(ticks)\r\n .outerTickSize(0)\r\n .tickFormat(formatter.format);\r\n return yAxis;\r\n }\r\n\r\n private static createAxisX(\r\n isScalar: boolean,\r\n series: PulseChartSeries[],\r\n originalScale: D3.Scale.GenericScale<D3.Scale.TimeScale | D3.Scale.LinearScale>,\r\n formatterOptions: ValueFormatterOptions,\r\n dateFormat: PulseChartXAxisDateFormat,\r\n position: XAxisPosition,\r\n widthOfXAxisLabel: number): PulseChartXAxisProperties[] {\r\n\r\n var scales = PulseChart.getXAxisScales(series, isScalar, originalScale);\r\n var xAxisProperties = new Array<PulseChartXAxisProperties>(scales.length);\r\n\r\n for(var i = 0, rotate = false; i < xAxisProperties.length; i++) {\r\n var values = PulseChart.getXAxisValuesToDisplay(<any>scales[i], rotate, isScalar, dateFormat, widthOfXAxisLabel);\r\n\r\n if(!rotate\r\n && position === XAxisPosition.Bottom\r\n && values.length < PulseChart.MinimumTicksToRotate) {\r\n var rotatedValues = PulseChart.getXAxisValuesToDisplay(<any>scales[i], true, isScalar, dateFormat, widthOfXAxisLabel);\r\n if(rotatedValues.length > values.length) {\r\n rotate = true;\r\n i = -1;\r\n continue;\r\n }\r\n }\r\n\r\n xAxisProperties[i] = <PulseChartXAxisProperties>{ values: values, scale: scales[i], rotate: rotate };\r\n }\r\n\r\n formatterOptions.tickCount = xAxisProperties.length && xAxisProperties.map(x => x.values.length).reduce((a, b) => a + b) * 5;\r\n formatterOptions.value = originalScale.domain()[0];\r\n formatterOptions.value2 = originalScale.domain()[1];\r\n\r\n xAxisProperties.forEach((properties: PulseChartXAxisProperties) => {\r\n var values: (Date | number)[] = properties.values.filter((value: Date | number) => value !== null);\r\n\r\n var formatter = valueFormatter.create(formatterOptions);\r\n properties.axis = d3.svg.axis()\r\n .scale(properties.scale)\r\n .tickValues(values)\r\n .tickFormat(formatter.format)\r\n .outerTickSize(0);\r\n });\r\n\r\n return xAxisProperties;\r\n }\r\n\r\n private static getXAxisScales(\r\n series: PulseChartSeries[],\r\n isScalar: boolean,\r\n originalScale: D3.Scale.GenericScale<D3.Scale.TimeScale | D3.Scale.LinearScale>): D3.Scale.GenericScale<D3.Scale.TimeScale | D3.Scale.LinearScale>[] {\r\n return series.map((seriesElement: PulseChartSeries) => {\r\n var dataPoints: PulseChartDataPoint[] = seriesElement.data,\r\n minValue: number | Date = dataPoints[0].categoryValue,\r\n maxValue: number | Date = dataPoints[dataPoints.length - 1].categoryValue,\r\n minX: number = originalScale(dataPoints[0].categoryValue),\r\n maxX: number = originalScale(dataPoints[dataPoints.length - 1].categoryValue);\r\n return PulseChart.createScale(isScalar, [minValue, maxValue], minX, maxX);\r\n });\r\n }\r\n\r\n private static getXAxisValuesToDisplay(\r\n scale: D3.Scale.TimeScale | D3.Scale.LinearScale,\r\n rotate: boolean,\r\n isScalar: boolean,\r\n dateFormat: PulseChartXAxisDateFormat,\r\n widthOfXAxisLabel: number): (Date | number)[] {\r\n var genScale = <D3.Scale.GenericScale<D3.Scale.TimeScale | D3.Scale.LinearScale>>scale;\r\n\r\n var tickWidth = rotate\r\n ? PulseChart.XAxisTickHeight * (rotate ? Math.abs(Math.sin(PulseChart.AxisTickRotateAngle * Math.PI / 180)) : 0)\r\n : widthOfXAxisLabel;\r\n var tickSpace = PulseChart.XAxisTickSpace;\r\n\r\n if(scale.range()[1] < tickWidth) {\r\n return [];\r\n }\r\n\r\n var minValue = scale.invert(scale.range()[0] + tickWidth/2);\r\n var maxValue = scale.invert(scale.range()[1] - tickWidth/2);\r\n var width = scale.range()[1] - scale.range()[0];\r\n\r\n var maxTicks = Math.floor((width + tickSpace) / (tickWidth + tickSpace));\r\n if(rotate) {\r\n maxTicks = Math.min(PulseChart.MinimumTicksToRotate, maxTicks);\r\n }\r\n\r\n var values = [];\r\n if(isScalar) {\r\n values = d3.range(<any>minValue, <any>maxValue, (<any>maxValue - <any>minValue) / (maxTicks * 100));\r\n } else {\r\n values = (dateFormat === PulseChartXAxisDateFormat.TimeOnly ? d3.time.minute : d3.time.day)\r\n .range(<any>minValue, <any>maxValue);\r\n }\r\n\r\n if(!values.length || _.last(values) < maxValue) {\r\n values.push(maxValue);\r\n }\r\n\r\n if(!maxTicks) {\r\n return [];\r\n }\r\n\r\n maxTicks = Math.min(values.length, maxTicks);\r\n\r\n var valuesIndexses = d3.scale.ordinal().domain(d3.range(maxTicks)).rangePoints([0, values.length - 1]).range();//randeRoundPoints is not defined\r\n values = valuesIndexses.map(x => values[Math.round(x)]);\r\n\r\n for(var i = 1; i < values.length; i++) {\r\n var prevXValue = genScale(values[i - 1]);\r\n var curXValue = genScale(values[i]);\r\n if(curXValue - prevXValue < tickWidth + tickSpace/3) {\r\n values.splice(i--, 1);\r\n }\r\n }\r\n\r\n return values;\r\n }\r\n\r\n private static getGroupIndex(index: number, grouped: DataViewValueColumnGroup[]): number {\r\n for (var i = 0; i < grouped.length; i++) {\r\n if (grouped[i].values && grouped[i].values[0] &&\r\n grouped[i].values[0].values[index] !== undefined &&\r\n grouped[i].values[0].values[index] !== null) {\r\n return i;\r\n }\r\n }\r\n\r\n return 0;\r\n }\r\n\r\n private static getGapWidths(values: Date[] | number[]): number[] {\r\n var result: number[] = [];\r\n for(var i = 0, prevVal = 0, length = values.length; i < length; i++) {\r\n if (!prevVal || !values[i]) {\r\n result.push(0);\r\n } else {\r\n result.push(<number>values[i] - prevVal);\r\n }\r\n\r\n prevVal = <number>values[i];\r\n }\r\n\r\n return result;\r\n }\r\n\r\n private static createScale(isScalar: boolean, domain: (number | Date)[], minX: number, maxX: number): D3.Scale.LinearScale | D3.Scale.TimeScale {\r\n var scale: D3.Scale.LinearScale | D3.Scale.TimeScale;\r\n\r\n if (isScalar) {\r\n scale = d3.scale.linear();\r\n } else {\r\n scale = d3.time.scale();\r\n }\r\n\r\n return scale\r\n .domain(domain)\r\n .range([minX, maxX]);\r\n }\r\n\r\n public data: PulseChartData;\r\n public margin: IMargin;\r\n public viewport: IViewport;\r\n public size: IViewport;\r\n public handleSelectionTimeout: number;\r\n public host: IVisualHostServices;\r\n\r\n private svg: D3.Selection;\r\n private chart: D3.Selection;\r\n private dots: D3.Selection;\r\n private yAxis: D3.Selection;\r\n private gaps: D3.Selection;\r\n\r\n private animationDot: D3.Selection;\r\n private lineX: D3.Svg.Line;\r\n private selectionManager: SelectionManager;\r\n private animator: IGenericAnimator;\r\n private animationHandler: PulseAnimator;\r\n private colors: IDataColorPalette;\r\n private rootSelection: D3.UpdateSelection;\r\n private animationSelection: D3.UpdateSelection;\r\n private lastSelectedPoint: SelectionId;\r\n\r\n public get runnerCounterPlaybackButtonsHeight(): number {\r\n return Math.max(PulseChart.PlaybackButtonsHeight, this.data && (this.data.runnerCounterHeight/2 + 17));\r\n }\r\n\r\n public get popupHeight(): number {\r\n return this.data\r\n && this.data.settings\r\n && this.data.settings.popup\r\n && this.data.settings.popup.show\r\n && this.data.settings.popup.height || 0;\r\n }\r\n\r\n public constructor(options?: PulseChartConstructorOptions) {\r\n if (options) {\r\n if (options.svg) {\r\n this.svg = options.svg;\r\n }\r\n }\r\n\r\n this.margin = PulseChart.DefaultMargin;\r\n }\r\n\r\n public init(options: VisualInitOptions): void {\r\n (<any>powerbi.formattingService).initialize();//Fixes the framework bug: \"Cannot read property 'getFormatString' of undefined\".\r\n this.host = options.host;\r\n this.selectionManager = new SelectionManager({ hostServices: this.host });\r\n var svg: D3.Selection = this.svg = d3.select(options.element.get(0))\r\n .append('svg')\r\n .classed('pulseChart', true);\r\n\r\n this.gaps = svg.append('g').classed(PulseChart.Gaps.class, true);\r\n this.yAxis = svg.append('g').attr('class', 'y axis');\r\n this.chart = svg.append('g').attr('class', PulseChart.Chart.class);\r\n this.dots = svg.append('g').attr('class', 'dots');\r\n this.animationDot = this.dots.append('circle').classed(PulseChart.AnimationDot.class, true).attr('display', 'none');\r\n\r\n this.animationHandler = new PulseAnimator(this, svg);\r\n\r\n var style: IVisualStyle = options.style;\r\n\r\n this.colors = style && style.colorPalette\r\n ? style.colorPalette.dataColors\r\n : new DataColorPalette();\r\n }\r\n\r\n public update(options: VisualUpdateOptions): void {\r\n if (!options || !options.dataViews || !options.dataViews[0]) {\r\n return;\r\n }\r\n\r\n this.viewport = $.extend({}, options.viewport);\r\n var dataView: DataView = options.dataViews[0];\r\n\r\n this.updateData(PulseChart.converter(dataView, this.colors));\r\n if (!this.validateData(this.data)) {\r\n this.clearAll(true);\r\n return;\r\n }\r\n\r\n var width = this.getChartWidth();\r\n this.calculateXAxisProperties(width);\r\n\r\n if(this.data.xScale.ticks(undefined).length < 2) {\r\n this.clearAll(true);\r\n return;\r\n }\r\n\r\n var height = this.getChartHeight(this.data.settings.xAxis.show\r\n && this.data.series.some((series: PulseChartSeries) => series.xAxisProperties.rotate));\r\n this.calculateYAxisProperties(height);\r\n\r\n this.size = { width: width, height: height };\r\n this.updateElements();\r\n\r\n this.render(true);\r\n }\r\n\r\n private updateData(data: PulseChartData): void {\r\n if(!this.data) {\r\n this.data = data;\r\n return;\r\n }\r\n\r\n var oldDataObj = this.getDataArrayToCompare(this.data);\r\n var newDataObj = this.getDataArrayToCompare(data);\r\n if(!_.isEqual(oldDataObj, newDataObj)) {\r\n this.clearAll(false);\r\n }\r\n\r\n this.data = data;\r\n }\r\n\r\n private getDataArrayToCompare(data: PulseChartData): any[] {\r\n if(!data || !data.series) {\r\n return null;\r\n }\r\n\r\n var dataPoints = <PulseChartDataPoint[]>_.flatten(data.series.map(x => x.data));\r\n return _.flatten(dataPoints.map(x =>\r\n {\r\n return x && _.flatten([\r\n [\r\n x.categoryValue,\r\n x.eventSize,\r\n x.groupIndex,\r\n x.runnerCounterValue,\r\n x.y,\r\n x.seriesIndex\r\n ],\r\n x.popupInfo && [x.popupInfo.description, x.popupInfo.title, x.popupInfo.value]\r\n ]);\r\n }));\r\n }\r\n\r\n private validateData(data: PulseChartData): boolean {\r\n if (!data) {\r\n return false;\r\n }\r\n\r\n if (data.categories.some(x => !(x instanceof Date || $.isNumeric(x)))) {\r\n return false;\r\n }\r\n\r\n return true;\r\n }\r\n\r\n private getChartWidth(): number {\r\n var marginRight = this.margin.right;\r\n if (this.data.settings.yAxis && this.data.settings.yAxis.show) {\r\n marginRight += PulseChart.MaxWidthOfYAxis;\r\n }\r\n\r\n var width = this.viewport.width - this.margin.left - marginRight;\r\n return Math.max(width, PulseChart.DefaultViewport.width);\r\n }\r\n\r\n private getChartHeight(xAxisRotated: boolean): number {\r\n var marginBottom = 10 + (xAxisRotated\r\n ? this.data.widthOfXAxisLabel * Math.abs(Math.sin(PulseChart.AxisTickRotateAngle * Math.PI / 180))\r\n : 3);\r\n\r\n if (!this.data.settings.popup.alwaysOnTop && this.popupHeight) {\r\n marginBottom = Math.max(this.margin.bottom + this.popupHeight, marginBottom);\r\n }\r\n\r\n var height = this.viewport.height\r\n - this.margin.top\r\n - this.runnerCounterPlaybackButtonsHeight\r\n - marginBottom\r\n - this.popupHeight;\r\n\r\n return Math.max(height, PulseChart.DefaultViewport.height);\r\n }\r\n\r\n private updateElements(): void {\r\n var chartMarginTop = this.margin.top + this.runnerCounterPlaybackButtonsHeight + this.popupHeight;\r\n this.svg.attr(this.viewport);\r\n this.svg.style('display', undefined);\r\n this.gaps.attr('transform', SVGUtil.translate(this.margin.left, chartMarginTop + (this.size.height / 2)));\r\n this.chart.attr('transform', SVGUtil.translate(this.margin.left, chartMarginTop));\r\n this.yAxis.attr('transform', SVGUtil.translate(this.size.width + this.margin.left, chartMarginTop));\r\n this.dots.attr('transform', SVGUtil.translate(this.margin.left, chartMarginTop));\r\n }\r\n\r\n public calculateXAxisProperties(width: number) {\r\n this.data.xScale = PulseChart.createScale(\r\n this.data.isScalar,\r\n [this.data.categories[0], this.data.categories[this.data.categories.length - 1]],\r\n 0,\r\n width);\r\n\r\n var xAxisProperties: PulseChartXAxisProperties[] = PulseChart.createAxisX(\r\n this.data.isScalar,\r\n this.data.series,\r\n <D3.Scale.LinearScale> this.data.xScale,\r\n $.extend({}, this.data.settings.xAxis.formatterOptions),\r\n this.data.settings.xAxis.dateFormat,\r\n this.data.settings.xAxis.position,\r\n this.data.widthOfXAxisLabel);\r\n\r\n this.data.series.forEach((series: PulseChartSeries, index: number) => {\r\n series.xAxisProperties = xAxisProperties[index];\r\n });\r\n }\r\n\r\n public calculateYAxisProperties(height: number): void {\r\n this.data.yScales = this.getYAxisScales(height);\r\n\r\n var domain: number[] = [];\r\n this.data.yScales.forEach((scale: D3.Scale.LinearScale) => domain = domain.concat(scale.domain()));\r\n this.data.commonYScale = <D3.Scale.LinearScale> PulseChart.createScale(\r\n true,\r\n [d3.max(domain), d3.min(domain)],\r\n 0,\r\n height);\r\n\r\n this.data.yAxis = PulseChart.createAxisY(this.data.commonYScale, height, this.data.settings.yAxis.formatterOptions);\r\n }\r\n\r\n private getYAxisScales(height: number): D3.Scale.LinearScale[] {\r\n var data: PulseChartData = this.data,\r\n stepOfHeight: number = height / data.grouped.length;\r\n\r\n return <D3.Scale.LinearScale[]> data.grouped.map((group: DataViewValueColumnGroup, index: number) => {\r\n var values: number[] = group.values[0].values.map(x => x || 0);\r\n\r\n var minValue: number = Number.MAX_VALUE,\r\n maxValue: number = -Number.MAX_VALUE;\r\n\r\n values.forEach((value: number) => {\r\n if (value < minValue) {\r\n minValue = value;\r\n }\r\n\r\n if (value > maxValue) {\r\n maxValue = value;\r\n }\r\n });\r\n if(maxValue === minValue) {\r\n var offset = maxValue === 0 ? 1 : Math.abs(maxValue/2);\r\n maxValue += offset;\r\n minValue -= offset;\r\n }\r\n\r\n return PulseChart.createScale(true, [maxValue, minValue], stepOfHeight * index, stepOfHeight * (index + 1));\r\n });\r\n }\r\n\r\n public get autoplayPauseDuration(): number {\r\n return 1000 * ((this.data && this.data.settings && this.data.settings.playback)\r\n ? this.data.settings.playback.autoplayPauseDuration\r\n : PulseChart.DefaultSettings.playback.autoplayPauseDuration);\r\n }\r\n\r\n public get isAutoPlay(): boolean {\r\n return this.data &&\r\n this.data.settings &&\r\n this.data.settings.playback &&\r\n this.data.settings.playback.autoplay;\r\n }\r\n\r\n public render(suppressAnimations: boolean) {\r\n var duration = AnimatorCommon.GetAnimationDuration(this.animator, suppressAnimations);\r\n var data = this.data;\r\n this.lastSelectedPoint = null;\r\n\r\n var xScale: D3.Scale.LinearScale = <D3.Scale.LinearScale>data.xScale,\r\n yScales: D3.Scale.LinearScale[] = <D3.Scale.LinearScale[]>data.yScales;\r\n\r\n this.lineX = d3.svg.line()\r\n .x((d: PulseChartDataPoint) => {\r\n return xScale(d.categoryValue);\r\n })\r\n .y((d: PulseChartDataPoint) => {\r\n return yScales[d.groupIndex](d.y);\r\n });\r\n\r\n if (this.data &&\r\n this.data.settings &&\r\n this.data.settings.playback &&\r\n this.data.settings.playback.color) {\r\n this.animationHandler.setControlsColor(this.data.settings.playback.color);\r\n }\r\n this.animationHandler.render();\r\n this.animationHandler.setRunnerCounterValue();\r\n\r\n this.renderAxes(data, duration);\r\n this.renderGaps(data, duration);\r\n }\r\n\r\n private renderAxes(data: PulseChartData, duration: number): void {\r\n this.renderXAxis(data, duration);\r\n this.renderYAxis(data, duration);\r\n }\r\n\r\n private renderXAxis(data: PulseChartData, duration: number): void {\r\n var axisNodeSelection: D3.Selection,\r\n axisNodeUpdateSelection: D3.UpdateSelection,\r\n //ticksSelection: D3.Selection,\r\n axisBoxUpdateSelection: D3.UpdateSelection,\r\n color: string = PulseChart.DefaultSettings.xAxis.color,\r\n fontColor: string = PulseChart.DefaultSettings.xAxis.fontColor;\r\n\r\n if (this.data && this.data.settings && this.data.settings.xAxis) {\r\n color = this.data.settings.xAxis.color;\r\n fontColor = this.data.settings.xAxis.fontColor;\r\n }\r\n\r\n axisNodeSelection = this.rootSelection.selectAll(PulseChart.XAxisNode.selector);\r\n axisNodeUpdateSelection = axisNodeSelection.data(data.series);\r\n\r\n axisNodeUpdateSelection\r\n .enter()\r\n .insert(\"g\", \"g.\" + PulseChart.LineContainer.class)\r\n .classed(PulseChart.XAxisNode.class, true);\r\n\r\n axisNodeUpdateSelection\r\n .call((selection: D3.Selection) => {\r\n selection.forEach((selectionElement: Element, index: number) => {\r\n d3.select(selectionElement[0])\r\n .call(data.series[index].xAxisProperties.axis.orient('bottom'));\r\n });\r\n });\r\n\r\n axisNodeUpdateSelection\r\n .exit()\r\n .remove();\r\n\r\n axisBoxUpdateSelection = axisNodeUpdateSelection\r\n .selectAll(\".tick\")\r\n .selectAll(\".axisBox\")\r\n .data([[]]);\r\n\r\n axisBoxUpdateSelection\r\n .enter()\r\n .insert(\"rect\", \"text\")\r\n .classed(\"axisBox\", true);\r\n\r\n axisBoxUpdateSelection\r\n .style('display', this.data.settings.xAxis.position === XAxisPosition.Center ? 'inherit' : 'none')\r\n .style('fill', this.data.settings.xAxis.backgroundColor);\r\n\r\n var tickRectY = this.data.settings.xAxis.position === XAxisPosition.Center ? -11 : 0;\r\n axisBoxUpdateSelection.attr({\r\n x: -(this.data.widthOfXAxisLabel / 2),\r\n y: tickRectY + \"px\",\r\n width: this.data.widthOfXAxisLabel,\r\n height: PulseChart.XAxisTickHeight + \"px\"\r\n });\r\n\r\n axisBoxUpdateSelection\r\n .exit()\r\n .remove();\r\n\r\n axisNodeUpdateSelection\r\n .style('stroke', this.data.settings.xAxis.position === XAxisPosition.Center ? color : \"none\")\r\n .style('display', this.data.settings.xAxis.show ? 'inherit' : 'none');\r\n\r\n axisNodeUpdateSelection.call(selection => {\r\n var rotate = selection.datum().xAxisProperties.rotate;\r\n var rotateCoeff = rotate ? Math.abs(Math.sin(PulseChart.AxisTickRotateAngle * Math.PI / 180)) : 0;\r\n var dy = tickRectY + 3;\r\n selection.selectAll(\"text\")\r\n .attr('transform', function() {\r\n return `translate(0, ${(dy + 9 + ($(this).width()/2) * rotateCoeff)}) rotate(${rotate ? PulseChart.AxisTickRotateAngle : 0})`;\r\n })\r\n .style('fill', fontColor)\r\n .style('stroke', \"none\")\r\n .attr('dy', -9);\r\n });\r\n\r\n axisNodeUpdateSelection.selectAll(\".domain\")\r\n .style('stroke', color);\r\n\r\n axisNodeUpdateSelection.selectAll(\".domain\").forEach((element: Element) => {\r\n $(element).insertBefore($(element).parent().children().first());\r\n });\r\n\r\n var xAxisTop: number = this.size.height;\r\n switch(this.data.settings.xAxis.position) {\r\n case XAxisPosition.Center:\r\n xAxisTop = xAxisTop/2;\r\n break;\r\n case XAxisPosition.Bottom:\r\n break;\r\n }\r\n\r\n axisNodeUpdateSelection.attr('transform', SVGUtil.translate(0, xAxisTop));\r\n }\r\n\r\n private renderYAxis(data: PulseChartData, duration: number): void {\r\n var yAxis: D3.Svg.Axis = data.yAxis,\r\n isShow: boolean = false,\r\n color: string = PulseChart.DefaultSettings.yAxis.color,\r\n fontColor: string = PulseChart.DefaultSettings.yAxis.fontColor;;\r\n\r\n yAxis.orient('right');\r\n\r\n if (this.data &&\r\n this.data.settings &&\r\n this.data.settings.yAxis &&\r\n this.data.settings.yAxis.show) {\r\n isShow = true;\r\n }\r\n\r\n if (this.data &&\r\n this.data.settings &&\r\n this.data.settings.yAxis &&\r\n this.data.settings.yAxis) {\r\n color = this.data.settings.yAxis.color;\r\n fontColor = this.data.settings.yAxis.fontColor;\r\n }\r\n\r\n this.yAxis\r\n .call(yAxis)\r\n .attr('display', isShow ? 'inline' : 'none');\r\n\r\n this.yAxis.selectAll('.domain, path, line').style('stroke', color);\r\n this.yAxis.selectAll('text').style('fill', fontColor);\r\n this.yAxis.selectAll('g.tick line')\r\n .attr('x1', -this.size.width);\r\n }\r\n\r\n public renderChart(): void {\r\n var data: PulseChartData = this.data;\r\n var series: PulseChartSeries[] = this.data.series;\r\n var selection: D3.UpdateSelection = this.rootSelection = this.chart.selectAll(PulseChart.LineNode.selector).data(series);\r\n\r\n var lineNode = selection\r\n .enter()\r\n .append('g')\r\n .classed(PulseChart.LineNode.class, true);\r\n\r\n lineNode\r\n .append('g')\r\n .classed(PulseChart.LineContainer.class, true);\r\n\r\n lineNode\r\n .append('g')\r\n .classed(PulseChart.TooltipContainer.class, true);\r\n\r\n lineNode\r\n .append('g')\r\n .classed(PulseChart.DotsContainer.class, true);\r\n\r\n if (this.animationHandler.isAnimated) {\r\n this.showAnimationDot();\r\n } else {\r\n this.hideAnimationDot();\r\n }\r\n\r\n this.drawTooltips(data, this.selectionManager.getSelectionIds());\r\n this.drawDots(data);\r\n this.drawLines(data);\r\n\r\n selection\r\n .exit()\r\n .remove();\r\n }\r\n\r\n private drawLinesStatic(limit: number, isAnimated: boolean): void {\r\n var node: ClassAndSelector = PulseChart.Line,\r\n nodeParent: ClassAndSelector = PulseChart.LineContainer,\r\n rootSelection: D3.UpdateSelection = this.rootSelection;\r\n\r\n var selection: D3.UpdateSelection = rootSelection\r\n .filter((d, index) => !isAnimated || index < limit)\r\n .select(nodeParent.selector)\r\n .selectAll(node.selector).data(d => [d]);\r\n\r\n selection\r\n .enter()\r\n .append('path')\r\n .classed(node.class, true);\r\n\r\n selection\r\n .style({\r\n 'fill': \"none\",\r\n 'stroke': (d: PulseChartSeries) => d.color,\r\n 'stroke-width': (d: PulseChartSeries) => `${d.width}px`\r\n });\r\n\r\n selection.attr('d', d => this.lineX(d.data));\r\n selection\r\n .exit()\r\n .remove();\r\n }\r\n\r\n private drawLinesStaticBeforeAnimation(limit: number): void {\r\n var node: ClassAndSelector = PulseChart.Line,\r\n nodeParent: ClassAndSelector = PulseChart.LineContainer,\r\n rootSelection: D3.UpdateSelection = this.rootSelection;\r\n\r\n this.animationSelection = rootSelection.filter((d, index) => {\r\n return index === limit;\r\n }).select(nodeParent.selector).selectAll(node.selector).data((d: PulseChartSeries) => [d]);\r\n\r\n this.animationSelection\r\n .enter()\r\n .append('path')\r\n .classed(node.class, true);\r\n\r\n this.animationSelection\r\n .style({\r\n 'fill': \"none\",\r\n 'stroke': (d: PulseChartSeries) => d.color,\r\n 'stroke-width': (d: PulseChartSeries) => `${d.width}px`\r\n });\r\n\r\n this.animationSelection\r\n .attr('d', (d: PulseChartSeries) => {\r\n var flooredStart = this.animationHandler.flooredPosition.index;\r\n\r\n if (flooredStart === 0) {\r\n this.moveAnimationDot(d.data[0]);\r\n return this.lineX([]);\r\n } else {\r\n var dataReduced: PulseChartDataPoint[] = d.data.slice(0, flooredStart + 1);\r\n this.moveAnimationDot(dataReduced[dataReduced.length - 1]);\r\n return this.lineX(dataReduced);\r\n }\r\n });\r\n\r\n this.animationSelection\r\n .exit()\r\n .remove();\r\n }\r\n\r\n private moveAnimationDot(d: PulseChartDataPoint): void {\r\n var xScale: D3.Scale.LinearScale = <D3.Scale.LinearScale>this.data.xScale,\r\n yScales: D3.Scale.LinearScale[] = <D3.Scale.LinearScale[]>this.data.yScales;\r\n\r\n this.animationDot\r\n .attr(\"cx\", xScale(d.x))\r\n .attr(\"cy\", yScales[d.groupIndex](d.y));\r\n }\r\n\r\n public playAnimation(delay: number = 0): void {\r\n var flooredStart = this.animationHandler.flooredPosition.index;\r\n this.showAnimationDot();\r\n this.animationSelection\r\n .transition()\r\n .delay(delay)\r\n .duration(this.animationDuration)\r\n .ease(\"linear\")\r\n .attrTween('d', (d: PulseChartSeries, index: number) => this.getInterpolation(d.data, flooredStart))\r\n .each(\"end\", (series: PulseChartSeries) => this.handleSelection(this.animationHandler.flooredPosition));\r\n }\r\n\r\n public pauseAnimation(): void {\r\n if (!this.animationSelection) {\r\n return;\r\n }\r\n\r\n this.hideAnimationDot();\r\n this.animationSelection.selectAll(\"path\").transition();\r\n\r\n this.animationSelection\r\n .transition()\r\n .duration(0)\r\n .delay(0);\r\n }\r\n\r\n public stopAnimation() {\r\n this.pauseAnimation();\r\n d3.timer.flush();\r\n }\r\n\r\n public findNextPoint(position: PulseChartAnimationPosition): PulseChartAnimationPosition {\r\n for (var i: number = position.series; i < this.data.series.length; i++) {\r\n var series: PulseChartSeries = this.data.series[i];\r\n\r\n for (var j: number = (i === position.series) ? Math.floor(position.index + 1) : 0; j < series.data.length; j++) {\r\n if (series.data[j] && series.data[j].popupInfo) {\r\n return {\r\n series: i,\r\n index: j\r\n };\r\n }\r\n }\r\n }\r\n\r\n return null;\r\n }\r\n\r\n public findPrevPoint(position: PulseChartAnimationPosition): PulseChartAnimationPosition {\r\n for (var i: number = position.series; i >= 0; i--) {\r\n var series: PulseChartSeries = this.data.series[i];\r\n\r\n for (var j: number = (i === position.series) ? Math.ceil(position.index - 1) : series.data.length; j >= 0; j--) {\r\n if (series.data[j] && series.data[j].popupInfo) {\r\n return {\r\n series: i,\r\n index: j\r\n };\r\n }\r\n }\r\n }\r\n\r\n return null;\r\n }\r\n\r\n public isAnimationSeriesAndIndexLast(position: PulseChartAnimationPosition): boolean {\r\n return this.isAnimationSeriesLast(position) && this.isAnimationIndexLast(position);\r\n }\r\n\r\n public isAnimationSeriesLast(position: PulseChartAnimationPosition): boolean {\r\n return (position.series >= (this.data.series.length - 1));\r\n }\r\n\r\n public isAnimationIndexLast(position: PulseChartAnimationPosition): boolean {\r\n var series: PulseChartSeries = this.data.series[position.series];\r\n return (position.index >= (series.data.length - 1));\r\n }\r\n\r\n private drawLines(data: PulseChartData): void {\r\n var positionSeries: number = this.animationHandler.position.series,\r\n isAnimated: boolean = this.animationHandler.isAnimated;\r\n\r\n this.drawLinesStatic(positionSeries, isAnimated);\r\n\r\n if (isAnimated) {\r\n this.drawLinesStaticBeforeAnimation(positionSeries);\r\n }\r\n }\r\n\r\n private showAnimationDot(): void {\r\n\r\n if (!this.animationHandler.isPlaying) {\r\n return;\r\n }\r\n var size: number = PulseChart.DefaultSettings.dots.size;\r\n\r\n if (this.data &&\r\n this.data.settings &&\r\n this.data.settings.dots &&\r\n this.data.settings.dots.size) {\r\n size = this.data.settings.dots.size;\r\n }\r\n\r\n this.animationDot\r\n .attr('display', 'inline')\r\n .attr(\"fill\", this.data.settings.dots.color)\r\n .style(\"opacity\", this.dotOpacity)\r\n .attr(\"r\", size);\r\n }\r\n\r\n private hideAnimationDot() {\r\n this.animationDot.attr('display', 'none');\r\n }\r\n\r\n private getInterpolation(data: PulseChartDataPoint[], start: number): (number) => string {\r\n if (!this.data) {\r\n return;\r\n }\r\n\r\n var xScale: D3.Scale.LinearScale = <D3.Scale.LinearScale>this.data.xScale,\r\n yScales: D3.Scale.LinearScale[] = <D3.Scale.LinearScale[]>this.data.yScales;\r\n var stop: number = start + 1;\r\n\r\n this.showAnimationDot();\r\n\r\n var lineFunction: D3.Svg.Line = d3.svg.line()\r\n .x(d => d.x)\r\n .y(d => d.y)\r\n .interpolate(\"linear\");\r\n\r\n var interpolatedLine = data.slice(0, start + 1).map((d: PulseChartDataPoint): PulseChartPointXY => {\r\n return {\r\n x: xScale(d.x),\r\n y: yScales[d.groupIndex](d.y)\r\n };\r\n });\r\n\r\n var x0: number = xScale(data[start].x);\r\n var x1: number = xScale(data[stop].x);\r\n\r\n var y0: number = yScales[data[start].groupIndex](data[start].y);\r\n var y1: number = yScales[data[stop].groupIndex](data[stop].y);\r\n\r\n var interpolateIndex: D3.Scale.LinearScale = d3.scale.linear()\r\n .domain([0, 1])\r\n .range([start, stop]);\r\n\r\n var interpolateX: D3.Scale.LinearScale = d3.scale.linear()\r\n .domain([0, 1])\r\n .range([x0, x1]);\r\n\r\n var interpolateY: D3.Scale.LinearScale = d3.scale.linear()\r\n .domain([0, 1])\r\n .range([y0, y1]);\r\n\r\n this.animationHandler.setRunnerCounterValue(start);\r\n\r\n return (t: number) => {\r\n if (!this.animationHandler.isPlaying) {\r\n return lineFunction(interpolatedLine);\r\n }\r\n\r\n var x: number = interpolateX(t);\r\n var y: number = interpolateY(t);\r\n\r\n this.animationDot\r\n .attr(\"cx\", x)\r\n .attr(\"cy\", y);\r\n\r\n interpolatedLine.push({ \"x\": x, \"y\": y });\r\n this.animationHandler.position.index = interpolateIndex(t);\r\n return lineFunction(interpolatedLine);\r\n };\r\n }\r\n\r\n public clearSelection(): void {\r\n if(this.data && this.data.hasHighlights || !this.selectionManager.hasSelection()) {\r\n (<any>this.selectionManager).selectedIds = [];\r\n } else {\r\n this.selectionManager.clear();\r\n }\r\n\r\n this.chart.selectAll(PulseChart.Tooltip.selector).remove();\r\n }\r\n\r\n private handleSelection(position: PulseChartAnimationPosition): void {\r\n if(!this.data) {\r\n return;\r\n }\r\n\r\n var animationPlayingIndex = this.animationHandler.animationPlayingIndex;\r\n var dataPoint: PulseChartDataPoint = this.data.series[position.series].data[position.index];\r\n var isLastDataPoint: boolean = this.animationHandler.isPlaying && this.isAnimationSeriesAndIndexLast(position);\r\n if ((!dataPoint || !dataPoint.popupInfo) && (this.animationHandler.isPlaying)) {\r\n if(isLastDataPoint) {\r\n setTimeout(() => this.animationHandler.toEnd(), 0);\r\n } else {\r\n this.animationHandler.play(0, true);\r\n }\r\n\r\n return;\r\n }\r\n\r\n if(isLastDataPoint) {\r\n setTimeout(() => this.animationHandler.toEnd(), 0);\r\n } else {\r\n this.animationHandler.pause();\r\n }\r\n\r\n this.selectionManager.select(dataPoint.identity).then((selectionIds: SelectionId[]) => {\r\n this.setSelection(selectionIds);\r\n clearTimeout(this.handleSelectionTimeout);\r\n this.handleSelectionTimeout = setTimeout(() => {\r\n if(this.animationHandler.animationPlayingIndex !== animationPlayingIndex) {\r\n return;\r\n }\r\n\r\n if(isLastDataPoint || this.animationHandler.isPaused) {\r\n this.clearSelection();\r\n }\r\n\r\n if (!isLastDataPoint && this.animationHandler.isPaused) {\r\n this.animationHandler.play();\r\n }\r\n }, this.pauseDuration);\r\n });\r\n }\r\n\r\n private get animationDuration(): number {\r\n return 1000 / ((this.data && this.data.settings && this.data.settings.playback)\r\n ? this.data.settings.playback.playSpeed\r\n : PulseChart.DefaultSettings.playback.playSpeed);\r\n }\r\n\r\n private get pauseDuration(): number {\r\n return 1000 * ((this.data && this.data.settings && this.data.settings.playback)\r\n ? this.data.settings.playback.pauseDuration\r\n : PulseChart.DefaultSettings.playback.pauseDuration);\r\n }\r\n\r\n private get dotOpacity(): number {\r\n return 1 - ((this.data && this.data.settings && this.data.settings.dots)\r\n ? this.data.settings.dots.transparency\r\n : PulseChart.DefaultSettings.dots.transparency) / 100;\r\n }\r\n\r\n private drawDots(data: PulseChartData): void {\r\n if (!data || !data.xScale) {\r\n return;\r\n }\r\n\r\n var xScale: D3.Scale.LinearScale = <D3.Scale.LinearScale>data.xScale,\r\n yScales: D3.Scale.LinearScale[] = <D3.Scale.LinearScale[]>data.yScales,\r\n node: ClassAndSelector = PulseChart.Dot,\r\n nodeParent: ClassAndSelector = PulseChart.DotsContainer,\r\n rootSelection: D3.UpdateSelection = this.rootSelection,\r\n dotColor: string = this.data.settings.dots.color,\r\n dotSize: number = this.data.settings.dots.size,\r\n isAnimated: boolean = this.animationHandler.isAnimated,\r\n position: PulseChartAnimationPosition = this.animationHandler.position;\r\n\r\n var selection: D3.UpdateSelection = rootSelection.filter((d, index) => !isAnimated || index <= position.series)\r\n .select(nodeParent.selector)\r\n .selectAll(node.selector)\r\n .data((d: PulseChartSeries, seriesIndex: number) => {\r\n return _.filter(d.data, (value: PulseChartDataPoint, valueIndex: number): boolean => {\r\n if (isAnimated && (seriesIndex === position.series) && (valueIndex > position.index)) {\r\n return false;\r\n }\r\n return (!!value.popupInfo);\r\n });\r\n });\r\n\r\n selection\r\n .enter()\r\n .append(\"circle\")\r\n .classed(node.class, true);\r\n\r\n selection\r\n .attr(\"cx\", (d: PulseChartDataPoint) => xScale(d.categoryValue))\r\n .attr(\"cy\", (d: PulseChartDataPoint) => yScales[d.groupIndex](d.y))\r\n .attr(\"r\", (d: PulseChartDataPoint) => d.eventSize || dotSize)\r\n .style(\"fill\", dotColor)\r\n .style(\"opacity\", this.dotOpacity)\r\n .style(\"cursor\", \"pointer\")\r\n .call(PulseChart.AddOnTouchClick, (d: PulseChartDataPoint) => {\r\n d3.event.stopPropagation();\r\n this.selectionManager.select(d.identity, d3.event.ctrlKey)\r\n .then((selectionIds: SelectionId[]) => this.setSelection(selectionIds));\r\n });\r\n\r\n selection\r\n .exit()\r\n .remove();\r\n }\r\n\r\n private renderGaps(data: PulseChartData, duration: number): void {\r\n var gaps: IRect[],\r\n gapsSelection: D3.UpdateSelection,\r\n gapsEnterSelection: D3.Selection,\r\n gapNodeSelection: D3.UpdateSelection,\r\n series: PulseChartSeries[] = data.series,\r\n isScalar: boolean = data.isScalar,\r\n xScale: D3.Scale.LinearScale = <D3.Scale.LinearScale>data.xScale;\r\n\r\n gaps = [{\r\n left: -4.5,\r\n top: -5,\r\n height: 10,\r\n width: 3\r\n }, {\r\n left: 1.5,\r\n top: -5,\r\n height: 10,\r\n width: 3\r\n }];\r\n\r\n gapsSelection = this.gaps.selectAll(PulseChart.Gap.selector)\r\n .data(series.slice(0, series.length - 1));\r\n\r\n gapsEnterSelection = gapsSelection\r\n .enter()\r\n .append(\"g\");\r\n\r\n gapsSelection\r\n .attr(\"transform\", (seriesElement: PulseChartSeries, index: number) => {\r\n var x: number,\r\n middleOfGap: number = seriesElement.widthOfGap / 2,\r\n categoryValue: number | Date = seriesElement.data[seriesElement.data.length - 1].categoryValue;\r\n\r\n if (isScalar) {\r\n x = xScale(middleOfGap + <number>categoryValue);\r\n } else {\r\n x = xScale(new Date(middleOfGap + ((<Date>categoryValue).getTime())));\r\n }\r\n\r\n return SVGUtil.translate(x, 0);\r\n });\r\n\r\n gapNodeSelection = gapsSelection.selectAll(PulseChart.GapNode.selector)\r\n .data(gaps);\r\n\r\n gapNodeSelection\r\n .enter()\r\n .append(\"rect\")\r\n .attr({\r\n x: (gap: IRect) => gap.left,\r\n y: (gap: IRect) => gap.top,\r\n height: (gap: IRect) => gap.height,\r\n width: (gap: IRect) => gap.width\r\n })\r\n .classed(PulseChart.GapNode.class, true);\r\n\r\n gapsEnterSelection.classed(PulseChart.Gap.class, true);\r\n\r\n gapsSelection\r\n .exit()\r\n .remove();\r\n\r\n gapNodeSelection\r\n .exit()\r\n .remove();\r\n }\r\n\r\n private setSelection(selectionIds?: SelectionId[]): void {\r\n if(!this.data) {\r\n return;\r\n }\r\n\r\n this.drawDots(this.data);\r\n this.drawTooltips(this.data, selectionIds);\r\n }\r\n\r\n private isPopupShow(d: PulseChartDataPoint, selectionIds?: SelectionId[]): boolean {\r\n if (!this.popupHeight || !d.popupInfo) {\r\n return false;\r\n }\r\n\r\n if (selectionIds) {\r\n return SelectionManager.containsSelection(selectionIds, d.identity);\r\n }\r\n\r\n return false;\r\n }\r\n\r\n private drawTooltips(data: PulseChartData, selectionIds?: SelectionId[]): void {\r\n var xScale: D3.Scale.LinearScale = <D3.Scale.LinearScale>data.xScale,\r\n yScales: D3.Scale.LinearScale[] = <D3.Scale.LinearScale[]>data.yScales,\r\n node: ClassAndSelector = PulseChart.Tooltip,\r\n nodeParent: ClassAndSelector = PulseChart.TooltipContainer;\r\n\r\n var rootSelection: D3.UpdateSelection = this.rootSelection;\r\n\r\n var line: D3.Svg.Line = d3.svg.line()\r\n .x(d => d.x)\r\n .y(d => d.y);\r\n\r\n var marginTop: number = PulseChart.DefaultTooltipSettings.marginTop;\r\n var width: number = this.data.settings.popup.width;\r\n var height: number = this.data.settings.popup.height;\r\n\r\n var topShift: number = 20;\r\n var tooltipShiftY = (y: number, groupIndex: number) => this.isHigherMiddle(y, groupIndex) ? (-1 * marginTop + topShift) : this.size.height + marginTop;\r\n\r\n var tooltipRoot: D3.UpdateSelection = rootSelection.select(nodeParent.selector).selectAll(node.selector)\r\n .data(d => {\r\n return _.filter(d.data, (value: PulseChartDataPoint) => this.isPopupShow(value, selectionIds));\r\n });\r\n\r\n tooltipRoot\r\n .enter()\r\n .append(\"g\")\r\n .classed(node.class, true);\r\n\r\n tooltipRoot\r\n .attr(\"transform\", (d: PulseChartDataPoint) => {\r\n var x: number = xScale(d.x) - width / 2;\r\n var y: number = tooltipShiftY(d.y, d.groupIndex);\r\n d.popupInfo.offsetX = Math.min(this.viewport.width - this.margin.right - width, Math.max(-this.margin.left, x)) - x;\r\n return SVGUtil.translate(x + d.popupInfo.offsetX, y);\r\n });\r\n\r\n var tooltipRect = tooltipRoot.selectAll(PulseChart.TooltipRect.selector).data(d => [d]);\r\n tooltipRect.enter().append(\"path\").classed(PulseChart.TooltipRect.class, true);\r\n tooltipRect\r\n .attr(\"display\", (d: PulseChartDataPoint) => d.popupInfo ? \"inherit\" : \"none\")\r\n .style('fill', this.data.settings.popup.color)\r\n .attr('d', (d: PulseChartDataPoint) => {\r\n var path = [\r\n {\r\n \"x\": -2,\r\n \"y\": this.isHigherMiddle(d.y, d.groupIndex) ? (-1 * marginTop) : 0,\r\n },\r\n {\r\n \"x\": -2,\r\n \"y\": this.isHigherMiddle(d.y, d.groupIndex) ? (-1 * (marginTop + height)) : height,\r\n },\r\n {\r\n \"x\": width - 2,\r\n \"y\": this.isHigherMiddle(d.y, d.groupIndex) ? (-1 * (marginTop + height)) : height,\r\n },\r\n {\r\n \"x\": width - 2,\r\n \"y\": this.isHigherMiddle(d.y, d.groupIndex) ? (-1 * marginTop) : 0,\r\n }\r\n ];\r\n return line(path);\r\n });\r\n\r\n var tooltipTriangle = tooltipRoot.selectAll(PulseChart.TooltipTriangle.selector).data(d => [d]);\r\n tooltipTriangle.enter().append(\"path\").classed(PulseChart.TooltipTriangle.class, true);\r\n tooltipTriangle\r\n .style('fill', this.data.settings.popup.color)\r\n .attr('d', (d: PulseChartDataPoint) => {\r\n var path = [\r\n {\r\n \"x\": width / 2 - 5 - d.popupInfo.offsetX,\r\n \"y\": this.isHigherMiddle(d.y, d.groupIndex) ? (-1 * marginTop) : 0,\r\n },\r\n {\r\n \"x\": width / 2 - d.popupInfo.offsetX,\r\n \"y\": this.isHigherMiddle(d.y, d.groupIndex) ? (-1 * (marginTop - 5)) : -5,\r\n },\r\n {\r\n \"x\": width / 2 + 5 - d.popupInfo.offsetX,\r\n \"y\": this.isHigherMiddle(d.y, d.groupIndex) ? (-1 * marginTop) : 0,\r\n },\r\n ];\r\n return line(path);\r\n })\r\n .style('stroke-width', \"1px\");\r\n\r\n var tooltipLine = tooltipRoot.selectAll(PulseChart.TooltipLine.selector).data(d => [d]);\r\n tooltipLine.enter().append(\"path\").classed(PulseChart.TooltipLine.class, true);\r\n tooltipLine\r\n .style('fill', this.data.settings.popup.color)\r\n .style('stroke', this.data.settings.popup.color)\r\n .style('stroke-width', \"1px\")\r\n .attr('d', (d: PulseChartDataPoint) => {\r\n var path = [\r\n {\r\n \"x\": width/2 - d.popupInfo.offsetX,\r\n \"y\": this.isHigherMiddle(d.y, d.groupIndex) ?\r\n yScales[d.groupIndex](d.y) + tooltipShiftY(d.y, d.groupIndex) - d.eventSize :\r\n yScales[d.groupIndex](d.y) - tooltipShiftY(d.y, d.groupIndex) + d.eventSize,\r\n },\r\n {\r\n \"x\": width/2 - d.popupInfo.offsetX,\r\n \"y\": this.isHigherMiddle(d.y, d.groupIndex) ? (-1 * marginTop) : 0, //end\r\n }];\r\n return line(path);\r\n });\r\n\r\n var isShowTime: boolean = this.data.settings.popup.showTime;\r\n var isShowTitle: boolean = this.data.settings.popup.showTitle;\r\n\r\n var timeRect = tooltipRoot.selectAll(PulseChart.TooltipTimeRect.selector).data(d => [d]);\r\n var timeDisplayStyle = { \"display\": isShowTime ? undefined : \"none\" };\r\n timeRect.enter().append(\"path\").classed(PulseChart.TooltipTimeRect.class, true);\r\n timeRect\r\n .style(\"fill\", this.data.settings.popup.timeFill)\r\n .style(timeDisplayStyle)\r\n .attr('d', (d: PulseChartDataPoint) => {\r\n var path = [\r\n {\r\n \"x\": width - this.data.widthOfTooltipValueLabel - 2,\r\n \"y\": this.isHigherMiddle(d.y, d.groupIndex) ? (-1 * (marginTop + height)) : 0,\r\n },\r\n {\r\n \"x\": width - this.data.widthOfTooltipValueLabel -2,\r\n \"y\": this.isHigherMiddle(d.y, d.groupIndex)\r\n ? (-1 * (marginTop + height - PulseChart.DefaultTooltipSettings.timeHeight))\r\n : PulseChart.DefaultTooltipSettings.timeHeight,\r\n },\r\n {\r\n \"x\": width - 2,\r\n \"y\": this.isHigherMiddle(d.y, d.groupIndex)\r\n ? (-1 * (marginTop + height - PulseChart.DefaultTooltipSettings.timeHeight))\r\n : PulseChart.DefaultTooltipSettings.timeHeight,\r\n },\r\n {\r\n \"x\": width - 2,\r\n \"y\": this.isHigherMiddle(d.y, d.groupIndex) ? (-1 * (marginTop + height)) : 0,\r\n }\r\n ];\r\n return line(path);\r\n });\r\n\r\n var time = tooltipRoot.selectAll(PulseChart.TooltipTime.selector).data(d => [d]);\r\n time.enter().append(\"text\").classed(PulseChart.TooltipTime.class, true);\r\n time\r\n .style(PulseChart.ConvertTextPropertiesToStyle(PulseChart.GetPopupValueTextProperties()))\r\n .style(timeDisplayStyle)\r\n .style(\"fill\", this.data.settings.popup.timeColor)\r\n .attr(\"x\", (d: PulseChartDataPoint) => width - this.data.widthOfTooltipValueLabel)\r\n .attr(\"y\", (d: PulseChartDataPoint) => this.isHigherMiddle(d.y, d.groupIndex)\r\n ? (-1 * (marginTop + height - PulseChart.DefaultTooltipSettings.timeHeight + 3))\r\n : PulseChart.DefaultTooltipSettings.timeHeight - 3)\r\n .text((d: PulseChartDataPoint) => d.popupInfo.value);\r\n\r\n var titleDisplayStyle = { \"display\": isShowTitle ? undefined : \"none\" };\r\n var title = tooltipRoot.selectAll(PulseChart.TooltipTitle.selector).data(d => [d]);\r\n title.enter().append(\"text\").classed(PulseChart.TooltipTitle.class, true);\r\n title\r\n .style(titleDisplayStyle)\r\n .style(PulseChart.ConvertTextPropertiesToStyle(PulseChart.GetPopupTitleTextProperties()))\r\n .style(\"fill\", this.data.settings.popup.fontColor)\r\n .attr(\"x\", (d: PulseChartDataPoint) => PulseChart.PopupTextPadding)\r\n .attr(\"y\", (d: PulseChartDataPoint) =>\r\n (this.isHigherMiddle(d.y, d.groupIndex) ? (-1 * (marginTop + height - 12)) : 12) + PulseChart.PopupTextPadding)\r\n .text((d: PulseChartDataPoint) => {\r\n if (!d.popupInfo) {\r\n return \"\";\r\n }\r\n var maxWidth = width - PulseChart.PopupTextPadding * 2 -\r\n (isShowTime ? (this.data.widthOfTooltipValueLabel - PulseChart.PopupTextPadding) : 0) - 10;\r\n return TextMeasurementService.getTailoredTextOrDefault(PulseChart.GetPopupTitleTextProperties(d.popupInfo.title), maxWidth);\r\n });\r\n\r\n var getDescriptionDimenstions = (d: PulseChartDataPoint): PulseChartElementDimensions => {\r\n var shiftY: number = PulseChart.PopupTextPadding + this.data.settings.popup.fontSize;\r\n\r\n var descriptionYOffset: number = shiftY + PulseChart.DefaultTooltipSettings.timeHeight;\r\n if (d.popupInfo) {\r\n shiftY = ((isShowTitle && d.popupInfo.title) || (isShowTime && d.popupInfo.value)) ? descriptionYOffset: shiftY;\r\n }\r\n\r\n return {\r\n y: this.isHigherMiddle(d.y, d.groupIndex)\r\n ? (-1 * (marginTop + height - shiftY))\r\n : shiftY,\r\n x: PulseChart.PopupTextPadding,\r\n width: width - PulseChart.PopupTextPadding * 2,\r\n height: height - shiftY,\r\n };\r\n };\r\n\r\n var description = tooltipRoot.selectAll(PulseChart.TooltipDescription.selector).data(d => [d]);\r\n description.enter().append(\"text\").classed(PulseChart.TooltipDescription.class, true);\r\n description\r\n .style(PulseChart.ConvertTextPropertiesToStyle(PulseChart.GetPopupDescriptionTextProperties(null, this.data.settings.popup.fontSize)))\r\n .style(\"fill\", this.data.settings.popup.fontColor)\r\n .text((d: PulseChartDataPoint) => d.popupInfo && d.popupInfo.description)\r\n //.call(d => d.forEach(x => x[0] &&\r\n // TextMeasurementService.wordBreak(x[0], width - 2 - PulseChart.PopupTextPadding * 2, height - PulseChart.DefaultTooltipSettings.timeHeight - PulseChart.PopupTextPadding * 2)))\r\n // TextMeasurementService.wordBreak(x[0], width - 2 - PulseChart.PopupTextPadding * 2, height - PulseChart.DefaultTooltipSettings.timeHeight - PulseChart.PopupTextPadding * 2)))\r\n .attr(\"y\", function(d: PulseChartDataPoint) {\r\n var descriptionDimenstions: PulseChartElementDimensions = getDescriptionDimenstions(d);\r\n var el: SVGTextElement = <any>d3.select(this)[0][0];\r\n TextMeasurementService.wordBreak(el, descriptionDimenstions.width, descriptionDimenstions.height);\r\n return 0;\r\n })\r\n .attr(\"transform\", function(d: PulseChartDataPoint) {\r\n var descriptionDimenstions: PulseChartElementDimensions = getDescriptionDimenstions(d);\r\n return SVGUtil.translate(0, descriptionDimenstions.y);\r\n });\r\n description.selectAll(\"tspan\").attr(\"x\", PulseChart.PopupTextPadding);\r\n\r\n tooltipRoot\r\n .exit()\r\n .remove();\r\n }\r\n\r\n private isHigherMiddle(value: number, groupIndex: number): boolean {\r\n if (this.data.settings.popup.alwaysOnTop) {\r\n return true;\r\n }\r\n\r\n if (this.data.yScales.length > 1) {\r\n return groupIndex === 0;\r\n }\r\n\r\n var domain: number[] = this.data.commonYScale.domain(),\r\n minValue: number = d3.min(domain),\r\n middleValue = Math.abs((d3.max(domain) - minValue) / 2);\r\n\r\n middleValue = middleValue === 0\r\n ? middleValue\r\n : minValue + middleValue;\r\n\r\n return value >= middleValue;\r\n }\r\n\r\n private static getObjectsFromDataView(dataView: DataView): DataViewObjects {\r\n if (!dataView ||\r\n !dataView.metadata ||\r\n !dataView.metadata.columns ||\r\n !dataView.metadata.objects) {\r\n return null;\r\n }\r\n\r\n return dataView.metadata.objects;\r\n }\r\n\r\n private static parseSettings(dataView: DataView, colors: IDataColorPalette, columns: PulseChartDataRoles<DataViewCategoricalColumn>): PulseChartSettings {\r\n var settings: PulseChartSettings = <PulseChartSettings>{},\r\n objects: DataViewObjects = PulseChart.getObjectsFromDataView(dataView);\r\n\r\n settings.xAxis = this.getAxisXSettings(objects, colors);\r\n settings.yAxis = this.getAxisYSettings(objects, colors);\r\n settings.popup = this.getPopupSettings(objects, colors);\r\n settings.dots = this.getDotsSettings(objects, colors);\r\n\r\n settings.series = this.getSeriesSettings(objects, colors);\r\n settings.gaps = this.getGapsSettings(objects);\r\n settings.playback = this.getPlaybackSettings(objects, colors);\r\n settings.runnerCounter = this.getRunnerCounterSettings(objects, colors, columns);\r\n\r\n return settings;\r\n }\r\n\r\n private static getPopupSettings(objects: DataViewObjects, colors: IDataColorPalette): PulseChartPopupSettings {\r\n var show = DataViewObjects.getValue<boolean>(\r\n objects,\r\n PulseChart.Properties[\"popup\"][\"show\"],\r\n PulseChart.DefaultSettings.popup.show);\r\n\r\n var alwaysOnTop: boolean = DataViewObjects.getValue<boolean>(\r\n objects,\r\n PulseChart.Properties[\"popup\"][\"alwaysOnTop\"],\r\n PulseChart.DefaultSettings.popup.alwaysOnTop);\r\n\r\n var width = Math.max(PulseChart.PopupMinWidth,\r\n Math.min(PulseChart.PopupMaxWidth, DataViewObjects.getValue<number>(\r\n objects,\r\n PulseChart.Properties[\"popup\"][\"width\"],\r\n PulseChart.DefaultSettings.popup.width)));\r\n\r\n var height: number = Math.max(PulseChart.PopupMinHeight,\r\n Math.min(PulseChart.PopupMaxHeight, DataViewObjects.getValue<number>(\r\n objects,\r\n PulseChart.Properties[\"popup\"][\"height\"],\r\n PulseChart.DefaultSettings.popup.height)));\r\n\r\n var colorHelper = new ColorHelper(\r\n colors,\r\n PulseChart.Properties[\"popup\"][\"color\"],\r\n PulseChart.DefaultSettings.popup.color);\r\n\r\n var color = colorHelper.getColorForMeasure(objects, \"\");\r\n\r\n var fontSize = parseInt(DataViewObjects.getValue<any>(\r\n objects,\r\n PulseChart.Properties[\"popup\"][\"fontSize\"],\r\n PulseChart.DefaultSettings.popup.fontSize), 10);\r\n\r\n var fontColorHelper = new ColorHelper(\r\n colors,\r\n PulseChart.Properties[\"popup\"][\"fontColor\"],\r\n PulseChart.DefaultSettings.popup.fontColor);\r\n\r\n var fontColor = fontColorHelper.getColorForMeasure(objects, \"\");\r\n\r\n var showTime = DataViewObjects.getValue<boolean>(\r\n objects,\r\n PulseChart.Properties[\"popup\"][\"showTime\"],\r\n PulseChart.DefaultSettings.popup.showTime);\r\n\r\n var showTitle = DataViewObjects.getValue<boolean>(\r\n objects,\r\n PulseChart.Properties[\"popup\"][\"showTitle\"],\r\n PulseChart.DefaultSettings.popup.showTitle);\r\n\r\n var timeColorHelper = new ColorHelper(\r\n colors,\r\n PulseChart.Properties[\"popup\"][\"timeColor\"],\r\n PulseChart.DefaultSettings.popup.timeColor);\r\n\r\n var timeColor = timeColorHelper.getColorForMeasure(objects, \"\");\r\n\r\n var timeFillHelper = new ColorHelper(\r\n colors,\r\n PulseChart.Properties[\"popup\"][\"timeFill\"],\r\n PulseChart.DefaultSettings.popup.timeFill);\r\n\r\n var timeFill = timeFillHelper.getColorForMeasure(objects, \"\");\r\n return {\r\n show: show,\r\n alwaysOnTop: alwaysOnTop,\r\n width: width,\r\n height: height,\r\n color: color,\r\n fontSize: fontSize,\r\n fontColor: fontColor,\r\n showTime: showTime,\r\n showTitle: showTitle,\r\n timeColor: timeColor,\r\n timeFill: timeFill,\r\n };\r\n }\r\n\r\n private static getDotsSettings(objects: DataViewObjects, colors: IDataColorPalette): PulseChartDotsSettings {\r\n var properties = PulseChart.Properties[\"dots\"],\r\n defaultSettings: PulseChartDotsSettings = PulseChart.DefaultSettings.dots;\r\n\r\n var colorHelper = new ColorHelper(\r\n colors,\r\n properties[\"color\"],\r\n defaultSettings.color);\r\n\r\n var color = colorHelper.getColorForMeasure(objects, \"\");\r\n\r\n var minSize: number = Math.max(0, Math.min(9999, DataViewObjects.getValue<number>(\r\n objects,\r\n properties[\"minSize\"],\r\n defaultSettings.minSize)));\r\n\r\n var maxSize: number = Math.max(minSize, Math.min(9999, DataViewObjects.getValue<number>(\r\n objects,\r\n properties[\"maxSize\"],\r\n defaultSettings.maxSize)));\r\n\r\n var size: number = Math.max(minSize, Math.min(maxSize, DataViewObjects.getValue<number>(\r\n objects,\r\n properties[\"size\"],\r\n defaultSettings.size)));\r\n\r\n var transparency: number = Math.max(0, Math.min(100, DataViewObjects.getValue<number>(\r\n objects,\r\n properties[\"transparency\"],\r\n defaultSettings.transparency)));\r\n\r\n return {\r\n color: color,\r\n size: size,\r\n minSize: minSize,\r\n maxSize: maxSize,\r\n transparency: transparency,\r\n };\r\n }\r\n\r\n private static getSeriesSettings(objects: DataViewObjects, colors: IDataColorPalette): PulseChartSeriesSetting {\r\n var width = Math.max(1, Math.min(100, DataViewObjects.getValue<number>(\r\n objects,\r\n PulseChart.Properties[\"series\"][\"width\"],\r\n PulseChart.DefaultSettings.series.width)));\r\n\r\n var colorHelper = new ColorHelper(\r\n colors,\r\n PulseChart.Properties[\"series\"][\"fill\"],\r\n PulseChart.DefaultSettings.series.fill);\r\n\r\n var fill = colorHelper.getColorForMeasure(objects, \"\");\r\n\r\n /*var showByDefault = DataViewObjects.getValue<boolean>(\r\n objects,\r\n PulseChart.Properties[\"series\"][\"showByDefault\"],\r\n PulseChart.DefaultSettings.series.showByDefault);*/\r\n\r\n return {\r\n width,\r\n fill,\r\n //showByDefault\r\n };\r\n }\r\n\r\n private static getGapsSettings(objects: DataViewObjects): PulseChartGapsSettings {\r\n var show = DataViewObjects.getValue<boolean>(\r\n objects,\r\n PulseChart.Properties[\"gaps\"][\"show\"],\r\n PulseChart.DefaultSettings.gaps.show);\r\n\r\n var visibleGapsPercentage = Math.max(1, Math.min(100, DataViewObjects.getValue<number>(\r\n objects,\r\n PulseChart.Properties[\"gaps\"][\"transparency\"],\r\n PulseChart.DefaultSettings.gaps.visibleGapsPercentage)));\r\n return {\r\n show: show,\r\n visibleGapsPercentage: visibleGapsPercentage\r\n };\r\n }\r\n\r\n private static getAxisXSettings(objects: DataViewObjects, colors: IDataColorPalette): PulseChartXAxisSettings {\r\n var properties = PulseChart.Properties[\"xAxis\"],\r\n defaultSettings: PulseChartXAxisSettings = PulseChart.DefaultSettings.xAxis;\r\n\r\n var color = new ColorHelper(colors,\r\n properties[\"color\"],\r\n defaultSettings.color).getColorForMeasure(objects, \"\");\r\n\r\n var fontColor = new ColorHelper(colors,\r\n properties[\"fontColor\"],\r\n defaultSettings.fontColor).getColorForMeasure(objects, \"\");\r\n\r\n var show = DataViewObjects.getValue<boolean>(\r\n objects,\r\n properties[\"show\"],\r\n defaultSettings.show);\r\n\r\n var position = DataViewObjects.getValue<XAxisPosition>(\r\n objects,\r\n properties[\"position\"],\r\n defaultSettings.position);\r\n\r\n var backgroundColor = new ColorHelper(colors,\r\n properties[\"backgroundColor\"],\r\n defaultSettings.backgroundColor).getColorForMeasure(objects, \"\");\r\n\r\n return {\r\n show: show,\r\n position: position,\r\n color: color,\r\n fontColor: fontColor,\r\n backgroundColor: backgroundColor\r\n };\r\n }\r\n\r\n private static getAxisYSettings(objects: DataViewObjects, colors: IDataColorPalette): PulseChartYAxisSettings {\r\n var properties = PulseChart.Properties[\"yAxis\"],\r\n defaultSettings: PulseChartYAxisSettings = PulseChart.DefaultSettings.yAxis;\r\n\r\n var colorHelper = new ColorHelper(\r\n colors,\r\n properties[\"color\"],\r\n defaultSettings.color);\r\n\r\n var color = colorHelper.getColorForMeasure(objects, \"\");\r\n\r\n var fontColorHelper = new ColorHelper(\r\n colors,\r\n properties[\"fontColor\"],\r\n defaultSettings.fontColor);\r\n\r\n var fontColor = fontColorHelper.getColorForMeasure(objects, \"\");\r\n\r\n var show = DataViewObjects.getValue<boolean>(\r\n objects,\r\n properties[\"show\"],\r\n defaultSettings.show);\r\n\r\n return {\r\n color: color,\r\n fontColor: fontColor,\r\n show: show,\r\n };\r\n }\r\n\r\n private static getPlaybackSettings(objects: DataViewObjects, colors: IDataColorPalette): PulseChartPlaybackSettings {\r\n var playbackSettings: PulseChartPlaybackSettings = <PulseChartPlaybackSettings> {};\r\n var properties = PulseChart.Properties[\"playback\"],\r\n defaultSettings: PulseChartPlaybackSettings = PulseChart.DefaultSettings.playback;\r\n\r\n playbackSettings.autoplay = DataViewObjects.getValue<boolean>(\r\n objects,\r\n properties[\"autoplay\"],\r\n defaultSettings.autoplay);\r\n\r\n playbackSettings.playSpeed = Math.max(1, Math.min(99999, DataViewObjects.getValue<number>(\r\n objects,\r\n properties[\"playSpeed\"],\r\n defaultSettings.playSpeed)));\r\n\r\n playbackSettings.pauseDuration = Math.max(0, Math.min(9999, DataViewObjects.getValue<number>(\r\n objects,\r\n properties[\"pauseDuration\"],\r\n defaultSettings.pauseDuration)));\r\n\r\n playbackSettings.autoplayPauseDuration = Math.max(1, Math.min(9999, DataViewObjects.getValue<number>(\r\n objects,\r\n properties[\"autoplayPauseDuration\"],\r\n defaultSettings.autoplayPauseDuration)));\r\n\r\n var colorHelper = new ColorHelper(\r\n colors,\r\n properties[\"color\"],\r\n defaultSettings.color);\r\n\r\n playbackSettings.color = colorHelper.getColorForMeasure(objects, \"\");\r\n\r\n var position:string = DataViewObjects.getValue<string>(objects, properties[\"position\"], \"\");\r\n if (position.length > 3) {\r\n try {\r\n playbackSettings.position = JSON.parse(position);\r\n } catch(ex) {}\r\n }\r\n\r\n playbackSettings.position = playbackSettings.position || defaultSettings.position;\r\n\r\n return playbackSettings;\r\n }\r\n\r\n private static getRunnerCounterSettings(\r\n objects: DataViewObjects,\r\n colors: IDataColorPalette,\r\n columns: PulseChartDataRoles<DataViewCategoricalColumn>): PulseChartRunnerCounterSettings {\r\n\r\n var show: boolean = DataViewObjects.getValue<boolean>(\r\n objects,\r\n PulseChart.Properties[\"runnerCounter\"][\"show\"],\r\n PulseChart.DefaultSettings.runnerCounter.show);\r\n\r\n var label: string = DataViewObjects.getValue<string>(\r\n objects,\r\n PulseChart.Properties[\"runnerCounter\"][\"label\"],\r\n columns.RunnerCounter && columns.RunnerCounter.source && columns.RunnerCounter.source.displayName\r\n || PulseChart.DefaultSettings.runnerCounter.label);\r\n\r\n var position = DataViewObjects.getValue<RunnerCounterPosition>(\r\n objects,\r\n PulseChart.Properties[\"runnerCounter\"][\"position\"],\r\n PulseChart.DefaultSettings.runnerCounter.position);\r\n\r\n var fontSize = parseInt(DataViewObjects.getValue<any>(\r\n objects,\r\n PulseChart.Properties[\"runnerCounter\"][\"fontSize\"],\r\n PulseChart.DefaultSettings.runnerCounter.fontSize), 10);\r\n\r\n var fontColor = new ColorHelper(\r\n colors,\r\n PulseChart.Properties[\"runnerCounter\"][\"fontColor\"],\r\n PulseChart.DefaultSettings.runnerCounter.fontColor)\r\n .getColorForMeasure(objects, \"\");\r\n\r\n return {\r\n show: show,\r\n label: label,\r\n position: position,\r\n fontSize: fontSize,\r\n fontColor: fontColor\r\n };\r\n }\r\n\r\n private clearAll(hide: boolean): void {\r\n this.gaps.selectAll(PulseChart.Gap.selector).remove();\r\n\r\n if (this.animationHandler) {\r\n this.animationHandler.reset();\r\n this.animationHandler.clear();\r\n }\r\n\r\n if(hide) {\r\n this.svg.style('display', \"none\");\r\n }\r\n\r\n this.clearChart();\r\n }\r\n\r\n public clearChart(): void {\r\n this.clearSelection();\r\n this.hideAnimationDot();\r\n this.chart.selectAll(PulseChart.Line.selector).remove();\r\n this.chart.selectAll(PulseChart.Dot.selector).remove();\r\n }\r\n\r\n public clearRedundant(position: PulseChartAnimationPosition): void {\r\n if(!this.data) {\r\n return;\r\n }\r\n\r\n var popups = this.chart.selectAll(PulseChart.Tooltip.selector).filter((data: PulseChartDataPoint) => {\r\n return data.seriesIndex < position.series || data.seriesIndex === position.series\r\n && this.data.series[data.seriesIndex].data[position.index].value >= data.value;\r\n });\r\n\r\n var selectedPopupsIds = popups.data().map((data: PulseChartDataPoint) => data.identity);\r\n this.clearSelection();\r\n\r\n if(selectedPopupsIds.length) {\r\n var popupsSelecting = selectedPopupsIds.map(id => this.selectionManager.select(id, true));\r\n (<JQueryPromise<{}>>$.when.apply(null, popupsSelecting))\r\n .then((selectionIds: SelectionId[]) => this.setSelection(selectionIds));\r\n }\r\n\r\n this.chart.selectAll(PulseChart.Line.selector).remove();\r\n this.chart.selectAll(PulseChart.Dot.selector).remove();\r\n }\r\n\r\n public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstanceEnumeration {\r\n var enumeration = new ObjectEnumerationBuilder();\r\n switch (options.objectName) {\r\n case \"general\": {\r\n this.readGeneralInstance(enumeration);\r\n break;\r\n }\r\n case \"popup\": {\r\n this.readPopupInstance(enumeration);\r\n break;\r\n }\r\n case \"dots\": {\r\n this.readDotsInstance(enumeration);\r\n break;\r\n }\r\n case \"xAxis\": {\r\n this.xAxisInstance(enumeration);\r\n break;\r\n }\r\n case \"yAxis\": {\r\n this.yAxisInstance(enumeration);\r\n break;\r\n }\r\n case \"series\": {\r\n this.readSeriesInstance(enumeration);\r\n break;\r\n }\r\n case \"gaps\": {\r\n this.readGapsInstance(enumeration);\r\n break;\r\n }\r\n case \"playback\": {\r\n this.readPlaybackInstance(enumeration);\r\n break;\r\n }\r\n case \"runnerCounter\": {\r\n this.readRunnerCounterInstance(enumeration);\r\n break;\r\n }\r\n }\r\n\r\n return enumeration.complete();\r\n }\r\n\r\n private getSettings(name: string): any {\r\n if (this.data && this.data.settings && this.data.settings[name]) {\r\n return this.data.settings[name];\r\n }\r\n return PulseChart.DefaultSettings[name];\r\n }\r\n\r\n private readGeneralInstance(enumeration: ObjectEnumerationBuilder): void {\r\n var instance: VisualObjectInstance = {\r\n objectName: \"general\",\r\n displayName: \"general\",\r\n selector: null,\r\n properties: {\r\n }\r\n };\r\n\r\n enumeration.pushInstance(instance);\r\n }\r\n\r\n private readPopupInstance(enumeration: ObjectEnumerationBuilder): void {\r\n var settings: PulseChartPopupSettings = this.getSettings(\"popup\");\r\n\r\n var popup: VisualObjectInstance = {\r\n objectName: \"popup\",\r\n displayName: \"popup\",\r\n selector: null,\r\n properties: {\r\n show: settings.show,\r\n alwaysOnTop: settings.alwaysOnTop,\r\n width: settings.width,\r\n height: settings.height,\r\n color: settings.color,\r\n fontColor: settings.fontColor,\r\n fontSize: settings.fontSize,\r\n showTime: settings.showTime,\r\n showTitle: settings.showTitle,\r\n timeColor: settings.timeColor,\r\n timeFill: settings.timeFill,\r\n }\r\n };\r\n\r\n enumeration.pushInstance(popup);\r\n }\r\n\r\n private readDotsInstance(enumeration: ObjectEnumerationBuilder): void {\r\n var settings: PulseChartDotsSettings = this.getSettings(\"dots\");\r\n\r\n var instance: VisualObjectInstance = {\r\n objectName: \"dots\",\r\n displayName: \"Dots\",\r\n selector: null,\r\n properties: {\r\n color: settings.color,\r\n size: settings.size,\r\n minSize: settings.minSize,\r\n maxSize: settings.maxSize,\r\n transparency: settings.transparency,\r\n }\r\n };\r\n\r\n enumeration.pushInstance(instance);\r\n }\r\n\r\n private xAxisInstance(enumeration: ObjectEnumerationBuilder): void {\r\n var settings: PulseChartXAxisSettings = this.getSettings(\"xAxis\");\r\n\r\n enumeration.pushInstance({\r\n objectName: \"xAxis\",\r\n displayName: \"xAxis\",\r\n selector: null,\r\n properties: {\r\n show: settings.show,\r\n position: settings.position,\r\n color: settings.color,\r\n fontColor: settings.fontColor,\r\n backgroundColor: settings.backgroundColor\r\n }\r\n });\r\n }\r\n\r\n private yAxisInstance(enumeration: ObjectEnumerationBuilder): void {\r\n var settings: PulseChartYAxisSettings = this.getSettings(\"yAxis\");\r\n\r\n enumeration.pushInstance({\r\n objectName: \"yAxis\",\r\n displayName: \"yAxis\",\r\n selector: null,\r\n properties: {\r\n color: settings.color,\r\n fontColor: settings.fontColor,\r\n show: settings.show\r\n }\r\n });\r\n }\r\n\r\n private readSeriesInstance(enumeration: ObjectEnumerationBuilder): void {\r\n var settings: PulseChartSeriesSetting = this.getSettings(\"series\");\r\n\r\n var series: VisualObjectInstance = {\r\n objectName: \"series\",\r\n displayName: \"series\",\r\n selector: null,\r\n properties: {\r\n fill: settings.fill,\r\n width: settings.width,\r\n //showByDefault: settings.showByDefault\r\n }\r\n };\r\n\r\n enumeration.pushInstance(series);\r\n }\r\n\r\n private readGapsInstance(enumeration: ObjectEnumerationBuilder): void {\r\n var settings: PulseChartGapsSettings = this.getSettings(\"gaps\");\r\n\r\n var gaps: VisualObjectInstance = {\r\n objectName: \"gaps\",\r\n selector: null,\r\n properties: {\r\n show: settings.show,\r\n transparency: settings.visibleGapsPercentage //visibleGapsPercentage\r\n }\r\n };\r\n\r\n enumeration.pushInstance(gaps);\r\n }\r\n\r\n private readPlaybackInstance(enumeration: ObjectEnumerationBuilder): void {\r\n var settings: PulseChartPlaybackSettings = this.getSettings(\"playback\");\r\n\r\n enumeration.pushInstance({\r\n objectName: \"playback\",\r\n displayName: \"playback\",\r\n selector: null,\r\n properties: {\r\n autoplay: settings.autoplay,\r\n playSpeed: settings.playSpeed,\r\n pauseDuration: settings.pauseDuration,\r\n autoplayPauseDuration: settings.autoplayPauseDuration,\r\n color: settings.color,\r\n }\r\n });\r\n }\r\n\r\n private readRunnerCounterInstance(enumeration: ObjectEnumerationBuilder): void {\r\n var runnerCounterSettings: PulseChartRunnerCounterSettings = this.getSettings(\"runnerCounter\");\r\n\r\n var instance: VisualObjectInstance = {\r\n objectName: \"runnerCounter\",\r\n selector: null,\r\n properties: {\r\n }\r\n };\r\n\r\n if(this.data &&\r\n this.data.columns &&\r\n this.data.columns.RunnerCounter) {\r\n instance.properties = {\r\n show: runnerCounterSettings.show,\r\n label: runnerCounterSettings.label,\r\n position: runnerCounterSettings.position,\r\n fontSize: runnerCounterSettings.fontSize,\r\n fontColor: runnerCounterSettings.fontColor\r\n };\r\n }\r\n\r\n enumeration.pushInstance(instance);\r\n }\r\n\r\n public destroy(): void {\r\n this.data = null;\r\n this.clearAll(true);\r\n }\r\n }\r\n\r\n enum PulseAnimatorStates {\r\n Ready,\r\n Play,\r\n Paused,\r\n Stopped,\r\n }\r\n\r\n export class PulseAnimator {\r\n private chart: PulseChart;\r\n private svg: D3.Selection;\r\n private animationPlay: D3.Selection;\r\n private animationPause: D3.Selection;\r\n private animationReset: D3.Selection;\r\n private animationToEnd: D3.Selection;\r\n private animationPrev: D3.Selection;\r\n private animationNext: D3.Selection;\r\n private runnerCounter: D3.Selection;\r\n private runnerCounterText: D3.Selection;\r\n\r\n private static AnimationPlay: ClassAndSelector = createClassAndSelector('animationPlay');\r\n private static AnimationPause: ClassAndSelector = createClassAndSelector('animationPause');\r\n private static AnimationReset: ClassAndSelector = createClassAndSelector('animationReset');\r\n private static AnimationToEnd: ClassAndSelector = createClassAndSelector('animationToEnd');\r\n private static AnimationPrev: ClassAndSelector = createClassAndSelector('animationPrev');\r\n private static AnimationNext: ClassAndSelector = createClassAndSelector('animationNext');\r\n private static RunnerCounter: ClassAndSelector = createClassAndSelector('runnerCounter');\r\n private animatorState: PulseAnimatorStates;\r\n\r\n public static get AnimationMinPosition(): PulseChartAnimationPosition {\r\n return { series: 0, index: 0 };\r\n }\r\n\r\n //private static ControlsDuration = 250;\r\n private static DimmedOpacity = 0.25;\r\n private static DefaultOpacity = 1;\r\n private static DefaultControlsColor = \"#777\";\r\n\r\n private container: D3.Selection;\r\n public animationPlayingIndex: number = 0;\r\n\r\n private runnerCounterValue: any;\r\n private runnerCounterTopLeftPosition: number = 180;\r\n private get runnerCounterPosition(): RunnerCounterPosition {\r\n return this.chart.data.settings.runnerCounter.position;\r\n }\r\n private get maxTextWidthOfRunnerCounterValue(): number {\r\n var top = this.runnerCounterPosition === RunnerCounterPosition.TopLeft || this.runnerCounterPosition === RunnerCounterPosition.TopRight;\r\n return this.chart.viewport.width - (top ? this.runnerCounterTopLeftPosition : 0);\r\n }\r\n\r\n private color: string;\r\n private isAutoPlayed: boolean = false;\r\n\r\n public get isAnimated(): boolean {\r\n return (this.animatorState === PulseAnimatorStates.Paused) ||\r\n (this.animatorState === PulseAnimatorStates.Play) ||\r\n (this.animatorState === PulseAnimatorStates.Stopped);\r\n }\r\n\r\n public get isPlaying(): boolean {\r\n return this.animatorState === PulseAnimatorStates.Play;\r\n }\r\n\r\n public get isPaused(): boolean {\r\n return this.animatorState === PulseAnimatorStates.Paused;\r\n }\r\n\r\n public get isStopped(): boolean {\r\n return this.animatorState === PulseAnimatorStates.Stopped;\r\n }\r\n\r\n constructor(chart: PulseChart, svg: D3.Selection) {\r\n this.chart = chart;\r\n this.svg = svg;\r\n\r\n this.setDefaultValues();\r\n\r\n var container = this.container = this.svg.append('g');\r\n\r\n this.animationPlay = container.append('g').classed(PulseAnimator.AnimationPlay.class, true);\r\n this.animationPlay\r\n .append(\"circle\")\r\n .attr(\"cx\", 12)\r\n .attr(\"cy\", 12)\r\n .attr(\"r\", 10)\r\n .attr(\"fill\", \"transparent\");\r\n this.animationPlay\r\n .call(PulseChart.AddOnTouchClick, () => this.play());\r\n\r\n this.animationPlay\r\n .append(\"path\")\r\n .attr(\"d\", \"M12 2c5.514 0 10 4.486 10 10s-4.486 10-10 10-10-4.486-10-10 4.486-10 10-10zm0-2c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm-3 17v-10l9 5.146-9 4.854z\");\r\n\r\n this.animationPause = container.append('g').classed(PulseAnimator.AnimationPause.class, true);\r\n this.animationPause\r\n .append(\"circle\")\r\n .attr(\"cx\", 12)\r\n .attr(\"cy\", 12)\r\n .attr(\"r\", 10)\r\n .attr(\"fill\", \"transparent\");\r\n this.animationPause\r\n .call(PulseChart.AddOnTouchClick, () => this.stop());\r\n\r\n this.animationPause\r\n .append(\"path\")\r\n .attr(\"d\", \"M12 2c5.514 0 10 4.486 10 10s-4.486 10-10 10-10-4.486-10-10 4.486-10 10-10zm0-2c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm-1 17h-3v-10h3v10zm5-10h-3v10h3v-10z\");\r\n\r\n this.animationReset = container.append('g').classed(PulseAnimator.AnimationReset.class, true);\r\n this.animationReset\r\n .append(\"circle\")\r\n .attr(\"cx\", 12)\r\n .attr(\"cy\", 12)\r\n .attr(\"r\", 10)\r\n .attr(\"fill\", \"transparent\");\r\n this.animationReset\r\n .call(PulseChart.AddOnTouchClick, () => this.reset());\r\n\r\n this.animationReset\r\n .append(\"path\")\r\n .attr(\"d\", \"M22 12c0 5.514-4.486 10-10 10s-10-4.486-10-10 4.486-10 10-10 10 4.486 10 10zm-22 0c0 6.627 5.373 12 12 12s12-5.373 12-12-5.373-12-12-12-12 5.373-12 12zm13 0l5-4v8l-5-4zm-5 0l5-4v8l-5-4zm-2 4h2v-8h-2v8z\");\r\n\r\n /* Prev */\r\n this.animationPrev = container.append('g').classed(PulseAnimator.AnimationPrev.class, true);\r\n this.animationPrev\r\n .append(\"circle\")\r\n .attr(\"cx\", 12)\r\n .attr(\"cy\", 12)\r\n .attr(\"r\", 10)\r\n .attr(\"fill\", \"transparent\");\r\n this.animationPrev\r\n .call(PulseChart.AddOnTouchClick, () => this.prev());\r\n\r\n this.animationPrev\r\n .append(\"path\")\r\n .attr(\"d\", \"M9.5 12l7.5-4.5v9l-7.5-4.5zm-4.5 0l6.5 4v-1.634l-3.943-2.366 3.943-2.366v-1.634l-6.5 4zm17 0c0 5.514-4.486 10-10 10s-10-4.486-10-10 4.486-10 10-10 10 4.486 10 10zm-22 0c0 6.627 5.373 12 12 12s12-5.373 12-12-5.373-12-12-12-12 5.373-12 12z\");\r\n\r\n /* Next */\r\n this.animationNext = container.append('g').classed(PulseAnimator.AnimationNext.class, true);\r\n this.animationNext\r\n .append(\"circle\")\r\n .attr(\"cx\", 12)\r\n .attr(\"cy\", 12)\r\n .attr(\"r\", 10)\r\n .attr(\"fill\", \"transparent\");\r\n this.animationNext\r\n .call(PulseChart.AddOnTouchClick, () => this.next());\r\n\r\n this.animationNext\r\n .append(\"path\")\r\n .attr(\"d\", \"M7 16.5v-9l7.5 4.5-7.5 4.5zm5.5-8.5v1.634l3.943 2.366-3.943 2.366v1.634l6.5-4-6.5-4zm-.5-6c5.514 0 10 4.486 10 10s-4.486 10-10 10-10-4.486-10-10 4.486-10 10-10zm0-2c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12z\")\r\n .attr(\"rotate\", 180);\r\n\r\n /* ToEnd */\r\n this.animationToEnd = container.append('g').classed(PulseAnimator.AnimationToEnd.class, true);\r\n this.animationToEnd\r\n .append(\"circle\")\r\n .attr(\"cx\", 12)\r\n .attr(\"cy\", 12)\r\n .attr(\"r\", 10)\r\n .attr(\"fill\", \"transparent\");\r\n this.animationToEnd\r\n .call(PulseChart.AddOnTouchClick, () => this.toEnd());\r\n\r\n this.animationToEnd\r\n .append(\"path\")\r\n .attr(\"d\", \"M12 2c5.514 0 10 4.486 10 10s-4.486 10-10 10-10-4.486-10-10 4.486-10 10-10zm0-2c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm-6 16v-8l5 4-5 4zm5 0v-8l5 4-5 4zm7-8h-2v8h2v-8z\");\r\n\r\n this.runnerCounter = container.append('g').classed(PulseAnimator.RunnerCounter.class, true);\r\n this.runnerCounterText = this.runnerCounter.append('text');\r\n this.setControlsColor(PulseAnimator.DefaultControlsColor);\r\n }\r\n\r\n private setDefaultValues() {\r\n this.position = PulseAnimator.AnimationMinPosition;\r\n this.animatorState = PulseAnimatorStates.Ready;\r\n this.runnerCounterValue = null;\r\n }\r\n\r\n public render(): void {\r\n this.renderControls();\r\n this.disableControls();\r\n\r\n if(!this.chart.isAutoPlay) {\r\n this.isAutoPlayed = true;\r\n if(this.savedPosition) {\r\n this.savedPosition = null;\r\n }\r\n }\r\n\r\n if(this.chart.isAutoPlay && this.isAutoPlayed\r\n && this.animatorState === PulseAnimatorStates.Play\r\n && !this.isPositionWasSaved\r\n && !_.isEqual(this.autoPlayPosition, this.savedPosition)) {\r\n this.chart.stopAnimation();\r\n this.isAutoPlayed = false;\r\n this.isPositionWasSaved = true;\r\n this.animatorState = PulseAnimatorStates.Ready;\r\n }\r\n\r\n if (this.animatorState === PulseAnimatorStates.Play) {\r\n this.play();\r\n } else if (this.chart.isAutoPlay && !this.isAutoPlayed && (this.animatorState === PulseAnimatorStates.Ready)) {\r\n //console.log(\"loaded \" + JSON.stringify(this.savedPosition));\r\n this.autoPlayPosition = this.savedPosition;\r\n this.isAutoPlayed = true;\r\n if(this.savedPosition\r\n && this.savedPosition.series < this.chart.data.series.length\r\n && this.savedPosition.index < this.chart.data.series[this.savedPosition.series].data.length) {\r\n this.position = this.savedPosition;\r\n }\r\n\r\n this.play(this.chart.autoplayPauseDuration);\r\n } else {\r\n this.chart.renderChart();\r\n }\r\n }\r\n\r\n public setControlsColor(color: string): void {\r\n this.color = color;\r\n }\r\n\r\n private renderControls(): void {\r\n this.show();\r\n\r\n this.animationPlay\r\n .attr('transform', SVGUtil.translate(0, 0))\r\n .attr(\"fill\", this.color);\r\n\r\n this.animationPause\r\n .attr('transform', SVGUtil.translate(30, 0))\r\n .attr(\"fill\", this.color);\r\n\r\n this.animationReset\r\n .attr('transform', SVGUtil.translate(60, 0))\r\n .attr(\"fill\", this.color);\r\n\r\n this.animationPrev\r\n .attr('transform', SVGUtil.translate(90, 0))\r\n .attr(\"fill\", this.color);\r\n\r\n this.animationNext\r\n .attr('transform', SVGUtil.translate(120, 0))\r\n .attr(\"fill\", this.color);\r\n\r\n this.animationToEnd\r\n .attr('transform', SVGUtil.translate(150, 0))\r\n .attr(\"fill\", this.color);\r\n\r\n this.runnerCounter\r\n .attr('fill', this.color)\r\n .attr('transform',\r\n SVGUtil.translate(this.runnerCounterPosition === RunnerCounterPosition.TopLeft\r\n ? this.runnerCounterTopLeftPosition\r\n : this.chart.viewport.width - 2,\r\n this.chart.data.runnerCounterHeight/2 + 7));\r\n this.runnerCounterText\r\n .style('text-anchor', this.runnerCounterPosition === RunnerCounterPosition.TopLeft ? \"start\" : \"end\" );\r\n\r\n if (this.chart.data && this.chart.data.settings) {\r\n this.runnerCounterText.style(PulseChart.ConvertTextPropertiesToStyle(\r\n PulseChart.GetRunnerCounterTextProperties(null, this.chart.data.settings.runnerCounter.fontSize)));\r\n this.runnerCounterText.style('fill', this.chart.data.settings.runnerCounter.fontColor);\r\n }\r\n\r\n this.drawCounterValue();\r\n }\r\n\r\n private static setControlVisiblity(element: D3.Selection, isVisible: boolean, isDisabled: boolean = false): void {\r\n element\r\n .style('opacity', isVisible ? PulseAnimator.DefaultOpacity : PulseAnimator.DimmedOpacity);\r\n if (isVisible) {\r\n element.attr('display', \"inline\");\r\n } else if (isDisabled) {\r\n element.attr('display', \"none\");\r\n }\r\n }\r\n\r\n private disableControls(): void {\r\n var showRunner = this.chart.data && this.chart.data.settings && this.chart.data.settings.runnerCounter.show;\r\n PulseAnimator.setControlVisiblity(this.animationReset, true);\r\n PulseAnimator.setControlVisiblity(this.animationToEnd, true);\r\n\r\n switch (this.animatorState) {\r\n case PulseAnimatorStates.Play:\r\n PulseAnimator.setControlVisiblity(this.animationPlay, false);\r\n\r\n PulseAnimator.setControlVisiblity(this.animationPrev, true);\r\n PulseAnimator.setControlVisiblity(this.animationNext, true);\r\n\r\n PulseAnimator.setControlVisiblity(this.animationPause, true);\r\n PulseAnimator.setControlVisiblity(this.runnerCounter, showRunner, true);\r\n break;\r\n case PulseAnimatorStates.Paused:\r\n PulseAnimator.setControlVisiblity(this.animationPlay, true);\r\n PulseAnimator.setControlVisiblity(this.animationPause, true);\r\n\r\n PulseAnimator.setControlVisiblity(this.animationPrev, true);\r\n PulseAnimator.setControlVisiblity(this.animationNext, true);\r\n\r\n PulseAnimator.setControlVisiblity(this.runnerCounter, showRunner, true);\r\n break;\r\n case PulseAnimatorStates.Stopped:\r\n PulseAnimator.setControlVisiblity(this.animationPlay, true);\r\n\r\n PulseAnimator.setControlVisiblity(this.animationPrev, true);\r\n PulseAnimator.setControlVisiblity(this.animationNext, true);\r\n\r\n PulseAnimator.setControlVisiblity(this.runnerCounter, showRunner, true);\r\n\r\n PulseAnimator.setControlVisiblity(this.animationPause, false);\r\n break;\r\n case PulseAnimatorStates.Ready:\r\n PulseAnimator.setControlVisiblity(this.animationPlay, true);\r\n\r\n PulseAnimator.setControlVisiblity(this.animationPrev, false);\r\n PulseAnimator.setControlVisiblity(this.animationNext, false);\r\n\r\n PulseAnimator.setControlVisiblity(this.animationPause, false);\r\n PulseAnimator.setControlVisiblity(this.runnerCounter, false, true);\r\n break;\r\n default:\r\n PulseAnimator.setControlVisiblity(this.animationPlay, true);\r\n\r\n PulseAnimator.setControlVisiblity(this.animationPrev, false);\r\n PulseAnimator.setControlVisiblity(this.animationNext, false);\r\n\r\n PulseAnimator.setControlVisiblity(this.animationPause, false);\r\n PulseAnimator.setControlVisiblity(this.runnerCounter, false, true);\r\n break;\r\n }\r\n }\r\n\r\n public show(): void {\r\n this.container.style('display', 'inline');\r\n }\r\n\r\n public setRunnerCounterValue(index?: number): void {\r\n var dataPoint: PulseChartDataPoint = this.chart.data\r\n && this.chart.data.series\r\n && this.chart.data.series[this.position.series]\r\n && this.chart.data.series[this.position.series].data\r\n && this.chart.data.series[this.position.series].data[$.isNumeric(index) ? index : this.flooredPosition.index];\r\n\r\n var runnerCounterValue: string = (dataPoint && dataPoint.runnerCounterValue != null)\r\n ? dataPoint.runnerCounterValue\r\n : \"\";\r\n\r\n if(dataPoint && dataPoint.runnerCounterFormatString) {\r\n var runnerCounterformatter = visuals.valueFormatter.create({ format: dataPoint.runnerCounterFormatString });\r\n runnerCounterValue = runnerCounterformatter.format(runnerCounterValue);\r\n }\r\n\r\n this.runnerCounterValue = this.chart.data.settings.runnerCounter.label + \" \" + runnerCounterValue;\r\n this.drawCounterValue();\r\n }\r\n\r\n private drawCounterValue(): void {\r\n var progressText = `${this.runnerCounterValue}`;\r\n this.runnerCounterText.text(progressText);\r\n TextMeasurementService.svgEllipsis(<any>this.runnerCounterText.node(), this.maxTextWidthOfRunnerCounterValue);\r\n }\r\n\r\n public play(delay: number = 0, renderDuringPlaying: boolean = false): void {\r\n if (this.animatorState === PulseAnimatorStates.Play && !renderDuringPlaying) {\r\n return;\r\n }\r\n\r\n if (this.animatorState === PulseAnimatorStates.Ready) {\r\n this.animationPlayingIndex++;\r\n this.chart.clearChart();\r\n }\r\n\r\n if (this.chart.isAnimationIndexLast(this.position)) {\r\n this.playNext();\r\n return;\r\n }\r\n\r\n if(this.animatorState === PulseAnimatorStates.Paused) {\r\n this.chart.clearSelection();\r\n }\r\n\r\n this.animatorState = PulseAnimatorStates.Play;\r\n this.chart.renderChart();\r\n this.chart.playAnimation(delay);\r\n this.disableControls();\r\n }\r\n\r\n public playNext(): void {\r\n this.pause();\r\n\r\n if (this.chart.isAnimationSeriesLast(this.position)) {\r\n this.setDefaultValues();\r\n this.chart.clearSelection();\r\n } else {\r\n this.position = {\r\n series: this.position.series + 1,\r\n index: PulseAnimator.AnimationMinPosition.index,\r\n };\r\n this.play();\r\n }\r\n }\r\n\r\n public pause(): void {\r\n if (this.animatorState === PulseAnimatorStates.Play) {\r\n this.animatorState = PulseAnimatorStates.Paused;\r\n this.chart.pauseAnimation();\r\n }\r\n\r\n this.disableControls();\r\n }\r\n\r\n public reset(): void {\r\n this.clearTimeouts();\r\n this.chart.stopAnimation();\r\n this.chart.clearSelection();\r\n this.chart.clearChart();\r\n\r\n this.setDefaultValues();\r\n this.animatorState = PulseAnimatorStates.Stopped;\r\n\r\n this.disableControls();\r\n this.savedPosition = null;\r\n }\r\n\r\n private next(): void {\r\n\r\n if (!this.isAnimated) {\r\n return;\r\n }\r\n\r\n this.stop();\r\n\r\n var newPosition: PulseChartAnimationPosition = this.chart.findNextPoint(this.position);\r\n if (newPosition) {\r\n this.position = newPosition;\r\n this.chart.renderChart();\r\n } else {\r\n this.toEnd();\r\n }\r\n }\r\n\r\n private prev(): void {\r\n if (!this.isAnimated) {\r\n return;\r\n }\r\n\r\n this.stop();\r\n var newPosition: PulseChartAnimationPosition = this.chart.findPrevPoint(this.position);\r\n if (newPosition) {\r\n this.chart.clearRedundant(newPosition);\r\n this.position = newPosition;\r\n this.chart.renderChart();\r\n } else {\r\n this.reset();\r\n }\r\n }\r\n\r\n public toEnd(): void {\r\n this.savedPosition = null;\r\n this.chart.stopAnimation();\r\n this.chart.clearSelection();\r\n this.chart.clearChart();\r\n this.setDefaultValues();\r\n this.disableControls();\r\n this.chart.renderChart();\r\n }\r\n\r\n public stop(): void {\r\n if (!this.isAnimated) {\r\n return;\r\n }\r\n\r\n this.drawCounterValue();\r\n this.savedPosition = this.position;\r\n this.chart.stopAnimation();\r\n this.animatorState = PulseAnimatorStates.Stopped;\r\n\r\n this.disableControls();\r\n }\r\n\r\n private positionValue: PulseChartAnimationPosition;\r\n\r\n public set position(position: PulseChartAnimationPosition) {\r\n this.positionValue = position;\r\n }\r\n\r\n public get position(): PulseChartAnimationPosition {\r\n return this.positionValue;\r\n }\r\n\r\n public get flooredPosition(): PulseChartAnimationPosition {\r\n return this.position && { series: this.position.series, index: Math.floor(this.position.index) };\r\n }\r\n\r\n private isPositionWasSaved: boolean = false;\r\n private autoPlayPosition: PulseChartAnimationPosition;\r\n public set savedPosition(position: PulseChartAnimationPosition) {\r\n if(!this.chart.isAutoPlay) {\r\n position = null;\r\n }\r\n\r\n if(this.chart.data && this.chart.data.settings && this.chart.data.settings.playback) {\r\n //console.log(\"saved \" + JSON.stringify(position));\r\n this.isPositionWasSaved = true;\r\n if(this.chart.data && this.chart.data.settings && this.chart.data.settings.playback) {\r\n this.chart.data.settings.playback.position = position;\r\n }\r\n\r\n this.chart.host.persistProperties(<VisualObjectInstancesToPersist>{\r\n merge: [{\r\n objectName: \"playback\",\r\n selector: null,\r\n properties: { position: position && JSON.stringify(position) || \"\" }\r\n }]});\r\n }\r\n }\r\n\r\n public get savedPosition(): PulseChartAnimationPosition {\r\n return this.chart.data\r\n && this.chart.data.settings\r\n && this.chart.data.settings.playback\r\n && this.chart.data.settings.playback.position;\r\n }\r\n\r\n public clear(): void {\r\n if (this.isAnimated) {\r\n this.chart.stopAnimation();\r\n }\r\n\r\n this.setDefaultValues();\r\n this.container.style('display', 'none');\r\n }\r\n\r\n public clearTimeouts(): void {\r\n clearTimeout(this.chart.handleSelectionTimeout);\r\n }\r\n }\r\n\r\n export module PulseChartDataLabelUtils {\r\n export function getDefaultPulseChartLabelSettings(): PulseChartChartDataLabelsSettings {\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\r\n export module PulseChartAxisPropertiesHelper {\r\n export function getCategoryAxisProperties(dataViewMetadata: DataViewMetadata, axisTitleOnByDefault?: boolean): DataViewObject {\r\n let toReturn: DataViewObject = {};\r\n\r\n if (!dataViewMetadata) {\r\n return toReturn;\r\n }\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\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\r\n if (!dataViewMetadata) {\r\n return toReturn;\r\n }\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\r\n return toReturn;\r\n }\r\n }\r\n}\r\n\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/CustomVisuals/visuals/pulseChart/visual/pulseChart.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 ClassAndSelector = jsCommon.CssConstants.ClassAndSelector;\r\n import SelectionManager = utility.SelectionManager;\r\n\r\n export interface LineDotPoint {\r\n time: number | Date;\r\n value: number;\r\n dot: number;\r\n sum: number;\r\n selector: SelectionId;\r\n }\r\n\r\n export interface Legend {\r\n text: string;\r\n transform?: string;\r\n dx?: string;\r\n dy?: string;\r\n }\r\n\r\n export interface LineDotChartViewModel {\r\n points: LineDotPoint[];\r\n settings: LineDotChartSettings;\r\n xAxis: IAxisProperties;\r\n yAxis: IAxisProperties;\r\n yAxis2: IAxisProperties;\r\n legends: Legend[];\r\n };\r\n\r\n export interface LineDotChartSettings {\r\n lineFill: string;\r\n lineThickness: number;\r\n dotFill: string;\r\n dotSizeMin: number;\r\n dotSizeMax: number;\r\n counterTitle: string;\r\n // precision: number;\r\n xAxisTitle: string;\r\n yAxisTitle: string;\r\n duration: number;\r\n isanimated: boolean;\r\n isstopped: boolean;\r\n };\r\n\r\n export class LineDotChart implements IVisual {\r\n private selectionManager: SelectionManager;\r\n private hostServices: IVisualHostServices;\r\n private isDateTime: boolean;\r\n\r\n private static DefaultSettings: LineDotChartSettings = {\r\n lineFill: 'rgb(102, 212, 204)',\r\n lineThickness: 3,\r\n dotFill: '#005c55',\r\n dotSizeMin: 4,\r\n dotSizeMax: 38,\r\n counterTitle: 'Total features',\r\n // precision: 2,\r\n xAxisTitle: '',\r\n yAxisTitle: '',\r\n duration: 20,\r\n isanimated: true,\r\n isstopped: true\r\n };\r\n\r\n /**\r\n * Informs the System what it can do\r\n * Fields, Formatting options, data reduction & QnA hints\r\n */\r\n public static capabilities: VisualCapabilities = {\r\n dataRoles: [\r\n {\r\n name: \"Date\",\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: 'Date'\r\n }, \r\n {\r\n name: \"Values\",\r\n kind: VisualDataRoleKind.Measure,\r\n displayName:'Values'\r\n },\r\n // {\r\n // name: \"Labels\",\r\n // kind: VisualDataRoleKind.Measure,\r\n // displayName: 'Labels'\r\n // }\r\n ],\r\n dataViewMappings: [{\r\n conditions: [{\r\n \"Date\": {\r\n min: 0,\r\n max: 1\r\n },\r\n \"Values\": {\r\n min: 0,\r\n max: 1\r\n },\r\n \"Labels\": {\r\n min: 0,\r\n max: 1\r\n }\r\n }],\r\n categorical: {\r\n categories: {\r\n for: { in: \"Date\" },\r\n dataReductionAlgorithm: { sample: {} }\r\n },\r\n values: {\r\n for: { in: \"Values\" }\r\n },\r\n // labels: {\r\n // for: { in: \"Labels\" }\r\n // }\r\n }\r\n }],\r\n objects: {\r\n general: {\r\n displayName: data.createDisplayNameGetter(\"Visual_General\"),\r\n properties: {\r\n formatString: {\r\n type: {\r\n formatting: {\r\n formatString: true\r\n }\r\n },\r\n }\r\n },\r\n },\r\n lineoptions: {\r\n displayName: 'Line',\r\n properties: {\r\n fill: {\r\n displayName: data.createDisplayNameGetter('Visual_Fill'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n lineThickness: {\r\n displayName: 'Thickness',\r\n type: { numeric: true }\r\n }\r\n }\r\n },\r\n dotoptions: {\r\n displayName: 'Dot',\r\n properties: {\r\n color: {\r\n displayName: data.createDisplayNameGetter('Visual_Fill'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n dotSizeMin: {\r\n displayName: 'Min size',\r\n type: { numeric: true }\r\n },\r\n dotSizeMax: {\r\n displayName: 'Min size',\r\n type: { numeric: true }\r\n }\r\n }\r\n },\r\n counteroptions: {\r\n displayName: 'Counter',\r\n properties: {\r\n counterTitle: {\r\n displayName: 'Title',\r\n type: { text: true }\r\n }\r\n }\r\n },\r\n misc: {\r\n displayName: 'Animation',\r\n properties: {\r\n isanimated: {\r\n displayName: 'Animated',\r\n type: { bool: true }\r\n },\r\n isstopped: {\r\n displayName: 'Stop on load',\r\n type: { bool: true }\r\n },\r\n duration: {\r\n displayName: 'Time',\r\n type: { numeric: true }\r\n }\r\n }\r\n }\r\n // ,\r\n // labels: {\r\n // displayName: data.createDisplayNameGetter('Visual_DataPointsLabels'),\r\n // properties: {\r\n // labelPrecision: {\r\n // displayName: data.createDisplayNameGetter('Visual_Precision'),\r\n // type: { numeric: true }\r\n // }\r\n // }\r\n // }\r\n }\r\n };\r\n\r\n private static Identity: ClassAndSelector = {\r\n \"class\": \"lineDotChart\",\r\n selector: \".lineDotChart\"\r\n };\r\n\r\n private static Axes: ClassAndSelector = {\r\n \"class\": \"axes\",\r\n selector: \".axes\"\r\n };\r\n\r\n private static Axis: ClassAndSelector = {\r\n \"class\": \"axis\",\r\n selector: \".axis\"\r\n };\r\n\r\n private static Legends: ClassAndSelector = {\r\n \"class\": \"legends\",\r\n selector: \".legends\"\r\n };\r\n\r\n private static Legend: ClassAndSelector = {\r\n \"class\": \"legend\",\r\n selector: \".legend\"\r\n };\r\n\r\n private static Values: ClassAndSelector = {\r\n \"class\": \"line\",\r\n selector: \".line\"\r\n };\r\n\r\n private static Properties: any = {\r\n general: {\r\n formatString: <DataViewObjectPropertyIdentifier>{\r\n objectName: \"general\",\r\n propertyName: \"formatString\"\r\n }\r\n },\r\n lineoptions: {\r\n fill: <DataViewObjectPropertyIdentifier>{\r\n objectName: \"lineoptions\",\r\n propertyName: \"fill\"\r\n },\r\n lineThickness: <DataViewObjectPropertyIdentifier>{\r\n objectName: \"lineoptions\",\r\n propertyName: \"lineThickness\"\r\n }\r\n },\r\n dotoptions: {\r\n color: <DataViewObjectPropertyIdentifier>{\r\n objectName: \"dotoptions\",\r\n propertyName: \"color\"\r\n },\r\n dotSizeMin: <DataViewObjectPropertyIdentifier>{\r\n objectName: \"dotoptions\",\r\n propertyName: \"dotSizeMin\"\r\n },\r\n dotSizeMax: <DataViewObjectPropertyIdentifier>{\r\n objectName: \"dotoptions\",\r\n propertyName: \"dotSizeMax\"\r\n }\r\n },\r\n counteroptions: {\r\n counterTitle: <DataViewObjectPropertyIdentifier>{\r\n objectName: \"counteroptions\",\r\n propertyName: \"counterTitle\"\r\n }\r\n },\r\n // labels: {\r\n // labelPrecision: <DataViewObjectPropertyIdentifier>{\r\n // objectName: \"labels\",\r\n // propertyName: \"labelPrecision\"\r\n // }\r\n // },\r\n misc: {\r\n isanimated: <DataViewObjectPropertyIdentifier>{\r\n objectName: \"misc\",\r\n propertyName: \"isanimated\"\r\n },\r\n isstopped: <DataViewObjectPropertyIdentifier>{\r\n objectName: \"misc\",\r\n propertyName: \"isstopped\"\r\n },\r\n duration: <DataViewObjectPropertyIdentifier>{\r\n objectName: \"misc\",\r\n propertyName: \"duration\"\r\n }\r\n }\r\n };\r\n\r\n private model: LineDotChartViewModel;\r\n private root: D3.Selection;\r\n private main: D3.Selection;\r\n private axes: D3.Selection;\r\n private axisX: D3.Selection;\r\n private axisY: D3.Selection;\r\n private axisY2: D3.Selection;\r\n private legends: D3.Selection;\r\n private line: D3.Selection;\r\n private colors: IDataColorPalette;\r\n\r\n private margin: IMargin = {\r\n top: 10,\r\n right: 30,\r\n bottom: 10,\r\n left: 10\r\n };\r\n\r\n private LegendSize: number = 50;\r\n private AxisSize: number = 30;\r\n\r\n /* One time setup*/\r\n public init(options: VisualInitOptions): void {\r\n this.hostServices = options.host;\r\n this.selectionManager = new SelectionManager({ hostServices: this.hostServices });\r\n this.root = d3.select(options.element.get(0))\r\n .append('svg')\r\n .classed(LineDotChart.Identity.class, true);\r\n\r\n this.root.on('click', (d: LineDotPoint) => { this.clearSelection(); } );\r\n\r\n this.main = this.root.append('g');\r\n this.axes = this.main.append('g').classed(LineDotChart.Axes.class, true);\r\n this.axisX = this.axes.append('g').classed(LineDotChart.Axis.class, true);\r\n this.axisY = this.axes.append('g').classed(LineDotChart.Axis.class, true);\r\n this.axisY2 = this.axes.append('g').classed(LineDotChart.Axis.class, true);\r\n this.legends = this.main.append('g').classed(LineDotChart.Legends.class, true);\r\n this.line = this.main.append('g').classed(LineDotChart.Values.class, true);\r\n\r\n this.colors = options.style && options.style.colorPalette\r\n ? options.style.colorPalette.dataColors\r\n : new DataColorPalette();\r\n }\r\n\r\n /* Called for data, size, formatting changes*/\r\n public update(options: VisualUpdateOptions) {\r\n if (!options.dataViews || !options.dataViews[0]) {\r\n return;\r\n }\r\n\r\n var viewport: IViewport = options.viewport;\r\n var model: LineDotChartViewModel = this.model = this.converter(options.dataViews[0], viewport);\r\n // if (!model) {\r\n // return;\r\n // }\r\n\r\n this.clearSelection();\r\n this.resize(viewport);\r\n // this.draw(model, !options.suppressAnimations);\r\n this.draw(model);\r\n }\r\n\r\n /*About to remove your visual, do clean up here */\r\n public destroy() {\r\n this.root = null;\r\n }\r\n\r\n public setIsStopped(isstopped: Boolean): void {\r\n var objects: VisualObjectInstancesToPersist = {\r\n merge: [\r\n <VisualObjectInstance>{\r\n objectName: \"misc\",\r\n selector: undefined,\r\n properties: {\r\n \"isstopped\": isstopped,\r\n }\r\n }\r\n ]\r\n };\r\n this.hostServices.persistProperties(objects);\r\n this.hostServices.onSelect({ data: [] });\r\n }\r\n\r\n public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstance[] {\r\n var instances: VisualObjectInstance[] = [];\r\n\r\n if (!this.model || !this.model.settings) {\r\n return instances;\r\n }\r\n\r\n var settings: LineDotChartSettings = this.model.settings;\r\n\r\n switch (options.objectName) {\r\n case \"lineoptions\":\r\n var lineoptions: VisualObjectInstance = {\r\n objectName: \"lineoptions\",\r\n displayName: \"lineoptions\",\r\n selector: null,\r\n properties: {\r\n fill: settings.lineFill,\r\n lineThickness: settings.lineThickness\r\n }\r\n };\r\n\r\n instances.push(lineoptions);\r\n break;\r\n\r\n case \"dotoptions\":\r\n var dotoptions: VisualObjectInstance = {\r\n objectName: \"dotoptions\",\r\n displayName: \"dotoptions\",\r\n selector: null,\r\n properties: {\r\n color: settings.dotFill,\r\n dotSizeMin: settings.dotSizeMin,\r\n dotSizeMax: settings.dotSizeMax\r\n }\r\n };\r\n\r\n instances.push(dotoptions);\r\n break;\r\n\r\n case \"counteroptions\":\r\n var counteroptions: VisualObjectInstance = {\r\n objectName: \"counteroptions\",\r\n displayName: \"counteroptions\",\r\n selector: null,\r\n properties: {\r\n counterTitle: settings.counterTitle\r\n }\r\n };\r\n\r\n instances.push(counteroptions);\r\n break;\r\n\r\n // case \"labels\":\r\n // var labels: VisualObjectInstance = {\r\n // objectName: \"labels\",\r\n // displayName: \"labels\",\r\n // selector: null,\r\n // properties: {\r\n // labelPrecision: settings.precision\r\n // }\r\n // };\r\n\r\n // instances.push(labels);\r\n // break;\r\n\r\n case \"misc\": \r\n var misc: VisualObjectInstance = {\r\n objectName: \"misc\",\r\n displayName: \"misc\",\r\n selector: null,\r\n properties: {\r\n isanimated: settings.isanimated,\r\n isstopped: settings.isstopped,\r\n duration: settings.duration\r\n }\r\n };\r\n\r\n instances.push(misc);\r\n break;\r\n }\r\n\r\n return instances;\r\n }\r\n\r\n private selectDot(dotelement: SVGCircleElement, selector: SelectionId) {\r\n var dot: D3.Selection = d3.select(dotelement);\r\n if (!dot.classed('point_selected')) {\r\n if (selector) {\r\n this.selectionManager.select(selector);\r\n }\r\n\r\n this.root.classed(\"filtered\", true);\r\n this.line.selectAll('circle.point')\r\n .classed('point_selected', false);\r\n d3.select(dotelement)\r\n .classed('point_selected', true);\r\n } else {\r\n this.clearSelection();\r\n }\r\n\r\n d3.event.stopPropagation();\r\n }\r\n\r\n private clearSelection(): void {\r\n this.root.classed(\"filtered\", false);\r\n this.root.selectAll(\"circle.point\").classed(\"point_selected\", false);\r\n this.selectionManager.clear();\r\n }\r\n\r\n // Convert a DataView into a view model\r\n private converter(dataView: DataView, viewport: IViewport): LineDotChartViewModel {\r\n if (!dataView.categorical ||\r\n !dataView.categorical.categories ||\r\n !dataView.categorical.categories[0] ||\r\n !dataView.categorical.categories[0].values ||\r\n !(dataView.categorical.categories[0].values.length > 0) ||\r\n !dataView.categorical ||\r\n !dataView.categorical.values ||\r\n !dataView.categorical.values[0] ||\r\n !dataView.categorical.values[0].values ||\r\n !(dataView.categorical.values[0].values.length > 0)\r\n ) {\r\n return null;\r\n }\r\n\r\n var values: any[] = [];\r\n var metadataColumn: DataViewMetadataColumn;\r\n var extent: any[];\r\n var min: any;\r\n var max: any;\r\n var that = this;\r\n\r\n var categoryType: ValueType = AxisHelper.getCategoryValueType(dataView.categorical.categories[0].source, true);\r\n this.isDateTime = AxisHelper.isDateTime(categoryType);\r\n var isScalar = true;\r\n\r\n var settings: LineDotChartSettings = this.parseSettings(dataView);\r\n var effectiveWidth: number = Math.max(0, viewport.width - this.margin.left - this.margin.right - this.LegendSize - this.AxisSize);\r\n var effectiveHeight: number = Math.max(0, viewport.height - this.margin.top - this.margin.bottom - this.LegendSize);\r\n\r\n var format: string = \"\";\r\n var formatter: IValueFormatter;\r\n\r\n // X for categories\r\n values = dataView.categorical.categories[0].values;\r\n metadataColumn = dataView.categorical.categories[0].source;\r\n extent = d3.extent(values);\r\n\r\n if (this.isDateTime) {\r\n min = extent[0].getTime();\r\n max = extent[1].getTime();\r\n\r\n min = new Date(min);\r\n max = new Date(max + (max - min)*.05);\r\n\r\n // var xDomain: number[] = isScalar ? [min, max] : [min.getTime(), max.getTime()]\r\n // var format: string = \"MMM dd yyyy HH:mm\";\r\n format = \"MMM dd yyyy\";\r\n formatter = valueFormatter.create({ format: format });\r\n } else {\r\n min = extent[0];\r\n max = extent[1];\r\n\r\n max = max + (max - min)*.05;\r\n\r\n formatter = valueFormatter.create({ value: 0 });\r\n }\r\n\r\n var xAxis = AxisHelper.createAxis({\r\n pixelSpan: effectiveWidth,\r\n dataDomain: [min, max],\r\n metaDataColumn: metadataColumn,\r\n formatString: null,\r\n //formatString: LineDotChart.Properties.general.formatString,\r\n outerPadding: 0,\r\n isCategoryAxis: true,\r\n isScalar: isScalar,\r\n isVertical: false,\r\n forcedTickCount: undefined,\r\n useTickIntervalForDisplayUnits: true,\r\n // axisPrecision: settings.precision,\r\n getValueFn: (index, type) => {\r\n if(that.isDateTime) {\r\n return formatter.format(new Date(index));\r\n } else {\r\n return index;\r\n }\r\n }\r\n });\r\n xAxis.formatter = formatter;\r\n\r\n metadataColumn = dataView.categorical.values[0].source;\r\n\r\n values = dataView.categorical.values[0].values;\r\n extent = d3.extent(values);\r\n min = extent[0];\r\n max = extent[1];\r\n\r\n var result: LineDotPoint[] = [];\r\n var value_sum: number = 0;\r\n var value: number = 0;\r\n var time: number = 0;\r\n var selector: SelectionId;\r\n\r\n for (var i = 0; i < dataView.categorical.categories[0].values.length; i++) {\r\n value = dataView.categorical.values[0].values[i];\r\n time = dataView.categorical.categories[0].values[i];\r\n value_sum += value;\r\n selector = SelectionId.createWithId(dataView.categorical.categories[0].identity[i]);\r\n result.push({\r\n dot: (value - min) / (max - min),\r\n value: value,\r\n sum: value_sum,\r\n time: time,\r\n selector: selector\r\n });\r\n }\r\n\r\n // make some space for counter + 25%\r\n value_sum = value_sum + (value_sum - min) * 0.10;\r\n\r\n var yAxis = AxisHelper.createAxis({\r\n pixelSpan: effectiveHeight,\r\n dataDomain: [min, value_sum],\r\n metaDataColumn: metadataColumn,\r\n formatString: null,\r\n outerPadding: 0,\r\n isCategoryAxis: false,\r\n isScalar: true,\r\n isVertical: true,\r\n useTickIntervalForDisplayUnits: true\r\n });\r\n\r\n var yAxis2 = AxisHelper.createAxis({\r\n pixelSpan: effectiveHeight,\r\n dataDomain: [min, value_sum],\r\n metaDataColumn: metadataColumn,\r\n formatString: null,\r\n outerPadding: 0,\r\n isCategoryAxis: false,\r\n isScalar: true,\r\n isVertical: true,\r\n useTickIntervalForDisplayUnits: true\r\n });\r\n yAxis2.axis.orient('right');\r\n\r\n // Show gridlines on the chart to make the values more readable.\r\n // TODO: Make this a configuration setting that can be toggled.\r\n // xAxis.axis = xAxis.axis.tickSize(-effectiveHeight);\r\n // yAxis.axis = yAxis.axis.tickSize(-effectiveWidth);\r\n return {\r\n points: result,\r\n settings: settings,\r\n xAxis: xAxis,\r\n yAxis: yAxis,\r\n yAxis2: yAxis2,\r\n legends: this.generateAxisLabels(viewport, settings)\r\n };\r\n }\r\n\r\n private parseSettings(dataView: DataView): LineDotChartSettings {\r\n if (!dataView ||\r\n !dataView.metadata ||\r\n !dataView.metadata.columns ||\r\n !dataView.metadata.columns[0]) {\r\n return null;\r\n }\r\n\r\n var objects: DataViewObjects = dataView.metadata.objects;\r\n var lineFillColorHelper: ColorHelper = new ColorHelper(this.colors, LineDotChart.Properties.lineoptions.fill, LineDotChart.DefaultSettings.lineFill);\r\n var dotFillColorHelper: ColorHelper = new ColorHelper(this.colors, LineDotChart.Properties.dotoptions.color, LineDotChart.DefaultSettings.dotFill);\r\n var xAxisTitle: string = LineDotChart.DefaultSettings.xAxisTitle;\r\n var yAxisTitle: string = LineDotChart.DefaultSettings.yAxisTitle;\r\n\r\n if (\r\n dataView.categorical.categories[0] &&\r\n dataView.categorical.categories[0].source &&\r\n dataView.categorical.categories[0].source.displayName &&\r\n dataView.categorical.values[0] &&\r\n dataView.categorical.values[0].source &&\r\n dataView.categorical.values[0].source.displayName) {\r\n xAxisTitle = dataView.categorical.categories[0].source.displayName;\r\n yAxisTitle = dataView.categorical.values[0].source.displayName;\r\n }\r\n\r\n var lineThickness: number = LineDotChart.DefaultSettings.lineThickness;\r\n var dotSizeMin: number = LineDotChart.DefaultSettings.dotSizeMin;\r\n var dotSizeMax: number = LineDotChart.DefaultSettings.dotSizeMax;\r\n var counterTitle: string = LineDotChart.DefaultSettings.counterTitle;\r\n var isanimated: boolean = LineDotChart.DefaultSettings.isanimated;\r\n var isstopped: boolean = LineDotChart.DefaultSettings.isstopped;\r\n var duration: number = LineDotChart.DefaultSettings.duration;\r\n if (objects) {\r\n lineThickness = DataViewObjects.getValue(\r\n objects,\r\n LineDotChart.Properties.lineoptions.lineThickness,\r\n LineDotChart.DefaultSettings.lineThickness\r\n );\r\n dotSizeMin = DataViewObjects.getValue(\r\n objects,\r\n LineDotChart.Properties.dotoptions.dotSizeMin,\r\n LineDotChart.DefaultSettings.dotSizeMin\r\n );\r\n dotSizeMax = DataViewObjects.getValue(\r\n objects,\r\n LineDotChart.Properties.dotoptions.dotSizeMax,\r\n LineDotChart.DefaultSettings.dotSizeMax\r\n );\r\n counterTitle = DataViewObjects.getValue(\r\n objects,\r\n LineDotChart.Properties.counteroptions.counterTitle,\r\n LineDotChart.DefaultSettings.counterTitle\r\n );\r\n isanimated = DataViewObjects.getValue(\r\n objects,\r\n LineDotChart.Properties.misc.isanimated,\r\n LineDotChart.DefaultSettings.isanimated\r\n );\r\n isstopped = DataViewObjects.getValue(\r\n objects,\r\n LineDotChart.Properties.misc.isstopped,\r\n LineDotChart.DefaultSettings.isstopped\r\n );\r\n duration = DataViewObjects.getValue(\r\n objects,\r\n LineDotChart.Properties.misc.duration,\r\n LineDotChart.DefaultSettings.duration\r\n );\r\n \r\n }\r\n\r\n return {\r\n // precision: LineDotChart.getPrecision(objects),\r\n xAxisTitle: xAxisTitle,\r\n yAxisTitle: yAxisTitle,\r\n lineFill: lineFillColorHelper.getColorForMeasure(objects, ''),\r\n lineThickness: lineThickness,\r\n dotFill: dotFillColorHelper.getColorForMeasure(objects, ''),\r\n dotSizeMin: dotSizeMin,\r\n dotSizeMax: dotSizeMax,\r\n counterTitle: counterTitle,\r\n isstopped: isstopped,\r\n isanimated: isanimated,\r\n duration: duration\r\n };\r\n }\r\n\r\n // private static getPrecision(objects: DataViewObjects): number {\r\n // if (!objects) {\r\n // return LineDotChart.DefaultSettings.precision;\r\n // }\r\n\r\n // var precision: number = DataViewObjects.getValue(\r\n // objects,\r\n // LineDotChart.Properties.labels.labelPrecision,\r\n // LineDotChart.DefaultSettings.precision);\r\n\r\n // if (precision < LineDotChart.MinPrecision) {\r\n // return LineDotChart.MinPrecision;\r\n // }\r\n\r\n // return precision;\r\n // }\r\n\r\n private generateAxisLabels(viewport: IViewport, settings: LineDotChartSettings): Legend[] {\r\n return [\r\n {\r\n transform: SVGUtil.translate(\r\n (viewport.width - this.margin.left - this.margin.right) / 2,\r\n (viewport.height - this.margin.top - this.margin.bottom)),\r\n text: settings.xAxisTitle,\r\n dx: \"1em\",\r\n dy: \"-1em\"\r\n }, {\r\n transform: SVGUtil.translateAndRotate(\r\n 0,\r\n (viewport.height - this.margin.top - this.margin.bottom) / 2,\r\n 0,\r\n 0,\r\n 270),\r\n text: settings.yAxisTitle,\r\n dx: \"3em\"\r\n }\r\n ];\r\n }\r\n\r\n private resize(viewport: IViewport): void {\r\n this.root.attr({\r\n 'height': Math.max(0, viewport.height),\r\n 'width': Math.max(0, viewport.width)\r\n });\r\n\r\n this.main.attr('transform', SVGUtil.translate(this.margin.left, this.margin.top));\r\n this.legends.attr('transform', SVGUtil.translate(this.margin.left, this.margin.top));\r\n this.line.attr('transform', SVGUtil.translate(this.margin.left + this.LegendSize, 0));\r\n this.axes.attr('transform', SVGUtil.translate(this.margin.left + this.LegendSize, 0));\r\n this.axisX.attr('transform', SVGUtil.translate(0, viewport.height - this.margin.top - this.margin.bottom - this.LegendSize));\r\n this.axisY2.attr('transform', SVGUtil.translate(viewport.width - this.margin.left - this.margin.right - this.LegendSize - this.AxisSize, 0));\r\n }\r\n\r\n private draw(model: LineDotChartViewModel): void {\r\n var that = this;\r\n // Clear canvas\r\n this.line.selectAll('*').remove();\r\n\r\n this.legends.selectAll('*').remove();\r\n this.axisX.selectAll('*').remove();\r\n this.axisY.selectAll('*').remove();\r\n this.axisY2.selectAll('*').remove();\r\n\r\n if (!model) {\r\n return;\r\n }\r\n\r\n this.renderLegends(model);\r\n\r\n if (model && model.points && model.points.length) {\r\n\r\n this.axisX.call(model.xAxis.axis);\r\n this.axisY.call(model.yAxis.axis);\r\n this.axisY2.call(model.yAxis2.axis);\r\n\r\n if (model.settings.isanimated) {\r\n var playBtn = this.line\r\n .append(\"g\")\r\n .classed(\"lineDotChart__playBtn\", true)\r\n .attr(\"transform\", \"translate(40, 20)\");\r\n\r\n playBtn\r\n .append(\"circle\")\r\n .attr(\"r\", 34 / 2);\r\n\r\n // play / reset buttin\r\n if (model.settings.isstopped) {\r\n playBtn\r\n .append(\"path\")\r\n .attr(\"d\", \"M0 2l10 6-10 6z\")\r\n .attr(\"transform\", \"translate(-4,-8)\");\r\n\r\n playBtn\r\n .on('click.lineDotChart__playBt', function() {\r\n that.setIsStopped(false);\r\n });\r\n\r\n return;\r\n } else {\r\n playBtn\r\n .append(\"path\")\r\n .attr(\"d\", \"M0 2l10 6-10 6z\")\r\n .attr(\"transform-origin\", \"center\")\r\n .attr(\"transform\", \"translate(6, 8) rotate(180)\");\r\n\r\n playBtn\r\n .append(\"rect\")\r\n .attr(\"width\", \"2\")\r\n .attr(\"height\", \"12\")\r\n .attr(\"transform\", \"translate(-7,-6)\");\r\n\r\n playBtn\r\n .on('click.lineDotChart__playBt', function() {\r\n that.setIsStopped(true);\r\n });\r\n }\r\n }\r\n\r\n var clip = this.line\r\n .append(\"clipPath\")\r\n .attr(\"id\", \"lineClip\")\r\n .append(\"rect\")\r\n .attr(\"x\", 0)\r\n .attr(\"y\", 0)\r\n .attr(\"width\", 1)\r\n .attr(\"height\", 10000);\r\n\r\n // Draw the line\r\n var line: D3.Svg.Line = d3.svg.line()\r\n .x((d: LineDotPoint) => model.xAxis.scale(d.time))\r\n .y((d: LineDotPoint) => model.yAxis.scale(d.sum));\r\n // .interpolate(\"basis\");\r\n\r\n var lineSelection: D3.UpdateSelection = this.line.selectAll('path.plot')\r\n .data([model.points]);\r\n\r\n lineSelection.enter().append('path');\r\n lineSelection\r\n .classed('plot', true)\r\n .attr('stroke', (d, i) => model.settings.lineFill)\r\n .attr('stroke-width', model.settings.lineThickness)\r\n .attr('d', line);\r\n\r\n var totalLength: number = (<SVGPathElement>lineSelection.node()).getTotalLength();\r\n var line_left = (<SVGPathElement>lineSelection.node()).getPointAtLength(0).x;\r\n var line_right = (<SVGPathElement>lineSelection.node()).getPointAtLength(totalLength).x;\r\n\r\n lineSelection\r\n .attr(\"clip-path\", \"url(\" + location.href + \"#lineClip)\");\r\n\r\n if(!model.settings.isanimated) {\r\n clip\r\n .interrupt()\r\n .attr('x', line_left)\r\n .attr('width', line_right - line_left);\r\n } else {\r\n clip\r\n .attr('x', line_left)\r\n .interrupt()\r\n .transition()\r\n .ease(\"linear\")\r\n .duration(model.settings.duration * 1000)\r\n .attr('width', line_right - line_left);\r\n }\r\n lineSelection\r\n .exit().remove();\r\n\r\n var point_time: number = 300;\r\n var counter_time: number = 0; // point_time / 100;\r\n\r\n // Draw the individual data points that will be shown on hover with a tooltip\r\n var lineTipSelection: D3.UpdateSelection = this.line.selectAll('circle.point')\r\n .data(model.points);\r\n\r\n var that = this;\r\n\r\n lineTipSelection.enter()\r\n .append('circle')\r\n .attr('fill', model.settings.dotFill)\r\n .attr('opacity', .77)\r\n .attr('r', (d: LineDotPoint) => model.settings.dotSizeMin + d.dot * (model.settings.dotSizeMax - model.settings.dotSizeMin))\r\n .classed('point', true)\r\n .on('mouseover.point', this.showDataPoint)\r\n .on('mouseout.point', this.hideDataPoint)\r\n .on(\"click.point\", function(d: LineDotPoint) {\r\n that.selectDot(this, d.selector);\r\n });\r\n\r\n if (!model.settings.isanimated) {\r\n lineTipSelection\r\n .interrupt()\r\n .attr('transform', (d: LineDotPoint) => \r\n 'translate(' + model.xAxis.scale(d.time) + ' ' + model.yAxis.scale(d.sum) + ') scale(1)'\r\n );\r\n } else {\r\n lineTipSelection\r\n .interrupt()\r\n .attr('transform', (d: LineDotPoint) => \r\n 'translate(' + model.xAxis.scale(d.time) + ' ' + model.yAxis.scale(d.sum) + ') scale(0.005)'\r\n )\r\n .transition()\r\n .duration(point_time)\r\n .delay((d: LineDotPoint, i: number) => this.pointDelay(model.points, i, model.settings.duration))\r\n .ease(\"linear\")\r\n .attr('transform', (d: LineDotPoint) => \r\n 'translate(' + model.xAxis.scale(d.time) + ' ' + model.yAxis.scale(d.sum) + ') scale(3.4)'\r\n )\r\n .transition()\r\n .duration(point_time)\r\n .delay((d: LineDotPoint, i: number) => this.pointDelay(model.points, i, model.settings.duration) + point_time)\r\n .ease(\"elastic\")\r\n .attr('transform', (d: LineDotPoint) =>\r\n 'translate(' + model.xAxis.scale(d.time) + ' ' + model.yAxis.scale(d.sum) + ') scale(1)'\r\n );\r\n }\r\n\r\n lineTipSelection.exit().remove();\r\n\r\n for (var i = 0; i < lineTipSelection[0].length; i++) {\r\n this.addTooltip(model, lineTipSelection[0][i]);\r\n }\r\n\r\n // Feature Counter text \r\n var lineTextSelection: D3.UpdateSelection = this.line.selectAll('text')\r\n .data(model.points);\r\n\r\n lineTextSelection.enter()\r\n .append(\"text\")\r\n .classed('text', true)\r\n .text((d: LineDotPoint, i: number) => {\r\n // if (model.points[i + 1]) {\r\n return model.settings.counterTitle + ' ' + (i + 1);\r\n // } else {\r\n // // TODO: CRAZY hard code\r\n // return model.settings.counterTitle + ' 265'\r\n // }\r\n })\r\n .attr('x', line_right - 260)\r\n .attr('y', 30);\r\n\r\n if (!model.settings.isanimated) {\r\n // opacity 1 only for last\r\n lineTextSelection\r\n .interrupt()\r\n .attr('transform', 'translate(0 0)')\r\n .attr('opacity', (d: LineDotPoint, i: number) => Number(i === model.points.length - 1));\r\n } else {\r\n lineTextSelection\r\n // .attr('transform', 'translate(-40 0)')\r\n .attr('opacity', 0)\r\n .interrupt()\r\n .transition()\r\n .duration(counter_time)\r\n .delay((d: LineDotPoint, i: number) => this.pointDelay(model.points, i, model.settings.duration))\r\n .attr('transform', 'translate(0 0)')\r\n .attr('opacity', 1)\r\n .transition()\r\n .duration(counter_time)\r\n .delay((d: LineDotPoint, i: number) => {\r\n if (model.points[i + 1]) {\r\n return this.pointDelay(model.points, i+1, model.settings.duration);\r\n } else {\r\n return Number.POSITIVE_INFINITY;\r\n }\r\n })\r\n // .attr('transform', 'translate(40 0)')\r\n .attr('opacity', 0)\r\n ;\r\n\r\n }\r\n lineTextSelection.exit().remove();\r\n\r\n }\r\n }\r\n\r\n private pointDelay(points: LineDotPoint[], num: number, animation_duration: number): number {\r\n if (!points.length || !points[num] || num === 0) {\r\n return 0;\r\n }\r\n if (this.isDateTime) {\r\n let time: Date = <Date>points[num].time;\r\n var min: number = (<Date>points[0].time).getTime();\r\n var max: number = (<Date>points[points.length - 1].time).getTime();\r\n var val: number = time.getTime();\r\n } else {\r\n let time: number = <number>points[num].time;\r\n var min: number = <number>points[0].time;\r\n var max: number = <number>points[points.length - 1].time;\r\n var val: number = time;\r\n }\r\n return animation_duration * 1000 * (val - min) / (max - min);\r\n }\r\n\r\n private showDataPoint(data: LineDotPoint, index: number): void {\r\n d3.select(<any>this).classed('show', true);\r\n }\r\n\r\n private hideDataPoint(data: LineDotPoint, index: number): void {\r\n d3.select(<any>this).classed('show', false);\r\n }\r\n\r\n private addTooltip(model: LineDotChartViewModel, element: any): void {\r\n var selection: D3.Selection = d3.select(element);\r\n var data: LineDotPoint = selection.datum();\r\n TooltipManager.addTooltip(selection, (event) => {\r\n return [\r\n {\r\n displayName: model.settings.xAxisTitle,\r\n value: model.xAxis.formatter.format(data.time)\r\n },\r\n {\r\n displayName: model.settings.yAxisTitle,\r\n value: data.value.toString()\r\n }\r\n ];\r\n });\r\n }\r\n\r\n private renderLegends(model: LineDotChartViewModel): void {\r\n var legendSelection: D3.UpdateSelection = this.legends\r\n .selectAll(LineDotChart.Legend.selector)\r\n .data(model.legends);\r\n\r\n legendSelection\r\n .enter()\r\n .append(\"svg:text\");\r\n\r\n legendSelection\r\n .attr(\"x\", 0)\r\n .attr(\"y\", 0)\r\n .attr(\"dx\", (item: Legend) => item.dx)\r\n .attr(\"dy\", (item: Legend) => item.dy)\r\n .attr(\"transform\", (item: Legend) => item.transform)\r\n .text((item: Legend) => item.text)\r\n .classed(LineDotChart.Legend.class, true);\r\n\r\n legendSelection\r\n .exit()\r\n .remove();\r\n }\r\n }\r\n}\r\n\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/CustomVisuals/visuals/lineDotChart/visual/lineDotChart.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 SunburstSlice {\r\n children?: SunburstSlice[];\r\n value?: any;\r\n color?: string;\r\n name?: string;\r\n parent?: SunburstSlice;\r\n selector: SelectionId;\r\n total: number;\r\n tooltipInfo?: TooltipDataItem[];\r\n }\r\n\r\n export interface SunburstViewModel {\r\n root: SunburstSlice;\r\n }\r\n\r\n export var sunburstRoleNames = {\r\n nodes: 'Nodes',\r\n values: 'Values',\r\n };\r\n\r\n export class Sunburst implements IVisual {\r\n private static minOpacity = 0.2;\r\n private svg: D3.Selection;\r\n private g: D3.Selection;\r\n private arc: D3.Svg.Arc;\r\n private total: number = 0;\r\n private viewport: IViewport;\r\n private colors: IDataColorPalette;\r\n private selectionManager: SelectionManager;\r\n\r\n private static roleNames = {\r\n nodes: 'Nodes',\r\n values: 'Values',\r\n };\r\n\r\n public static capabilities: VisualCapabilities = {\r\n dataRoles: [\r\n {\r\n name: Sunburst.roleNames.nodes,\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: 'Groups'\r\n }, {\r\n name: Sunburst.roleNames.values,\r\n kind: VisualDataRoleKind.Measure,\r\n 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: {\r\n type: { formatting: { formatString: true } },\r\n },\r\n },\r\n },\r\n },\r\n dataViewMappings: [{\r\n conditions: [\r\n { 'Groups': { min: 0 }, 'Values': { max: 1 } },\r\n ],\r\n matrix: {\r\n rows: {\r\n for: { in: Sunburst.roleNames.nodes },\r\n },\r\n values: {\r\n for: { in: Sunburst.roleNames.values }\r\n },\r\n }\r\n }],\r\n };\r\n\r\n public init(options: VisualInitOptions): void {\r\n this.arc = d3.svg.arc()\r\n .startAngle(function(d) { return d.x; })\r\n .endAngle(function(d) { return d.x + d.dx; })\r\n .innerRadius(function(d) { return Math.sqrt(d.y); })\r\n .outerRadius(function(d) { return Math.sqrt(d.y + d.dy); });\r\n\r\n this.colors = options.style.colorPalette.dataColors;\r\n this.selectionManager = new SelectionManager({ hostServices: options.host });\r\n this.svg = d3.select(options.element.get(0)).append('svg');\r\n this.svg.classed('mainDrawArea', true);\r\n this.g = this.svg.append('g');\r\n this.g.classed(\"container\", true);\r\n this.svg.append(\"text\")\r\n .classed(\"sunBurstPercentageFixed\", true);\r\n\r\n this.svg.on('mousedown', (d) => {\r\n this.svg.selectAll(\"path\").style(\"opacity\", 1);\r\n this.svg.select(\".sunBurstPercentageFixed\").style(\"opacity\", 0);\r\n this.selectionManager.clear();\r\n });\r\n }\r\n\r\n private static setAllUnhide(selection): void {\r\n selection.attr(\"setUnHide\", \"true\");\r\n }\r\n\r\n public update(options: VisualUpdateOptions): void {\r\n if (options.dataViews.length > 0) {\r\n var data = this.converter(options.dataViews[0], this.colors);\r\n this.viewport = options.viewport;\r\n this.updateInternal(data);\r\n }\r\n }\r\n\r\n private updateInternal(dataRootNode: SunburstSlice): void {\r\n this.svg.attr({\r\n 'height': this.viewport.height,\r\n 'width': this.viewport.width\r\n });\r\n this.g.attr('transform', SVGUtil.translate(this.viewport.width / 2, this.viewport.height / 2));\r\n var radius = Math.min(this.viewport.width, this.viewport.height) / 2;\r\n var partition = d3.layout.partition()\r\n .size([2 * Math.PI, radius * radius])\r\n .value((d) => { return d.value; });\r\n var path = this.g.datum(dataRootNode).selectAll(\"path\")\r\n .data(partition.nodes);\r\n path.enter().append(\"path\");\r\n path.attr(\"display\", (d) => { return d.depth ? null : \"none\"; })\r\n .attr(\"d\", this.arc)\r\n .style(\"stroke\", \"#fff\")\r\n .style(\"fill\", (d) => { return d.color; })\r\n .style(\"fill-rule\", \"evenodd\")\r\n .on(\"mousedown\", (d) => {\r\n if (d.selector) {\r\n this.selectionManager.select(d.selector);\r\n }\r\n d3.selectAll(\"path\").call(Sunburst.setAllUnhide).attr('setUnHide', null);\r\n this.highlightPath(d, this, true);\r\n var percentageFixedText = this.svg.select(\".sunBurstPercentageFixed\");\r\n var percentage = this.total === 0 ? 0 : (100 * d.total / this.total).toPrecision(3);\r\n percentageFixedText.text(d ? percentage + \"%\" : \"\");\r\n percentageFixedText.style(\"fill\", d.color);\r\n this.onResize();\r\n event.stopPropagation();\r\n });\r\n this.renderTooltip(path);\r\n path.exit().remove();\r\n\r\n this.onResize();\r\n }\r\n\r\n // Get all parents of the node\r\n private static getTreePath(node) {\r\n var path = [];\r\n var current = node;\r\n while (current.parent) {\r\n path.unshift(current);\r\n current = current.parent;\r\n }\r\n return path;\r\n }\r\n\r\n private onResize(): void {\r\n var width = this.viewport.width;\r\n var height = this.viewport.height;\r\n var percentageFixedText = this.svg.select(\".sunBurstPercentageFixed\");\r\n var textWidth = powerbi.TextMeasurementService.measureSvgTextElementWidth(percentageFixedText[0][0]);\r\n\r\n percentageFixedText.style(\"opacity\", 1);\r\n percentageFixedText.attr(\"y\", (height / 2 + 4));\r\n percentageFixedText.attr(\"x\", ((width / 2) - (textWidth / 2)));\r\n }\r\n\r\n private highlightPath(d, sunBurst, setUnhide): void {\r\n var parentsArray = d ? Sunburst.getTreePath(d) : [];\r\n // Set opacity for all the segments.\r\n sunBurst.svg.selectAll(\"path\").each(function() {\r\n if (d3.select(this).attr('setUnHide') !== 'true') {\r\n d3.select(this).style(\"opacity\", Sunburst.minOpacity);\r\n }\r\n });\r\n // Highlight only ancestors of the current segment.\r\n sunBurst.svg.selectAll(\"path\")\r\n .filter(function(node) {\r\n return (parentsArray.indexOf(node) >= 0);\r\n }).each(function() {\r\n d3.select(this).style(\"opacity\", 1);\r\n if (setUnhide === true) {\r\n d3.select(this).attr('setUnHide', 'true');\r\n }\r\n });\r\n }\r\n\r\n private renderTooltip(selection: D3.UpdateSelection): void {\r\n TooltipManager.addTooltip(selection, (tooltipEvent: TooltipEvent) => {\r\n return (<SunburstSlice>tooltipEvent.data).tooltipInfo;\r\n });\r\n }\r\n\r\n private static getTooltipData(displayName: string, value: number): TooltipDataItem[] {\r\n return [{\r\n displayName: displayName,\r\n value: value < 0 ? \"\" : value.toString()\r\n }];\r\n }\r\n\r\n private covertTreeNodeToSunBurstNode(originParentNode: DataViewTreeNode, sunburstParentNode: SunburstSlice,\r\n colors: IColorScale, pathIdentity: DataViewScopeIdentity[], color): SunburstSlice {\r\n var selector: powerbi.data.Selector;\r\n if (originParentNode.identity) {\r\n pathIdentity = pathIdentity.concat([originParentNode.identity]);\r\n selector = { data: pathIdentity, };\r\n }\r\n\r\n var selectionId = pathIdentity.length === 0 ? null : new SelectionId(selector, false);\r\n var valueToSet = originParentNode.values ? originParentNode.values[0].value : 0;\r\n\r\n var newSunNode: SunburstSlice = {\r\n name: originParentNode.name,\r\n value: Math.max(valueToSet, 0),\r\n selector: selectionId,\r\n total: valueToSet\r\n };\r\n if (originParentNode.value) {\r\n newSunNode.color = color ? color : colors.getColor(originParentNode.value).value;\r\n }\r\n this.total += newSunNode.value;\r\n if (originParentNode.children && originParentNode.children.length > 0) {\r\n\r\n newSunNode.tooltipInfo = Sunburst.getTooltipData(originParentNode.value, -1);\r\n\r\n newSunNode.children = [];\r\n for (var i = 0; i < originParentNode.children.length; i++) {\r\n var newChild = this.covertTreeNodeToSunBurstNode(originParentNode.children[i], newSunNode, colors, pathIdentity, newSunNode.color);\r\n newSunNode.children.push(newChild);\r\n newSunNode.total += newChild.total;\r\n }\r\n } else {\r\n newSunNode.tooltipInfo = Sunburst.getTooltipData(originParentNode.value, valueToSet);\r\n }\r\n if (sunburstParentNode) {\r\n newSunNode.parent = sunburstParentNode;\r\n }\r\n\r\n return newSunNode;\r\n }\r\n\r\n public converter(dataView: DataView, colors: IDataColorPalette): SunburstSlice {\r\n var colorScale = colors.getNewColorScale();\r\n this.total = 0;\r\n var root: SunburstSlice = this.covertTreeNodeToSunBurstNode(dataView.matrix.rows.root, null, colorScale, [], undefined);\r\n\r\n return root;\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/CustomVisuals/visuals/sunburst/visual/sunburst.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 export let sunburstCustom: IVisualPlugin = {\r\n name: 'sunburstCustom',\r\n watermarkKey: 'defaultWatermark',\r\n capabilities: samples.Sunburst.capabilities,\r\n create: () => new samples.Sunburst()\r\n };\r\n\r\n export let asterPlot: IVisualPlugin = {\r\n name: 'asterPlot',\r\n capabilities: samples.AsterPlot.capabilities,\r\n create: () => new samples.AsterPlot()\r\n };\r\n\r\n export var tornadoChart: IVisualPlugin = {\r\n name: \"tornadoChart\",\r\n capabilities: samples.TornadoChart.capabilities,\r\n create: () => new samples.TornadoChart()\r\n };\r\n\r\n export var sankeyDiagram: IVisualPlugin = {\r\n name: \"sankeyDiagram\",\r\n capabilities: samples.SankeyDiagram.capabilities,\r\n create: () => new samples.SankeyDiagram()\r\n };\r\n\r\n export let mekkoChart: IVisualPlugin = {\r\n name: 'mekkoChart',\r\n watermarkKey: 'mekko',\r\n capabilities: samples.MekkoChart.capabilities,\r\n create: () => new samples.MekkoChart({ chartType: samples.MekkoChartType.HundredPercentStackedColumn }),\r\n customizeQuery: ColumnChart.customizeQuery,\r\n getSortableRoles: (visualSortableOptions?: VisualSortableOptions) => ColumnChart.getSortableRoles(visualSortableOptions),\r\n };\r\n \r\n export var bulletChart: IVisualPlugin = {\r\n name: \"bulletChart\",\r\n capabilities: samples.BulletChart.capabilities,\r\n create: () => new samples.BulletChart()\r\n };\r\n\t\r\n\texport var wordCloud: IVisualPlugin = {\r\n name: \"wordCloud\",\r\n capabilities: samples.WordCloud.capabilities,\r\n create: () => new samples.WordCloud()\r\n };\r\n\t\r\n\texport var chicletSlicer: IVisualPlugin = {\r\n name: 'chicletSlicer',\r\n capabilities: samples.ChicletSlicer.capabilities,\r\n create: () => new samples.ChicletSlicer()\r\n };\r\n\t\r\n\texport var chordChart: IVisualPlugin = {\r\n name: \"chordChart\",\r\n capabilities: samples.ChordChart.capabilities,\r\n create: () => new samples.ChordChart()\r\n };\r\n\t\r\n\texport var enhancedScatterChart: IVisualPlugin = {\r\n name: 'enhancedScatterChart',\r\n capabilities: samples.EnhancedScatterChart.capabilities,\r\n create: () => new samples.EnhancedScatterChart()\r\n };\r\n\t\r\n\texport var radarChart: IVisualPlugin = {\r\n name: 'radarChart',\r\n capabilities: samples.RadarChart.capabilities,\r\n create: () => new samples.RadarChart()\r\n };\r\n\t\r\n\texport var dotPlot: IVisualPlugin = {\r\n name: 'dotPlot',\r\n capabilities: samples.DotPlot.capabilities,\r\n create: () => new samples.DotPlot()\r\n };\r\n\r\n export var histogram: IVisualPlugin = {\r\n name: \"histogram\",\r\n capabilities: samples.Histogram.capabilities,\r\n create: () => new samples.Histogram()\r\n };\r\n\r\n\texport var timeline: IVisualPlugin = {\r\n name: 'timeline',\r\n capabilities: samples.Timeline.capabilities,\r\n create: () => new samples.Timeline()\r\n };\r\n\t\r\n\texport var forceGraph: IVisualPlugin = {\r\n name: \"forceGraph\",\r\n capabilities: samples.ForceGraph.capabilities,\r\n create: () => new samples.ForceGraph()\r\n };\r\n\r\n export let gantt: IVisualPlugin = {\r\n name: \"gantt\",\r\n capabilities: samples.Gantt.capabilities,\r\n create: () => new samples.Gantt()\r\n };\r\n\r\n export let streamGraph: IVisualPlugin = {\r\n name: \"streamGraph\",\r\n capabilities: samples.StreamGraph.capabilities,\r\n create: () => new samples.StreamGraph()\r\n };\r\n\r\n export let pulseChart: IVisualPlugin = {\r\n name: \"pulseChart\",\r\n capabilities: samples.PulseChart.capabilities,\r\n create: () => new samples.PulseChart()\r\n };\r\n\r\n export var lineDotChart: IVisualPlugin = {\r\n name: \"lineDotChart\",\r\n capabilities: samples.LineDotChart.capabilities,\r\n create: () => new samples.LineDotChart()\r\n };\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/CustomVisuals/plugins.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;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;;;;;;;ACfA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;AClCA;;;;;;;ACAA;;;;;;ACAA;;;;;;ACAA;;;;;;ACAA;;;;;;ACAA;;;;;;ACAA;;;;;;ACAA;;;;;;ACAA;;;;;;ACAA;;;;;;ACAA;;;;;;ACAA;;;;;;ACAA;;;;;;ACAA;;;;;;ACAA;;;;;;ACAA;;;;;;ACAA;;;;;;ACAA;;;;;;ACAA;;;;;;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;;;;;;;;;;;;;;;;ACpCA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA;AACA;AACA;AAEA;AACA;AACA;AAkCA;AAAA;AA6BA;AAvBA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AAEA;AACA;AACA;AACA;AACA;AAAA;AAEA;AAEA;AACA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAjBA;AAmBA;AAWA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAuBA;AAAA;AACA;AACA;AACA;AAGA;;;AA7BA;AAEA;AAAA;AACA;AACA;;;AAAA;AAGA;AADA;AACA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;AAMA;AAAA;AACA;AACA;;;AARA;AAEA;AAAA;AACA;AACA;AAcA;AAAA;AACA;AACA;;;AAhBA;AAmBA;AADA;AACA;AACA;AACA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AAEA;AAAA;AAiCA;AAhCA;AAOA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AAEA;AAAA;AAoGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAvHA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAIA;AACA;AAEA;AACA;AAEA;AAIA;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;AACA;AACA;AAEA;AACA;AAIA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAsBA;AAAA;AAxHA;AA0HA;AAAA;AAuDA;AACA;AACA;AACA;AAtDA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAFA;AAGA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AAEA;AApDA;AAyDA;AAAA;AA1DA;AA4DA;AAAA;AAktBA;AA9kBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AASA;AACA;AAQA;AACA;AAEA;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;AAEA;AACA;AACA;AACA;AACA;AAEA;AASA;AACA;AAQA;AACA;AAEA;AACA;AAEA;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;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAcA;AAAA;AACA;AACA;;;AAAA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AAAA;AACA;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;AAGA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAOA;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;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;AAAA;AAOA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAEA;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;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;AACA;AACA;AACA;AAEA;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;AACA;AACA;AACA;AAKA;AACA;AAhtBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;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;AAglBA;AAAA;AAltBA;AAmtBA;AAAA;AAAA;AACA;;;;;;;;;;;;;;;;AC7oCA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA;AA6FA;AAAA;AA0BA;AArBA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAIA;AACA;AAAA;AAEA;AA2BA;AAdA;AAqBA;AACA;AACA;AACA;AACA;AApCA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAAA;AAWA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAeA;AAAA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAMA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AAKA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AA1JA;AA2JA;AAAA;AAEA;AAAA;AAmBA;AAlBA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AAKA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAnBA;AAqBA;AACA;AACA;AAFA;AAIA;AAqgBA;AAlDA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAoBA;AAmBA;AACA;AACA;AACA;AACA;AACA;AACA;AAvXA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAyFA;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;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AAEA;AACA;AAEA;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;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;AAEA;AAOA;AAKA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAKA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAGA;AACA;AAGA;AAKA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAqCA;AAAA;AACA;AACA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;AACA;AACA;;;AAAA;AAWA;AAAA;AACA;AAGA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAOA;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;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAKA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAKA;AACA;AAEA;AACA;AAEA;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;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAGA;AAEA;AACA;AACA;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;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AAAA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAKA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAQA;AAMA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAGA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAIA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAAA;AACA;AAKA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAGA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAGA;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;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAIA;AAKA;AAKA;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;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;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;AAEA;AACA;AACA;AAvwCA;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;AACA;AACA;AACA;AACA;AACA;AACA;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;AAgBA;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;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;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAghCA;AAAA;AAzwCA;AA0wCA;AAAA;AAAA;AACA;;;;;;;;;;;;;;;;ACnmDA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA;AACA;AAgKA;AAIA;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;AAEA;AACA;AACA;AAAA;AA3CA;AA6CA;AAAA;AA4nBA;AApmBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AASA;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;AAEA;;AAEA;AACA;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;;;AAGA;AACA;AACA;AASA;AAGA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAWA;AACA;AACA;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;AAEA;AASA;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;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AASA;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;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;AAMA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAMA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;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;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;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;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;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;AAEA;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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AA1nBA;AACA;AACA;AACA;AAkBA;AAsmBA;AAAA;AA5nBA;AAmoBA;;AAEA;AACA;AA+WA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAvGA;AAAA;AACA;AACA;AACA;AACA;AACA;AAoGA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAKA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAGA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAGA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAGA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;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;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAMA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAGA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;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;AAEA;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;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;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;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;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;AAEA;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;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;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;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;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;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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AASA;AACA;AAEA;AACA;AACA;AAEA;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;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AASA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAiBA;AACA;AAIA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AASA;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;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAYA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;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;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;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;AAEA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;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;AACA;AACA;AAEA;AACA;AACA;AACA;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;AAEA;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;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;;;;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AAhoDA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;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;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;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;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AASA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAoDA;AAwxCA;AAAA;AAloDA;AAooDA;AAiBA;AAEA;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;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;;;;AAIA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AAQA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAKA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAlBA;AAoBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAoCA;AAgCA;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;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAIA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAgBA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAMA;AAGA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AASA;AACA;AACA;AAOA;AAEA;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;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;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;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;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;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;AACA;AAGA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;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;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;AAEA;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;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;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAIA;AACA;AACA;AAEA;AACA;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;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;AAEA;AACA;AAEA;AACA;AACA;;;;;;AAMA;AACA;AAIA;AACA;AAIA;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;AAIA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;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;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;AAEA;AAEA;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;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;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;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;AAEA;AAAA;AACA;AAAA;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;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;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;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AA/jCA;AAEA;AACA;AA6jCA;AAAA;AAjkCA;AAmkCA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAIA;AACA;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;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAOA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AAvBA;AAwBA;AAAA;AAAA;AACA;;;;;;;;;;;;;;;;ACr/HA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA;AAEA;AACA;AAwFA;AAgLA;AAjCA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AA0BA;AACA;AACA;AACA;AACA;AACA;AAxDA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAsBA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAAA;AAcA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAGA;AAEA;AAEA;AAEA;AAEA;AACA;AAEA;AACA;AAGA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;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;AAEA;AAkBA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AAKA;AACA;AAGA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAIA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAKA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AAEA;AAKA;AAIA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AAEA;AAMA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAKA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAEA;AACA;AAIA;AAEA;AAEA;AACA;AAEA;AACA;AAEA;AAMA;AAGA;AAEA;AACA;AAMA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAAA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;;AAEA;AACA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAMA;AAIA;AACA;AAGA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AAEA;AAEA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAGA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AAAA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAGA;AAEA;AAEA;AAEA;AAEA;AACA;AAEA;AAAA;AACA;AAIA;AACA;AACA;AAEA;AAEA;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;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AAGA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAQA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAAA;AAKA;AACA;AAGA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AAAA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAGA;AACA;AACA;AAEA;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;AA3lCA;AAEA;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;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;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;AAi+BA;AAAA;AA7lCA;AA8lCA;AAAA;AAAA;AACA;;;;;;;;;;;;;;;;ACvtCA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;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;AA2FA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AAoBA;AAk+BA;AArwBA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;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;AAIA;AAAA;AACA;AACA;AACA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AACA;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;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAKA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAGA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;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;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;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;AAEA;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;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;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;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;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;AAIA;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;AACA;AACA;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;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;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;AAEA;AACA;AAGA;AAEA;AAEA;AACA;AAEA;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;AAEA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAKA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;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;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;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;AAp/BA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAkxBA;AAAA;AAt/BA;AAw/BA;AACA;AAAA;AAWA;AACA;AACA;AAEA;AACA;AACA;AACA;AAHA;AAKA;AACA;AACA;AAEA;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;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAWA;AAAA;AAgCA;AA7BA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AACA;AACA;AAAA;AAhCA;AAiCA;AAAA;AAAA;AACA;;;;;;;;;;;;;;;;ACtyCA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AACA;AAEA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA;AACA;AASA;AACA;AACA;AACA;AACA;AAJA;AAIA;AAmDA;AAIA;AAHA;AAIA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAEA;AAWA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAuBA;AAAA;AACA;AACA;AACA;AAGA;;;AA7BA;AAEA;AAAA;AACA;AACA;;;AAAA;AAGA;AADA;AACA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;AAMA;AAAA;AACA;AACA;;;AARA;AAEA;AAAA;AACA;AACA;AAcA;AAAA;AACA;AACA;;;AAhBA;AAmBA;AADA;AACA;AACA;AACA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AAEA;AAAA;AAoGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AArHA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAIA;AACA;AAEA;AACA;AAEA;AAIA;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;AACA;AACA;AAEA;AACA;AAIA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAoBA;AAAA;AAtHA;AAwHA;AAAA;AAuDA;AACA;AACA;AACA;AAtDA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAFA;AAGA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AAEA;AApDA;AAyDA;AAAA;AA1DA;AA4DA;AAwaA;AArCA;AAGA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AA2BA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAnQA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;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;AAEA;AAEA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AADA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAKA;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;AAEA;AAMA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAmDA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AAAA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAMA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAKA;AAEA;AAIA;AACA;AACA;AACA;AAEA;AACA;AAOA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AASA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAQA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AASA;AACA;AAEA;AACA;AAGA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AASA;AACA;AAEA;AACA;AAIA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAIA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AADA;AAEA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AAAA;AADA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAAA;AACA;AAKA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAzkCA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;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;AAEA;AACA;AACA;AACA;AACA;AACA;AA+5BA;AAAA;AA3kCA;AA6kCA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AATA;AAWA;AACA;AAIA;AACA;AANA;AAOA;AAAA;AACA;AAAA;AAAA;AACA;;;;;;;;;;;;;;;;AC/jDA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA;AACA;AAEA;AACA;AAcA;AAAA;AACA;AACA;AACA;AAFA;AAGA;AAiBA;;;AAGA;AACA;AAaA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;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;AAxMA;AACA;AAwMA;AAAA;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;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAiFA;AA6SA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAxEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;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;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;AAEA;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;AAEA;AACA;AAEA;AACA;AAEA;AACA;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;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;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;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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;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;AAEA;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;AAEA;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;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;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;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;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAAA;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;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;AAEA;AACA;AAEA;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;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AA56BA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAkBA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAksBA;AAAA;AA96BA;AAg7BA;AAAA;AACA;AAcA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;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;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;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;AACA;AAEA;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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAnJA;AAoJA;AAEA;AACA;AAAA;AAUA;AACA;AACA;AAEA;AACA;AACA;AACA;AAHA;AAKA;AACA;AACA;AAEA;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;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAaA;AAAA;AAiJA;AAxIA;AAAA;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;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;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;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;AAAA;AAjJA;AAmJA;AAAA;AAAA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AA5BA;AA6BA;AAAA;AACA;AAAA;AAAA;AACA;;;;;;;;;;;;;;;;AChxDA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA;AAmCA;AASA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;;;AALA;AAOA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;;;AALA;AAOA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;AAEA;AACA;AACA;;;AAJA;AAMA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AAEA;AAAA;AAmBA;AAlBA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AAEA;AAAA;AA+FA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AA3GA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAIA;AACA;AAEA;AACA;AAEA;AAIA;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;AACA;AACA;AAEA;AAKA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAeA;AAAA;AA5GA;AA8GA;AAAA;AAuDA;AACA;AACA;AACA;AACA;AAvDA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAFA;AAGA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AAEA;AApDA;AA0DA;AAAA;AA3DA;AA6DA;AAAA;AAm1BA;AA1rBA;AAAA;AACA;AACA;;;AAAA;AAOA;AAAA;AACA;AACA;;;AAAA;AACA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;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;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAAA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AASA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;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;AAEA;AACA;AACA;AACA;AACA;AAEA;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;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAIA;AAEA;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;AAEA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAAA;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;AAEA;AACA;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;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AAEA;AAGA;AACA;AAEA;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;AAEA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAAA;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;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAj1BA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;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;AAEA;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;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAssBA;AAAA;AAn1BA;AAo1BA;AAAA;AAAA;AACA;;;;;;;;;;;;;;;;ACxpCA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA;AACA;AACA;AACA;AACA;AACA;AAGA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AAGA;AAWA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAOA;AACA;AAEA;AACA;AAEA;AAGA;AAEA;AACA;AACA;AACA;AAEA;AAGA;AAGA;AACA;AACA;AAEA;AAuHA;AAAA;AAoFA;AA06FA;AA/5FA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;;;AALA;AAQA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;;;AALA;AAQA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAoYA;;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAQA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;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;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAKA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AAMA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAAA;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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAOA;AACA;AACA;AAEA;AAiBA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAQA;AACA;AAEA;AAIA;AAIA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAcA;AACA;AACA;AAEA;AAEA;AACA;AAIA;AAMA;AAEA;AACA;AACA;AAEA;AACA;AACA;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;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;AAOA;AAOA;AACA;AAIA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAIA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAKA;AAmBA;AACA;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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAcA;AAQA;AAEA;AAKA;AACA;AAEA;AACA;AAIA;AAYA;AACA;AAGA;AAEA;AACA;AACA;AAEA;AAYA;AAEA;AAGA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AASA;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;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;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAMA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAMA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAEA;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;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;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;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAMA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAKA;AAIA;AACA;AAEA;AAcA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAMA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAcA;AACA;AAIA;AAEA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAMA;AACA;AACA;AACA;AACA;AACA;AAEA;AASA;AAEA;AACA;AACA;AAEA;AAIA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAIA;AACA;AAIA;AAEA;AAEA;AACA;AACA;AACA;AAMA;AACA;AACA;AAIA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAEA;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;AAEA;AAIA;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;AAEA;AAKA;AAIA;AACA;AACA;AAEA;AAGA;AAGA;AACA;AACA;AACA;AACA;AAEA;AAKA;AACA;AAEA;AACA;AAIA;AACA;AAEA;AACA;AAEA;AAKA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;AAKA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AAKA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAGA;AAGA;AAEA;AACA;AAEA;AACA;AAEA;;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AAUA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAMA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AAWA;AAIA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAIA;AAAA;AACA;AAUA;AACA;AACA;AACA;AACA;AAEA;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;AAEA;AACA;AACA;AACA;AACA;AAEA;AAGA;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;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAIA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAIA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;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;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AAMA;AAIA;AAGA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAGA;AAEA;AAEA;AAEA;AAEA;AAEA;AAMA;AACA;AASA;AACA;AAEA;AACA;AAGA;AACA;AAEA;AAKA;AACA;AACA;AACA;AACA;AACA;AAEA;AAMA;AAGA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAGA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;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;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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;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;AAEA;AAKA;AAKA;AAKA;AAKA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAIA;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;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;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;AAEA;AACA;AACA;AACA;AA5/FA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAuFA;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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AA8BA;;AAEA;AACA;AAk+EA;AAAA;AA9/FA;AAqgGA;AAGA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AAAA;AAxBA;AAgCA;AAQA;AACA;AACA;AACA;AAEA;AACA;AAGA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAMA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AAEA;AAMA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AArEA;AAsEA;AAAA;AAAA;AACA;;;;;;;;;;;;;;;;AC31GA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AACA;AACA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA;AAkBA;AAAA;AAWA;AAUA;AACA;AACA;AACA;AAopCA;AAp+BA;AACA;AACA;AAEA;AACA;AAEA;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;AACA;AAAA;AAEA;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;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;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;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;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AAEA;AAAA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;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;AAEA;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;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAAA;AAEA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;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;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAAA;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;AAEA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AAAA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAAA;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;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAAA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;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;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAvoCA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;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;AAs+BA;AAAA;AA5qCA;AA6qCA;AAAA;AAAA;AAEA;AACA;AACA;AACA;;;;;;;;;;;;;;;;ACtuCA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA;AACA;AA0CA;;AAEA;AACA;AAAA;AAoBA;AAjBA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AApBA;AAsBA;AA0TA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AApIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAMA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAMA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAQA;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;AACA;AACA;AAeA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAKA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAKA;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;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AAIA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAIA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;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;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAGA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAKA;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;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;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;AAEA;AACA;AACA;AAEA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAGA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAh8BA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;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;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;AAqBA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AAmwBA;AAAA;AAl8BA;AAm8BA;AAAA;AAAA;AACA;;;;;;;;;;;;;;;;ACtiCA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA;AACA;AAEA;AACA;AACA;AA8FA;AAIA;AACA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAjBA;AAkBA;AAAA;AAnBA;AAqBA;AAmYA;AAvEA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AA0BA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAvBA;AAAA;AACA;AACA;AACA;;;AAAA;AAsBA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AAEA;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;AAEA;AAeA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AAIA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;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;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAGA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AAOA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAGA;AACA;AAEA;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAIA;AAKA;AACA;AAEA;AAEA;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;AAEA;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;AAEA;AACA;AAKA;AAEA;AACA;AAKA;AAEA;AACA;AAIA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAKA;AAEA;AACA;AAKA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAKA;AAEA;AACA;AAKA;AAEA;AACA;AAKA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAKA;AAEA;AACA;AAIA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAIA;AAEA;AACA;AAKA;AAEA;AACA;AAKA;AAEA;AACA;AAKA;AAEA;AACA;AAIA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAKA;AAEA;AACA;AAKA;AAEA;AACA;AAKA;AAEA;AACA;AAMA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAIA;AAEA;AACA;AAMA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAKA;AAEA;AACA;AAMA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAIA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAIA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AAGA;AAIA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AAMA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAAA;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;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAKA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAIA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAGA;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;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;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAMA;AACA;AAKA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAKA;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;AAEA;AACA;AAtlDA;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;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;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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AAsxCA;AAAA;AAzlDA;AA0lDA;AAAA;AAAA;AACA;;;;;;;;;;;;;;;;AChvDA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAHA;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;AAuEA;AAmRA;AAhKA;AACA;AACA;AACA;AACA;AACA;AAWA;AAMA;AACA;AACA;AACA;AACA;AAEA;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;AAuGA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAhHA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAMA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;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;AAgBA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;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;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;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;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;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;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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AAEA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAMA;AACA;AAKA;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;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAIA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAprBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAukBA;AAAA;AAxrBA;AAgsBA;AAAA;AAuBA;AAlBA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AAvBA;AAwBA;AAAA;AAAA;AACA;;;;;;;;;;;;;;;;ACr5BA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCA;AAEA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AAJA;AAMA;AAAA;AA0FA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAjHA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAIA;AACA;AAEA;AACA;AAEA;AAEA;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;AACA;AAEA;AAKA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AA0BA;AAAA;AAlHA;AAoHA;AAAA;AAyBA;AACA;AACA;AACA;AACA;AACA;AACA;AA3BA;AACA;AACA;AAGA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AAtBA;AA8BA;AAAA;AA/BA;AAmEA;AAAA;AAuBA;AA2lBA;AAzmBA;AAAA;AACA;AACA;;;AAAA;AAGA;AAAA;AACA;AACA;;;AAAA;AAUA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAJA;AAOA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAJA;AAOA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAyKA;AACA;AACA;AAEA;AACA;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;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;AACA;AACA;AACA;AAAA;AACA;AAAA;AACA;AAAA;AAAA;AAAA;AACA;AAAA;AAAA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AAAA;AACA;AACA;AAAA;AAEA;AACA;AAEA;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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAEA;AAAA;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;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AALA;AAQA;AACA;AACA;AACA;AACA;AAAA;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;AAEA;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;AAAA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AADA;AAEA;AACA;AACA;AADA;AAEA;AAEA;AACA;AACA;AAhnBA;AACA;AAEA;AACA;AACA;AACA;AACA;AA8DA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAuYA;AAAA;AAlnBA;AAmnBA;AAAA;AAAA;AACA;;;;;;;;;;;;;;;;ACr3BA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AALA;AAOA;AACA;AACA;AACA;AACA;AACA;AAmDA;AAUA;AAAA;AAGA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAkCA;AAAA;AAKA;AACA;AACA;AACA;AAuQA;AAg4BA;AAx5BA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAAA;AAuBA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAKA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;;;;AAIA;AACA;AACA;AACA;AACA;AAEA;;;;AAIA;AACA;AAAA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;;;AAGA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;;AAKA;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;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;;;AAGA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;;;AAGA;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;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;AAGA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAIA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAMA;AACA;AAKA;AACA;AAEA;AAAA;AACA;AACA;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;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;;;;AAIA;AACA;AAAA;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;AAEA;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;AACA;AACA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;;;AAGA;AACA;AACA;AACA;AACA;AAEA;;;AAGA;AACA;AACA;AAIA;AACA;AAEA;;;AAGA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;;;AAGA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;;;;AAIA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAKA;AAEA;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;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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AApoCA;AACA;AACA;AACA;AACA;AACA;AACA;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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;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;AAy5BA;AAAA;AA/oCA;AAwpCA;AAAA;AAsBA;AAnBA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AAtBA;AAwBA;AAAA;AAmBA;AAlBA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AAKA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAnBA;AAoBA;AAAA;AAAA;AACA;;;;;;;;;;;;;;;;ACp3CA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;;;;;;AAEA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;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;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AANA;AAkFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAgCA;AAAA;AACA;AAuIA;AApIA;;AAEA;AACA;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;AAEA;;;;;;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;;;;;;AAMA;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;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AAxIA;AA0IA;AAAA;AAAA;AAAA;AAoBA;AAnBA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AApBA;AAsBA;AAAA;AAAA;AAAA;AAqBA;AApBA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AArBA;AAuBA;AAAA;AAAA;AAAA;AAoBA;AAnBA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AApBA;AAsBA;AAAA;AAAA;AAAA;AAwCA;AAvCA;;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAxCA;AA0CA;AAAA;AAAA;AAAA;AAoBA;AAnBA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AApBA;AAsBA;AAqCA;AACA;AACA;AACA;AACA;AACA;AArCA;;;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;;;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AASA;;;;AAIA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;AAGA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAvFA;AAyFA;AAAA;AA4HA;AA3HA;;;AAGA;AACA;AACA;AACA;AAEA;;;AAGA;AACA;AACA;AACA;AAEA;;;AAGA;AACA;AACA;AACA;AAEA;;;;;;AAMA;AACA;AACA;AACA;AACA;AAEA;;;AAGA;AACA;AACA;AACA;AACA;AAEA;;;AAGA;AACA;AACA;AACA;AACA;AAEA;;;;;;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;;;;;AAQA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;;;;;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AA5HA;AAsMA;AA6BA;AA7BA;AA8BA;AACA;AACA;AACA;AACA;AACA;AA5BA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAUA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AAjDA;AAmDA;AAAA;AAAA;AACA;AACA;AAyBA;AA+IA;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;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;AAirBA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAwKA;AA/1BA;;;;;AAKA;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;AAEA;AACA;AACA;AAEA;AACA;AAEA;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;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;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;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;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;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;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;AAEA;;AAEA;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;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;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;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AACA;AAEA;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;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;AACA;AACA;AACA;AACA;AAEA;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;AACA;AACA;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;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;AAAA;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;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AAGA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAYA;AAAA;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;AAAA;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;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;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;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AA7hCA;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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AA25BA;AAAA;AAnkCA;AAokCA;AAAA;AAAA;AACA;;;;;;;;;;;;;;;;ACh3DA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA;AAEA;AAkDA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AAAA;AAyBA;AApBA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAEA;AAAA;AAgOA;AA0yBA;AAzxBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAKA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAGA;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;AAEA;AACA;AAEA;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;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AAEA;AACA;AACA;AACA;AAEA;AAIA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AAEA;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;AAEA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAGA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;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;AAEA;AAGA;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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAGA;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;AAEA;AACA;AAGA;AACA;AACA;AACA;AAEA;AACA;AAKA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAIA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;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;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;AAEA;AACA;AACA;AACA;AAEA;AACA;AAGA;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;AAEA;AACA;AACA;AACA;AAEA;AACA;AAxgCA;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;AAEA;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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AA4yBA;AAAA;AA1gCA;AA2gCA;AAAA;AAAA;AACA;;;;;;;;;;;;;;;;ACrqCA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAyGA;AACA;AACA;AACA;AACA;AACA;AALA;AAOA;AACA;AACA;AACA;AACA;AAJA;AAMA;AACA;AACA;AACA;AAHA;AAKA;AACA;AACA;AACA;AAHA;AAyIA;AA67BA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAvoBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AA0BA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAgGA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAMA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;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;AAEA;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;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;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;AACA;AAEA;AACA;AAEA;AAIA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AASA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAIA;AACA;AAKA;AACA;AACA;AAEA;AAMA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AAyBA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAAA;AAYA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAMA;AASA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAMA;AACA;AAEA;AACA;AAGA;AACA;AAEA;AAGA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAIA;AACA;AACA;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;AACA;AAEA;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;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAGA;AAEA;AAEA;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;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAAA;AACA;AAIA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AAIA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AACA;AAEA;AAAA;AAAA;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;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAGA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;AACA;AAEA;AAUA;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;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAIA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AAKA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;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;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;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;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAIA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAGA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAKA;AAKA;AAMA;AAMA;AAKA;AAEA;AAKA;AAKA;AAEA;AAKA;AAKA;AAKA;AAEA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAGA;AAKA;AAEA;AAKA;AAKA;AAKA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAKA;AAKA;AAEA;;;AAGA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAKA;AAIA;AACA;AACA;AACA;AACA;AAEA;AACA;AAGA;AAIA;AAIA;AAKA;AAKA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAGA;AAKA;AAEA;AAKA;AAEA;AAKA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAGA;AAKA;AAKA;AAKA;AAKA;AAKA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AAEA;AAEA;AACA;AAEA;AAKA;AAKA;AAIA;AAEA;AAKA;AAKA;AAIA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AAEA;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;AACA;AACA;AACA;AACA;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;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;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;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;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;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;AAEA;AACA;AAEA;AACA;AACA;AACA;AA5nFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;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;AAgBA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AA6DA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;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;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;AAkoEA;AAAA;AA9nFA;AAgoFA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AAgEA;AAhEA;AA+BA;AAGA;AAUA;AA2bA;AAtaA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;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;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AA9IA;AAAA;AACA;AACA;;;AAAA;AAYA;AAAA;AACA;AACA;;;AAAA;AACA;AAAA;AACA;AACA;AACA;;;AAAA;AAKA;AAAA;AACA;AACA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAuGA;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;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAAA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAIA;AAIA;AACA;AACA;AANA;AACA;AACA;;;AAAA;AAMA;AAAA;AACA;AACA;;;AAAA;AAIA;AAqBA;AACA;AACA;AACA;AACA;AACA;AA1BA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAAA;AASA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AApgBA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AAqfA;AAAA;AAjhBA;AAmhBA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAVA;AAWA;AAEA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AA9BA;AAgCA;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;AACA;AACA;AAEA;AACA;AAtCA;AAuCA;AACA;AAAA;AAAA;AACA;;;;;;;;;;;;;;;;AC3hHA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA;AAwBA;AAeA;AAEA;AAAA;AA4PA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AA+uBA;AA7uBA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;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;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;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;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;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;AAAA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;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;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;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;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;AAEA;AACA;AACA;AACA;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;AAKA;AAKA;AAKA;AAKA;AAKA;AAKA;AAMA;AAEA;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;AAEA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;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;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;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;AAEA;AACA;AACA;AACA;AACA;AAAA;AAEA;AAAA;AACA;AACA;AACA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AAEA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AAGA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;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;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AA7+BA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAWA;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;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;AAowBA;AAAA;AAn/BA;AAo/BA;AAAA;AAAA;AACA;;;;;;;;;;;;;;;;AC5jCA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA;AAgBA;AACA;AACA;AACA;AAEA;AAAA;AAKA;AA8NA;AA/KA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;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;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;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;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;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;AAEA;AACA;AAjOA;AASA;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;AAiLA;AAAA;AAnOA;AAoOA;AAAA;AAAA;AACA;;;;;;;;;;;;;;;;ACvRA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;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;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;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;AAAA;AAAA;AACA;;;;","sourceRoot":""}
\No newline at end of file