├── .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 | 
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 |
--------------------------------------------------------------------------------