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",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests"
|
||||
"test"
|
||||
],
|
||||
"dependencies": {
|
||||
"webgl-heatmap": "*"
|
||||
|
|
48
package.json
48
package.json
|
@ -1,18 +1,17 @@
|
|||
{
|
||||
"name": "powerbi-visuals-globemap",
|
||||
"description": "GlobeMap",
|
||||
"version": "1.4.3",
|
||||
"version": "1.4.7",
|
||||
"author": {
|
||||
"name": "Microsoft",
|
||||
"email": "pbicvsupport@microsoft.com"
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "bower install && pbiviz update 1.5.0",
|
||||
"typings": "typings",
|
||||
"postinstall": "bower install && pbiviz update 1.6.0",
|
||||
"pbiviz": "pbiviz",
|
||||
"start": "pbiviz start",
|
||||
"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",
|
||||
"test": "karma start"
|
||||
},
|
||||
|
@ -22,36 +21,35 @@
|
|||
"url": "git+https://github.com/Microsoft/PowerBI-visuals-globemap.git"
|
||||
},
|
||||
"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",
|
||||
"powerbi-visuals-utils-chartutils": "0.2.1",
|
||||
"powerbi-visuals-utils-colorutils": "0.2.1",
|
||||
"powerbi-visuals-utils-dataviewutils": "1.0.1"
|
||||
"globalize": "0.1.0-a2",
|
||||
"jquery": "3.1.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": {
|
||||
"@types/d3": "3.5.36",
|
||||
"@types/jasmine": "2.5.37",
|
||||
"@types/jasmine-jquery": "1.5.28",
|
||||
"@types/jasmine": "2.5.47",
|
||||
"@types/jasmine-jquery": "1.5.30",
|
||||
"@types/jquery": "2.0.41",
|
||||
"@types/lodash": "4.14.43",
|
||||
"@types/lodash": "4.14.55",
|
||||
"@types/three": "0.0.19",
|
||||
"bingmaps": "1.0.12",
|
||||
"bower": "1.8.0",
|
||||
"jasmine": "2.5.2",
|
||||
"jasmine": "2.6.0",
|
||||
"jasmine-jquery": "2.1.1",
|
||||
"karma": "1.3.0",
|
||||
"karma": "1.6.0",
|
||||
"karma-chrome-launcher": "2.0.0",
|
||||
"karma-jasmine": "1.0.2",
|
||||
"karma-typescript-preprocessor": "0.3.0",
|
||||
"powerbi-visuals-tools": "1.5.0",
|
||||
"powerbi-visuals-utils-testutils": "0.2.2",
|
||||
"powerbi-visuals-utils-typeutils": "^0.2.1",
|
||||
"tslint": "^4.4.2",
|
||||
"tslint-microsoft-contrib": "^4.0.0",
|
||||
"karma-jasmine": "1.1.0",
|
||||
"karma-typescript-preprocessor": "0.3.1",
|
||||
"powerbi-visuals-tools": "1.6.3",
|
||||
"powerbi-visuals-utils-testutils": "1.0.0",
|
||||
"tslint": "4.5.1",
|
||||
"tslint-microsoft-contrib": "^4.0.1",
|
||||
"typescript": "2.1.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,12 +4,12 @@
|
|||
"displayName": "GlobeMap",
|
||||
"guid": "GlobeMap1447669447624",
|
||||
"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",
|
||||
"supportUrl": "http://community.powerbi.com",
|
||||
"gitHubUrl": "https://github.com/Microsoft/powerbi-visuals-globemap"
|
||||
},
|
||||
"apiVersion": "1.5.0",
|
||||
"apiVersion": "1.6.0",
|
||||
"author": {
|
||||
"name": "Microsoft",
|
||||
"email": "pbicvsupport@microsoft.com"
|
||||
|
@ -20,7 +20,7 @@
|
|||
"externalJS": [
|
||||
"node_modules/jquery/dist/jquery.min.js",
|
||||
"node_modules/lodash/lodash.min.js",
|
||||
"node_modules/d3/d3.js",
|
||||
"node_modules/d3/d3.min.js",
|
||||
"node_modules/three/three.js",
|
||||
"src/lib/OrbitControls.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-formattingutils/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-colorutils/lib/index.js",
|
||||
"bower_components/webgl-heatmap/webgl-heatmap.js"
|
||||
|
|
|
@ -25,165 +25,7 @@
|
|||
*/
|
||||
|
||||
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 {
|
||||
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
|
||||
* @param url The URL to modify
|
||||
|
@ -202,11 +44,11 @@ namespace powerbi.extensibility.utils {
|
|||
return result;
|
||||
}
|
||||
|
||||
result += '?' + _.chain(parameters)
|
||||
result += "?" + _.chain(parameters)
|
||||
.toPairs()
|
||||
.map(pair => pair.join('='))
|
||||
.map(pair => pair.join("="))
|
||||
.value()
|
||||
.join('&');
|
||||
.join("&");
|
||||
|
||||
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 {
|
||||
let elem: HTMLAnchorElement = document.createElement('a');
|
||||
let elem: HTMLAnchorElement = document.createElement("a");
|
||||
elem.href = url;
|
||||
|
||||
return elem.search;
|
||||
|
@ -284,7 +77,7 @@ namespace powerbi.extensibility.utils {
|
|||
return null;
|
||||
}
|
||||
|
||||
if (_.startsWith(queryString, '?')) {
|
||||
if (_.startsWith(queryString, "?")) {
|
||||
queryString = queryString.substring(1);
|
||||
}
|
||||
|
||||
|
|
|
@ -29,44 +29,12 @@ module powerbi.extensibility.visual {
|
|||
import DataViewValueColumns = powerbi.DataViewValueColumns;
|
||||
import DataViewCategoricalColumn = powerbi.DataViewCategoricalColumn;
|
||||
import DataViewValueColumn = powerbi.DataViewValueColumn;
|
||||
|
||||
// powerbi.extensibility.utils.dataview
|
||||
import converterHelper = powerbi.extensibility.utils.dataview.converterHelper;
|
||||
|
||||
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 {
|
||||
let categorical = dataView && dataView.categorical;
|
||||
let categories = categorical && categorical.categories || [];
|
||||
|
@ -87,12 +55,6 @@ module powerbi.extensibility.visual {
|
|||
(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 Series: T = null;
|
||||
public X: T = null;
|
||||
|
|
|
@ -25,7 +25,10 @@
|
|||
*/
|
||||
|
||||
module powerbi.extensibility.visual {
|
||||
// powerbi.extensibility.utils.interactivity
|
||||
import SelectableDataPoint = powerbi.extensibility.utils.interactivity.SelectableDataPoint;
|
||||
|
||||
// powerbi.extensibility.geocoder
|
||||
import ILocation = powerbi.extensibility.geocoder.ILocation;
|
||||
|
||||
export interface GlobeMapData {
|
||||
|
@ -52,6 +55,32 @@ module powerbi.extensibility.visual {
|
|||
color: 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);
|
||||
|
||||
entry.request = $.ajax({
|
||||
url: url,
|
||||
dataType: 'jsonp',
|
||||
|
|
|
@ -1,13 +1,18 @@
|
|||
module powerbi.extensibility.geocoder {
|
||||
import IPromise = powerbi.IPromise;
|
||||
import IRect = powerbi.extensibility.utils.svg.IRect;
|
||||
|
||||
/** Defines geocoding services. */
|
||||
export interface GeocodeOptions {
|
||||
/** promise that should abort the request when resolved */
|
||||
timeout?: IPromise<any>;
|
||||
}
|
||||
|
||||
export interface IRect {
|
||||
left: number;
|
||||
top: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export interface IGeocoder {
|
||||
geocode(query: string, category?: string, options?: GeocodeOptions): IPromise<IGeocodeCoordinate>;
|
||||
geocodeBoundary(latitude: number, longitude: number, category: string, levelOfDetail?: number, maxGeoData?: number, options?: GeocodeOptions): IPromise<IGeocodeBoundaryCoordinate>;
|
||||
|
|
|
@ -25,8 +25,9 @@
|
|||
*/
|
||||
|
||||
module powerbi.extensibility.geocoder {
|
||||
|
||||
// powerbi.extensibility.utils.formatting
|
||||
import IStorageService = powerbi.extensibility.utils.formatting.IStorageService;
|
||||
import LocalStorageService = powerbi.extensibility.utils.formatting.LocalStorageService;
|
||||
|
||||
interface GeocodeCacheEntry {
|
||||
coordinate: IGeocodeCoordinate;
|
||||
|
@ -41,7 +42,7 @@ module powerbi.extensibility.geocoder {
|
|||
|
||||
export function createGeocodingCache(maxCacheSize: number, maxCacheSizeOverflow: number, localStorageService?: IStorageService): IGeocodingCache {
|
||||
if (!localStorageService) {
|
||||
localStorageService = new powerbi.extensibility.utils.formatting.LocalStorageService();
|
||||
localStorageService = new LocalStorageService();
|
||||
}
|
||||
return new GeocodingCache(maxCacheSize, maxCacheSizeOverflow, localStorageService);
|
||||
}
|
||||
|
|
598
src/globemap.ts
598
src/globemap.ts
|
@ -1,4 +1,4 @@
|
|||
/*
|
||||
/*
|
||||
* Power BI Visualizations
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
|
@ -24,51 +24,44 @@
|
|||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
let WebGLHeatmap: any = window['createWebGLHeatmap'];
|
||||
let GlobeMapCanvasLayers: JQuery[];
|
||||
let WebGLHeatmap: any = window["createWebGLHeatmap"];
|
||||
|
||||
module powerbi.extensibility.visual {
|
||||
// powerbi.extensibility.geocoder
|
||||
import IGeocoder = powerbi.extensibility.geocoder.IGeocoder;
|
||||
import IGeocodeCoordinate = powerbi.extensibility.geocoder.IGeocodeCoordinate;
|
||||
import IPromise = powerbi.IPromise;
|
||||
import Rectangle = powerbi.extensibility.utils.svg.touch.Rectangle;
|
||||
import ILocation = powerbi.extensibility.geocoder.ILocation;
|
||||
|
||||
// powerbi.extensibility.utils.dataview
|
||||
import converterHelper = powerbi.extensibility.utils.dataview.converterHelper;
|
||||
|
||||
// powerbi.extensibility.utils.color
|
||||
import ColorHelper = powerbi.extensibility.utils.color.ColorHelper;
|
||||
|
||||
import ClassAndSelector = powerbi.extensibility.utils.svg.CssConstants.ClassAndSelector;
|
||||
import createClassAndSelector = powerbi.extensibility.utils.svg.CssConstants.createClassAndSelector;
|
||||
import DataViewPropertyValue = powerbi.DataViewPropertyValue;
|
||||
import SelectableDataPoint = powerbi.extensibility.utils.interactivity.SelectableDataPoint;
|
||||
// powerbi.visuals
|
||||
import ISelectionId = powerbi.visuals.ISelectionId;
|
||||
|
||||
// powerbi.extensibility.utils.formatting
|
||||
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 IAxisProperties = powerbi.extensibility.utils.chart.axis.IAxisProperties;
|
||||
import IVisualHost = powerbi.extensibility.visual.IVisualHost;
|
||||
import svg = powerbi.extensibility.utils.svg;
|
||||
import axis = powerbi.extensibility.utils.chart.axis;
|
||||
import textMeasurementService = powerbi.extensibility.utils.formatting.textMeasurementService;
|
||||
import ValueType = utils.type.ValueType;
|
||||
import DataViewObjectsParser = utils.dataview.DataViewObjectsParser;
|
||||
import IColorPalette = powerbi.extensibility.IColorPalette;
|
||||
import LocalStorageService = powerbi.extensibility.utils.formatting.LocalStorageService;
|
||||
import IStorageService = powerbi.extensibility.utils.formatting.IStorageService;
|
||||
|
||||
// powerbi.extensibility.utils.type
|
||||
import ValueType = powerbi.extensibility.utils.type.ValueType;
|
||||
|
||||
interface ExtendedPromise<T> extends IPromise<T> {
|
||||
always(value: any): void;
|
||||
}
|
||||
|
||||
export class GlobeMap implements IVisual {
|
||||
private localStorageService: IStorageService;
|
||||
public static MercartorSphere: any;
|
||||
private static GlobeSettings = {
|
||||
autoRotate: false,
|
||||
earthRadius: 30,
|
||||
cameraRadius: 100,
|
||||
earthSegments: 100,
|
||||
heatmapSize: 1000,
|
||||
heatmapSize: 1024,
|
||||
heatPointSize: 7,
|
||||
heatIntensity: 10,
|
||||
heatmapScaleOnZoom: 0.95,
|
||||
|
@ -79,7 +72,13 @@ module powerbi.extensibility.visual {
|
|||
cameraAnimDuration: 1000, // 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 root: JQuery;
|
||||
private rendererContainer: JQuery;
|
||||
|
@ -111,7 +110,6 @@ module powerbi.extensibility.visual {
|
|||
private hoveredBar: any;
|
||||
private averageBarVector: THREE.Vector3;
|
||||
private zoomContainer: d3.Selection<any>;
|
||||
private zoomControl: d3.Selection<any>;
|
||||
public colors: IColorPalette;
|
||||
private animationFrameId: number;
|
||||
private cameraAnimationFrameId: number;
|
||||
|
@ -126,15 +124,13 @@ module powerbi.extensibility.visual {
|
|||
|| (_.isEmpty(categorical.Height) && _.isEmpty(categorical.Heat))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const properties: GlobeMapSettings = GlobeMapSettings.getDefault() as GlobeMapSettings;
|
||||
const settings: GlobeMapSettings = GlobeMap.parseSettings(dataView);
|
||||
const groupedColumns: GlobeMapColumns<DataViewValueColumn>[] | any = GlobeMapColumns.getGroupedValueColumns(dataView);
|
||||
const dataPoints: any = [];
|
||||
let seriesDataPoints: 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 heights: any;
|
||||
let heightsBySeries: any;
|
||||
|
@ -158,9 +154,9 @@ module powerbi.extensibility.visual {
|
|||
// creating a matrix for drawing values by series later.
|
||||
for (let i: number = 0; i < groupedColumns.length; i++) {
|
||||
const values: any = groupedColumns[i].Height.values;
|
||||
|
||||
seriesDataPoints[i] = GlobeMap.createDataPointForEnumeration(
|
||||
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++) {
|
||||
if (!heights[j]) {
|
||||
heights[j] = 0;
|
||||
|
@ -192,13 +188,11 @@ module powerbi.extensibility.visual {
|
|||
heightsBySeries = [];
|
||||
seriesDataPoints[0] = GlobeMap.createDataPointForEnumeration(
|
||||
dataView, groupedColumns[0].Height.source, 0, dataView.metadata, colorHelper, colors, visualHost);
|
||||
seriesDataPoints[0].color = settings.dataPoint.fill;
|
||||
}
|
||||
} else {
|
||||
heightsBySeries = [];
|
||||
heights = [];
|
||||
}
|
||||
|
||||
if (!_.isEmpty(categorical.Heat)) {
|
||||
if (groupedColumns.length > 1) {
|
||||
heats = [];
|
||||
|
@ -262,7 +256,6 @@ module powerbi.extensibility.visual {
|
|||
dataPoints.push(renderDatum);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
dataView: dataView,
|
||||
dataPoints: dataPoints,
|
||||
|
@ -296,19 +289,20 @@ module powerbi.extensibility.visual {
|
|||
|
||||
const label: string = valueFormatter.format(nameForFormat, valueFormatter.getFormatString(sourceForFormat, null));
|
||||
|
||||
let measureValues = values[0];
|
||||
const categoryColumn: DataViewCategoryColumn = {
|
||||
source: values[seriesIndex].source,
|
||||
source: measureValues.source,
|
||||
values: null,
|
||||
identity: [values[seriesIndex].identity]
|
||||
identity: [measureValues.identity]
|
||||
};
|
||||
|
||||
const identity: ISelectionId = visualHost.createSelectionIdBuilder()
|
||||
.withCategory(categoryColumn, 0)
|
||||
.withMeasure(values[seriesIndex].source.queryName)
|
||||
.withMeasure(measureValues.source.queryName)
|
||||
.createSelectionId();
|
||||
|
||||
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
|
||||
? colorHelper.getColorForMeasure(metaData.objects, "")
|
||||
: 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 {
|
||||
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) {
|
||||
|
||||
this.currentLanguage = options.host.locale;
|
||||
this.localStorageService = new LocalStorageService();
|
||||
this.root = $("<div>").appendTo(options.element)
|
||||
.attr('drag-resize-disabled', "true")
|
||||
.attr("drag-resize-disabled", "true")
|
||||
.css({
|
||||
'position': "absolute"
|
||||
"position": "absolute"
|
||||
});
|
||||
|
||||
this.visualHost = options.host;
|
||||
|
@ -351,12 +376,16 @@ module powerbi.extensibility.visual {
|
|||
}
|
||||
|
||||
private setup(): void {
|
||||
this.initTextures();
|
||||
this.initMercartorSphere();
|
||||
this.initZoomControl();
|
||||
this.initScene();
|
||||
this.initHeatmap();
|
||||
this.initMercartorSphere();
|
||||
this.initTextures().then(
|
||||
() => {
|
||||
this.earth = this.createEarth();
|
||||
this.scene.add(this.earth);
|
||||
this.readyToRender = true;
|
||||
});
|
||||
this.initZoomControl();
|
||||
this.initHeatmap();
|
||||
this.initRayCaster();
|
||||
}
|
||||
private static cameraFov: number = 35;
|
||||
|
@ -366,6 +395,26 @@ module powerbi.extensibility.visual {
|
|||
private static ambientLight: number = 0x000000;
|
||||
private static directionalLight: number = 0xffffff;
|
||||
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 {
|
||||
this.renderer = new THREE.WebGLRenderer({ antialias: true, preserveDrawingBuffer: true });
|
||||
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 light1: 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(light1);
|
||||
this.scene.add(light2);
|
||||
this.scene.add(earth);
|
||||
|
||||
light1.position.set(20, 20, 20);
|
||||
light2.position.set(0, 0, -20);
|
||||
|
@ -447,7 +494,7 @@ module powerbi.extensibility.visual {
|
|||
}
|
||||
|
||||
private static dollyX: number = 0.95;
|
||||
public zoomClicked(zoomDirection: any): void {
|
||||
public zoomClicked(zoomDirection: number): void {
|
||||
if (this.orbitControls.enabled === false) {
|
||||
return;
|
||||
}
|
||||
|
@ -462,7 +509,7 @@ module powerbi.extensibility.visual {
|
|||
this.animateCamera(this.camera.position);
|
||||
}
|
||||
|
||||
public rotateCam(deltaX: number, deltaY: number) {
|
||||
public rotateCam(deltaX: number, deltaY: number): void {
|
||||
if (!this.orbitControls.enabled) {
|
||||
return;
|
||||
}
|
||||
|
@ -472,33 +519,145 @@ module powerbi.extensibility.visual {
|
|||
this.animateCamera(this.camera.position);
|
||||
}
|
||||
|
||||
private initTextures() {
|
||||
if (!GlobeMapCanvasLayers) {
|
||||
private initTextures(): JQueryPromise<{}> {
|
||||
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.
|
||||
GlobeMapCanvasLayers = [];
|
||||
|
||||
for (let level: number = 2; level <= 5; ++level) {
|
||||
const canvas: JQuery = this.getBingMapCanvas(level);
|
||||
GlobeMapCanvasLayers.push(canvas);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// Can't execute in for loop because variable assignement gets overwritten
|
||||
const createTexture: (canvas: JQuery) => THREE.Texture = (canvas: JQuery) => {
|
||||
const texture: THREE.Texture = new THREE.Texture(<HTMLCanvasElement>canvas.get(0));
|
||||
private getBingMapsServerMetadata(): JQueryPromise<BingResourceMetadata> {
|
||||
return $.ajax(GlobeMap.metadataUrl)
|
||||
.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;
|
||||
canvas.on("ready", () => {
|
||||
this.loadTiles(canvas, tiles, () => {
|
||||
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]));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
return { x: x, y: y };
|
||||
}
|
||||
|
||||
private initHeatmap() {
|
||||
|
@ -531,19 +690,12 @@ module powerbi.extensibility.visual {
|
|||
}
|
||||
const maxDistance: number = GlobeMap.GlobeSettings.cameraRadius - GlobeMap.GlobeSettings.earthRadius;
|
||||
const distance: number = (this.camera.position.length() - GlobeMap.GlobeSettings.earthRadius) / maxDistance;
|
||||
let texture: THREE.Texture;
|
||||
const oneOfFive: number = 1 / 5;
|
||||
const twoOfFive: number = 2 / 5;
|
||||
const threeOfFive: number = 3 / 5;
|
||||
|
||||
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];
|
||||
let texture: THREE.Texture = this.mapTextures[0];
|
||||
for (let divider = 1; divider <= GlobeMap.maxResolutionLevel; divider++) {
|
||||
if (distance <= divider / GlobeMap.maxResolutionLevel) {
|
||||
texture = this.mapTextures[GlobeMap.maxResolutionLevel - divider];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ((<any>this.earth.material).map !== texture) {
|
||||
|
@ -558,18 +710,15 @@ module powerbi.extensibility.visual {
|
|||
}
|
||||
|
||||
public update(options: VisualUpdateOptions): void {
|
||||
|
||||
if (options.dataViews === undefined || options.dataViews === null) {
|
||||
return;
|
||||
}
|
||||
this.layout.viewport = options.viewport;
|
||||
this.root.css(this.layout.viewportIn);
|
||||
const sixPointsToAdd: number = 6;
|
||||
this.zoomContainer.style({
|
||||
'padding-left': (this.layout.viewportIn.width - parseFloat(this.zoomControl.attr("width")) + sixPointsToAdd) + "px", // Fix for chrome
|
||||
'display': this.layout.viewportIn.height > $(this.zoomContainer.node()).height()
|
||||
&& this.layout.viewportIn.width > $(this.zoomContainer.node()).width()
|
||||
? null : 'none'
|
||||
"display": this.layout.viewportIn.height > GlobeMap.ZoomControlSettings.height
|
||||
&& this.layout.viewportIn.width > GlobeMap.ZoomControlSettings.width
|
||||
? null : "none"
|
||||
});
|
||||
|
||||
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();
|
||||
const data: GlobeMapData = GlobeMap.converter(options.dataViews[0], this.colors, this.visualHost);
|
||||
if (data) {
|
||||
|
@ -603,9 +752,9 @@ module powerbi.extensibility.visual {
|
|||
if (!this.data) {
|
||||
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) => {
|
||||
return d.location = d.location || this.globeMapLocationCache[d.placeKey];
|
||||
return d.location = this.globeMapLocationCache[d.placeKey] || d.location;
|
||||
});
|
||||
|
||||
if (!this.readyToRender) {
|
||||
|
@ -627,7 +776,9 @@ module powerbi.extensibility.visual {
|
|||
for (let i: number = 0; i < len; ++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;
|
||||
}
|
||||
|
||||
|
@ -658,7 +809,7 @@ module powerbi.extensibility.visual {
|
|||
const dataPointToolTip: any = [];
|
||||
if (renderDatum.heightBySeries) {
|
||||
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]);
|
||||
}
|
||||
dataPointToolTip.push(renderDatum.seriesToolTipData[c]);
|
||||
|
@ -713,7 +864,8 @@ module powerbi.extensibility.visual {
|
|||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -947,7 +1099,7 @@ module powerbi.extensibility.visual {
|
|||
this.camera = null;
|
||||
if (this.renderer) {
|
||||
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) {
|
||||
extension.loseContext();
|
||||
}
|
||||
|
@ -976,143 +1128,69 @@ module powerbi.extensibility.visual {
|
|||
|
||||
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() {
|
||||
const radius: number = 17;
|
||||
const zoomControlWidth: number = radius * GlobeMap.zoomControlRatio;
|
||||
const zoomControlHeight: number = radius * GlobeMap.zoomControlRatio;
|
||||
const startX: number = radius * GlobeMap.radiusRatio;
|
||||
const startY: number = radius + GlobeMap.radiusRatio;
|
||||
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) {
|
||||
const controlContainer: HTMLElement = document.createElement("div");
|
||||
controlContainer.classList.add("controls-container");
|
||||
controlContainer.innerHTML = GlobeMap.ZoomControlSettings.markup;
|
||||
this.root.append(controlContainer);
|
||||
function onMouseDown(callback: (element: SVGElement) => void) {
|
||||
(d3.event as MouseEvent).stopPropagation();
|
||||
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() {
|
||||
if (GlobeMap.MercartorSphere) return;
|
||||
|
||||
|
@ -1218,79 +1296,5 @@ module powerbi.extensibility.visual {
|
|||
MercartorSphere.prototype = Object.create(THREE.Geometry.prototype);
|
||||
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 {
|
||||
// powerbi.extensibility.utils.dataview
|
||||
import DataViewObjectsParser = powerbi.extensibility.utils.dataview.DataViewObjectsParser;
|
||||
|
||||
export class GlobeMapSettings extends DataViewObjectsParser {
|
||||
|
@ -32,6 +33,5 @@ module powerbi.extensibility.visual {
|
|||
}
|
||||
|
||||
export class DataPointSettings {
|
||||
public fill: string = "#005c55";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,11 +25,12 @@
|
|||
*/
|
||||
|
||||
module powerbi.extensibility.visual {
|
||||
// powerbi
|
||||
import IViewport = powerbi.IViewport;
|
||||
|
||||
// powerbi.visuals
|
||||
import IMargin = powerbi.extensibility.utils.chart.axis.IMargin;
|
||||
export interface IMargin {
|
||||
top: number;
|
||||
bottom: number;
|
||||
left: number;
|
||||
right: number;
|
||||
}
|
||||
|
||||
export class VisualLayout {
|
||||
private marginValue: IMargin;
|
||||
|
|
|
@ -1,26 +1,31 @@
|
|||
.globeMapView{
|
||||
.globeMapView {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.zoomContainer{
|
||||
position: absolute;
|
||||
bottom: -5px;
|
||||
.controls-container {
|
||||
position: fixed;
|
||||
right: 5px;
|
||||
bottom: 5px;
|
||||
z-index: 1000;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.zoomContainerSvg{
|
||||
.controls {
|
||||
pointer-events: all;
|
||||
width: 145;
|
||||
height: 145;
|
||||
}
|
||||
|
||||
.zoomControlCircle{
|
||||
.control {
|
||||
circle {
|
||||
fill: white;
|
||||
stroke: gray;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.zoomControlPath{
|
||||
path, rect {
|
||||
fill: gray;
|
||||
}
|
||||
}
|
|
@ -29,15 +29,12 @@
|
|||
/// <reference path="../node_modules/@types/jasmine-jquery/index.d.ts" />
|
||||
|
||||
// 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
|
||||
/// <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-svgutils/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-colorutils/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 {
|
||||
// powerbi.extensibility.utils.test
|
||||
import VisualBuilderBase = powerbi.extensibility.utils.test.VisualBuilderBase;
|
||||
import renderTimeout = powerbi.extensibility.utils.test.helpers.renderTimeout;
|
||||
|
||||
// GlobeMap1447669447624
|
||||
import VisualClass = powerbi.extensibility.visual.GlobeMap1447669447624.GlobeMap;
|
||||
import VisualPlugin = powerbi.visuals.plugins.GlobeMap1447669447624;
|
||||
|
||||
export class GlobeMapBuilder extends VisualBuilderBase<VisualClass> {
|
||||
private static ChangeAllType: number = 62;
|
||||
constructor(width: number, height: number) {
|
||||
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) {
|
||||
return new VisualClass(options);
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ module powerbi.extensibility.visual.test {
|
|||
|
||||
beforeEach(() => {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
|
||||
visualBuilder = new GlobeMapBuilder(1000, 500);
|
||||
visualBuilder = new GlobeMapBuilder(1024, 1024);
|
||||
|
||||
defaultDataViewBuilder = new GlobeMapDataViewBuilder();
|
||||
dataView = defaultDataViewBuilder.getDataView();
|
||||
|
|
|
@ -10,15 +10,13 @@
|
|||
"declaration": true
|
||||
},
|
||||
"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-interactivityutils/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-chartutils/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/bingmaps/scripts/MicrosoftMaps/Microsoft.Maps.d.ts",
|
||||
"src/UrlUtils/UrlUtils.ts",
|
||||
"src/dataInterfaces.ts",
|
||||
"src/settings.ts",
|
||||
|
|
|
@ -23,7 +23,6 @@
|
|||
"spaces"
|
||||
],
|
||||
"no-duplicate-variable": true,
|
||||
"no-eval": true,
|
||||
"no-internal-module": false,
|
||||
"no-trailing-whitespace": true,
|
||||
"no-unsafe-finally": true,
|
||||
|
|
Загрузка…
Ссылка в новой задаче