Feature/add world map (#150)

* Add a global heat map

* Update changelog
pull/151/head
Thomas 3 years ago committed by GitHub
parent 74954bc51d
commit 2eafc042ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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

@ -0,0 +1,10 @@
<ngx-skeleton-loader
*ngIf="isLoading"
animation="pulse"
class="h-100"
[theme]="{
width: '100%'
}"
></ngx-skeleton-loader>
<div class="align-items-center d-flex h-100 w-100" id="svgMap"></div>

@ -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);
}
}

@ -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;
}
}

@ -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 {}

@ -105,7 +105,7 @@
<div class="col-md-6">
<mat-card class="mb-3">
<mat-card-header class="w-100">
<mat-card-title i18n>By Continent</mat-card-title>
<mat-card-title i18n>By Currency</mat-card-title>
<gf-toggle
[defaultValue]="period"
[isLoading]="false"
@ -115,11 +115,11 @@
</mat-card-header>
<mat-card-content>
<gf-portfolio-proportion-chart
key="name"
key="currency"
[baseCurrency]="user?.settings?.baseCurrency"
[isInPercent]="false"
[isInPercent]="true"
[locale]="user?.settings?.locale"
[positions]="continents"
[positions]="positions"
></gf-portfolio-proportion-chart>
</mat-card-content>
</mat-card>
@ -127,7 +127,7 @@
<div class="col-md-6">
<mat-card class="mb-3">
<mat-card-header class="w-100">
<mat-card-title i18n>By Country</mat-card-title>
<mat-card-title i18n>By Exchange</mat-card-title>
<gf-toggle
[defaultValue]="period"
[isLoading]="false"
@ -137,11 +137,11 @@
</mat-card-header>
<mat-card-content>
<gf-portfolio-proportion-chart
key="name"
key="exchange"
[baseCurrency]="user?.settings?.baseCurrency"
[isInPercent]="false"
[isInPercent]="true"
[locale]="user?.settings?.locale"
[positions]="countries"
[positions]="positions"
></gf-portfolio-proportion-chart>
</mat-card-content>
</mat-card>
@ -149,7 +149,7 @@
<div class="col-md-6">
<mat-card class="mb-3">
<mat-card-header class="w-100">
<mat-card-title i18n>By Currency</mat-card-title>
<mat-card-title i18n>By Continent</mat-card-title>
<gf-toggle
[defaultValue]="period"
[isLoading]="false"
@ -159,11 +159,11 @@
</mat-card-header>
<mat-card-content>
<gf-portfolio-proportion-chart
key="currency"
key="name"
[baseCurrency]="user?.settings?.baseCurrency"
[isInPercent]="true"
[isInPercent]="false"
[locale]="user?.settings?.locale"
[positions]="positions"
[positions]="continents"
></gf-portfolio-proportion-chart>
</mat-card-content>
</mat-card>
@ -171,7 +171,7 @@
<div class="col-md-6">
<mat-card class="mb-3">
<mat-card-header class="w-100">
<mat-card-title i18n>By Exchange</mat-card-title>
<mat-card-title i18n>By Country</mat-card-title>
<gf-toggle
[defaultValue]="period"
[isLoading]="false"
@ -181,11 +181,11 @@
</mat-card-header>
<mat-card-content>
<gf-portfolio-proportion-chart
key="exchange"
key="name"
[baseCurrency]="user?.settings?.baseCurrency"
[isInPercent]="true"
[isInPercent]="false"
[locale]="user?.settings?.locale"
[positions]="positions"
[positions]="countries"
></gf-portfolio-proportion-chart>
</mat-card-content>
</mat-card>
@ -211,7 +211,28 @@
</mat-card>
</div>
</div>
<div class="d-none d-sm-block row">
<div class="row world-map-chart">
<div class="col-lg">
<mat-card class="mb-3">
<mat-card-header class="w-100">
<mat-card-title i18n>Global Heat Map</mat-card-title>
<gf-toggle
[defaultValue]="period"
[isLoading]="false"
[options]="periodOptions"
(change)="onChangePeriod($event.value)"
></gf-toggle>
</mat-card-header>
<mat-card-content>
<gf-world-map-chart
[baseCurrency]="user?.settings?.baseCurrency"
[countries]="countries"
></gf-world-map-chart>
</mat-card-content>
</mat-card>
</div>
</div>
<div class="row">
<div class="col-lg">
<mat-card class="mb-3">
<mat-card-header>

@ -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

@ -7,6 +7,14 @@
}
}
.world-map-chart {
.mat-card {
.mat-card-content {
aspect-ratio: 16 / 9;
}
}
}
.mat-card {
.mat-card-header {
::ng-deep {

@ -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';

@ -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"

@ -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"

Loading…
Cancel
Save