├── .gitignore ├── assets ├── icon.png ├── thumb.png └── screen.png ├── .travis.yml ├── dependencies.json ├── .vscode ├── launch.json └── settings.json ├── tsconfig.json ├── style └── visual.less ├── package.json ├── .github └── workflows │ ├── build.yml │ └── codeql.yml ├── README.md ├── pbiviz.json ├── tslint.json ├── SECURITY.md ├── src ├── objectEnumerationUtility.ts └── visual.ts ├── capabilities.json └── script.r /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .tmp 3 | dist 4 | .api 5 | *.log -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/PowerBI-visuals-corrplot/HEAD/assets/icon.png -------------------------------------------------------------------------------- /assets/thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/PowerBI-visuals-corrplot/HEAD/assets/thumb.png -------------------------------------------------------------------------------- /assets/screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/PowerBI-visuals-corrplot/HEAD/assets/screen.png -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | sudo: required 4 | dist: trusty 5 | language: node_js 6 | node_js: 7 | - "7" 8 | install: 9 | - npm install 10 | script: 11 | - npm run lint 12 | - npm run package 13 | notifications: 14 | email: false -------------------------------------------------------------------------------- /dependencies.json: -------------------------------------------------------------------------------- 1 | { 2 | "cranPackages": [ 3 | { 4 | "name": "corrplot", 5 | "displayName": "corrplot: Visualization of a Correlation Matrix", 6 | "url": "https://cran.r-project.org/web/packages/corrplot/index.html" 7 | } 8 | ] 9 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | "configurations": [ 4 | { 5 | "name": "Debugger", 6 | "type": "chrome", 7 | "request": "attach", 8 | "port": 9222, 9 | "sourceMaps": true, 10 | "webRoot": "${cwd}/" 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "emitDecoratorMetadata": true, 5 | "experimentalDecorators": true, 6 | "target": "ES5", 7 | "sourceMap": true, 8 | "out": "./.tmp/build/visual.js" 9 | }, 10 | "files": [ 11 | ".api/v1.6.0/PowerBI-visuals.d.ts", 12 | "src/objectEnumerationUtility.ts", 13 | "src/visual.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /style/visual.less: -------------------------------------------------------------------------------- 1 | .rcv_autoScaleImageContainer { 2 | position: relative; 3 | 4 | .rcv_autoScaleImage { 5 | max-width: 100%; 6 | max-height: 100%; 7 | position: absolute; 8 | top: 50%; 9 | /* @noflip */ 10 | left: 50%; 11 | transform: translateY(-50%) translateX(-50%); 12 | -webkit-transform: translateY(-50%) translateX(-50%); 13 | } 14 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "visual", 3 | "scripts": { 4 | "postinstall": "pbiviz update 1.6.0", 5 | "pbiviz": "pbiviz", 6 | "package": "pbiviz package", 7 | "lint": "tslint -r \"node_modules/tslint-microsoft-contrib\" \"+(src)/**/*.ts\"", 8 | "start": "pbiviz start", 9 | "test": "echo \"Error: no test specified\"" 10 | }, 11 | "devDependencies": { 12 | "tslint": "^4.4.2", 13 | "tslint-microsoft-contrib": "^4.0.0", 14 | "powerbi-visuals-tools": "1.6.2" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 4, 3 | "editor.insertSpaces": true, 4 | "files.eol": "\n", 5 | "files.watcherExclude": { 6 | "**/.git/objects/**": true, 7 | "**/node_modules/**": true, 8 | ".tmp": true 9 | }, 10 | "files.exclude": { 11 | ".tmp": true 12 | }, 13 | "search.exclude": { 14 | ".tmp": true, 15 | "typings": true 16 | }, 17 | "json.schemas": [ 18 | { 19 | "fileMatch": [ 20 | "/pbiviz.json" 21 | ], 22 | "url": "./.api/v1.6.0/schema.pbiviz.json" 23 | }, 24 | { 25 | "fileMatch": [ 26 | "/capabilities.json" 27 | ], 28 | "url": "./.api/v1.6.0/schema.capabilities.json" 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: [ main, dev, certification] 6 | pull_request: 7 | branches: [ main, dev, certification ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | node-version: [18.x, 20.x] 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Use Node.js ${{ matrix.node-version }} 19 | uses: actions/setup-node@v1 20 | with: 21 | node-version: ${{ matrix.node-version }} 22 | - run: npm audit 23 | continue-on-error: true 24 | - run: npm outdated 25 | continue-on-error: true 26 | - run: npm ci 27 | - run: npm run eslint --if-present 28 | - run: npm run lint --if-present 29 | - run: npm run package 30 | - run: npm test 31 | env: 32 | CI: true 33 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [main, dev, certification] 6 | pull_request: 7 | branches: [main, dev, certification] 8 | schedule: 9 | - cron: '0 0 * * 3' 10 | 11 | jobs: 12 | analyze: 13 | name: Analyze 14 | runs-on: ubuntu-latest 15 | timeout-minutes: 60 16 | permissions: 17 | actions: read 18 | contents: read 19 | security-events: write 20 | 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | language: ['typescript'] 25 | 26 | steps: 27 | - name: Checkout repository 28 | uses: actions/checkout@v4 29 | with: 30 | fetch-depth: 2 31 | 32 | - name: Use Node.js 18 33 | uses: actions/setup-node@v2 34 | with: 35 | node-version: 18.x 36 | 37 | - name: Install Dependencies 38 | run: npm ci 39 | 40 | - name: Initialize CodeQL 41 | uses: github/codeql-action/init@v3 42 | with: 43 | languages: ${{ matrix.language }} 44 | 45 | - name: Autobuild 46 | uses: github/codeql-action/autobuild@v3 47 | 48 | - name: Perform CodeQL Analysis 49 | uses: github/codeql-action/analyze@v3 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PowerBI-visuals-corrplot 2 | 3 | ![Correlation plot screenshot](https://az158878.vo.msecnd.net/marketing/Partner_21474836617/Product_42949680603/Asset_7a868097-1931-41b4-ae52-80b66476dbf1/CorrelationPlotscreenshot2.png) 4 | # Overview 5 | Correlation plots can be used to quickly find insights. It is used to investigate the dependence between multiple variables at the same time and to highlight the most correlated variables in a data table. In this visual, correlation coefficients are colored according to the value. Correlation matrix can be also reordered according to the degree of association between variables or clustered using hierarchical clustering algorithm. The usage of this visual is very simple and intuitive. 6 | 7 | Here is how it works: 8 | * Define numerical variables to be examined (two or more columns) 9 | * Use numerous formatting controls to refine the visual apperance of the plot 10 | 11 | R package dependencies(auto-installed): corrplot 12 | 13 | Supports R versions: R 3.3.1, R 3.3.0, MRO 3.3.1, MRO 3.3.0, MRO 3.2.2 14 | 15 | See also [Correlation plot at Microsoft Office store](https://store.office.com/en-us/app.aspx?assetid=WA104380814&sourcecorrid=22b62fb2-dcfc-4f65-98f8-1f8506b4fb69&searchapppos=0&ui=en-US&rs=en-US&ad=US&appredirect=false) -------------------------------------------------------------------------------- /pbiviz.json: -------------------------------------------------------------------------------- 1 | { 2 | "visual": { 3 | "name": "PowerBI-visuals-corrplot", 4 | "displayName": "Correlation plot", 5 | "guid": "PBI_CV_FC22EF20_6A7C_4F30_B29B_144CD82E5279", 6 | "visualClassName": "Visual", 7 | "version": "1.0.1", 8 | "description": "Correlation plots can be used to quickly find insights. It is very useful to highlight the most correlated variables in a data table. In this visual, correlation coefficients is colored according to the value. Correlation matrix can be also reordered according to the degree of association between variables. Download this sample to see how a correlation plot can inform marketing strategies in different market segments.

Service prerequisites: R-powered custom visual is used in service seamlessly

Desktop prerequisites: To run R scripts in Power BI Desktop, you must separately install R on your local computer.
You can download and install R for free from the Revolution Open download page or the CRAN Repository

R package dependencies(auto-installed): corrplot
", 9 | "supportUrl": "http://community.powerbi.com/", 10 | "gitHubUrl": "https://github.com/microsoft/PowerBI-visuals-corrplot" 11 | }, 12 | "apiVersion": "1.6.0", 13 | "author": { 14 | "name": "Microsoft", 15 | "email": "pbicvsupport@microsoft.com" 16 | }, 17 | "assets": { 18 | "icon": "assets/icon.png" 19 | }, 20 | "externalJS": [], 21 | "style": "style/visual.less", 22 | "capabilities": "capabilities.json", 23 | "dependencies": "dependencies.json" 24 | } 25 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "class-name": true, 4 | "comment-format": [ 5 | true, 6 | "check-space" 7 | ], 8 | "indent": [ 9 | true, 10 | "spaces" 11 | ], 12 | "no-duplicate-variable": true, 13 | "no-eval": true, 14 | "no-internal-module": false, 15 | "no-trailing-whitespace": true, 16 | "no-unsafe-finally": true, 17 | "no-var-keyword": true, 18 | "one-line": [ 19 | true, 20 | "check-open-brace", 21 | "check-whitespace" 22 | ], 23 | "quotemark": [ 24 | false, 25 | "double" 26 | ], 27 | "semicolon": [ 28 | true, 29 | "always" 30 | ], 31 | "triple-equals": [ 32 | true, 33 | "allow-null-check" 34 | ], 35 | "typedef-whitespace": [ 36 | true, 37 | { 38 | "call-signature": "nospace", 39 | "index-signature": "nospace", 40 | "parameter": "nospace", 41 | "property-declaration": "nospace", 42 | "variable-declaration": "nospace" 43 | } 44 | ], 45 | "variable-name": [ 46 | true, 47 | "ban-keywords" 48 | ], 49 | "whitespace": [ 50 | true, 51 | "check-branch", 52 | "check-decl", 53 | "check-operator", 54 | "check-separator", 55 | "check-type" 56 | ], 57 | "insecure-random": true, 58 | "no-banned-terms": true, 59 | "no-cookies": true, 60 | "no-delete-expression": true, 61 | "no-disable-auto-sanitization": true, 62 | "no-document-domain": true, 63 | "no-document-write": true, 64 | "no-exec-script": true, 65 | "no-function-constructor-with-string-args": true, 66 | "no-http-string": [ 67 | true, 68 | "http://www.example.com/?.*", 69 | "http://www.examples.com/?.*" 70 | ], 71 | "no-inner-html": true, 72 | "no-octal-literal": true, 73 | "no-reserved-keywords": true, 74 | "no-string-based-set-immediate": true, 75 | "no-string-based-set-interval": true, 76 | "no-string-based-set-timeout": true, 77 | "non-literal-require": true, 78 | "possible-timing-attack": true, 79 | "react-anchor-blank-noopener": true, 80 | "react-iframe-missing-sandbox": true, 81 | "react-no-dangerous-html": true 82 | } 83 | } -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/objectEnumerationUtility.ts: -------------------------------------------------------------------------------- 1 | module powerbi.extensibility.visual { 2 | /** 3 | * Gets property value for a particular object. 4 | * 5 | * @function 6 | * @param {DataViewObjects} objects - Map of defined objects. 7 | * @param {string} objectName - Name of desired object. 8 | * @param {string} propertyName - Name of desired property. 9 | * @param {T} defaultValue - Default value of desired property. 10 | */ 11 | export function getValue(objects: DataViewObjects, objectName: string, propertyName: string, defaultValue: T ): T { 12 | if (objects) { 13 | let object = objects[objectName]; 14 | if (object) { 15 | let property: T = object[propertyName]; 16 | if (property !== undefined) { 17 | return property; 18 | } 19 | } 20 | } 21 | return defaultValue; 22 | } 23 | 24 | /** 25 | * Gets property value for a particular object. 26 | * 27 | * @function 28 | * @param {DataViewObjects} objects - Map of defined objects. 29 | * @param {string} objectName - Name of desired object. 30 | * @param {string} propertyName - Name of desired property. 31 | * @param {T} defaultValue - Default value of desired property. 32 | */ 33 | export function getValueMinMax(objects: DataViewObjects, objectName: string, propertyName: string, defaultValue: T, minVal: T, maxVal: T ): T { 34 | if (objects) { 35 | let object = objects[objectName]; 36 | if (object) { 37 | let property: T = object[propertyName]; 38 | if (property < minVal) { 39 | return minVal; 40 | } 41 | if (property > maxVal) { 42 | return maxVal; 43 | } 44 | if (property !== undefined) { 45 | return property; 46 | } 47 | } 48 | } 49 | return defaultValue; 50 | } 51 | 52 | 53 | /** 54 | * Gets property value for a particular object. 55 | * 56 | * @function 57 | * @param {DataViewObjects} objects - Map of defined objects. 58 | * @param {string} objectName - Name of desired object. 59 | * @param {string} propertyName - Name of desired property. 60 | * @param {T} defaultValue - Default value of desired property. 61 | */ 62 | export function getValueNumberMinMax(objects: DataViewObjects, objectName: string, propertyName: string, defaultValue: number, minValue: number, maxValue: number ) { 63 | if (objects) { 64 | let object = objects[objectName]; 65 | if (object) { 66 | let property = object[propertyName]; 67 | if (property !== undefined) { 68 | if (property > maxValue) { 69 | return maxValue; 70 | } 71 | if (property < minValue) { 72 | return minValue; 73 | } 74 | return property; 75 | } 76 | } 77 | } 78 | return defaultValue; 79 | } 80 | 81 | 82 | /** 83 | * Gets conditional property value for a particular object of type string 84 | * 85 | * @function 86 | * @param {string} inVal - current value of parameter 87 | * @param {string} contrVal - control value 88 | * @param {string} contrVal2Compare - specific string to be compared with contrVal 89 | * @param {boolean} logic - true / false "logic" 90 | * @param {string} outValIfCondTrue - output value if comparison (contrVal == contrVal2Compare) comes out as "logic" 91 | */ 92 | export function ifStringReturnString(inVal: string, contrVal: string, contrVal2Compare: string, outValIfCondTrue: string, logic: boolean, applyNow: boolean) { 93 | if (applyNow && contrVal === contrVal2Compare && logic === true) 94 | return outValIfCondTrue; 95 | 96 | if (applyNow && contrVal !== contrVal2Compare && logic === false) 97 | return outValIfCondTrue; 98 | 99 | return inVal; 100 | } 101 | 102 | export function inMinMax(a: number, mi: number, ma: number) { 103 | if (a < mi) 104 | return mi; 105 | if (a > ma) 106 | return ma; 107 | return a; 108 | } 109 | 110 | /** 111 | * Gets property value for a particular object in a category. 112 | * 113 | * @function 114 | * @param {DataViewCategoryColumn} category - List of category objects. 115 | * @param {number} index - Index of category object. 116 | * @param {string} objectName - Name of desired object. 117 | * @param {string} propertyName - Name of desired property. 118 | * @param {T} defaultValue - Default value of desired property. 119 | */ 120 | export function getCategoricalObjectValue(category: DataViewCategoryColumn, index: number, objectName: string, propertyName: string, defaultValue: T): T { 121 | let categoryObjects = category.objects; 122 | 123 | if (categoryObjects) { 124 | let categoryObject: DataViewObject = categoryObjects[index]; 125 | if (categoryObject) { 126 | let object = categoryObject[objectName]; 127 | if (object) { 128 | let property: T = object[propertyName]; 129 | if (property !== undefined) { 130 | return property; 131 | } 132 | } 133 | } 134 | } 135 | return defaultValue; 136 | } 137 | } -------------------------------------------------------------------------------- /capabilities.json: -------------------------------------------------------------------------------- 1 | { 2 | "dataRoles": [ 3 | { 4 | "displayName": "Values", 5 | "description": "Numeric variables. At least 2 rows. At least 2 columns.", 6 | "kind": "GroupingOrMeasure", 7 | "name": "Values" 8 | } 9 | ], 10 | "dataViewMappings": [ 11 | { 12 | "conditions": [ 13 | { "Values": { "max": 1000 } } 14 | ], 15 | "scriptResult": { 16 | "dataInput": { 17 | "table": { 18 | "rows": { 19 | "for": { 20 | "in": "Values" 21 | }, 22 | "dataReductionAlgorithm": { 23 | "top": { } 24 | } 25 | } 26 | } 27 | }, 28 | "script": { 29 | "scriptProviderDefault": "R", 30 | "scriptOutputType": "png", 31 | "source": { 32 | "objectName": "rcv_script", 33 | "propertyName": "source" 34 | }, 35 | "provider": { 36 | "objectName": "rcv_script", 37 | "propertyName": "provider" 38 | } 39 | } 40 | } 41 | } 42 | ], 43 | "objects": { 44 | "rcv_script": { 45 | "properties": { 46 | "provider": { 47 | "type": { "text": true } 48 | }, 49 | "source": { 50 | "type": { 51 | "scripting": { "source": true } 52 | } 53 | } 54 | } 55 | }, 56 | "settings_corrplot_params":{ 57 | "displayName": "Correlation plot parameters", 58 | "properties": { 59 | "show": { 60 | "type": {"bool": true} 61 | }, 62 | "mytype":{ 63 | "displayName": "Matrix shape", 64 | "description": "Full matrix, lower triangular or upper triangular matrix", 65 | "type": { 66 | "enumeration": [ 67 | { 68 | "displayName": "full", 69 | "value": "full" 70 | }, 71 | { 72 | "displayName": "upper", 73 | "value": "upper" 74 | }, 75 | { 76 | "displayName": "lower", 77 | "value": "lower" 78 | } 79 | ] 80 | } 81 | }, 82 | "method": { 83 | "displayName": " Element shape", 84 | "description": "The shape of single element in the correlation matrix", 85 | "type": { 86 | "enumeration": [ 87 | { 88 | "displayName": "circle", 89 | "value": "circle" 90 | }, 91 | { 92 | "displayName": "square", 93 | "value": "square" 94 | }, 95 | { 96 | "displayName": "ellipse", 97 | "value": "ellipse" 98 | }, 99 | { 100 | "displayName": "number", 101 | "value": "number" 102 | }, 103 | { 104 | "displayName": "shade", 105 | "value": "shade" 106 | }, 107 | { 108 | "displayName": "color", 109 | "value": "color" 110 | }, 111 | { 112 | "displayName": "pie", 113 | "value": "pie" 114 | } 115 | ] 116 | } 117 | }, 118 | "addrect": { 119 | "displayName": "Draw clusters", 120 | "description": "Select number of clusters to detect. Clusters are shown as black square frames on matrix. ", 121 | "type": { 122 | "enumeration": [ 123 | { 124 | "displayName": "none", 125 | "value": "0" 126 | }, 127 | { 128 | "displayName": "auto", 129 | "value": "auto" 130 | }, 131 | { 132 | "displayName": "2", 133 | "value": "2" 134 | }, 135 | { 136 | "displayName": "3", 137 | "value": "3" 138 | }, 139 | { 140 | "displayName": "4", 141 | "value": "4" 142 | }, 143 | { 144 | "displayName": "5", 145 | "value": "5" 146 | }, 147 | { 148 | "displayName": "6", 149 | "value": "6" 150 | }, 151 | { 152 | "displayName": "7", 153 | "value": "7" 154 | }, 155 | { 156 | "displayName": "8", 157 | "value": "8" 158 | }, 159 | { 160 | "displayName": "9", 161 | "value": "9" 162 | }, 163 | { 164 | "displayName": "10", 165 | "value": "10" 166 | } 167 | ] 168 | } 169 | }, 170 | "order":{ 171 | "displayName": "Order", 172 | "description": "The ordering method of the correlation matrix: `AOE` for the angular order of the eigenvectors, `FPC` for the first principal component order, `hclust` for the hierarchical clustering order", 173 | "type": { 174 | "enumeration": [ 175 | { 176 | "displayName": "original", 177 | "value": "original" 178 | }, 179 | { 180 | "displayName": "hclust", 181 | "value": "hclust" 182 | }, 183 | { 184 | "displayName": "alphabet", 185 | "value": "alphabet" 186 | }, 187 | { 188 | "displayName": "AOE", 189 | "value": "AOE" 190 | }, 191 | { 192 | "displayName": "FPC", 193 | "value": "FPC" 194 | } 195 | ] 196 | } 197 | } 198 | } 199 | }, 200 | "settings_labels_params":{ 201 | "displayName": "Labels", 202 | "description": "Control the appearance of labels", 203 | "properties": { 204 | "show": { 205 | "type": {"bool": true} 206 | }, 207 | "textSize":{ 208 | "displayName": "Font size", 209 | "type": { 210 | "numeric": true 211 | } 212 | }, 213 | "tl_col": { 214 | "displayName": "Color", 215 | "type": { "fill": { "solid": { "color": true }}} 216 | } 217 | } 218 | }, 219 | "settings_coeff_params":{ 220 | "displayName": "Correlation coefficients", 221 | "description": "Control the appearance of correlation coefficients in the plot", 222 | "properties": { 223 | "show": { 224 | "type": {"bool": true} 225 | }, 226 | "addCoef_col": { 227 | "displayName": "Color", 228 | "type": { "fill": { "solid": { "color": true }}} 229 | }, 230 | "number_digits": { 231 | "displayName": "# digits", 232 | "description": "Number of digits after decimal point", 233 | "type": { 234 | "enumeration": [ 235 | { 236 | "displayName": "1", 237 | "value": "1" 238 | }, 239 | { 240 | "displayName": "2", 241 | "value": "2" 242 | }, 243 | { 244 | "displayName": "3", 245 | "value": "3" 246 | } 247 | ] 248 | } 249 | }, 250 | "textSize":{ 251 | "displayName": "Font size", 252 | "type": { 253 | "numeric": true 254 | } 255 | } 256 | } 257 | }, 258 | "settings_additional_params":{ 259 | "displayName": "Additional settings", 260 | "properties": { 261 | "show": { 262 | "type": {"bool": true} 263 | }, 264 | "showWarnings":{ 265 | "displayName": "Show warnings", 266 | "type": { 267 | "bool": true 268 | } 269 | } 270 | } 271 | } 272 | }, 273 | "suppressDefaultTitle": true 274 | } -------------------------------------------------------------------------------- /src/visual.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Power BI Visual CLI 3 | * 4 | * Copyright (c) Microsoft Corporation 5 | * All rights reserved. 6 | * MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the ""Software""), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | module powerbi.extensibility.visual { 27 | 28 | interface VisualSettingsCorrplotParams { 29 | show: boolean; 30 | mytype: string; 31 | method: string; 32 | addrect: string; 33 | order: string; 34 | } 35 | 36 | interface VisualSettingsLabelsParams { 37 | show: boolean; 38 | textSize: number; 39 | tl_col: string; 40 | } 41 | interface VisualSettingsCoeffParams { 42 | show: boolean; 43 | addCoef_col: string; 44 | number_digits: string; 45 | textSize: number; 46 | 47 | } 48 | interface VisualSettingsAdditionalParams { 49 | show: boolean; 50 | showWarnings: boolean; 51 | } 52 | 53 | export class Visual implements IVisual { 54 | private imageDiv: HTMLDivElement; 55 | private imageElement: HTMLImageElement; 56 | 57 | private settings_corrplot_params: VisualSettingsCorrplotParams; 58 | private settings_labels_params: VisualSettingsLabelsParams; 59 | private settings_coeff_params: VisualSettingsCoeffParams; 60 | private settings_additional_params: VisualSettingsAdditionalParams; 61 | 62 | 63 | public constructor(options: VisualConstructorOptions) { 64 | this.imageDiv = document.createElement('div'); 65 | this.imageDiv.className = 'rcv_autoScaleImageContainer'; 66 | options.element.appendChild(this.imageDiv); 67 | 68 | this.imageElement = document.createElement('img'); 69 | this.imageElement.className = 'rcv_autoScaleImage'; 70 | 71 | this.imageDiv.appendChild(this.imageElement); 72 | 73 | this.settings_corrplot_params = { 74 | show: false, 75 | method: "circle", 76 | mytype: "full", 77 | order: "original", 78 | addrect: "none", 79 | }; 80 | 81 | this.settings_labels_params = { 82 | show: false, 83 | textSize: 10, 84 | tl_col: "red", 85 | }; 86 | this.settings_coeff_params = { 87 | show: false, 88 | addCoef_col: "black", 89 | number_digits: "1", 90 | textSize: 8 91 | }; 92 | this.settings_additional_params = { 93 | show: false, 94 | showWarnings: false, 95 | }; 96 | } 97 | 98 | public update(options: VisualUpdateOptions) { 99 | let dataViews: DataView[] = options.dataViews; 100 | if (!dataViews || dataViews.length === 0) 101 | return; 102 | 103 | let dataView: DataView = dataViews[0]; 104 | if (!dataView || !dataView.metadata) 105 | return; 106 | 107 | this.settings_corrplot_params = { 108 | show: getValue(dataView.metadata.objects, 'settings_corrplot_params', 'show', false), 109 | method: getValue(dataView.metadata.objects, 'settings_corrplot_params', 'method', "circle"), 110 | mytype: getValue(dataView.metadata.objects, 'settings_corrplot_params', 'mytype', "full"), 111 | addrect: getValue(dataView.metadata.objects, 'settings_corrplot_params', 'addrect', "0"), 112 | order: getValue(dataView.metadata.objects, 'settings_corrplot_params', 'order', "original"), 113 | 114 | 115 | }; 116 | 117 | 118 | this.settings_labels_params = { 119 | show: getValue(dataView.metadata.objects, 'settings_labels_params', 'show', false), 120 | textSize: getValueMinMax(dataView.metadata.objects, 'settings_labels_params', 'textSize', 10, 5, 50), 121 | tl_col: getValue(dataView.metadata.objects, 'settings_labels_params', 'tl_col', "red"), 122 | }; 123 | this.settings_coeff_params = { 124 | show: getValue(dataView.metadata.objects, 'settings_coeff_params', 'show', false), 125 | addCoef_col: getValue(dataView.metadata.objects, 'settings_coeff_params', 'addCoef_col', "black"), 126 | number_digits: getValue(dataView.metadata.objects, 'settings_coeff_params', 'number_digits', "1"), 127 | textSize: getValue(dataView.metadata.objects, 'settings_coeff_params', 'textSize', 8) 128 | 129 | }; 130 | this.settings_additional_params = { 131 | show: getValue(dataView.metadata.objects, 'settings_additional_params', 'show', false), 132 | showWarnings: getValue(dataView.metadata.objects, 'settings_additional_params', 'showWarnings', false) 133 | }; 134 | 135 | let imageUrl: string = null; 136 | if (dataView.scriptResult && dataView.scriptResult.payloadBase64) { 137 | imageUrl = "data:image/png;base64," + dataView.scriptResult.payloadBase64; 138 | } 139 | 140 | if (imageUrl) { 141 | this.imageElement.src = imageUrl; 142 | } else { 143 | this.imageElement.src = null; 144 | } 145 | 146 | this.onResizing(options.viewport); 147 | } 148 | 149 | public onResizing(finalViewport: IViewport): void { 150 | this.imageDiv.style.height = finalViewport.height + 'px'; 151 | this.imageDiv.style.width = finalViewport.width + 'px'; 152 | } 153 | 154 | public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstanceEnumeration { 155 | let objectName = options.objectName; 156 | let objectEnumeration = []; 157 | 158 | switch (objectName) { 159 | case 'settings_corrplot_params': 160 | if (this.settings_corrplot_params.addrect === "0") { 161 | objectEnumeration.push({ 162 | objectName: objectName, 163 | properties: { 164 | show: this.settings_corrplot_params.show, 165 | method: this.settings_corrplot_params.method, 166 | addrect: this.settings_corrplot_params.addrect, 167 | mytype: this.settings_corrplot_params.mytype, 168 | order: this.settings_corrplot_params.order 169 | }, 170 | selector: null 171 | }); 172 | } 173 | else { 174 | objectEnumeration.push({ 175 | objectName: objectName, 176 | properties: { 177 | show: this.settings_corrplot_params.show, 178 | method: this.settings_corrplot_params.method, 179 | addrect: this.settings_corrplot_params.addrect, 180 | }, 181 | selector: null 182 | }); 183 | 184 | } 185 | break; 186 | 187 | case 'settings_labels_params': 188 | objectEnumeration.push({ 189 | objectName: objectName, 190 | properties: { 191 | show: this.settings_labels_params.show, 192 | textSize: this.settings_labels_params.textSize, 193 | tl_col: this.settings_labels_params.tl_col 194 | }, 195 | selector: null 196 | }); 197 | break; 198 | case 'settings_coeff_params': 199 | objectEnumeration.push({ 200 | objectName: objectName, 201 | properties: { 202 | show: this.settings_coeff_params.show, 203 | number_digits: this.settings_coeff_params.number_digits, 204 | addCoef_col: this.settings_coeff_params.addCoef_col, 205 | textSize: this.settings_coeff_params.textSize, 206 | }, 207 | selector: null 208 | }); 209 | break; 210 | case 'settings_additional_params': 211 | objectEnumeration.push({ 212 | objectName: objectName, 213 | properties: { 214 | show: this.settings_additional_params.show, 215 | showWarnings: this.settings_additional_params.showWarnings, 216 | }, 217 | selector: null 218 | }); 219 | break; 220 | }; 221 | 222 | return objectEnumeration; 223 | } 224 | } 225 | } -------------------------------------------------------------------------------- /script.r: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. All rights reserved. 2 | 3 | # Third Party Programs. This software enables you to obtain software applications from other sources. 4 | # Those applications are offered and distributed by third parties under their own license terms. 5 | # Microsoft is not developing, distributing or licensing those applications to you, but instead, 6 | # as a convenience, enables you to use this software to obtain those applications directly from 7 | # the application providers. 8 | # By using the software, you acknowledge and agree that you are obtaining the applications directly 9 | # from the third party providers and under separate license terms, and that it is your responsibility to locate, 10 | # understand and comply with those license terms. 11 | # Microsoft grants you no license rights for third-party software or applications that is obtained using this software. 12 | 13 | 14 | ##PBI_R_VISUAL: VIZGAL_CORRPLOT Graphical display of a correlation matrix. 15 | # Computes and visualizes a correlation matrix. Used to investigate the 16 | # dependency between multiple variables at the same time. It also contains some algorithms 17 | # to do matrix reordering and grouping of variables. 18 | # INPUT: 19 | # The input dataset should include at least two numerical non-constant columns 20 | # 21 | # EXAMPLES: 22 | # #for R environment 23 | # dataset<-mtcars #assign dataset 24 | # source("visGal_corrplot.R") #create graphics 25 | # 26 | # WARNINGS: 27 | # 28 | # CREATION DATE: 06/01/2016 29 | # 30 | # LAST UPDATE: 07/26/2016 31 | # 32 | # VERSION: 0.0.1 33 | # 34 | # R VERSION TESTED: 3.2.2 35 | # 36 | # AUTHOR: pbicvsupport@microsoft.com 37 | # 38 | # REFERENCES: https://cran.r-project.org/web/packages/corrplot/vignettes/corrplot-intro.html 39 | 40 | 41 | #save(list = ls(all.names = TRUE), file='C:/Users/boefraty/projects/PBI/R/tempData.Rda') 42 | #load(file='C:/Users/boefraty/projects/PBI/R/tempData.Rda') 43 | 44 | cutStr2Show = function(strText, strCex = 0.8, abbrTo = 100, isH = TRUE, maxChar = 0, partAvailable = 1) 45 | { 46 | # strText = text to modify 47 | # strCex = font size 48 | # abbrTo = very long string will be abbreviated to "abbrTo" characters 49 | # isH = "is horizontal" ? 50 | # maxChar = text smaller than maxChar is replaced by NULL 51 | # partAvailable = which portion of window is available for text, in [0,1] 52 | 53 | if(is.null(strText)) 54 | return (NULL) 55 | 56 | SCL = 0.094*strCex 57 | pardin = par()$din 58 | gStand = partAvailable*(isH*pardin[1]+(1-isH)*pardin[2]) /SCL 59 | 60 | # if very very long abbreviate 61 | if(nchar(strText)>abbrTo && nchar(strText)> 1) 62 | strText = abbreviate(strText, abbrTo) 63 | 64 | # if looooooong convert to lo... 65 | if(nchar(strText)>round(gStand) && nchar(strText)> 1) 66 | strText = paste(substring(strText,1,floor(gStand)),"...",sep="") 67 | 68 | # if shorter than maxChar remove 69 | if(gStand<=maxChar) 70 | strText = NULL 71 | 72 | return(strText) 73 | } 74 | 75 | 76 | verifyIfToShowCoeff = function(numR, numDigs, coeffCex, partAvailble = 0.75) 77 | { 78 | myThre = 0.1 79 | lenPerD = partAvailble*min(par()$din)/(numR*(numDigs+1)*coeffCex) 80 | return(lenPerD>myThre) 81 | } 82 | verifyIfToShowColorScale = function(numR, partAvailble = 0.05) 83 | { 84 | myThre = 0.02 85 | lenPerD = partAvailble*par()$din[1]/(numR) 86 | return(lenPerD>myThre) 87 | } 88 | 89 | getValueNumericMinMaxDefault = function(val,minVal = -Inf,maxVal = Inf,defVal = NA) 90 | { 91 | if(!is.numeric(val) && !is.na(defVal)) 92 | val = defVal 93 | 94 | if(valmaxVal) 98 | val = maxVal 99 | 100 | return(val) 101 | } 102 | 103 | 104 | 105 | if(!exists("dataset") && exists("Values")) 106 | dataset = Values 107 | 108 | #PBI_EXAMPLE_DATASET for debugging purposes 109 | if(!exists("dataset")) 110 | { 111 | require("datasets") 112 | dataset=data.frame(mtcars) #mpg,cyl,disp,hp,drat,wt,qsec,vs,am,gear,carb 113 | } 114 | 115 | ############ User Parameters ######### 116 | 117 | if(exists("settings_corrplot_params_show") && settings_corrplot_params_show == FALSE) 118 | rm(list= ls(pattern = "settings_corrplot_params_")) 119 | if(exists("settings_labels_params_show") && settings_labels_params_show == FALSE) 120 | rm(list= ls(pattern = "settings_labels_params_")) 121 | if(exists("settings_coeff_params_show") && settings_coeff_params_show == FALSE) 122 | rm(list= ls(pattern = "settings_coeff_params_")) 123 | if(exists("settings_additional_params_show") && settings_additional_params_show == FALSE) 124 | rm(list= ls(pattern = "settings_additional_params_")) 125 | 126 | ##PBI_PARAM: Should warnings text be displayed? 127 | #Type:logical, Default:TRUE, Range:NA, PossibleValues:NA, Remarks: NA 128 | showWarnings = FALSE 129 | if(exists("settings_additional_params_showWarnings")) 130 | showWarnings = settings_additional_params_showWarnings 131 | 132 | ##PBI_PARAM: visualization method of items inside the table 133 | #Type:string, Default:'circle', Range:NA, PossibleValues:("circle", "square", "ellipse", "number", "shade", "color", "pie"), Remarks: NA 134 | method = 'circle' 135 | if(exists("settings_corrplot_params_method")) 136 | method = settings_corrplot_params_method 137 | 138 | ##PBI_PARAM: layout type, defines if we display full, lower triangular or upper triangular matrix. 139 | #Type:string, Default:'full', Range:NA, PossibleValues:("full", "upper", "lower"), Remarks: If rectangles are added, this option is switched to "full" 140 | type = 'full' 141 | if(exists("settings_corrplot_params_mytype")) 142 | type = settings_corrplot_params_mytype 143 | 144 | ##PBI_PARAM: order of raws is one of "original","hclust"(hierarchical clustering order), "alphabet", "AOE"(angular order of the eigenvectors), "FPC"( first principal component order) 145 | #Type:string, Default:'original', Range:NA, PossibleValues:("original","hclust","alphabet", "AOE", "FPC"), Remarks: NA 146 | order='original' 147 | if(exists("settings_corrplot_params_order")) 148 | order = settings_corrplot_params_order 149 | 150 | 151 | ##PBI_PARAM: number of clusters to be drawn on top of correlation matrix as rectangles 152 | #if order is "hclust" and type is "full" visual will draw rectangles around the correlation matrix 153 | # addrect can also be integer or NULL (add nothing) or NaN (auto) 154 | #Type:unsigned integer, Default:NaN, Range:NA, PossibleValues:NA, Remarks: If equals NaN, will find number automatically 155 | addrect= 0 156 | if(exists("settings_corrplot_params_addrect")) 157 | { 158 | addrect = as.numeric(settings_corrplot_params_addrect) 159 | if(is.na(addrect) || addrect>1) 160 | { 161 | order='hclust' 162 | type = 'full' 163 | } 164 | 165 | } 166 | ###############Library Declarations############### 167 | 168 | libraryRequireInstall = function(packageName, ...) 169 | { 170 | if(!require(packageName, character.only = TRUE)) 171 | warning(paste("*** The package: '", packageName, "' was not installed ***",sep="")) 172 | } 173 | 174 | libraryRequireInstall("corrplot") 175 | 176 | ###############Internal parameters definitions################# 177 | ##PBI_PARAM: color of text label 178 | #Type:string, Default:'orange', Range:NA, PossibleValues:("red","black","green", "blue", "gray"), 179 | #Remarks: see colors() function for full list of built-in color names 180 | tl.col = "red" 181 | if(exists("settings_labels_params_tl_col")) 182 | tl.col = settings_labels_params_tl_col 183 | 184 | ##PBI_PARAM: font size of text label 185 | #Type:numeric, Default:0.9, Range:(0,Inf], PossibleValues:NA, 186 | #Remarks: NA 187 | tl.cex = 1 188 | if(exists("settings_labels_params_textSize")) 189 | tl.cex = as.numeric(settings_labels_params_textSize)/10 190 | 191 | ##PBI_PARAM: Color of coefficients added on the graph. If NULL (default), add no coefficients 192 | #Type:string, Default:NULL, Range:NA, PossibleValues:("white","black","green","gray",NULL), 193 | #Remarks: NA 194 | addCoef.col = NA 195 | if(exists("settings_coeff_params_addCoef_col")) 196 | addCoef.col = (settings_coeff_params_addCoef_col) 197 | 198 | ##PBI_PARAM: size of coefficients added on the graph. 199 | #Type:numeric, Default:0.6, Range:[0,1], PossibleValues:NA, 200 | #Remarks: NA 201 | number.cex = 0.8 202 | if(exists("settings_coeff_params_textSize")) 203 | number.cex = as.numeric(settings_coeff_params_textSize)/10 204 | 205 | 206 | ##PBI_PARAM: the number of decimal digits to be added into the plot 207 | #Type:numeric, Default:1, Range:[1,3], PossibleValues:NA, 208 | #Remarks: NA 209 | number.digits = 1*as.numeric(exists("settings_coeff_params_show")) 210 | 211 | if(exists("settings_coeff_params_number_digits")) 212 | { 213 | number.digits = as.numeric(settings_coeff_params_number_digits) 214 | if(is.na(number.digits)) 215 | { 216 | addCoef.col = NULL 217 | number.digits = 0 218 | } 219 | } 220 | 221 | if(number.digits == 0) 222 | addCoef.col = NULL 223 | 224 | ##PBI_PARAM: the default margin definition of the plot 225 | #Type:vector, Default:c(1.0, 0.75, 0.75, 0.6), Range:NA, PossibleValues:NA, 226 | #Remarks: NA 227 | defMar = c(0.5, 0.25, 0.25, 0.1) + 0.5 228 | 229 | MAX_CHAR_TL = 50 # maximum characters for text label 230 | MAX_PART_TL = 0.5 # maximum space for text label 231 | clpos = "r" #location of color scale 232 | ###############Internal functions definitions################# 233 | 234 | #by default will group variables in log of number of columns clusters 235 | autoDetectAddrect <- function(dataset) { ceiling(log(ncol(dataset))) } 236 | 237 | #verify if the column is numeric and non-constant 238 | correctColumn <- function(someColumn) { is.numeric(someColumn) && length(unique(someColumn)) > 1 } 239 | 240 | 241 | ###############Upfront input correctness validations (where possible)################# 242 | pbiWarning <- NULL 243 | 244 | #PBI_COMMENT: verify correctness of dataset 245 | useColumns <- sapply(dataset,correctColumn) 246 | if(showWarnings && sum(useColumns) < ncol(dataset)) 247 | pbiWarning <- "Some columns are not numeric, or constant." 248 | 249 | dataset <- as.data.frame(dataset[,useColumns]) 250 | nc <- ncol(dataset) 251 | nr <- nrow(dataset) 252 | 253 | nnn = names(dataset) 254 | nnn1 = sapply(nnn,cutStr2Show, strCex = tl.cex, abbrTo = MAX_CHAR_TL, isH = (par()$din[1] 1) #enable adding rectangle by type=full 266 | type <- 'full' 267 | } 268 | 269 | ##############Main Visualization script########### 270 | if(!is.null(addCoef.col)) 271 | if(!verifyIfToShowCoeff(nc, number.digits, number.cex, partAvailble = 0.75)) 272 | { 273 | addCoef.col = NULL 274 | pbiWarning<-paste(pbiWarning, "Not enough space for coefficients.", sep=" ") 275 | } 276 | if(!verifyIfToShowColorScale(nc)) 277 | clpos = "n" 278 | #PBI_COMMENT: Create visual 279 | if(nc > 1 && nr > 1){ 280 | #PBI_COMMENT: compute correlation matrix, allow for missing values 281 | M <- cor(dataset, use="pairwise.complete.obs") 282 | par(xpd = TRUE) 283 | 284 | if(type == "upper") 285 | { 286 | defMar = c(0.25, 0.25, 3.5, 0.1) + 0.5 287 | tl.cex = tl.cex*0.95 288 | } 289 | corrplot(M, method=method, order=order, type=type, addrect=addrect, 290 | mar = defMar, tl.col = tl.col, tl.cex=tl.cex, 291 | number.digits=number.digits, number.cex=number.cex, addCoef.col=addCoef.col, 292 | cl.pos =clpos) 293 | }else{ #empty correlation plot 294 | plot.new() 295 | pbiWarning<-paste(pbiWarning, "Not enough input dimensions.", sep=" ") 296 | } 297 | #add warning as subtitle 298 | if(showWarnings) 299 | { 300 | pbiWarning <- cutStr2Show(pbiWarning,strCex = 0.8, abbrTo = 100, isH = TRUE) 301 | title(main = NULL, sub = pbiWarning, outer = FALSE, col.sub = "gray50", cex.sub = 0.75) 302 | } 303 | 304 | --------------------------------------------------------------------------------