diff --git a/apps/client/src/app/components/portfolio-proportion-chart/portfolio-proportion-chart.component.html b/apps/client/src/app/components/portfolio-proportion-chart/portfolio-proportion-chart.component.html
deleted file mode 100644
index 8e86acd33..000000000
--- a/apps/client/src/app/components/portfolio-proportion-chart/portfolio-proportion-chart.component.html
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
diff --git a/apps/client/src/app/components/portfolio-proportion-chart/portfolio-proportion-chart.component.scss b/apps/client/src/app/components/portfolio-proportion-chart/portfolio-proportion-chart.component.scss
deleted file mode 100644
index 5d4e87f30..000000000
--- a/apps/client/src/app/components/portfolio-proportion-chart/portfolio-proportion-chart.component.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-:host {
- display: block;
-}
diff --git a/apps/client/src/app/components/portfolio-proportion-chart/portfolio-proportion-chart.component.ts b/apps/client/src/app/components/portfolio-proportion-chart/portfolio-proportion-chart.component.ts
deleted file mode 100644
index e98d594a1..000000000
--- a/apps/client/src/app/components/portfolio-proportion-chart/portfolio-proportion-chart.component.ts
+++ /dev/null
@@ -1,322 +0,0 @@
-import {
- ChangeDetectionStrategy,
- Component,
- Input,
- OnChanges,
- OnDestroy,
- OnInit,
- ViewChild
-} from '@angular/core';
-import { UNKNOWN_KEY } from '@ghostfolio/common/config';
-import { getTextColor } from '@ghostfolio/common/helper';
-import { PortfolioPosition } from '@ghostfolio/common/interfaces';
-import { Currency } from '@prisma/client';
-import { Tooltip } from 'chart.js';
-import { LinearScale } from 'chart.js';
-import { ArcElement } from 'chart.js';
-import { DoughnutController } from 'chart.js';
-import { Chart } from 'chart.js';
-import ChartDataLabels from 'chartjs-plugin-datalabels';
-import * as Color from 'color';
-
-@Component({
- selector: 'gf-portfolio-proportion-chart',
- changeDetection: ChangeDetectionStrategy.OnPush,
- templateUrl: './portfolio-proportion-chart.component.html',
- styleUrls: ['./portfolio-proportion-chart.component.scss']
-})
-export class PortfolioProportionChartComponent
- implements OnChanges, OnDestroy, OnInit
-{
- @Input() baseCurrency: Currency;
- @Input() isInPercent: boolean;
- @Input() keys: string[];
- @Input() locale: string;
- @Input() maxItems?: number;
- @Input() showLabels = false;
- @Input() positions: {
- [symbol: string]: Pick & { value: number };
- };
-
- @ViewChild('chartCanvas') chartCanvas;
-
- public chart: Chart;
- public isLoading = true;
-
- private colorMap: {
- [symbol: string]: string;
- } = {
- [UNKNOWN_KEY]: `rgba(${getTextColor()}, 0.12)`
- };
-
- public constructor() {
- Chart.register(
- ArcElement,
- ChartDataLabels,
- DoughnutController,
- LinearScale,
- Tooltip
- );
- }
-
- public ngOnInit() {}
-
- public ngOnChanges() {
- if (this.positions) {
- this.initialize();
- }
- }
-
- public ngOnDestroy() {
- this.chart?.destroy();
- }
-
- private initialize() {
- this.isLoading = true;
- const chartData: {
- [symbol: string]: {
- color?: string;
- subCategory: { [symbol: string]: { value: number } };
- value: number;
- };
- } = {};
-
- Object.keys(this.positions).forEach((symbol) => {
- if (this.positions[symbol][this.keys[0]]) {
- if (chartData[this.positions[symbol][this.keys[0]]]) {
- chartData[this.positions[symbol][this.keys[0]]].value +=
- this.positions[symbol].value;
-
- if (
- chartData[this.positions[symbol][this.keys[0]]].subCategory[
- this.positions[symbol][this.keys[1]]
- ]
- ) {
- chartData[this.positions[symbol][this.keys[0]]].subCategory[
- this.positions[symbol][this.keys[1]]
- ].value += this.positions[symbol].value;
- } else {
- chartData[this.positions[symbol][this.keys[0]]].subCategory[
- this.positions[symbol][this.keys[1]] ?? UNKNOWN_KEY
- ] = { value: this.positions[symbol].value };
- }
- } else {
- chartData[this.positions[symbol][this.keys[0]]] = {
- subCategory: {},
- value: this.positions[symbol].value
- };
-
- if (this.positions[symbol][this.keys[1]]) {
- chartData[this.positions[symbol][this.keys[0]]].subCategory = {
- [this.positions[symbol][this.keys[1]]]: {
- value: this.positions[symbol].value
- }
- };
- }
- }
- } else {
- if (chartData[UNKNOWN_KEY]) {
- chartData[UNKNOWN_KEY].value += this.positions[symbol].value;
- } else {
- chartData[UNKNOWN_KEY] = {
- subCategory: this.keys[1]
- ? { [this.keys[1]]: { value: 0 } }
- : undefined,
- value: this.positions[symbol].value
- };
- }
- }
- });
-
- let chartDataSorted = Object.entries(chartData)
- .sort((a, b) => {
- return a[1].value - b[1].value;
- })
- .reverse();
-
- if (this.maxItems && chartDataSorted.length > this.maxItems) {
- // Add surplus items to unknown group
- const rest = chartDataSorted.splice(
- this.maxItems,
- chartDataSorted.length - 1
- );
-
- let unknownItem = chartDataSorted.find((charDataItem) => {
- return charDataItem[0] === UNKNOWN_KEY;
- });
-
- if (!unknownItem) {
- const index = chartDataSorted.push([
- UNKNOWN_KEY,
- { subCategory: {}, value: 0 }
- ]);
- unknownItem = chartDataSorted[index];
- }
-
- rest.forEach((restItem) => {
- if (unknownItem?.[1]) {
- unknownItem[1] = {
- subCategory: {},
- value: unknownItem[1].value + restItem[1].value
- };
- }
- });
-
- // Sort data again
- chartDataSorted = chartDataSorted
- .sort((a, b) => {
- return a[1].value - b[1].value;
- })
- .reverse();
- }
-
- chartDataSorted.forEach(([symbol, item], index) => {
- if (this.colorMap[symbol]) {
- // Reuse color
- item.color = this.colorMap[symbol];
- } else {
- const color =
- this.getColorPalette()[index % this.getColorPalette().length];
-
- // Store color for reuse
- this.colorMap[symbol] = color;
-
- item.color = color;
- }
- });
-
- const backgroundColorSubCategory: string[] = [];
- const dataSubCategory: number[] = [];
- const labelSubCategory: string[] = [];
-
- chartDataSorted.forEach(([, item]) => {
- let lightnessRatio = 0.2;
-
- Object.keys(item.subCategory).forEach((subCategory) => {
- backgroundColorSubCategory.push(
- Color(item.color).lighten(lightnessRatio).hex()
- );
- dataSubCategory.push(item.subCategory[subCategory].value);
- labelSubCategory.push(subCategory);
-
- lightnessRatio += 0.1;
- });
- });
-
- const datasets = [
- {
- backgroundColor: chartDataSorted.map(([, item]) => {
- return item.color;
- }),
- borderWidth: 0,
- data: chartDataSorted.map(([, item]) => {
- return item.value;
- })
- }
- ];
-
- let labels = chartDataSorted.map(([label]) => {
- return label;
- });
-
- if (this.keys[1]) {
- datasets.unshift({
- backgroundColor: backgroundColorSubCategory,
- borderWidth: 0,
- data: dataSubCategory
- });
-
- labels = labelSubCategory.concat(labels);
- }
-
- const data = {
- datasets,
- labels
- };
-
- if (this.chartCanvas) {
- if (this.chart) {
- this.chart.data = data;
- this.chart.update();
- } else {
- this.chart = new Chart(this.chartCanvas.nativeElement, {
- data,
- options: {
- cutout: '70%',
- layout: {
- padding: this.showLabels === true ? 100 : 0
- },
- plugins: {
- datalabels: {
- color: (context) => {
- return this.getColorPalette()[
- context.dataIndex % this.getColorPalette().length
- ];
- },
- display: this.showLabels === true ? 'auto' : false,
- labels: {
- index: {
- align: 'end',
- anchor: 'end',
- formatter: (value, context) => {
- return value > 0
- ? context.chart.data.labels[context.dataIndex]
- : '';
- },
- offset: 8
- }
- }
- },
- legend: { display: false },
- tooltip: {
- callbacks: {
- label: (context) => {
- const labelIndex =
- (data.datasets[context.datasetIndex - 1]?.data?.length ??
- 0) + context.dataIndex;
- const label = context.chart.data.labels[labelIndex];
-
- if (this.isInPercent) {
- const value = 100 * context.raw;
- return `${label} (${value.toFixed(2)}%)`;
- } else {
- const value = context.raw;
- return `${label} (${value.toLocaleString(this.locale, {
- maximumFractionDigits: 2,
- minimumFractionDigits: 2
- })} ${this.baseCurrency})`;
- }
- }
- }
- }
- }
- },
- type: 'doughnut'
- });
- }
-
- this.isLoading = false;
- }
- }
-
- /**
- * Color palette, inspired by https://yeun.github.io/open-color
- */
- private getColorPalette() {
- //
- return [
- '#329af0', // blue 5
- '#20c997', // teal 5
- '#94d82d', // lime 5
- '#ff922b', // orange 5
- '#f06595', // pink 5
- '#845ef7', // violet 5
- '#5c7cfa', // indigo 5
- '#22b8cf', // cyan 5
- '#51cf66', // green 5
- '#fcc419', // yellow 5
- '#ff6b6b', // red 5
- '#cc5de8' // grape 5
- ];
- }
-}
diff --git a/apps/client/src/app/components/portfolio-proportion-chart/portfolio-proportion-chart.module.ts b/apps/client/src/app/components/portfolio-proportion-chart/portfolio-proportion-chart.module.ts
deleted file mode 100644
index 9472b1cfa..000000000
--- a/apps/client/src/app/components/portfolio-proportion-chart/portfolio-proportion-chart.module.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { CommonModule } from '@angular/common';
-import { NgModule } from '@angular/core';
-import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
-
-import { PortfolioProportionChartComponent } from './portfolio-proportion-chart.component';
-
-@NgModule({
- declarations: [PortfolioProportionChartComponent],
- exports: [PortfolioProportionChartComponent],
- imports: [CommonModule, NgxSkeletonLoaderModule],
- providers: []
-})
-export class GfPortfolioProportionChartModule {}
diff --git a/apps/client/src/app/pages/portfolio/allocations/allocations-page.module.ts b/apps/client/src/app/pages/portfolio/allocations/allocations-page.module.ts
index 26ef795ff..809b29bd7 100644
--- a/apps/client/src/app/pages/portfolio/allocations/allocations-page.module.ts
+++ b/apps/client/src/app/pages/portfolio/allocations/allocations-page.module.ts
@@ -1,10 +1,10 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatCardModule } from '@angular/material/card';
-import { GfPortfolioProportionChartModule } 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 { GfPortfolioProportionChartModule } from '@ghostfolio/ui/portfolio-proportion-chart/portfolio-proportion-chart.module';
import { AllocationsPageRoutingModule } from './allocations-page-routing.module';
import { AllocationsPageComponent } from './allocations-page.component';
diff --git a/libs/common/src/lib/helper.ts b/libs/common/src/lib/helper.ts
index cfbbcd603..572724621 100644
--- a/libs/common/src/lib/helper.ts
+++ b/libs/common/src/lib/helper.ts
@@ -123,7 +123,7 @@ export function resolveFearAndGreedIndex(aValue: number) {
return { emoji: 'ð', text: 'Neutral' };
} else if (aValue < 75) {
return { emoji: 'ð', text: 'Greed' };
- } else if (aValue >= 75) {
+ } else {
return { emoji: 'ðĪŠ', text: 'Extreme Greed' };
}
}
diff --git a/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.stories.ts b/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.stories.ts
index 3ed928745..6d541c911 100644
--- a/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.stories.ts
+++ b/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.stories.ts
@@ -1,14 +1,17 @@
+import { CommonModule } from '@angular/common';
import { Meta, Story, moduleMetadata } from '@storybook/angular';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { PortfolioProportionChartComponent } from './portfolio-proportion-chart.component';
+import { Currency } from '.prisma/client';
export default {
- title: 'Value',
+ title: 'Portfolio Proportion Chart',
component: PortfolioProportionChartComponent,
decorators: [
moduleMetadata({
- imports: [NgxSkeletonLoaderModule]
+ declarations: [PortfolioProportionChartComponent],
+ imports: [CommonModule, NgxSkeletonLoaderModule]
})
]
} as Meta;
@@ -19,5 +22,17 @@ const Template: Story = (
props: args
});
-export const Loading = Template.bind({});
-Loading.args = {};
+export const Simple = Template.bind({});
+Simple.args = {
+ baseCurrency: Currency.USD,
+ keys: ['name'],
+ locale: 'en-US',
+ positions: {
+ Africa: { name: 'Africa', value: 983.22461479889288 },
+ Asia: { name: 'Asia', value: 12074.754633964973 },
+ Europe: { name: 'Europe', value: 34432.837085290535 },
+ 'North America': { name: 'North America', value: 26539.89987780503 },
+ Oceania: { name: 'Oceania', value: 1402.220605072031 },
+ 'South America': { name: 'South America', value: 4938.25202180719859 }
+ }
+};
diff --git a/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts b/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts
index 261c97d08..5001fc758 100644
--- a/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts
+++ b/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts
@@ -1,10 +1,11 @@
import {
+ AfterViewInit,
ChangeDetectionStrategy,
Component,
+ ElementRef,
Input,
OnChanges,
OnDestroy,
- OnInit,
ViewChild
} from '@angular/core';
import { UNKNOWN_KEY } from '@ghostfolio/common/config';
@@ -25,7 +26,9 @@ import * as Color from 'color';
templateUrl: './portfolio-proportion-chart.component.html',
styleUrls: ['./portfolio-proportion-chart.component.scss']
})
-export class PortfolioProportionChartComponent implements OnChanges, OnDestroy {
+export class PortfolioProportionChartComponent
+ implements AfterViewInit, OnChanges, OnDestroy
+{
@Input() baseCurrency: Currency;
@Input() isInPercent = false;
@Input() keys: string[] = [];
@@ -36,7 +39,7 @@ export class PortfolioProportionChartComponent implements OnChanges, OnDestroy {
[symbol: string]: Pick & { value: number };
} = {};
- @ViewChild('chartCanvas') chartCanvas;
+ @ViewChild('chartCanvas') chartCanvas: ElementRef;
public chart: Chart;
public isLoading = true;
@@ -57,6 +60,12 @@ export class PortfolioProportionChartComponent implements OnChanges, OnDestroy {
);
}
+ public ngAfterViewInit() {
+ if (this.positions) {
+ this.initialize();
+ }
+ }
+
public ngOnChanges() {
if (this.positions) {
this.initialize();
diff --git a/libs/ui/tsconfig.json b/libs/ui/tsconfig.json
index 93b76eee5..849a0b361 100644
--- a/libs/ui/tsconfig.json
+++ b/libs/ui/tsconfig.json
@@ -15,7 +15,7 @@
],
"compilerOptions": {
"forceConsistentCasingInFileNames": true,
- "strict": true,
+ "strict": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},