From 2eafc042adb92e5dda38a493ce796c2556eed53c Mon Sep 17 00:00:00 2001 From: Thomas <4159106+dtslvr@users.noreply.github.com> Date: Tue, 8 Jun 2021 21:59:46 +0200 Subject: [PATCH] Feature/add world map (#150) * Add a global heat map * Update changelog --- CHANGELOG.md | 4 + .../world-map-chart.component.html | 10 +++ .../world-map-chart.component.scss | 24 ++++++ .../world-map-chart.component.ts | 79 +++++++++++++++++++ .../world-map-chart/world-map-chart.module.ts | 13 +++ .../pages/tools/analysis/analysis-page.html | 55 +++++++++---- .../tools/analysis/analysis-page.module.ts | 2 + .../pages/tools/analysis/analysis-page.scss | 8 ++ apps/client/src/styles.scss | 2 + package.json | 1 + yarn.lock | 12 +++ 11 files changed, 193 insertions(+), 17 deletions(-) create mode 100644 apps/client/src/app/components/world-map-chart/world-map-chart.component.html create mode 100644 apps/client/src/app/components/world-map-chart/world-map-chart.component.scss create mode 100644 apps/client/src/app/components/world-map-chart/world-map-chart.component.ts create mode 100644 apps/client/src/app/components/world-map-chart/world-map-chart.module.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f2455b55..0de2a9470 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +- Added a global heat map to visualize positions by country + ## 1.12.0 - 06.06.2021 ### Added diff --git a/apps/client/src/app/components/world-map-chart/world-map-chart.component.html b/apps/client/src/app/components/world-map-chart/world-map-chart.component.html new file mode 100644 index 000000000..22abdeef6 --- /dev/null +++ b/apps/client/src/app/components/world-map-chart/world-map-chart.component.html @@ -0,0 +1,10 @@ + + +
diff --git a/apps/client/src/app/components/world-map-chart/world-map-chart.component.scss b/apps/client/src/app/components/world-map-chart/world-map-chart.component.scss new file mode 100644 index 000000000..e43cac3e9 --- /dev/null +++ b/apps/client/src/app/components/world-map-chart/world-map-chart.component.scss @@ -0,0 +1,24 @@ +:host { + display: block; + height: 100%; + + ::ng-deep { + .loader { + height: 100% !important; + } + + .svgMap-map-wrapper { + background: transparent; + + .svgMap-map-controls-wrapper { + display: none; + } + } + } +} + +:host-context(.is-dark-theme) { + .svgMap-tooltip { + background: var(--dark-background); + } +} diff --git a/apps/client/src/app/components/world-map-chart/world-map-chart.component.ts b/apps/client/src/app/components/world-map-chart/world-map-chart.component.ts new file mode 100644 index 000000000..0ab6e36d3 --- /dev/null +++ b/apps/client/src/app/components/world-map-chart/world-map-chart.component.ts @@ -0,0 +1,79 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + Input, + OnChanges, + OnDestroy, + OnInit +} from '@angular/core'; +import { primaryColorHex } from '@ghostfolio/common/config'; +import { getCssVariable, getTextColor } from '@ghostfolio/common/helper'; +import { Currency } from '@prisma/client'; +import svgMap from 'svgmap'; + +@Component({ + selector: 'gf-world-map-chart', + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: './world-map-chart.component.html', + styleUrls: ['./world-map-chart.component.scss'] +}) +export class WorldMapChartComponent implements OnChanges, OnDestroy, OnInit { + @Input() baseCurrency: Currency; + @Input() countries: { [code: string]: { name: string; value: number } }; + + public isLoading = true; + public svgMapElement; + + public constructor(private changeDetectorRef: ChangeDetectorRef) {} + + public ngOnInit() {} + + public ngOnChanges() { + if (this.countries) { + this.destroySvgMap(); + + this.initialize(); + } + } + + public ngOnDestroy() { + this.destroySvgMap(); + } + + private initialize() { + this.svgMapElement = new svgMap({ + colorMax: primaryColorHex, + colorMin: '#d3f4f3', + colorNoData: `rgba(${getTextColor()}, ${getCssVariable( + '--palette-foreground-divider-alpha' + )})`, + data: { + applyData: 'value', + data: { + value: { + format: `{0} ${this.baseCurrency}` + } + }, + values: this.countries + }, + hideFlag: true, + minZoom: 1.06, + maxZoom: 1.06, + targetElementID: 'svgMap' + }); + + setTimeout(() => { + this.isLoading = false; + + this.changeDetectorRef.markForCheck(); + }, 500); + } + + private destroySvgMap() { + this.svgMapElement?.mapWrapper?.remove(); + this.svgMapElement?.tooltip?.remove(); + + this.svgMapElement = null; + } +} diff --git a/apps/client/src/app/components/world-map-chart/world-map-chart.module.ts b/apps/client/src/app/components/world-map-chart/world-map-chart.module.ts new file mode 100644 index 000000000..823a9620c --- /dev/null +++ b/apps/client/src/app/components/world-map-chart/world-map-chart.module.ts @@ -0,0 +1,13 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; + +import { WorldMapChartComponent } from './world-map-chart.component'; + +@NgModule({ + declarations: [WorldMapChartComponent], + exports: [WorldMapChartComponent], + imports: [CommonModule, NgxSkeletonLoaderModule], + providers: [] +}) +export class GfWorldMapChartModule {} diff --git a/apps/client/src/app/pages/tools/analysis/analysis-page.html b/apps/client/src/app/pages/tools/analysis/analysis-page.html index 0b0ff2310..de4a75a1d 100644 --- a/apps/client/src/app/pages/tools/analysis/analysis-page.html +++ b/apps/client/src/app/pages/tools/analysis/analysis-page.html @@ -105,7 +105,7 @@
- By Continent + By Currency @@ -127,7 +127,7 @@
- By Country + By Exchange @@ -149,7 +149,7 @@
- By Currency + By Continent @@ -171,7 +171,7 @@
- By Exchange + By Country @@ -211,7 +211,28 @@
-
+
+
+ + + Global Heat Map + + + + + + +
+
+
diff --git a/apps/client/src/app/pages/tools/analysis/analysis-page.module.ts b/apps/client/src/app/pages/tools/analysis/analysis-page.module.ts index c3081dc9e..3c714a320 100644 --- a/apps/client/src/app/pages/tools/analysis/analysis-page.module.ts +++ b/apps/client/src/app/pages/tools/analysis/analysis-page.module.ts @@ -6,6 +6,7 @@ import { PortfolioPositionsChartModule } from '@ghostfolio/client/components/por import { PortfolioProportionChartModule } from '@ghostfolio/client/components/portfolio-proportion-chart/portfolio-proportion-chart.module'; import { GfPositionsTableModule } from '@ghostfolio/client/components/positions-table/positions-table.module'; import { GfToggleModule } from '@ghostfolio/client/components/toggle/toggle.module'; +import { GfWorldMapChartModule } from '@ghostfolio/client/components/world-map-chart/world-map-chart.module'; import { AnalysisPageRoutingModule } from './analysis-page-routing.module'; import { AnalysisPageComponent } from './analysis-page.component'; @@ -19,6 +20,7 @@ import { AnalysisPageComponent } from './analysis-page.component'; GfInvestmentChartModule, GfPositionsTableModule, GfToggleModule, + GfWorldMapChartModule, MatCardModule, PortfolioPositionsChartModule, PortfolioProportionChartModule diff --git a/apps/client/src/app/pages/tools/analysis/analysis-page.scss b/apps/client/src/app/pages/tools/analysis/analysis-page.scss index 5e986a89a..db8237522 100644 --- a/apps/client/src/app/pages/tools/analysis/analysis-page.scss +++ b/apps/client/src/app/pages/tools/analysis/analysis-page.scss @@ -7,6 +7,14 @@ } } + .world-map-chart { + .mat-card { + .mat-card-content { + aspect-ratio: 16 / 9; + } + } + } + .mat-card { .mat-card-header { ::ng-deep { diff --git a/apps/client/src/styles.scss b/apps/client/src/styles.scss index fbb597475..52c9d1f22 100644 --- a/apps/client/src/styles.scss +++ b/apps/client/src/styles.scss @@ -4,6 +4,8 @@ @import '~angular-material-css-vars/main'; +@import '~svgmap/dist/svgMap'; + $mat-css-dark-theme-selector: '.is-dark-theme'; $mat-css-light-theme-selector: '.is-light-theme'; diff --git a/package.json b/package.json index de9bd674e..46bb6ec3c 100644 --- a/package.json +++ b/package.json @@ -97,6 +97,7 @@ "reflect-metadata": "0.1.13", "round-to": "5.0.0", "rxjs": "6.6.7", + "svgmap": "2.1.1", "uuid": "8.3.2", "yahoo-finance": "0.3.6", "zone.js": "0.11.4" diff --git a/yarn.lock b/yarn.lock index 99efb34ef..42a354e51 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12382,6 +12382,18 @@ supports-hyperlinks@^2.0.0: has-flag "^4.0.0" supports-color "^7.0.0" +svg-pan-zoom@^3.6.1: + version "3.6.1" + resolved "https://registry.yarnpkg.com/svg-pan-zoom/-/svg-pan-zoom-3.6.1.tgz#f880a1bb32d18e9c625d7715350bebc269b450cf" + integrity sha512-JaKkGHHfGvRrcMPdJWkssLBeWqM+Isg/a09H7kgNNajT1cX5AztDTNs+C8UzpCxjCTRrG34WbquwaovZbmSk9g== + +svgmap@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/svgmap/-/svgmap-2.1.1.tgz#355c259cf4e04b20d2d39bab05d0e718ade942ff" + integrity sha512-1blZYMYDXq8H3xykzgBJRh5q+XPd5JLOJ8K7UuZI6ab2D3hngiVcr+Z1olfy7DH9Xf9AOCTpt4Id7iVD8cKD0A== + dependencies: + svg-pan-zoom "^3.6.1" + svgo@^1.0.0: version "1.3.2" resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.3.2.tgz#b6dc511c063346c9e415b81e43401145b96d4167"