Optimize requests (#6)
* fix issues: #16942 Globe Map 1.4.5: If Legend field is not emply, filter working incorrectly #17076 Color selection for Legend property is not working * Update packages Fix bug: If Legend field is not empty, filter working incorrectly * Fix THREE.WebGELRenderer error * Fix getting Bing Maps tiles * Add metadata server * Update packages * Remove unused code * Optimize rendering * Use Promise-based async operations * Use static markup for control buttons group * Fix comments * Remove empty lines
This commit is contained in:
Родитель
9929196cc7
Коммит
5852e42c81
|
@ -0,0 +1,33 @@
|
||||||
|
{
|
||||||
|
"editor.tabSize": 4,
|
||||||
|
"editor.insertSpaces": true,
|
||||||
|
"files.eol": "\n",
|
||||||
|
"files.watcherExclude": {
|
||||||
|
"**/.git/objects/**": true,
|
||||||
|
"**/node_modules/**": true,
|
||||||
|
".tmp": true
|
||||||
|
},
|
||||||
|
"files.exclude": {
|
||||||
|
".tmp": true,
|
||||||
|
"coverage": true
|
||||||
|
},
|
||||||
|
"search.exclude": {
|
||||||
|
".tmp": true,
|
||||||
|
"dist": true,
|
||||||
|
"coverage": true
|
||||||
|
},
|
||||||
|
"json.schemas": [
|
||||||
|
{
|
||||||
|
"fileMatch": [
|
||||||
|
"/pbiviz.json"
|
||||||
|
],
|
||||||
|
"url": "./.api/v1.6.0/schema.pbiviz.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fileMatch": [
|
||||||
|
"/capabilities.json"
|
||||||
|
],
|
||||||
|
"url": "./.api/v1.6.0/schema.capabilities.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -11,8 +11,7 @@
|
||||||
"**/.*",
|
"**/.*",
|
||||||
"node_modules",
|
"node_modules",
|
||||||
"bower_components",
|
"bower_components",
|
||||||
"test",
|
"test"
|
||||||
"tests"
|
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"webgl-heatmap": "*"
|
"webgl-heatmap": "*"
|
||||||
|
|
48
package.json
48
package.json
|
@ -1,18 +1,17 @@
|
||||||
{
|
{
|
||||||
"name": "powerbi-visuals-globemap",
|
"name": "powerbi-visuals-globemap",
|
||||||
"description": "GlobeMap",
|
"description": "GlobeMap",
|
||||||
"version": "1.4.3",
|
"version": "1.4.7",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Microsoft",
|
"name": "Microsoft",
|
||||||
"email": "pbicvsupport@microsoft.com"
|
"email": "pbicvsupport@microsoft.com"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "bower install && pbiviz update 1.5.0",
|
"postinstall": "bower install && pbiviz update 1.6.0",
|
||||||
"typings": "typings",
|
|
||||||
"pbiviz": "pbiviz",
|
"pbiviz": "pbiviz",
|
||||||
"start": "pbiviz start",
|
"start": "pbiviz start",
|
||||||
"package": "pbiviz package",
|
"package": "pbiviz package",
|
||||||
"lint": "node node_modules/tslint/bin/tslint -r \"node_modules/tslint-microsoft-contrib\" \"+(src|test)/**/*.ts\"",
|
"lint": "tslint -r \"node_modules/tslint-microsoft-contrib\" \"+(src|test)/**/*.ts\"",
|
||||||
"pretest": "pbiviz package --resources --no-minify --no-pbiviz",
|
"pretest": "pbiviz package --resources --no-minify --no-pbiviz",
|
||||||
"test": "karma start"
|
"test": "karma start"
|
||||||
},
|
},
|
||||||
|
@ -22,36 +21,35 @@
|
||||||
"url": "git+https://github.com/Microsoft/PowerBI-visuals-globemap.git"
|
"url": "git+https://github.com/Microsoft/PowerBI-visuals-globemap.git"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"globalize": "0.1.0-a2",
|
|
||||||
"three": "0.75.0",
|
|
||||||
"lodash": "4.16.2",
|
|
||||||
"moment": "2.15.1",
|
|
||||||
"jquery": "3.1.1",
|
|
||||||
"d3": "3.5.5",
|
"d3": "3.5.5",
|
||||||
"powerbi-visuals-utils-chartutils": "0.2.1",
|
"globalize": "0.1.0-a2",
|
||||||
"powerbi-visuals-utils-colorutils": "0.2.1",
|
"jquery": "3.1.1",
|
||||||
"powerbi-visuals-utils-dataviewutils": "1.0.1"
|
"lodash": "4.17.4",
|
||||||
|
"powerbi-visuals-utils-colorutils": "0.2.2",
|
||||||
|
"powerbi-visuals-utils-dataviewutils": "1.1.1",
|
||||||
|
"powerbi-visuals-utils-formattingutils": "1.0.0",
|
||||||
|
"powerbi-visuals-utils-interactivityutils": "0.3.1",
|
||||||
|
"powerbi-visuals-utils-typeutils": "1.0.0",
|
||||||
|
"three": "0.75.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/d3": "3.5.36",
|
"@types/d3": "3.5.36",
|
||||||
"@types/jasmine": "2.5.37",
|
"@types/jasmine": "2.5.47",
|
||||||
"@types/jasmine-jquery": "1.5.28",
|
"@types/jasmine-jquery": "1.5.30",
|
||||||
"@types/jquery": "2.0.41",
|
"@types/jquery": "2.0.41",
|
||||||
"@types/lodash": "4.14.43",
|
"@types/lodash": "4.14.55",
|
||||||
"@types/three": "0.0.19",
|
"@types/three": "0.0.19",
|
||||||
"bingmaps": "1.0.12",
|
|
||||||
"bower": "1.8.0",
|
"bower": "1.8.0",
|
||||||
"jasmine": "2.5.2",
|
"jasmine": "2.6.0",
|
||||||
"jasmine-jquery": "2.1.1",
|
"jasmine-jquery": "2.1.1",
|
||||||
"karma": "1.3.0",
|
"karma": "1.6.0",
|
||||||
"karma-chrome-launcher": "2.0.0",
|
"karma-chrome-launcher": "2.0.0",
|
||||||
"karma-jasmine": "1.0.2",
|
"karma-jasmine": "1.1.0",
|
||||||
"karma-typescript-preprocessor": "0.3.0",
|
"karma-typescript-preprocessor": "0.3.1",
|
||||||
"powerbi-visuals-tools": "1.5.0",
|
"powerbi-visuals-tools": "1.6.3",
|
||||||
"powerbi-visuals-utils-testutils": "0.2.2",
|
"powerbi-visuals-utils-testutils": "1.0.0",
|
||||||
"powerbi-visuals-utils-typeutils": "^0.2.1",
|
"tslint": "4.5.1",
|
||||||
"tslint": "^4.4.2",
|
"tslint-microsoft-contrib": "^4.0.1",
|
||||||
"tslint-microsoft-contrib": "^4.0.0",
|
|
||||||
"typescript": "2.1.4"
|
"typescript": "2.1.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,12 @@
|
||||||
"displayName": "GlobeMap",
|
"displayName": "GlobeMap",
|
||||||
"guid": "GlobeMap1447669447624",
|
"guid": "GlobeMap1447669447624",
|
||||||
"visualClassName": "GlobeMap",
|
"visualClassName": "GlobeMap",
|
||||||
"version": "1.4.5",
|
"version": "1.4.7",
|
||||||
"description": "A 3D visual using WebGL for plotting locations, with category values displayed as bar heights and heat maps.\n\nShift+Click on bar to change center point. \nSlicing data points will animate to average location.\n\nAttributions:\nthree.js - https://github.com/mrdoob/three.js/\nwebgl-heatmap - https://github.com/pyalot/webgl-heatmap",
|
"description": "A 3D visual using WebGL for plotting locations, with category values displayed as bar heights and heat maps.\n\nShift+Click on bar to change center point. \nSlicing data points will animate to average location.\n\nAttributions:\nthree.js - https://github.com/mrdoob/three.js/\nwebgl-heatmap - https://github.com/pyalot/webgl-heatmap",
|
||||||
"supportUrl": "http://community.powerbi.com",
|
"supportUrl": "http://community.powerbi.com",
|
||||||
"gitHubUrl": "https://github.com/Microsoft/powerbi-visuals-globemap"
|
"gitHubUrl": "https://github.com/Microsoft/powerbi-visuals-globemap"
|
||||||
},
|
},
|
||||||
"apiVersion": "1.5.0",
|
"apiVersion": "1.6.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Microsoft",
|
"name": "Microsoft",
|
||||||
"email": "pbicvsupport@microsoft.com"
|
"email": "pbicvsupport@microsoft.com"
|
||||||
|
@ -20,7 +20,7 @@
|
||||||
"externalJS": [
|
"externalJS": [
|
||||||
"node_modules/jquery/dist/jquery.min.js",
|
"node_modules/jquery/dist/jquery.min.js",
|
||||||
"node_modules/lodash/lodash.min.js",
|
"node_modules/lodash/lodash.min.js",
|
||||||
"node_modules/d3/d3.js",
|
"node_modules/d3/d3.min.js",
|
||||||
"node_modules/three/three.js",
|
"node_modules/three/three.js",
|
||||||
"src/lib/OrbitControls.js",
|
"src/lib/OrbitControls.js",
|
||||||
"node_modules/powerbi-visuals-utils-typeutils/lib/index.js",
|
"node_modules/powerbi-visuals-utils-typeutils/lib/index.js",
|
||||||
|
@ -30,7 +30,6 @@
|
||||||
"node_modules/powerbi-visuals-utils-dataviewutils/lib/index.js",
|
"node_modules/powerbi-visuals-utils-dataviewutils/lib/index.js",
|
||||||
"node_modules/powerbi-visuals-utils-formattingutils/lib/index.js",
|
"node_modules/powerbi-visuals-utils-formattingutils/lib/index.js",
|
||||||
"node_modules/powerbi-visuals-utils-interactivityutils/lib/index.js",
|
"node_modules/powerbi-visuals-utils-interactivityutils/lib/index.js",
|
||||||
"node_modules/powerbi-visuals-utils-chartutils/lib/index.js",
|
|
||||||
"node_modules/powerbi-visuals-utils-dataviewutils/lib/index.js",
|
"node_modules/powerbi-visuals-utils-dataviewutils/lib/index.js",
|
||||||
"node_modules/powerbi-visuals-utils-colorutils/lib/index.js",
|
"node_modules/powerbi-visuals-utils-colorutils/lib/index.js",
|
||||||
"bower_components/webgl-heatmap/webgl-heatmap.js"
|
"bower_components/webgl-heatmap/webgl-heatmap.js"
|
||||||
|
|
|
@ -25,165 +25,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace powerbi.extensibility.utils {
|
namespace powerbi.extensibility.utils {
|
||||||
import RegExpExtensions = powerbi.extensibility.utils.type.RegExpExtensions;
|
|
||||||
export module Deprecated {
|
|
||||||
export const escape: (s: string) => string = window['escape'];
|
|
||||||
export const unescape: (s: string) => string = window['unescape'];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TextMatch {
|
|
||||||
start: number;
|
|
||||||
end: number;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export module UrlUtils {
|
export module UrlUtils {
|
||||||
const urlRegex = /http[s]?:\/\/(\S)+/gi;
|
|
||||||
|
|
||||||
export function isValidUrl(value: string): boolean {
|
|
||||||
if (_.isEmpty(value)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let match: RegExpExecArray = RegExpExtensions.run(urlRegex, value);
|
|
||||||
if (!!match && match.index === 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Tests whether a URL is valid.
|
|
||||||
* @param url The url to be tested.
|
|
||||||
* @returns Whether the provided url is valid.
|
|
||||||
**/
|
|
||||||
export function isValidImageUrl(url: string): boolean {
|
|
||||||
// For now, passes for any valid Url
|
|
||||||
return isValidUrl(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function findAllValidUrls(text: string): TextMatch[] {
|
|
||||||
if (_.isEmpty(text)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find all urls in the text.
|
|
||||||
// TODO: This could potentially be expensive, maybe include a cap here for text with many urls?
|
|
||||||
let urlRanges: TextMatch[] = [];
|
|
||||||
let matches: RegExpExecArray;
|
|
||||||
let start: number = 0;
|
|
||||||
while ((matches = RegExpExtensions.run(urlRegex, text, start)) !== null) {
|
|
||||||
let url: any = matches[0];
|
|
||||||
let end: number = matches.index + url.length;
|
|
||||||
urlRanges.push({
|
|
||||||
start: matches.index,
|
|
||||||
end: end,
|
|
||||||
text: url,
|
|
||||||
});
|
|
||||||
start = end;
|
|
||||||
}
|
|
||||||
|
|
||||||
return urlRanges;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isDataUri(uri: string): boolean {
|
|
||||||
return uri && uri.indexOf('data:') === 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getBase64ContentFromDataUri(uri: string): string {
|
|
||||||
if (!isDataUri(uri)) {
|
|
||||||
throw new Error("Expected data uri");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Locate the base 64 content from the URL (e.g. "data:image/png;base64,xxxxx=")
|
|
||||||
const base64Token = ";base64,";
|
|
||||||
let indexBase64TokenStart: number = uri.indexOf(base64Token);
|
|
||||||
if (indexBase64TokenStart < 0) {
|
|
||||||
throw new Error("Expected base 64 content in data url");
|
|
||||||
}
|
|
||||||
|
|
||||||
let indexBase64Start: number = indexBase64TokenStart + base64Token.length;
|
|
||||||
return uri.substr(indexBase64Start, uri.length - indexBase64Start);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a base64 data URI for a string with a UTF-8 character encoding.
|
|
||||||
* @param rawText {string} The text string to be encapsulated. It is the raw Javascript string
|
|
||||||
*/
|
|
||||||
export function makeUTF8EncodedBase64DataUri(contentType: string, rawText: string): string {
|
|
||||||
return "data:" + contentType + ";base64," + UrlUtils.utoa(rawText);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function makeJsonDataUri(rawJson: string): string {
|
|
||||||
return makeUTF8EncodedBase64DataUri("application/json", rawJson);
|
|
||||||
}
|
|
||||||
|
|
||||||
// btoa does not work for char codes > 0xff. for these we have to UTF-8 encode it
|
|
||||||
// first. cleverly combining the deprecated functions unescape/escape with
|
|
||||||
// encode/decodeURIComponent gets the browser to do all the work. in case
|
|
||||||
// unescape/escape are not present, use slower Javascript implementations.
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/btoa#Unicode_strings
|
|
||||||
// http://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html
|
|
||||||
// http://www.ecma-international.org/publications/files/ECMA-ST-ARCH/ECMA-262,%201st%20edition,%20June%201997.pdf#sec-15.1.2.4
|
|
||||||
|
|
||||||
// exported for testing
|
|
||||||
export function escapeSlow(s: string): string {
|
|
||||||
if (!s) {
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.replace(/[^*+\-./0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz]/g, unescaped => {
|
|
||||||
let escaped: any = unescaped.charCodeAt(0).toString(16).toUpperCase();
|
|
||||||
switch (escaped.length) {
|
|
||||||
case 1: return '%0' + escaped;
|
|
||||||
case 2: return '%' + escaped;
|
|
||||||
case 3: return '%u0' + escaped;
|
|
||||||
default: return '%u' + escaped;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// exported for testing
|
|
||||||
export function unescapeSlow(s: string): string {
|
|
||||||
if (!s) {
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.replace(/%([0-9a-fA-F]{2})|%u([0-9a-fA-F]{4})/g, (_, short, long) => {
|
|
||||||
return String.fromCharCode(parseInt(short || long, 16));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const unescape: (s: string) => string = Deprecated.unescape || unescapeSlow;
|
|
||||||
const escape: (s: string) => string = Deprecated.escape || escapeSlow;
|
|
||||||
|
|
||||||
export function encodeUTF8(s: string): string {
|
|
||||||
return unescape(encodeURIComponent(s));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function decodeUTF8(s: string): string {
|
|
||||||
return decodeURIComponent(escape(s));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function utoa(s: string): string {
|
|
||||||
return btoa(encodeUTF8(s));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function atou(s: string): string {
|
|
||||||
return decodeUTF8(atob(s));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns the set of query parameters in a URL */
|
|
||||||
export function getQueryParameters(url: string): _.Dictionary<string> {
|
|
||||||
const query = getQueryString(url);
|
|
||||||
|
|
||||||
if (!query) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return parseQueryString(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a URL, set the provided query string parameters
|
* Given a URL, set the provided query string parameters
|
||||||
* @param url The URL to modify
|
* @param url The URL to modify
|
||||||
|
@ -202,11 +44,11 @@ namespace powerbi.extensibility.utils {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
result += '?' + _.chain(parameters)
|
result += "?" + _.chain(parameters)
|
||||||
.toPairs()
|
.toPairs()
|
||||||
.map(pair => pair.join('='))
|
.map(pair => pair.join("="))
|
||||||
.value()
|
.value()
|
||||||
.join('&');
|
.join("&");
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -222,57 +64,8 @@ namespace powerbi.extensibility.utils {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ParsedUrl {
|
|
||||||
scheme: string;
|
|
||||||
host: string;
|
|
||||||
path: string;
|
|
||||||
query: string;
|
|
||||||
fragment: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parseUrl(url: string): ParsedUrl {
|
|
||||||
// see http://www.ietf.org/rfc/rfc3986.txt, Appendix B (around page 50)
|
|
||||||
// ^(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*)(?:\?([^#]*))?(?:#(.*))?
|
|
||||||
// 1 2 3 4 5
|
|
||||||
// http://www.ics.uci.edu/pub/ietf/uri/#Related
|
|
||||||
// scheme = $1 = http
|
|
||||||
// host = $2 = www.ics.uci.edu
|
|
||||||
// path = $3 = /pub/ietf/uri/
|
|
||||||
// query = $4 = <undefined>
|
|
||||||
// fragment = $5 = Related
|
|
||||||
let matches: RegExpMatchArray = url.match(/^(?:([^:\/?#]+):)?(?:\/\/([^\/?#]*))?([^?#]*)(?:\?([^#]*))?(?:#(.*))?/);
|
|
||||||
if (matches) {
|
|
||||||
return {
|
|
||||||
scheme: matches[1],
|
|
||||||
host: matches[2],
|
|
||||||
path: matches[3],
|
|
||||||
query: matches[4],
|
|
||||||
fragment: matches[5]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getHost(url: string): string {
|
|
||||||
let parsed: ParsedUrl = parseUrl(url);
|
|
||||||
return parsed && parsed.host;
|
|
||||||
}
|
|
||||||
|
|
||||||
const HostnameRegex: any = /https?:\/\/[^\/]+/i;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns everything in a URL after the hostname. Per RFC 3986, this is known as the absolute path reference.
|
|
||||||
* @example for "https://foo.bar/hello/world", return "/hello/world".
|
|
||||||
*/
|
|
||||||
export function getAbsolutePath(url: string): string {
|
|
||||||
if (!url) {
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
return url.replace(HostnameRegex, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
function getQueryString(url: string): string {
|
function getQueryString(url: string): string {
|
||||||
let elem: HTMLAnchorElement = document.createElement('a');
|
let elem: HTMLAnchorElement = document.createElement("a");
|
||||||
elem.href = url;
|
elem.href = url;
|
||||||
|
|
||||||
return elem.search;
|
return elem.search;
|
||||||
|
@ -284,7 +77,7 @@ namespace powerbi.extensibility.utils {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_.startsWith(queryString, '?')) {
|
if (_.startsWith(queryString, "?")) {
|
||||||
queryString = queryString.substring(1);
|
queryString = queryString.substring(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,44 +29,12 @@ module powerbi.extensibility.visual {
|
||||||
import DataViewValueColumns = powerbi.DataViewValueColumns;
|
import DataViewValueColumns = powerbi.DataViewValueColumns;
|
||||||
import DataViewCategoricalColumn = powerbi.DataViewCategoricalColumn;
|
import DataViewCategoricalColumn = powerbi.DataViewCategoricalColumn;
|
||||||
import DataViewValueColumn = powerbi.DataViewValueColumn;
|
import DataViewValueColumn = powerbi.DataViewValueColumn;
|
||||||
|
|
||||||
|
// powerbi.extensibility.utils.dataview
|
||||||
import converterHelper = powerbi.extensibility.utils.dataview.converterHelper;
|
import converterHelper = powerbi.extensibility.utils.dataview.converterHelper;
|
||||||
|
|
||||||
export class GlobeMapColumns<T> {
|
export class GlobeMapColumns<T> {
|
||||||
|
|
||||||
public static getColumnSources(dataView: DataView): GlobeMapColumns<DataViewMetadataColumn> {
|
|
||||||
return this.getColumnSourcesT<DataViewMetadataColumn>(dataView);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static getTableValues(dataView: DataView): GlobeMapColumns<any> | DataViewTable {
|
|
||||||
let table: DataViewTable = dataView && dataView.table;
|
|
||||||
let columns: GlobeMapColumns<any> = this.getColumnSourcesT<any[]>(dataView);
|
|
||||||
return columns && table && _.mapValues(
|
|
||||||
columns, (n: DataViewMetadataColumn, i) => n && table.rows.map(row => row[n.index]));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static getTableRows(dataView: DataView): GlobeMapColumns<any> | DataViewTable | GlobeMapColumns<any>[] {
|
|
||||||
let table: DataViewTable = dataView && dataView.table;
|
|
||||||
let columns: GlobeMapColumns<any> = this.getColumnSourcesT<any[]>(dataView);
|
|
||||||
return columns && table && table.rows.map(row =>
|
|
||||||
_.mapValues(columns, (n: DataViewMetadataColumn, i) => n && row[n.index]));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static getCategoricalValues(dataView: DataView): DataViewCategorical | GlobeMapColumns<any[]> {
|
|
||||||
let categorical: DataViewCategorical = dataView && dataView.categorical;
|
|
||||||
let categories: DataViewCategoryColumn[] = categorical && categorical.categories || [];
|
|
||||||
let values: DataViewValueColumns = categorical && categorical.values || <DataViewValueColumns>[];
|
|
||||||
let series: DataViewCategorical = categorical && values.source && this.getSeriesValues(dataView);
|
|
||||||
return categorical && _.mapValues(new this<any[]>(), (n, i) =>
|
|
||||||
(<DataViewCategoricalColumn[]>_.toArray(categories)).concat(_.toArray(values))
|
|
||||||
.filter(x => x.source.roles && x.source.roles[i]).map(x => x.values)[0]
|
|
||||||
|| values.source && values.source.roles && values.source.roles[i] && series);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static getSeriesValues(dataView: DataView) {
|
|
||||||
return dataView && dataView.categorical && dataView.categorical.values
|
|
||||||
&& dataView.categorical.values.map(x => converterHelper.getSeriesName(x.source));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static getCategoricalColumns(dataView: DataView): DataViewCategorical | any {
|
public static getCategoricalColumns(dataView: DataView): DataViewCategorical | any {
|
||||||
let categorical = dataView && dataView.categorical;
|
let categorical = dataView && dataView.categorical;
|
||||||
let categories = categorical && categorical.categories || [];
|
let categories = categorical && categorical.categories || [];
|
||||||
|
@ -87,12 +55,6 @@ module powerbi.extensibility.visual {
|
||||||
(n, i) => g.values.filter(v => v.source.roles[i])[0]));
|
(n, i) => g.values.filter(v => v.source.roles[i])[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static getColumnSourcesT<T>(dataView: DataView): any {
|
|
||||||
let columns: any = dataView && dataView.metadata && dataView.metadata.columns;
|
|
||||||
return columns && _.mapValues(
|
|
||||||
new this<T>(), (n, i) => columns.filter(x => x.roles && x.roles[i])[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Category: T = null;
|
public Category: T = null;
|
||||||
public Series: T = null;
|
public Series: T = null;
|
||||||
public X: T = null;
|
public X: T = null;
|
||||||
|
|
|
@ -25,7 +25,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
module powerbi.extensibility.visual {
|
module powerbi.extensibility.visual {
|
||||||
|
// powerbi.extensibility.utils.interactivity
|
||||||
import SelectableDataPoint = powerbi.extensibility.utils.interactivity.SelectableDataPoint;
|
import SelectableDataPoint = powerbi.extensibility.utils.interactivity.SelectableDataPoint;
|
||||||
|
|
||||||
|
// powerbi.extensibility.geocoder
|
||||||
import ILocation = powerbi.extensibility.geocoder.ILocation;
|
import ILocation = powerbi.extensibility.geocoder.ILocation;
|
||||||
|
|
||||||
export interface GlobeMapData {
|
export interface GlobeMapData {
|
||||||
|
@ -52,6 +55,32 @@ module powerbi.extensibility.visual {
|
||||||
color: string;
|
color: string;
|
||||||
category?: string;
|
category?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BingMetadata {
|
||||||
|
resourceSets: ResourceSet[];
|
||||||
|
statusCode: string;
|
||||||
|
statusDescription: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ResourceSet {
|
||||||
|
resources: BingResourceMetadata[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BingResourceMetadata {
|
||||||
|
imageHeight: number;
|
||||||
|
imageWidth: number;
|
||||||
|
imageUrl: string;
|
||||||
|
imageUrlSubdomains: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TileMap {
|
||||||
|
[quadKey: string]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CanvasCoordinate {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -700,7 +700,6 @@ module powerbi.extensibility.geocoder {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.activeEntries.push(entry);
|
this.activeEntries.push(entry);
|
||||||
|
|
||||||
entry.request = $.ajax({
|
entry.request = $.ajax({
|
||||||
url: url,
|
url: url,
|
||||||
dataType: 'jsonp',
|
dataType: 'jsonp',
|
||||||
|
|
|
@ -1,13 +1,18 @@
|
||||||
module powerbi.extensibility.geocoder {
|
module powerbi.extensibility.geocoder {
|
||||||
import IPromise = powerbi.IPromise;
|
import IPromise = powerbi.IPromise;
|
||||||
import IRect = powerbi.extensibility.utils.svg.IRect;
|
|
||||||
|
|
||||||
/** Defines geocoding services. */
|
/** Defines geocoding services. */
|
||||||
export interface GeocodeOptions {
|
export interface GeocodeOptions {
|
||||||
/** promise that should abort the request when resolved */
|
/** promise that should abort the request when resolved */
|
||||||
timeout?: IPromise<any>;
|
timeout?: IPromise<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IRect {
|
||||||
|
left: number;
|
||||||
|
top: number;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IGeocoder {
|
export interface IGeocoder {
|
||||||
geocode(query: string, category?: string, options?: GeocodeOptions): IPromise<IGeocodeCoordinate>;
|
geocode(query: string, category?: string, options?: GeocodeOptions): IPromise<IGeocodeCoordinate>;
|
||||||
geocodeBoundary(latitude: number, longitude: number, category: string, levelOfDetail?: number, maxGeoData?: number, options?: GeocodeOptions): IPromise<IGeocodeBoundaryCoordinate>;
|
geocodeBoundary(latitude: number, longitude: number, category: string, levelOfDetail?: number, maxGeoData?: number, options?: GeocodeOptions): IPromise<IGeocodeBoundaryCoordinate>;
|
||||||
|
|
|
@ -25,8 +25,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
module powerbi.extensibility.geocoder {
|
module powerbi.extensibility.geocoder {
|
||||||
|
// powerbi.extensibility.utils.formatting
|
||||||
import IStorageService = powerbi.extensibility.utils.formatting.IStorageService;
|
import IStorageService = powerbi.extensibility.utils.formatting.IStorageService;
|
||||||
|
import LocalStorageService = powerbi.extensibility.utils.formatting.LocalStorageService;
|
||||||
|
|
||||||
interface GeocodeCacheEntry {
|
interface GeocodeCacheEntry {
|
||||||
coordinate: IGeocodeCoordinate;
|
coordinate: IGeocodeCoordinate;
|
||||||
|
@ -41,7 +42,7 @@ module powerbi.extensibility.geocoder {
|
||||||
|
|
||||||
export function createGeocodingCache(maxCacheSize: number, maxCacheSizeOverflow: number, localStorageService?: IStorageService): IGeocodingCache {
|
export function createGeocodingCache(maxCacheSize: number, maxCacheSizeOverflow: number, localStorageService?: IStorageService): IGeocodingCache {
|
||||||
if (!localStorageService) {
|
if (!localStorageService) {
|
||||||
localStorageService = new powerbi.extensibility.utils.formatting.LocalStorageService();
|
localStorageService = new LocalStorageService();
|
||||||
}
|
}
|
||||||
return new GeocodingCache(maxCacheSize, maxCacheSizeOverflow, localStorageService);
|
return new GeocodingCache(maxCacheSize, maxCacheSizeOverflow, localStorageService);
|
||||||
}
|
}
|
||||||
|
|
608
src/globemap.ts
608
src/globemap.ts
|
@ -1,4 +1,4 @@
|
||||||
/*
|
/*
|
||||||
* Power BI Visualizations
|
* Power BI Visualizations
|
||||||
*
|
*
|
||||||
* Copyright (c) Microsoft Corporation
|
* Copyright (c) Microsoft Corporation
|
||||||
|
@ -24,51 +24,44 @@
|
||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let WebGLHeatmap: any = window['createWebGLHeatmap'];
|
let WebGLHeatmap: any = window["createWebGLHeatmap"];
|
||||||
let GlobeMapCanvasLayers: JQuery[];
|
|
||||||
|
|
||||||
module powerbi.extensibility.visual {
|
module powerbi.extensibility.visual {
|
||||||
|
// powerbi.extensibility.geocoder
|
||||||
import IGeocoder = powerbi.extensibility.geocoder.IGeocoder;
|
import IGeocoder = powerbi.extensibility.geocoder.IGeocoder;
|
||||||
import IGeocodeCoordinate = powerbi.extensibility.geocoder.IGeocodeCoordinate;
|
import IGeocodeCoordinate = powerbi.extensibility.geocoder.IGeocodeCoordinate;
|
||||||
import IPromise = powerbi.IPromise;
|
|
||||||
import Rectangle = powerbi.extensibility.utils.svg.touch.Rectangle;
|
|
||||||
import ILocation = powerbi.extensibility.geocoder.ILocation;
|
import ILocation = powerbi.extensibility.geocoder.ILocation;
|
||||||
|
|
||||||
|
// powerbi.extensibility.utils.dataview
|
||||||
import converterHelper = powerbi.extensibility.utils.dataview.converterHelper;
|
import converterHelper = powerbi.extensibility.utils.dataview.converterHelper;
|
||||||
|
|
||||||
|
// powerbi.extensibility.utils.color
|
||||||
import ColorHelper = powerbi.extensibility.utils.color.ColorHelper;
|
import ColorHelper = powerbi.extensibility.utils.color.ColorHelper;
|
||||||
|
|
||||||
import ClassAndSelector = powerbi.extensibility.utils.svg.CssConstants.ClassAndSelector;
|
// powerbi.visuals
|
||||||
import createClassAndSelector = powerbi.extensibility.utils.svg.CssConstants.createClassAndSelector;
|
import ISelectionId = powerbi.visuals.ISelectionId;
|
||||||
import DataViewPropertyValue = powerbi.DataViewPropertyValue;
|
|
||||||
import SelectableDataPoint = powerbi.extensibility.utils.interactivity.SelectableDataPoint;
|
// powerbi.extensibility.utils.formatting
|
||||||
import IValueFormatter = powerbi.extensibility.utils.formatting.IValueFormatter;
|
import IValueFormatter = powerbi.extensibility.utils.formatting.IValueFormatter;
|
||||||
import IInteractivityService = powerbi.extensibility.utils.interactivity.IInteractivityService;
|
|
||||||
import IMargin = powerbi.extensibility.utils.chart.axis.IMargin;
|
|
||||||
import IInteractiveBehavior = powerbi.extensibility.utils.interactivity.IInteractiveBehavior;
|
|
||||||
import ISelectionHandler = powerbi.extensibility.utils.interactivity.ISelectionHandler;
|
|
||||||
import appendClearCatcher = powerbi.extensibility.utils.interactivity.appendClearCatcher;
|
|
||||||
import createInteractivityService = powerbi.extensibility.utils.interactivity.createInteractivityService;
|
|
||||||
import valueFormatter = powerbi.extensibility.utils.formatting.valueFormatter;
|
import valueFormatter = powerbi.extensibility.utils.formatting.valueFormatter;
|
||||||
import IAxisProperties = powerbi.extensibility.utils.chart.axis.IAxisProperties;
|
import LocalStorageService = powerbi.extensibility.utils.formatting.LocalStorageService;
|
||||||
import IVisualHost = powerbi.extensibility.visual.IVisualHost;
|
import IStorageService = powerbi.extensibility.utils.formatting.IStorageService;
|
||||||
import svg = powerbi.extensibility.utils.svg;
|
|
||||||
import axis = powerbi.extensibility.utils.chart.axis;
|
// powerbi.extensibility.utils.type
|
||||||
import textMeasurementService = powerbi.extensibility.utils.formatting.textMeasurementService;
|
import ValueType = powerbi.extensibility.utils.type.ValueType;
|
||||||
import ValueType = utils.type.ValueType;
|
|
||||||
import DataViewObjectsParser = utils.dataview.DataViewObjectsParser;
|
|
||||||
import IColorPalette = powerbi.extensibility.IColorPalette;
|
|
||||||
|
|
||||||
interface ExtendedPromise<T> extends IPromise<T> {
|
interface ExtendedPromise<T> extends IPromise<T> {
|
||||||
always(value: any): void;
|
always(value: any): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class GlobeMap implements IVisual {
|
export class GlobeMap implements IVisual {
|
||||||
|
private localStorageService: IStorageService;
|
||||||
public static MercartorSphere: any;
|
public static MercartorSphere: any;
|
||||||
private static GlobeSettings = {
|
private static GlobeSettings = {
|
||||||
autoRotate: false,
|
autoRotate: false,
|
||||||
earthRadius: 30,
|
earthRadius: 30,
|
||||||
cameraRadius: 100,
|
cameraRadius: 100,
|
||||||
earthSegments: 100,
|
earthSegments: 100,
|
||||||
heatmapSize: 1000,
|
heatmapSize: 1024,
|
||||||
heatPointSize: 7,
|
heatPointSize: 7,
|
||||||
heatIntensity: 10,
|
heatIntensity: 10,
|
||||||
heatmapScaleOnZoom: 0.95,
|
heatmapScaleOnZoom: 0.95,
|
||||||
|
@ -79,7 +72,13 @@ module powerbi.extensibility.visual {
|
||||||
cameraAnimDuration: 1000, // ms
|
cameraAnimDuration: 1000, // ms
|
||||||
clickInterval: 200 // ms
|
clickInterval: 200 // ms
|
||||||
};
|
};
|
||||||
|
private static ChangeDataType: number = 2;
|
||||||
|
private static ChangeAllType: number = 62;
|
||||||
|
private static DataPointFillProperty: DataViewObjectPropertyIdentifier = {
|
||||||
|
objectName: "dataPoint",
|
||||||
|
propertyName: "fill"
|
||||||
|
};
|
||||||
|
private static CountTilesPerSegment: number = 4;
|
||||||
private layout: VisualLayout;
|
private layout: VisualLayout;
|
||||||
private root: JQuery;
|
private root: JQuery;
|
||||||
private rendererContainer: JQuery;
|
private rendererContainer: JQuery;
|
||||||
|
@ -111,7 +110,6 @@ module powerbi.extensibility.visual {
|
||||||
private hoveredBar: any;
|
private hoveredBar: any;
|
||||||
private averageBarVector: THREE.Vector3;
|
private averageBarVector: THREE.Vector3;
|
||||||
private zoomContainer: d3.Selection<any>;
|
private zoomContainer: d3.Selection<any>;
|
||||||
private zoomControl: d3.Selection<any>;
|
|
||||||
public colors: IColorPalette;
|
public colors: IColorPalette;
|
||||||
private animationFrameId: number;
|
private animationFrameId: number;
|
||||||
private cameraAnimationFrameId: number;
|
private cameraAnimationFrameId: number;
|
||||||
|
@ -126,15 +124,13 @@ module powerbi.extensibility.visual {
|
||||||
|| (_.isEmpty(categorical.Height) && _.isEmpty(categorical.Heat))) {
|
|| (_.isEmpty(categorical.Height) && _.isEmpty(categorical.Heat))) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const properties: GlobeMapSettings = GlobeMapSettings.getDefault() as GlobeMapSettings;
|
const properties: GlobeMapSettings = GlobeMapSettings.getDefault() as GlobeMapSettings;
|
||||||
const settings: GlobeMapSettings = GlobeMap.parseSettings(dataView);
|
const settings: GlobeMapSettings = GlobeMap.parseSettings(dataView);
|
||||||
const groupedColumns: GlobeMapColumns<DataViewValueColumn>[] | any = GlobeMapColumns.getGroupedValueColumns(dataView);
|
const groupedColumns: GlobeMapColumns<DataViewValueColumn>[] | any = GlobeMapColumns.getGroupedValueColumns(dataView);
|
||||||
const dataPoints: any = [];
|
const dataPoints: any = [];
|
||||||
let seriesDataPoints: any = [];
|
let seriesDataPoints: any = [];
|
||||||
let locations: any = [];
|
let locations: any = [];
|
||||||
const colorHelper: ColorHelper = new ColorHelper(colors, null, properties.dataPoint.fill);
|
const colorHelper: ColorHelper = new ColorHelper(colors, GlobeMap.DataPointFillProperty);
|
||||||
|
|
||||||
let locationType: any;
|
let locationType: any;
|
||||||
let heights: any;
|
let heights: any;
|
||||||
let heightsBySeries: any;
|
let heightsBySeries: any;
|
||||||
|
@ -158,9 +154,9 @@ module powerbi.extensibility.visual {
|
||||||
// creating a matrix for drawing values by series later.
|
// creating a matrix for drawing values by series later.
|
||||||
for (let i: number = 0; i < groupedColumns.length; i++) {
|
for (let i: number = 0; i < groupedColumns.length; i++) {
|
||||||
const values: any = groupedColumns[i].Height.values;
|
const values: any = groupedColumns[i].Height.values;
|
||||||
|
|
||||||
seriesDataPoints[i] = GlobeMap.createDataPointForEnumeration(
|
seriesDataPoints[i] = GlobeMap.createDataPointForEnumeration(
|
||||||
dataView, groupedColumns[i].Height.source, i, null, colorHelper, colors, visualHost);
|
dataView, groupedColumns[i].Height.source, i, null, colorHelper, colors, visualHost);
|
||||||
seriesDataPoints[i].color = settings.dataPoint.fill;
|
|
||||||
for (let j: number = 0; j < values.length; j++) {
|
for (let j: number = 0; j < values.length; j++) {
|
||||||
if (!heights[j]) {
|
if (!heights[j]) {
|
||||||
heights[j] = 0;
|
heights[j] = 0;
|
||||||
|
@ -192,13 +188,11 @@ module powerbi.extensibility.visual {
|
||||||
heightsBySeries = [];
|
heightsBySeries = [];
|
||||||
seriesDataPoints[0] = GlobeMap.createDataPointForEnumeration(
|
seriesDataPoints[0] = GlobeMap.createDataPointForEnumeration(
|
||||||
dataView, groupedColumns[0].Height.source, 0, dataView.metadata, colorHelper, colors, visualHost);
|
dataView, groupedColumns[0].Height.source, 0, dataView.metadata, colorHelper, colors, visualHost);
|
||||||
seriesDataPoints[0].color = settings.dataPoint.fill;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
heightsBySeries = [];
|
heightsBySeries = [];
|
||||||
heights = [];
|
heights = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_.isEmpty(categorical.Heat)) {
|
if (!_.isEmpty(categorical.Heat)) {
|
||||||
if (groupedColumns.length > 1) {
|
if (groupedColumns.length > 1) {
|
||||||
heats = [];
|
heats = [];
|
||||||
|
@ -262,7 +256,6 @@ module powerbi.extensibility.visual {
|
||||||
dataPoints.push(renderDatum);
|
dataPoints.push(renderDatum);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
dataView: dataView,
|
dataView: dataView,
|
||||||
dataPoints: dataPoints,
|
dataPoints: dataPoints,
|
||||||
|
@ -296,19 +289,20 @@ module powerbi.extensibility.visual {
|
||||||
|
|
||||||
const label: string = valueFormatter.format(nameForFormat, valueFormatter.getFormatString(sourceForFormat, null));
|
const label: string = valueFormatter.format(nameForFormat, valueFormatter.getFormatString(sourceForFormat, null));
|
||||||
|
|
||||||
|
let measureValues = values[0];
|
||||||
const categoryColumn: DataViewCategoryColumn = {
|
const categoryColumn: DataViewCategoryColumn = {
|
||||||
source: values[seriesIndex].source,
|
source: measureValues.source,
|
||||||
values: null,
|
values: null,
|
||||||
identity: [values[seriesIndex].identity]
|
identity: [measureValues.identity]
|
||||||
};
|
};
|
||||||
|
|
||||||
const identity: ISelectionId = visualHost.createSelectionIdBuilder()
|
const identity: ISelectionId = visualHost.createSelectionIdBuilder()
|
||||||
.withCategory(categoryColumn, 0)
|
.withCategory(categoryColumn, 0)
|
||||||
.withMeasure(values[seriesIndex].source.queryName)
|
.withMeasure(measureValues.source.queryName)
|
||||||
.createSelectionId();
|
.createSelectionId();
|
||||||
|
|
||||||
const category: any = <string>converterHelper.getSeriesName(source);
|
const category: any = <string>converterHelper.getSeriesName(source);
|
||||||
const objects: any = <any>columns.objects;
|
const objects: any = <any>columns.objects || <any>source.objects;
|
||||||
const color: string = objects && objects.dataPoint ? objects.dataPoint.fill.solid.color : metaData && metaData.objects
|
const color: string = objects && objects.dataPoint ? objects.dataPoint.fill.solid.color : metaData && metaData.objects
|
||||||
? colorHelper.getColorForMeasure(metaData.objects, "")
|
? colorHelper.getColorForMeasure(metaData.objects, "")
|
||||||
: colors.getColor(seriesIndex).value;
|
: colors.getColor(seriesIndex).value;
|
||||||
|
@ -321,16 +315,47 @@ module powerbi.extensibility.visual {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private addAnInstanceToEnumeration(
|
||||||
|
instanceEnumeration: VisualObjectInstanceEnumeration,
|
||||||
|
instance: VisualObjectInstance): void {
|
||||||
|
|
||||||
|
if ((instanceEnumeration as VisualObjectInstanceEnumerationObject).instances) {
|
||||||
|
(instanceEnumeration as VisualObjectInstanceEnumerationObject)
|
||||||
|
.instances
|
||||||
|
.push(instance);
|
||||||
|
} else {
|
||||||
|
(instanceEnumeration as VisualObjectInstance[]).push(instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstance[] | VisualObjectInstanceEnumerationObject {
|
public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstance[] | VisualObjectInstanceEnumerationObject {
|
||||||
return GlobeMapSettings.enumerateObjectInstances(this.settings || GlobeMapSettings.getDefault(), options);
|
let instances: VisualObjectInstanceEnumeration = GlobeMapSettings.enumerateObjectInstances(this.settings || GlobeMapSettings.getDefault(), options);
|
||||||
|
switch (options.objectName) {
|
||||||
|
case "dataPoint": if (this.data && this.data.seriesDataPoints) {
|
||||||
|
for (let i: number = 0; i < this.data.seriesDataPoints.length; i++) {
|
||||||
|
let dataPoint: GlobeMapSeriesDataPoint = this.data.seriesDataPoints[i];
|
||||||
|
this.addAnInstanceToEnumeration(instances, {
|
||||||
|
objectName: "dataPoint",
|
||||||
|
displayName: dataPoint.label,
|
||||||
|
selector: ColorHelper.normalizeSelector((dataPoint.identity as ISelectionId).getSelector()),
|
||||||
|
properties: {
|
||||||
|
fill: { solid: { color: dataPoint.color } }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return instances;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(options: VisualConstructorOptions) {
|
constructor(options: VisualConstructorOptions) {
|
||||||
|
this.currentLanguage = options.host.locale;
|
||||||
|
this.localStorageService = new LocalStorageService();
|
||||||
this.root = $("<div>").appendTo(options.element)
|
this.root = $("<div>").appendTo(options.element)
|
||||||
.attr('drag-resize-disabled', "true")
|
.attr("drag-resize-disabled", "true")
|
||||||
.css({
|
.css({
|
||||||
'position': "absolute"
|
"position": "absolute"
|
||||||
});
|
});
|
||||||
|
|
||||||
this.visualHost = options.host;
|
this.visualHost = options.host;
|
||||||
|
@ -351,12 +376,16 @@ module powerbi.extensibility.visual {
|
||||||
}
|
}
|
||||||
|
|
||||||
private setup(): void {
|
private setup(): void {
|
||||||
this.initTextures();
|
|
||||||
this.initMercartorSphere();
|
|
||||||
this.initZoomControl();
|
|
||||||
this.initScene();
|
this.initScene();
|
||||||
|
this.initMercartorSphere();
|
||||||
|
this.initTextures().then(
|
||||||
|
() => {
|
||||||
|
this.earth = this.createEarth();
|
||||||
|
this.scene.add(this.earth);
|
||||||
|
this.readyToRender = true;
|
||||||
|
});
|
||||||
|
this.initZoomControl();
|
||||||
this.initHeatmap();
|
this.initHeatmap();
|
||||||
this.readyToRender = true;
|
|
||||||
this.initRayCaster();
|
this.initRayCaster();
|
||||||
}
|
}
|
||||||
private static cameraFov: number = 35;
|
private static cameraFov: number = 35;
|
||||||
|
@ -366,6 +395,26 @@ module powerbi.extensibility.visual {
|
||||||
private static ambientLight: number = 0x000000;
|
private static ambientLight: number = 0x000000;
|
||||||
private static directionalLight: number = 0xffffff;
|
private static directionalLight: number = 0xffffff;
|
||||||
private static directionalLightIntensity: number = 0.4;
|
private static directionalLightIntensity: number = 0.4;
|
||||||
|
private static tileSize: number = 256;
|
||||||
|
private static maxResolutionLevel: number = 4;
|
||||||
|
private static metadataUrl: string = `https://dev.virtualearth.net/REST/V1/Imagery/Metadata/Road?output=json&uriScheme=https&key=${powerbi.extensibility.geocoder.Settings.BingKey}`;
|
||||||
|
private static reserveBindMapsMetadata: BingResourceMetadata = {
|
||||||
|
imageUrl: "https://{subdomain}.tiles.virtualearth.net/tiles/r{quadkey}.jpeg?g=0&mkt={culture}",
|
||||||
|
imageUrlSubdomains: [
|
||||||
|
"t1",
|
||||||
|
"t2",
|
||||||
|
"t3",
|
||||||
|
"t4",
|
||||||
|
"t5",
|
||||||
|
"t6",
|
||||||
|
"t7"
|
||||||
|
],
|
||||||
|
imageHeight: 256,
|
||||||
|
imageWidth: 256
|
||||||
|
};
|
||||||
|
private currentLanguage: string = "en-GB";
|
||||||
|
private static TILE_STORAGE_KEY = "GLOBEMAP_TILES_STORAGE";
|
||||||
|
private static TILE_LANGUAGE_CULTURE = "GLOBEMAP_TILE_LANGUAGE_CULTURE";
|
||||||
private initScene(): void {
|
private initScene(): void {
|
||||||
this.renderer = new THREE.WebGLRenderer({ antialias: true, preserveDrawingBuffer: true });
|
this.renderer = new THREE.WebGLRenderer({ antialias: true, preserveDrawingBuffer: true });
|
||||||
this.rendererContainer = $("<div>").appendTo(this.root).addClass("globeMapView");
|
this.rendererContainer = $("<div>").appendTo(this.root).addClass("globeMapView");
|
||||||
|
@ -390,12 +439,10 @@ module powerbi.extensibility.visual {
|
||||||
const ambientLight: THREE.AmbientLight = new THREE.AmbientLight(GlobeMap.ambientLight);
|
const ambientLight: THREE.AmbientLight = new THREE.AmbientLight(GlobeMap.ambientLight);
|
||||||
const light1: THREE.DirectionalLight = new THREE.DirectionalLight(GlobeMap.directionalLight, GlobeMap.directionalLightIntensity);
|
const light1: THREE.DirectionalLight = new THREE.DirectionalLight(GlobeMap.directionalLight, GlobeMap.directionalLightIntensity);
|
||||||
const light2: THREE.DirectionalLight = new THREE.DirectionalLight(GlobeMap.directionalLight, GlobeMap.directionalLightIntensity);
|
const light2: THREE.DirectionalLight = new THREE.DirectionalLight(GlobeMap.directionalLight, GlobeMap.directionalLightIntensity);
|
||||||
const earth: THREE.Mesh = this.earth = this.createEarth();
|
|
||||||
|
|
||||||
this.scene.add(ambientLight);
|
this.scene.add(ambientLight);
|
||||||
this.scene.add(light1);
|
this.scene.add(light1);
|
||||||
this.scene.add(light2);
|
this.scene.add(light2);
|
||||||
this.scene.add(earth);
|
|
||||||
|
|
||||||
light1.position.set(20, 20, 20);
|
light1.position.set(20, 20, 20);
|
||||||
light2.position.set(0, 0, -20);
|
light2.position.set(0, 0, -20);
|
||||||
|
@ -447,7 +494,7 @@ module powerbi.extensibility.visual {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static dollyX: number = 0.95;
|
private static dollyX: number = 0.95;
|
||||||
public zoomClicked(zoomDirection: any): void {
|
public zoomClicked(zoomDirection: number): void {
|
||||||
if (this.orbitControls.enabled === false) {
|
if (this.orbitControls.enabled === false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -462,7 +509,7 @@ module powerbi.extensibility.visual {
|
||||||
this.animateCamera(this.camera.position);
|
this.animateCamera(this.camera.position);
|
||||||
}
|
}
|
||||||
|
|
||||||
public rotateCam(deltaX: number, deltaY: number) {
|
public rotateCam(deltaX: number, deltaY: number): void {
|
||||||
if (!this.orbitControls.enabled) {
|
if (!this.orbitControls.enabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -472,33 +519,145 @@ module powerbi.extensibility.visual {
|
||||||
this.animateCamera(this.camera.position);
|
this.animateCamera(this.camera.position);
|
||||||
}
|
}
|
||||||
|
|
||||||
private initTextures() {
|
private initTextures(): JQueryPromise<{}> {
|
||||||
if (!GlobeMapCanvasLayers) {
|
this.mapTextures = [];
|
||||||
|
const tileCulture: string = this.localStorageService.getData(GlobeMap.TILE_LANGUAGE_CULTURE);
|
||||||
|
let tileCache: TileMap[] = this.localStorageService.getData(GlobeMap.TILE_STORAGE_KEY);
|
||||||
|
if (!tileCache || tileCulture !== this.currentLanguage) {
|
||||||
// Initialize once, since this is a CPU + Network heavy operation.
|
// Initialize once, since this is a CPU + Network heavy operation.
|
||||||
GlobeMapCanvasLayers = [];
|
return this.getBingMapsServerMetadata()
|
||||||
|
.then((metadata: BingResourceMetadata) => {
|
||||||
|
tileCache = [];
|
||||||
|
let urlTemplate = metadata.imageUrl.replace("{culture}", this.currentLanguage);
|
||||||
|
for (let level: number = 1; level <= GlobeMap.maxResolutionLevel; ++level) {
|
||||||
|
let levelTiles = this.generateQuadsByLevel(level, urlTemplate, metadata.imageUrlSubdomains);
|
||||||
|
this.mapTextures.push(this.createTexture(level, levelTiles));
|
||||||
|
tileCache.push(levelTiles);
|
||||||
|
}
|
||||||
|
this.localStorageService.setData(GlobeMap.TILE_STORAGE_KEY, tileCache);
|
||||||
|
this.localStorageService.setData(GlobeMap.TILE_LANGUAGE_CULTURE, this.currentLanguage);
|
||||||
|
return tileCache;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
for (let level: number = 1; level <= GlobeMap.maxResolutionLevel; ++level) {
|
||||||
|
this.mapTextures.push(this.createTexture(level, tileCache[level - 1]));
|
||||||
|
}
|
||||||
|
return jQuery.when(tileCache);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (let level: number = 2; level <= 5; ++level) {
|
private getBingMapsServerMetadata(): JQueryPromise<BingResourceMetadata> {
|
||||||
const canvas: JQuery = this.getBingMapCanvas(level);
|
return $.ajax(GlobeMap.metadataUrl)
|
||||||
GlobeMapCanvasLayers.push(canvas);
|
.then((data: BingMetadata) => {
|
||||||
|
if (data.resourceSets.length) {
|
||||||
|
let resourceSet = data.resourceSets[0];
|
||||||
|
if (resourceSet && resourceSet.resources.length) {
|
||||||
|
return resourceSet.resources[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw "Bing Maps API response was changed. Please update code for new version";
|
||||||
|
})
|
||||||
|
.fail((error: any) => {
|
||||||
|
console.error(JSON.stringify(error));
|
||||||
|
return GlobeMap.reserveBindMapsMetadata;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate Bing tile object by map level
|
||||||
|
* @see https://msdn.microsoft.com/en-us/library/bb259689.aspx
|
||||||
|
* @private
|
||||||
|
* @param {number} level map lavel
|
||||||
|
* @param {string} urlTemplate url template
|
||||||
|
* @example https://ecn.{subdomain}.tiles.virtualearth.net/tiles/r{quadkey}.jpeg?g=5691&mkt={culture}&shading=hill
|
||||||
|
* @param {string[]} subdomains list of subdomauns
|
||||||
|
* @returns {{ [quadKey: string]: string }} Object <quadKey> : <image url>
|
||||||
|
* @memberOf GlobeMap
|
||||||
|
*/
|
||||||
|
private generateQuadsByLevel(level: number, urlTemplate: string, subdomains: string[]): TileMap {
|
||||||
|
const result: TileMap = {};
|
||||||
|
let currentSubDomainNumber: number = 0;
|
||||||
|
const generateQuard = (currentLevel: number = 0, quadKey: string = ""): void => {
|
||||||
|
if (currentLevel < level) {
|
||||||
|
for (let i = 0; i < GlobeMap.CountTilesPerSegment; i++) {
|
||||||
|
generateQuard(currentLevel + 1, `${quadKey}${i}`);
|
||||||
|
}
|
||||||
|
} else if (currentLevel === level) {
|
||||||
|
result[quadKey] = urlTemplate.replace("{subdomain}", subdomains[currentSubDomainNumber]).replace("{quadkey}", quadKey);
|
||||||
|
currentSubDomainNumber++;
|
||||||
|
currentSubDomainNumber = currentSubDomainNumber < subdomains.length ? currentSubDomainNumber : 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
generateQuard();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private createTexture(level: number, tiles: TileMap): THREE.Texture {
|
||||||
|
const numSegments: number = Math.pow(2, level);
|
||||||
|
const canvasSize: number = GlobeMap.tileSize * numSegments;
|
||||||
|
const canvas: HTMLCanvasElement = document.createElement("canvas");
|
||||||
|
canvas.width = canvasSize;
|
||||||
|
canvas.height = canvasSize;
|
||||||
|
const texture: THREE.Texture = new THREE.Texture(canvas);
|
||||||
|
texture.needsUpdate = true;
|
||||||
|
this.loadTiles(canvas, tiles, () => {
|
||||||
|
texture.needsUpdate = true;
|
||||||
|
this.needsRender = true;
|
||||||
|
});
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load tiles of Bing Maps
|
||||||
|
* @param jCanvas jQuery convas object
|
||||||
|
* @param tiles map of tiles
|
||||||
|
* @param successCallback call this function when all tiles of the map are successfully loaded
|
||||||
|
*/
|
||||||
|
private loadTiles(canvasEl: HTMLCanvasElement, tiles: TileMap, successCallback: Function): void {
|
||||||
|
let tilesLoaded: number = 0;
|
||||||
|
const countTiles: number = Object.keys(tiles).length;
|
||||||
|
const canvasContext: CanvasRenderingContext2D = canvasEl.getContext("2d");
|
||||||
|
for (let quadKey in tiles) {
|
||||||
|
if (tiles.hasOwnProperty(quadKey)) {
|
||||||
|
const coords: any = this.getCoordByQuadKey(quadKey);
|
||||||
|
const tile: HTMLImageElement = new Image();
|
||||||
|
tile.onload = (event: Event) => {
|
||||||
|
tilesLoaded++;
|
||||||
|
canvasContext.drawImage(tile, coords.x * GlobeMap.tileSize, coords.y * GlobeMap.tileSize, GlobeMap.tileSize, GlobeMap.tileSize);
|
||||||
|
if (tilesLoaded === countTiles) {
|
||||||
|
successCallback();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// So the canvas doesn't get tainted
|
||||||
|
tile.crossOrigin = "";
|
||||||
|
tile.src = tiles[quadKey];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get coordinates by Bing Maps quard name
|
||||||
|
* @private
|
||||||
|
* @param {string} quard Bing Maps quard name
|
||||||
|
* @returns {CanvasCoordinate} image coordinate
|
||||||
|
* @memberOf GlobeMap
|
||||||
|
*/
|
||||||
|
private getCoordByQuadKey(quard: string): CanvasCoordinate {
|
||||||
|
const last: number = quard.length - 1;
|
||||||
|
let x: number = 0;
|
||||||
|
let y: number = 0;
|
||||||
|
|
||||||
|
for (let i: number = last; i >= 0; i--) {
|
||||||
|
const chr: string = quard.charAt(i);
|
||||||
|
const pow: number = Math.pow(2, last - i);
|
||||||
|
switch (chr) {
|
||||||
|
case "1": x += pow; break;
|
||||||
|
case "2": y += pow; break;
|
||||||
|
case "3": x += pow; y += pow; break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Can't execute in for loop because variable assignement gets overwritten
|
return { x: x, y: y };
|
||||||
const createTexture: (canvas: JQuery) => THREE.Texture = (canvas: JQuery) => {
|
|
||||||
const texture: THREE.Texture = new THREE.Texture(<HTMLCanvasElement>canvas.get(0));
|
|
||||||
texture.needsUpdate = true;
|
|
||||||
canvas.on("ready", () => {
|
|
||||||
texture.needsUpdate = true;
|
|
||||||
this.needsRender = true;
|
|
||||||
});
|
|
||||||
return texture;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
this.mapTextures = [];
|
|
||||||
for (let i: number = 0; i < GlobeMapCanvasLayers.length; ++i) {
|
|
||||||
this.mapTextures.push(createTexture(GlobeMapCanvasLayers[i]));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private initHeatmap() {
|
private initHeatmap() {
|
||||||
|
@ -531,19 +690,12 @@ module powerbi.extensibility.visual {
|
||||||
}
|
}
|
||||||
const maxDistance: number = GlobeMap.GlobeSettings.cameraRadius - GlobeMap.GlobeSettings.earthRadius;
|
const maxDistance: number = GlobeMap.GlobeSettings.cameraRadius - GlobeMap.GlobeSettings.earthRadius;
|
||||||
const distance: number = (this.camera.position.length() - GlobeMap.GlobeSettings.earthRadius) / maxDistance;
|
const distance: number = (this.camera.position.length() - GlobeMap.GlobeSettings.earthRadius) / maxDistance;
|
||||||
let texture: THREE.Texture;
|
let texture: THREE.Texture = this.mapTextures[0];
|
||||||
const oneOfFive: number = 1 / 5;
|
for (let divider = 1; divider <= GlobeMap.maxResolutionLevel; divider++) {
|
||||||
const twoOfFive: number = 2 / 5;
|
if (distance <= divider / GlobeMap.maxResolutionLevel) {
|
||||||
const threeOfFive: number = 3 / 5;
|
texture = this.mapTextures[GlobeMap.maxResolutionLevel - divider];
|
||||||
|
break;
|
||||||
if (distance <= oneOfFive) {
|
}
|
||||||
texture = this.mapTextures[3];
|
|
||||||
} else if (distance <= twoOfFive) {
|
|
||||||
texture = this.mapTextures[2];
|
|
||||||
} else if (distance <= threeOfFive) {
|
|
||||||
texture = this.mapTextures[1];
|
|
||||||
} else {
|
|
||||||
texture = this.mapTextures[0];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((<any>this.earth.material).map !== texture) {
|
if ((<any>this.earth.material).map !== texture) {
|
||||||
|
@ -558,18 +710,15 @@ module powerbi.extensibility.visual {
|
||||||
}
|
}
|
||||||
|
|
||||||
public update(options: VisualUpdateOptions): void {
|
public update(options: VisualUpdateOptions): void {
|
||||||
|
|
||||||
if (options.dataViews === undefined || options.dataViews === null) {
|
if (options.dataViews === undefined || options.dataViews === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.layout.viewport = options.viewport;
|
this.layout.viewport = options.viewport;
|
||||||
this.root.css(this.layout.viewportIn);
|
this.root.css(this.layout.viewportIn);
|
||||||
const sixPointsToAdd: number = 6;
|
|
||||||
this.zoomContainer.style({
|
this.zoomContainer.style({
|
||||||
'padding-left': (this.layout.viewportIn.width - parseFloat(this.zoomControl.attr("width")) + sixPointsToAdd) + "px", // Fix for chrome
|
"display": this.layout.viewportIn.height > GlobeMap.ZoomControlSettings.height
|
||||||
'display': this.layout.viewportIn.height > $(this.zoomContainer.node()).height()
|
&& this.layout.viewportIn.width > GlobeMap.ZoomControlSettings.width
|
||||||
&& this.layout.viewportIn.width > $(this.zoomContainer.node()).width()
|
? null : "none"
|
||||||
? null : 'none'
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.layout.viewportChanged) {
|
if (this.layout.viewportChanged) {
|
||||||
|
@ -581,7 +730,7 @@ module powerbi.extensibility.visual {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.type === VisualUpdateType.Data || options.type === VisualUpdateType.All) {
|
if (options.type === GlobeMap.ChangeDataType || options.type === GlobeMap.ChangeAllType) {
|
||||||
this.cleanHeatAndBar();
|
this.cleanHeatAndBar();
|
||||||
const data: GlobeMapData = GlobeMap.converter(options.dataViews[0], this.colors, this.visualHost);
|
const data: GlobeMapData = GlobeMap.converter(options.dataViews[0], this.colors, this.visualHost);
|
||||||
if (data) {
|
if (data) {
|
||||||
|
@ -603,9 +752,9 @@ module powerbi.extensibility.visual {
|
||||||
if (!this.data) {
|
if (!this.data) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.data.dataPoints.forEach(d => this.geocodeRenderDatum(d));
|
this.data.dataPoints.forEach(d => this.geocodeRenderDatum(d)); // all coordinates (latitude/longitude) will be gained here
|
||||||
this.data.dataPoints.forEach((d) => {
|
this.data.dataPoints.forEach((d) => {
|
||||||
return d.location = d.location || this.globeMapLocationCache[d.placeKey];
|
return d.location = this.globeMapLocationCache[d.placeKey] || d.location;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!this.readyToRender) {
|
if (!this.readyToRender) {
|
||||||
|
@ -627,7 +776,9 @@ module powerbi.extensibility.visual {
|
||||||
for (let i: number = 0; i < len; ++i) {
|
for (let i: number = 0; i < len; ++i) {
|
||||||
const renderDatum: GlobeMapDataPoint = this.data.dataPoints[i];
|
const renderDatum: GlobeMapDataPoint = this.data.dataPoints[i];
|
||||||
|
|
||||||
if (!renderDatum.location || renderDatum.location.longitude === undefined || renderDatum.location.latitude === undefined) {
|
if (!renderDatum.location || renderDatum.location.longitude === undefined || renderDatum.location.latitude === undefined
|
||||||
|
|| (renderDatum.location.longitude === 0 && renderDatum.location.latitude === 0)
|
||||||
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -658,7 +809,7 @@ module powerbi.extensibility.visual {
|
||||||
const dataPointToolTip: any = [];
|
const dataPointToolTip: any = [];
|
||||||
if (renderDatum.heightBySeries) {
|
if (renderDatum.heightBySeries) {
|
||||||
for (let c: number = 0; c < renderDatum.heightBySeries.length; c++) {
|
for (let c: number = 0; c < renderDatum.heightBySeries.length; c++) {
|
||||||
if (renderDatum.heightBySeries[c]) {
|
if (renderDatum.heightBySeries[c] || renderDatum.heightBySeries[c] === 0) {
|
||||||
measuresBySeries.push(renderDatum.heightBySeries[c]);
|
measuresBySeries.push(renderDatum.heightBySeries[c]);
|
||||||
}
|
}
|
||||||
dataPointToolTip.push(renderDatum.seriesToolTipData[c]);
|
dataPointToolTip.push(renderDatum.seriesToolTipData[c]);
|
||||||
|
@ -713,7 +864,8 @@ module powerbi.extensibility.visual {
|
||||||
}
|
}
|
||||||
|
|
||||||
private geocodeRenderDatum(renderDatum: GlobeMapDataPoint) {
|
private geocodeRenderDatum(renderDatum: GlobeMapDataPoint) {
|
||||||
if (renderDatum.location || this.globeMapLocationCache[renderDatum.placeKey]) {
|
// zero valued locations should be updated
|
||||||
|
if ((renderDatum.location && renderDatum.location.longitude !== 0 && renderDatum.location.latitude !== 0) || this.globeMapLocationCache[renderDatum.placeKey]) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -947,7 +1099,7 @@ module powerbi.extensibility.visual {
|
||||||
this.camera = null;
|
this.camera = null;
|
||||||
if (this.renderer) {
|
if (this.renderer) {
|
||||||
if (this.renderer.context) {
|
if (this.renderer.context) {
|
||||||
const extension: any = this.renderer.context.getExtension('WEBGL_lose_context');
|
const extension: any = this.renderer.context.getExtension("WEBGL_lose_context");
|
||||||
if (extension) {
|
if (extension) {
|
||||||
extension.loseContext();
|
extension.loseContext();
|
||||||
}
|
}
|
||||||
|
@ -976,143 +1128,69 @@ module powerbi.extensibility.visual {
|
||||||
|
|
||||||
this.hideTooltip();
|
this.hideTooltip();
|
||||||
}
|
}
|
||||||
|
private static ZoomControlSettings = {
|
||||||
|
height: 145,
|
||||||
|
width: 145,
|
||||||
|
markup: `
|
||||||
|
<svg width="145" height="145" class="controls">
|
||||||
|
<g class="control js-control--move-up">
|
||||||
|
<circle cx="85" cy="20" r="17" />
|
||||||
|
<path d="M85 8 l12 20 a40,70 0 0,0 -24,0z" />
|
||||||
|
</g>
|
||||||
|
<g class="control js-control--move-right">
|
||||||
|
<circle cx="119" cy="54" r="17" class="zoomControlCircle" />
|
||||||
|
<path d="M130.9 54 l-20 -12 a70,40 0 0,1 0,24z" class="zoomControlPath" />
|
||||||
|
</g>
|
||||||
|
<g class="control js-control--move-down">
|
||||||
|
<circle cx="85" cy="88" r="17" />
|
||||||
|
<path d="M 85 100 l12 -20 a40,70 0 0,1 -24,0z" />
|
||||||
|
</g>
|
||||||
|
<g class="control js-control--move-left">
|
||||||
|
<circle cx="51" cy="54" r="17" />
|
||||||
|
<path d="M39 54 l20 -12 a70,40 0 0,0 0,24z" />
|
||||||
|
</g>
|
||||||
|
<g class="control js-control--zoom-down">
|
||||||
|
<circle cx="51" cy="122" r="17" />
|
||||||
|
<rect x="42" y="120" width="17" height="6" class="zoomControlPath" />
|
||||||
|
</g>
|
||||||
|
<g class="control js-control--zoom-up">
|
||||||
|
<circle cx="119" cy="122" r="17" />
|
||||||
|
<rect x="110.5" y="120" width="17" height="6" />
|
||||||
|
<rect x="116" y="114" width="6" height="17" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
`,
|
||||||
|
zoomStep: 1,
|
||||||
|
angleOfRotation: 5
|
||||||
|
|
||||||
private static zoomControlRatio: number = 8.5;
|
};
|
||||||
private static radiusRatio: number = 3;
|
|
||||||
private static gapRadiusRatio: number = 2;
|
|
||||||
private initZoomControl() {
|
private initZoomControl() {
|
||||||
const radius: number = 17;
|
const controlContainer: HTMLElement = document.createElement("div");
|
||||||
const zoomControlWidth: number = radius * GlobeMap.zoomControlRatio;
|
controlContainer.classList.add("controls-container");
|
||||||
const zoomControlHeight: number = radius * GlobeMap.zoomControlRatio;
|
controlContainer.innerHTML = GlobeMap.ZoomControlSettings.markup;
|
||||||
const startX: number = radius * GlobeMap.radiusRatio;
|
this.root.append(controlContainer);
|
||||||
const startY: number = radius + GlobeMap.radiusRatio;
|
function onMouseDown(callback: (element: SVGElement) => void) {
|
||||||
const gap: number = radius * GlobeMap.gapRadiusRatio;
|
|
||||||
|
|
||||||
this.zoomContainer = d3.select(this.root[0])
|
|
||||||
.append('div')
|
|
||||||
.classed('zoomContainer', true);
|
|
||||||
|
|
||||||
this.zoomControl = this.zoomContainer.append("svg").attr({
|
|
||||||
'width': zoomControlWidth,
|
|
||||||
'height': zoomControlHeight
|
|
||||||
}).classed('zoomContainerSvg', true);
|
|
||||||
|
|
||||||
const bottom: d3.Selection<any> = this.zoomControl.append("g")
|
|
||||||
.on("mousedown", () => onMouseDown(() => this.rotateCam(0, -5)));
|
|
||||||
|
|
||||||
bottom.append("circle")
|
|
||||||
.attr({
|
|
||||||
cx: startX + gap,
|
|
||||||
cy: startY + (2 * gap),
|
|
||||||
r: radius
|
|
||||||
})
|
|
||||||
.classed('zoomControlCircle', true);
|
|
||||||
bottom.append("path")
|
|
||||||
.attr({
|
|
||||||
d: "M" + (startX + (2 * radius)) + " " + (startY + (radius * 4.7)) + " l12 -20 a40,70 0 0,1 -24,0z",
|
|
||||||
fill: "gray"
|
|
||||||
});
|
|
||||||
|
|
||||||
const left: d3.Selection<any> = this.zoomControl
|
|
||||||
.append("g")
|
|
||||||
.on("mousedown", () => onMouseDown(() => this.rotateCam(5, 0)));
|
|
||||||
left.append("circle")
|
|
||||||
.attr({
|
|
||||||
cx: startX,
|
|
||||||
cy: startY + gap,
|
|
||||||
r: radius
|
|
||||||
})
|
|
||||||
.classed('zoomControlCircle', true);
|
|
||||||
left.append("path")
|
|
||||||
.attr({
|
|
||||||
d: "M" + (startX - radius / 1.5) + " " + (startY + (radius * 2)) + " l20 -12 a70,40 0 0,0 0,24z"
|
|
||||||
})
|
|
||||||
.classed('zoomControlPath', true);
|
|
||||||
|
|
||||||
const top: d3.Selection<any> = this.zoomControl
|
|
||||||
.append("g")
|
|
||||||
.on("mousedown", () => onMouseDown(() => this.rotateCam(0, 5)));
|
|
||||||
top
|
|
||||||
.append("circle").attr({
|
|
||||||
cx: startX + gap,
|
|
||||||
cy: startY,
|
|
||||||
r: radius
|
|
||||||
})
|
|
||||||
.classed('zoomControlCircle', true);
|
|
||||||
top
|
|
||||||
.append("path").attr({
|
|
||||||
d: "M" + (startX + (2 * radius)) + " " + (startY - (radius / 1.5)) + " l12 20 a40,70 0 0,0 -24,0z"
|
|
||||||
}).classed('zoomControlPath', true);
|
|
||||||
|
|
||||||
const right: d3.Selection<any> = this.zoomControl
|
|
||||||
.append("g")
|
|
||||||
.on("mousedown", () => onMouseDown(() => this.rotateCam(-5, 0)));
|
|
||||||
right
|
|
||||||
.append("circle").attr({
|
|
||||||
cx: startX + (2 * gap),
|
|
||||||
cy: startY + gap,
|
|
||||||
r: radius
|
|
||||||
})
|
|
||||||
.classed('zoomControlCircle', true);
|
|
||||||
right
|
|
||||||
.append("path").attr({
|
|
||||||
d: "M" + (startX + (4.7 * radius)) + " " + (startY + (radius * 2)) + " l-20 -12 a70,40 0 0,1 0,24z"
|
|
||||||
}).classed('zoomControlPath', true);
|
|
||||||
|
|
||||||
const zoomIn: d3.Selection<any> = this.zoomControl
|
|
||||||
.append("g")
|
|
||||||
.on("mousedown", () => onMouseDown(() => this.zoomClicked(-1)));
|
|
||||||
zoomIn.append("circle")
|
|
||||||
.attr({
|
|
||||||
cx: startX + 4 * radius,
|
|
||||||
cy: startY + 6 * radius,
|
|
||||||
r: radius
|
|
||||||
})
|
|
||||||
.classed('zoomControlCircle', true);
|
|
||||||
zoomIn.append("rect")
|
|
||||||
.attr({
|
|
||||||
x: startX + 3.5 * radius,
|
|
||||||
y: startY + 5.9 * radius,
|
|
||||||
width: radius,
|
|
||||||
height: radius / 3,
|
|
||||||
fill: "gray"
|
|
||||||
});
|
|
||||||
zoomIn.append("rect")
|
|
||||||
.attr({
|
|
||||||
x: startX + (4 * radius) - radius / 6,
|
|
||||||
y: startY + 5.55 * radius,
|
|
||||||
width: radius / 3,
|
|
||||||
height: radius
|
|
||||||
})
|
|
||||||
.classed('zoomControlPath', true);
|
|
||||||
|
|
||||||
const zoomOut: d3.Selection<any> = this.zoomControl
|
|
||||||
.append("g")
|
|
||||||
.on("mousedown", () => onMouseDown(() => this.zoomClicked(1)));
|
|
||||||
zoomOut
|
|
||||||
.append("circle").attr({
|
|
||||||
cx: startX,
|
|
||||||
cy: startY + 6 * radius,
|
|
||||||
r: radius
|
|
||||||
})
|
|
||||||
.classed('zoomControlCircle', true);
|
|
||||||
zoomOut.append("rect")
|
|
||||||
.attr({
|
|
||||||
x: startX - (radius / 2),
|
|
||||||
y: startY + 5.9 * radius,
|
|
||||||
width: radius,
|
|
||||||
height: radius / 3
|
|
||||||
})
|
|
||||||
.classed('zoomControlPath', true);
|
|
||||||
|
|
||||||
function onMouseDown(callback: () => void) {
|
|
||||||
(d3.event as MouseEvent).stopPropagation();
|
(d3.event as MouseEvent).stopPropagation();
|
||||||
if ((<any>d3.event).button === 0) {
|
if ((<any>d3.event).button === 0) {
|
||||||
callback();
|
callback((<any>d3.event).currentTarget);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.zoomContainer = d3.select(controlContainer);
|
||||||
|
this.zoomContainer
|
||||||
|
.selectAll("g")
|
||||||
|
.on("mousedown", () => onMouseDown(
|
||||||
|
(element: SVGElement) => {
|
||||||
|
const controlType = element.classList.toString().split(" ").filter(className => className.search("js-") !== -1)[0];
|
||||||
|
switch (controlType) {
|
||||||
|
case "js-control--move-up": this.rotateCam(0, GlobeMap.ZoomControlSettings.angleOfRotation); break;
|
||||||
|
case "js-control--move-down": this.rotateCam(0, -GlobeMap.ZoomControlSettings.angleOfRotation); break;
|
||||||
|
case "js-control--move-left": this.rotateCam(GlobeMap.ZoomControlSettings.angleOfRotation, 0); break;
|
||||||
|
case "js-control--move-right": this.rotateCam(-GlobeMap.ZoomControlSettings.angleOfRotation, 0); break;
|
||||||
|
case "js-control--zoom-up": this.zoomClicked(-GlobeMap.ZoomControlSettings.zoomStep); break;
|
||||||
|
case "js-control--zoom-down": this.zoomClicked(GlobeMap.ZoomControlSettings.zoomStep); break;
|
||||||
|
}
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
private initMercartorSphere() {
|
private initMercartorSphere() {
|
||||||
if (GlobeMap.MercartorSphere) return;
|
if (GlobeMap.MercartorSphere) return;
|
||||||
|
|
||||||
|
@ -1218,79 +1296,5 @@ module powerbi.extensibility.visual {
|
||||||
MercartorSphere.prototype = Object.create(THREE.Geometry.prototype);
|
MercartorSphere.prototype = Object.create(THREE.Geometry.prototype);
|
||||||
GlobeMap.MercartorSphere = MercartorSphere;
|
GlobeMap.MercartorSphere = MercartorSphere;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getBingMapCanvas(resolution): JQuery {
|
|
||||||
const tileSize: number = 256;
|
|
||||||
let numSegments: number = Math.pow(2, resolution);
|
|
||||||
let numTiles: number = numSegments * numSegments;
|
|
||||||
let tilesLoaded: number = 0;
|
|
||||||
const canvasSize: number = tileSize * numSegments;
|
|
||||||
const canvas: JQuery = $('<canvas/>').attr({ width: canvasSize, height: canvasSize });
|
|
||||||
|
|
||||||
const canvasElem: HTMLCanvasElement = <any>canvas.get(0);
|
|
||||||
const canvasContext: CanvasRenderingContext2D = canvasElem.getContext("2d");
|
|
||||||
|
|
||||||
function generateQuads(res, quad) {
|
|
||||||
if (res <= resolution) {
|
|
||||||
if (res === resolution) {
|
|
||||||
loadTile(quad);
|
|
||||||
}
|
|
||||||
|
|
||||||
generateQuads(res + 1, quad + "0");
|
|
||||||
generateQuads(res + 1, quad + "1");
|
|
||||||
generateQuads(res + 1, quad + "2");
|
|
||||||
generateQuads(res + 1, quad + "3");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadTile(quad) {
|
|
||||||
const template: any = "https://t{server}.tiles.virtualearth.net/tiles/r{quad}.jpeg?g=0&mkt={language}";
|
|
||||||
const numServers: number = 7;
|
|
||||||
const server: number = Math.round(Math.random() * numServers);
|
|
||||||
const languages: string = "languages";
|
|
||||||
const language: string = (navigator[languages] && navigator[languages].length) ? navigator[languages][0] : navigator.language;
|
|
||||||
const url: any = template.replace("{server}", server)
|
|
||||||
.replace("{quad}", quad)
|
|
||||||
.replace("{language}", language);
|
|
||||||
const coords: any = getCoords(quad);
|
|
||||||
const tile: HTMLImageElement = new Image();
|
|
||||||
tile.onload = () => {
|
|
||||||
tilesLoaded++;
|
|
||||||
canvasContext.drawImage(tile, coords.x * tileSize, coords.y * tileSize, tileSize, tileSize);
|
|
||||||
if (tilesLoaded === numTiles) {
|
|
||||||
canvas.trigger("ready", resolution);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// So the canvas doesn't get tainted
|
|
||||||
tile.crossOrigin = '';
|
|
||||||
tile.src = url;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCoords(quad) {
|
|
||||||
let x: number = 0;
|
|
||||||
let y: number = 0;
|
|
||||||
const last: number = quad.length - 1;
|
|
||||||
|
|
||||||
for (let i: number = last; i >= 0; i--) {
|
|
||||||
const chr: any = quad.charAt(i);
|
|
||||||
const pow: number = Math.pow(2, last - i);
|
|
||||||
|
|
||||||
if (chr === "1") {
|
|
||||||
x += pow;
|
|
||||||
} else if (chr === "2") {
|
|
||||||
y += pow;
|
|
||||||
} else if (chr === "3") {
|
|
||||||
x += pow;
|
|
||||||
y += pow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { x: x, y: y };
|
|
||||||
}
|
|
||||||
|
|
||||||
generateQuads(0, "");
|
|
||||||
return canvas;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
module powerbi.extensibility.visual {
|
module powerbi.extensibility.visual {
|
||||||
|
// powerbi.extensibility.utils.dataview
|
||||||
import DataViewObjectsParser = powerbi.extensibility.utils.dataview.DataViewObjectsParser;
|
import DataViewObjectsParser = powerbi.extensibility.utils.dataview.DataViewObjectsParser;
|
||||||
|
|
||||||
export class GlobeMapSettings extends DataViewObjectsParser {
|
export class GlobeMapSettings extends DataViewObjectsParser {
|
||||||
|
@ -32,6 +33,5 @@ module powerbi.extensibility.visual {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DataPointSettings {
|
export class DataPointSettings {
|
||||||
public fill: string = "#005c55";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,11 +25,12 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
module powerbi.extensibility.visual {
|
module powerbi.extensibility.visual {
|
||||||
// powerbi
|
export interface IMargin {
|
||||||
import IViewport = powerbi.IViewport;
|
top: number;
|
||||||
|
bottom: number;
|
||||||
// powerbi.visuals
|
left: number;
|
||||||
import IMargin = powerbi.extensibility.utils.chart.axis.IMargin;
|
right: number;
|
||||||
|
}
|
||||||
|
|
||||||
export class VisualLayout {
|
export class VisualLayout {
|
||||||
private marginValue: IMargin;
|
private marginValue: IMargin;
|
||||||
|
|
|
@ -1,26 +1,31 @@
|
||||||
.globeMapView{
|
.globeMapView {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.zoomContainer{
|
.controls-container {
|
||||||
position: absolute;
|
position: fixed;
|
||||||
bottom: -5px;
|
right: 5px;
|
||||||
|
bottom: 5px;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.zoomContainerSvg{
|
.controls {
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
|
width: 145;
|
||||||
|
height: 145;
|
||||||
}
|
}
|
||||||
|
|
||||||
.zoomControlCircle{
|
.control {
|
||||||
fill: white;
|
circle {
|
||||||
stroke: gray;
|
fill: white;
|
||||||
opacity: 0.5;
|
stroke: gray;
|
||||||
}
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
.zoomControlPath{
|
path, rect {
|
||||||
fill: gray;
|
fill: gray;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -29,15 +29,12 @@
|
||||||
/// <reference path="../node_modules/@types/jasmine-jquery/index.d.ts" />
|
/// <reference path="../node_modules/@types/jasmine-jquery/index.d.ts" />
|
||||||
|
|
||||||
// Power BI API
|
// Power BI API
|
||||||
/// <reference path="../.api/v1.5.0/PowerBI-visuals.d.ts" />
|
/// <reference path="../.api/v1.6.0/PowerBI-visuals.d.ts" />
|
||||||
|
|
||||||
// Power BI Extensibility
|
// Power BI Extensibility
|
||||||
/// <reference path="../node_modules/powerbi-visuals-utils-dataviewutils/lib/index.d.ts" />
|
/// <reference path="../node_modules/powerbi-visuals-utils-dataviewutils/lib/index.d.ts" />
|
||||||
/// <reference path="../node_modules/powerbi-visuals-utils-typeutils/lib/index.d.ts" />
|
/// <reference path="../node_modules/powerbi-visuals-utils-typeutils/lib/index.d.ts" />
|
||||||
/// <reference path="../node_modules/powerbi-visuals-utils-svgutils/lib/index.d.ts" />
|
|
||||||
/// <reference path="../node_modules/powerbi-visuals-utils-interactivityutils/lib/index.d.ts" />
|
/// <reference path="../node_modules/powerbi-visuals-utils-interactivityutils/lib/index.d.ts" />
|
||||||
|
|
||||||
/// <reference path="../node_modules/powerbi-visuals-utils-dataviewutils/lib/index.d.ts" />
|
|
||||||
/// <reference path="../node_modules/powerbi-visuals-utils-formattingutils/lib/index.d.ts" />
|
/// <reference path="../node_modules/powerbi-visuals-utils-formattingutils/lib/index.d.ts" />
|
||||||
/// <reference path="../node_modules/powerbi-visuals-utils-colorutils/lib/index.d.ts"/>
|
/// <reference path="../node_modules/powerbi-visuals-utils-colorutils/lib/index.d.ts"/>
|
||||||
/// <reference path="../node_modules/powerbi-visuals-utils-testutils/lib/index.d.ts"/>
|
/// <reference path="../node_modules/powerbi-visuals-utils-testutils/lib/index.d.ts"/>
|
||||||
|
|
|
@ -29,16 +29,35 @@
|
||||||
module powerbi.extensibility.visual.test {
|
module powerbi.extensibility.visual.test {
|
||||||
// powerbi.extensibility.utils.test
|
// powerbi.extensibility.utils.test
|
||||||
import VisualBuilderBase = powerbi.extensibility.utils.test.VisualBuilderBase;
|
import VisualBuilderBase = powerbi.extensibility.utils.test.VisualBuilderBase;
|
||||||
|
import renderTimeout = powerbi.extensibility.utils.test.helpers.renderTimeout;
|
||||||
|
|
||||||
// GlobeMap1447669447624
|
// GlobeMap1447669447624
|
||||||
import VisualClass = powerbi.extensibility.visual.GlobeMap1447669447624.GlobeMap;
|
import VisualClass = powerbi.extensibility.visual.GlobeMap1447669447624.GlobeMap;
|
||||||
import VisualPlugin = powerbi.visuals.plugins.GlobeMap1447669447624;
|
import VisualPlugin = powerbi.visuals.plugins.GlobeMap1447669447624;
|
||||||
|
|
||||||
export class GlobeMapBuilder extends VisualBuilderBase<VisualClass> {
|
export class GlobeMapBuilder extends VisualBuilderBase<VisualClass> {
|
||||||
|
private static ChangeAllType: number = 62;
|
||||||
constructor(width: number, height: number) {
|
constructor(width: number, height: number) {
|
||||||
super(width, height, VisualPlugin.name);
|
super(width, height, VisualPlugin.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public update(dataView: DataView[] | DataView, updateType?: VisualUpdateType): void {
|
||||||
|
this.visual.update(<VisualUpdateOptions>{
|
||||||
|
dataViews: _.isArray(dataView) ? dataView : [dataView],
|
||||||
|
viewport: this.viewport,
|
||||||
|
type: updateType
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateRenderTimeout(
|
||||||
|
dataViews: DataView[] | DataView,
|
||||||
|
fn: Function,
|
||||||
|
updateType: VisualUpdateType = GlobeMapBuilder.ChangeAllType,
|
||||||
|
timeout?: number): number {
|
||||||
|
this.update(dataViews, updateType);
|
||||||
|
return renderTimeout(fn, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
protected build(options: any) {
|
protected build(options: any) {
|
||||||
return new VisualClass(options);
|
return new VisualClass(options);
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,7 @@ module powerbi.extensibility.visual.test {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
|
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
|
||||||
visualBuilder = new GlobeMapBuilder(1000, 500);
|
visualBuilder = new GlobeMapBuilder(1024, 1024);
|
||||||
|
|
||||||
defaultDataViewBuilder = new GlobeMapDataViewBuilder();
|
defaultDataViewBuilder = new GlobeMapDataViewBuilder();
|
||||||
dataView = defaultDataViewBuilder.getDataView();
|
dataView = defaultDataViewBuilder.getDataView();
|
||||||
|
|
|
@ -10,15 +10,13 @@
|
||||||
"declaration": true
|
"declaration": true
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
".api/v1.5.0/PowerBI-visuals.d.ts",
|
".api/v1.6.0/PowerBI-visuals.d.ts",
|
||||||
"node_modules/powerbi-visuals-utils-formattingutils/lib/index.d.ts",
|
"node_modules/powerbi-visuals-utils-formattingutils/lib/index.d.ts",
|
||||||
"node_modules/powerbi-visuals-utils-interactivityutils/lib/index.d.ts",
|
"node_modules/powerbi-visuals-utils-interactivityutils/lib/index.d.ts",
|
||||||
"node_modules/powerbi-visuals-utils-typeutils/lib/index.d.ts",
|
"node_modules/powerbi-visuals-utils-typeutils/lib/index.d.ts",
|
||||||
"node_modules/powerbi-visuals-utils-svgutils/lib/index.d.ts",
|
"node_modules/powerbi-visuals-utils-svgutils/lib/index.d.ts",
|
||||||
"node_modules/powerbi-visuals-utils-chartutils/lib/index.d.ts",
|
|
||||||
"node_modules/powerbi-visuals-utils-dataviewutils/lib/index.d.ts",
|
"node_modules/powerbi-visuals-utils-dataviewutils/lib/index.d.ts",
|
||||||
"node_modules/powerbi-visuals-utils-colorutils/lib/index.d.ts",
|
"node_modules/powerbi-visuals-utils-colorutils/lib/index.d.ts",
|
||||||
"node_modules/bingmaps/scripts/MicrosoftMaps/Microsoft.Maps.d.ts",
|
|
||||||
"src/UrlUtils/UrlUtils.ts",
|
"src/UrlUtils/UrlUtils.ts",
|
||||||
"src/dataInterfaces.ts",
|
"src/dataInterfaces.ts",
|
||||||
"src/settings.ts",
|
"src/settings.ts",
|
||||||
|
|
|
@ -23,7 +23,6 @@
|
||||||
"spaces"
|
"spaces"
|
||||||
],
|
],
|
||||||
"no-duplicate-variable": true,
|
"no-duplicate-variable": true,
|
||||||
"no-eval": true,
|
|
||||||
"no-internal-module": false,
|
"no-internal-module": false,
|
||||||
"no-trailing-whitespace": true,
|
"no-trailing-whitespace": true,
|
||||||
"no-unsafe-finally": true,
|
"no-unsafe-finally": true,
|
||||||
|
|
Загрузка…
Ссылка в новой задаче