Skip to content

Commit

Permalink
Fixed deselection in Slicer wasn't apply to Globe map (#104)
Browse files Browse the repository at this point in the history
* Fix cache processing

* Increment visual version

* Remove redundant promises in MemoryCache

* Wrap createLocalStorageCache in native promise

* Remove coordsInLocalStorage prop

* Replaced promise chaining  with await

* Fix geocoder

* Add tests for memoryCache, localStorageCache and geocoder

* Mock fetch request

* Rename syncLocalStorageStatus method

* Remove debugger

* Fix audit, update dependencies

---------

Co-authored-by: Iuliia Kulagina <[email protected]>
  • Loading branch information
kullJul and Iuliia Kulagina committed Apr 11, 2024
1 parent 9438dc0 commit 1faa8ea
Show file tree
Hide file tree
Showing 9 changed files with 729 additions and 662 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 3.3.3.0
* Fixed deselection in Slicer wasn't apply to Globe map
* Update powerbi-visuals-api to 5.9.0
* Refactored cacheManager, LocalStorageCache, MemoryCache

## 3.3.2.0
* Fixed console error "undefined CustomVisualObjects"

Expand Down
1,032 changes: 495 additions & 537 deletions package-lock.json

Large diffs are not rendered by default.

24 changes: 12 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "powerbi-visuals-globemap",
"description": "GlobeMap",
"version": "3.3.2.0",
"version": "3.3.3.0",
"author": {
"name": "Microsoft",
"email": "[email protected]"
Expand All @@ -25,13 +25,13 @@
"lodash.clone": "^4.5.0",
"lodash.isempty": "^4.4.0",
"lodash.mapvalues": "^4.6.0",
"powerbi-visuals-api": "^5.8.0",
"powerbi-visuals-utils-colorutils": "^6.0.3",
"powerbi-visuals-utils-dataviewutils": "^6.0.2",
"powerbi-visuals-utils-formattingmodel": "^6.0.1",
"powerbi-visuals-utils-formattingutils": "^6.1.0",
"powerbi-visuals-utils-interactivityutils": "^6.0.3",
"powerbi-visuals-utils-typeutils": "^6.0.1",
"powerbi-visuals-api": "^5.9.0",
"powerbi-visuals-utils-colorutils": "^6.0.4",
"powerbi-visuals-utils-dataviewutils": "^6.0.3",
"powerbi-visuals-utils-formattingmodel": "^6.0.2",
"powerbi-visuals-utils-formattingutils": "^6.1.1",
"powerbi-visuals-utils-interactivityutils": "^6.0.4",
"powerbi-visuals-utils-typeutils": "^6.0.3",
"three": "^0.157.0"
},
"devDependencies": {
Expand All @@ -58,13 +58,13 @@
"karma-webpack": "^5.0.1",
"less": "^4.2.0",
"less-loader": "^11.1.4",
"playwright-chromium": "^1.42.0",
"powerbi-visuals-tools": "^5.4.2",
"powerbi-visuals-utils-testutils": "^6.1.0",
"playwright-chromium": "^1.43.0",
"powerbi-visuals-tools": "^5.4.3",
"powerbi-visuals-utils-testutils": "^6.1.1",
"style-loader": "^3.3.4",
"ts-loader": "^9.5.1",
"ts-node": "^10.9.2",
"typescript": "^5.2.2",
"webpack": "^5.90.3"
"webpack": "^5.91.0"
}
}
4 changes: 2 additions & 2 deletions pbiviz.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
"displayName": "GlobeMap",
"guid": "GlobeMap1447669447625",
"visualClassName": "GlobeMap",
"version": "3.3.2.0",
"version": "3.3.3.0",
"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": "https://aka.ms/customvisualscommunity",
"gitHubUrl": "https://github.com/Microsoft/powerbi-visuals-globemap"
},
"apiVersion": "5.8.0",
"apiVersion": "5.9.0",
"author": {
"name": "Microsoft",
"email": "[email protected]"
Expand Down
90 changes: 27 additions & 63 deletions src/cache/CacheManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,37 +12,31 @@ export class CacheManager {
private memoryCache: MemoryCache;
private localStorageCache: LocalStorageCache;
private bingGeocoder: BingGeocoder;
private coordsInLocalStorage: ILocationDictionary;
private localStorageService: IVisualLocalStorageV2Service;

constructor(localStorageService: IVisualLocalStorageV2Service) {
this.memoryCache = new MemoryCache(CacheSettings.MaxCacheSize, CacheSettings.MaxCacheSizeOverflow);
this.localStorageService = localStorageService;
this.bingGeocoder = new BingGeocoder();
this.coordsInLocalStorage = {};
constructor(localStorageService: IVisualLocalStorageV2Service, memoryCache?: MemoryCache, localStorageCache?: LocalStorageCache, bingGeocoder?: BingGeocoder) {
this.memoryCache = memoryCache ?? new MemoryCache(CacheSettings.MaxCacheSize, CacheSettings.MaxCacheSizeOverflow);
this.bingGeocoder = bingGeocoder ?? new BingGeocoder();
this.localStorageCache = localStorageCache ?? new LocalStorageCache(localStorageService);
}

private createLocalStorageCache(): IPromise2<LocalStorageCache, void> {
const cache: LocalStorageCache = new LocalStorageCache(this.localStorageService);

return cache.syncStatus()
.then(status => {
private syncLocalStorageCacheStatus(): Promise<void> {
return new Promise((resolve) => {
this.localStorageCache.syncStatus()
.then((status: powerbi.PrivilegeStatus) => {
console.log(`Received local storage status: ${status}`);
this.localStorageCache = cache;
return cache
});
resolve();
})
});
}

private getLocationsFromBing = async (locations: string[], locationsInMemory: string[], locationsDictionary: ILocationKeyDictionary): Promise<ILocationDictionary> => {
private getLocationsFromBing = async (locations: string[], locationsDictionary: ILocationKeyDictionary): Promise<ILocationDictionary> => {
console.log("Getting locations from Bing...");

locationsDictionary = locations
.reduce((obj, key) => ({ ...obj, [key]: locationsDictionary[key] }), {});

const coordinatesFromBing = await this.bingGeocoder.geocode(locations);
const resultObject = Object.assign({}, locationsInMemory, this.coordsInLocalStorage, coordinatesFromBing);

return resultObject;
return coordinatesFromBing;
}

public async loadCoordinates(locationsDictionary: ILocationKeyDictionary): Promise<ILocationDictionary> {
Expand All @@ -56,58 +50,28 @@ export class CacheManager {
let locations: string[] = Object.keys(locationsDictionary);

// Load from memory
const coordsInMemory: ILocationDictionary = await this.memoryCache.loadCoordinates(locations); // {"London": {"lat": 54, "lon": 34"}, "Moscow": {"lat": 64, "lon": 54"}
const coordsInMemory: ILocationDictionary = this.memoryCache.loadCoordinates(locations); // {"London": {"lat": 54, "lon": 34"}, "Moscow": {"lat": 64, "lon": 54"}
locationsInMemory = Object.keys(coordsInMemory);

locations = locations.filter(loc => !locationsInMemory.includes(loc));

if (locations.length === 0) {
result = Object.assign({}, coordsInMemory);
return result;
}


// Load from localStorage
if (isEmpty(this.coordsInLocalStorage)) {
return this.createLocalStorageCache()
.then(cache => cache.loadCoordinates(locations))
.then(async (coordinatesPromise: Promise<ILocationDictionary>) => {
const coordinates = await coordinatesPromise;
if (coordinates && Object.keys(coordinates).length > 0) {
if (isEmpty(this.coordsInLocalStorage)) {
this.coordsInLocalStorage = coordinates;
}

if (this.coordsInLocalStorage) {
const locationsInLocalStorage = Object.keys(this.coordsInLocalStorage);
locations = locations.filter(loc => !locationsInLocalStorage.includes(loc));

if (locations.length === 0) {
result = Object.assign({}, locationsInMemory, this.coordsInLocalStorage);
return result;
}

// Load additional locations from Bing
const locationsFromBing = await this.getLocationsFromBing(locations, locationsInMemory, locationsDictionary);
return locationsFromBing;
}
}
else {
console.log("Local storage is empty, will attempt to load the coordinates from Bing API");

const locationsFromBing = await this.getLocationsFromBing(locations, locationsInMemory, locationsDictionary);
return locationsFromBing;
}

}).catch(async (e) => {
console.error("Error while loading coordinates from local storage", e);
const locationsFromBing = await this.getLocationsFromBing(locations, locationsInMemory, locationsDictionary);
return locationsFromBing;
});
}
else {
return this.coordsInLocalStorage;
// Load from local storage
await this.syncLocalStorageCacheStatus();
const coordsInLocalStorage: ILocationDictionary = await this.localStorageCache.loadCoordinates(locations);
const locationsInlocalStorage = Object.keys(coordsInLocalStorage);
locations = locations.filter(loc => !locationsInlocalStorage.includes(loc));
if (locations.length === 0) {
result = Object.assign({}, coordsInMemory, coordsInLocalStorage);
return result;
}

// Load from Bing
const coordsFromBing: ILocationDictionary = await this.getLocationsFromBing(locations, locationsDictionary);
result = Object.assign({}, coordsInMemory, coordsInLocalStorage, coordsFromBing);
return result;
}

public saveCoordinates(coordinates: ILocationDictionary): IPromise2<void, void> {
Expand Down
6 changes: 3 additions & 3 deletions src/cache/MemoryCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@ export class MemoryCache {
this.maxCacheSizeOverflow = maxCacheSizeOverflow;
}

public async loadCoordinates(keys: string[]): Promise<ILocationDictionary> {
public loadCoordinates(keys: string[]): ILocationDictionary {
console.log("Loading from memory cache...");

if (!keys || !keys.length) {
return;
}

const locations: ILocationDictionary = {};
for (const key in this.geocodeCache) {
for (const key of keys) {
if (this.geocodeCache[key]) {
this.geocodeCache[key].hitCount++;
locations[key] = this.geocodeCache[key].coordinate;
Expand All @@ -44,7 +44,7 @@ export class MemoryCache {
return locations;
}

public saveCoordinates(coordinates: ILocationDictionary): Promise<void> {
public saveCoordinates(coordinates: ILocationDictionary): void {
console.log("Saving coordinates to memory cache...");

if (!coordinates) {
Expand Down
19 changes: 12 additions & 7 deletions src/geocoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,18 @@ export class BingGeocoder {
for (let i = 0; i < responseJson.resourceSets.length; i++) {
const currentSet: BingGeocodeResourceSet = responseJson.resourceSets[i];

const coordinates: number[] = currentSet.resources[0].point.coordinates;

const latitude: number = coordinates[0];
const longitude: number = coordinates[1];

const name: string = batch[i];
result[name] = { latitude, longitude };
if (!currentSet.resources[0]?.point){
console.log(`Could not get coordinates of '${batch[i]}' from Bing`);
}
else {
const coordinates: number[] = currentSet.resources[0].point.coordinates;

const latitude: number = coordinates[0];
const longitude: number = coordinates[1];

const name: string = batch[i];
result[name] = { latitude, longitude };
}
}
}));
} catch (e) {
Expand Down
46 changes: 22 additions & 24 deletions test/visualData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,28 @@ export class GlobeMapData extends TestDataViewBuilder {
80.237908
];

public coordinatesMock: ILocationDictionary = {
"addis ababa, ethiopia": {latitude: 9.03582859, longitude: 38.75241089},
"ahmedabad, india": {latitude: 23.0145092, longitude: 72.59175873},
"cairo, egypt": {latitude: 30.04348755, longitude: 31.23529243},
"cape town, south africa": {latitude: -33.92710876, longitude: 18.42006111},
"casablanca, morocco": {latitude: 33.59451294, longitude: -7.6200285},
"chennai, india": {latitude: 13.07209206, longitude: 80.20185852},
"durban, south africa": {latitude: -29.88188934, longitude: 30.98084259},
"jakarta, indonesia": {latitude: -6.17475653, longitude: 106.82707214},
"jeddah, saudi arabia": {latitude: 21.48730469, longitude: 39.18133545},
"lagos, nigeria": {latitude: 6.45505762, longitude: 3.39417958},
"lima, peru": {latitude: -12.06210613, longitude: -77.03652191},
"london, united kingdom": {latitude: 51.50740814, longitude: -0.12772401},
"mexico city, mexico": {latitude: 19.43267822, longitude: -99.13420868},
"new taipei city, republic of china": {latitude: 25.01170921, longitude: 121.46588135},
"riyadh, saudi arabia": {latitude: 24.69496918, longitude: 46.72412872},
"shanghai, china": {latitude: 31.23036957, longitude: 121.47370148},
"shenzhen, china": {latitude: 22.54368019, longitude: 114.0579071},
"surat, india": {latitude: 21.20350838, longitude: 72.83922577},
"tehran, iran": {latitude: 35.68925095, longitude: 51.38959885}
};

public valuesValue: number[] = getRandomNumbers(this.valuesSourceDestination.length, 10, 500);

public getDataView(columnNames?: string[]): DataView {
Expand Down Expand Up @@ -147,28 +169,4 @@ export class GlobeMapData extends TestDataViewBuilder {
}
], columnNames).build();
}

public getCoordinatesMock(): ILocationDictionary{
return {
"addis ababa, ethiopia": {latitude: 9.03582859, longitude: 38.75241089},
"ahmedabad, india": {latitude: 23.0145092, longitude: 72.59175873},
"cairo, egypt": {latitude: 30.04348755, longitude: 31.23529243},
"cape town, south africa": {latitude: -33.92710876, longitude: 18.42006111},
"casablanca, morocco": {latitude: 33.59451294, longitude: -7.6200285},
"chennai, india": {latitude: 13.07209206, longitude: 80.20185852},
"durban, south africa": {latitude: -29.88188934, longitude: 30.98084259},
"jakarta, indonesia": {latitude: -6.17475653, longitude: 106.82707214},
"jeddah, saudi arabia": {latitude: 21.48730469, longitude: 39.18133545},
"lagos, nigeria": {latitude: 6.45505762, longitude: 3.39417958},
"lima, peru": {latitude: -12.06210613, longitude: -77.03652191},
"london, united kingdom": {latitude: 51.50740814, longitude: -0.12772401},
"mexico city, mexico": {latitude: 19.43267822, longitude: -99.13420868},
"new taipei city, republic of china": {latitude: 25.01170921, longitude: 121.46588135},
"riyadh, saudi arabia": {latitude: 24.69496918, longitude: 46.72412872},
"shanghai, china": {latitude: 31.23036957, longitude: 121.47370148},
"shenzhen, china": {latitude: 22.54368019, longitude: 114.0579071},
"surat, india": {latitude: 21.20350838, longitude: 72.83922577},
"tehran, iran": {latitude: 35.68925095, longitude: 51.38959885}
};
}
}
Loading

0 comments on commit 1faa8ea

Please sign in to comment.