Feature/improve color assignment with annualized performance in treemap chart (#3657)
* Improve color assignment * Update changelog
This commit is contained in:
parent
2bbad8f4b0
commit
c34959896c
@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
- Improved the color assignment in the chart of the holdings tab on the home page (experimental)
|
||||||
- Enabled Catalan (`ca`) as an option in the user settings (experimental)
|
- Enabled Catalan (`ca`) as an option in the user settings (experimental)
|
||||||
- Enabled Polish (`pl`) as an option in the user settings (experimental)
|
- Enabled Polish (`pl`) as an option in the user settings (experimental)
|
||||||
- Improved the language localization for Portuguese (`pt`)
|
- Improved the language localization for Portuguese (`pt`)
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
|
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
|
||||||
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
|
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
|
||||||
import { getInterval } from '@ghostfolio/api/helper/portfolio.helper';
|
|
||||||
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor';
|
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor';
|
||||||
import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor';
|
import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor';
|
||||||
|
import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper';
|
||||||
import type {
|
import type {
|
||||||
AssetProfileIdentifier,
|
AssetProfileIdentifier,
|
||||||
BenchmarkMarketDataDetails,
|
BenchmarkMarketDataDetails,
|
||||||
@ -113,7 +113,7 @@ export class BenchmarkController {
|
|||||||
@Param('symbol') symbol: string,
|
@Param('symbol') symbol: string,
|
||||||
@Query('range') dateRange: DateRange = 'max'
|
@Query('range') dateRange: DateRange = 'max'
|
||||||
): Promise<BenchmarkMarketDataDetails> {
|
): Promise<BenchmarkMarketDataDetails> {
|
||||||
const { endDate, startDate } = getInterval(
|
const { endDate, startDate } = getIntervalFromDateRange(
|
||||||
dateRange,
|
dateRange,
|
||||||
new Date(startDateString)
|
new Date(startDateString)
|
||||||
);
|
);
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
|
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
|
||||||
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
|
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
|
||||||
import { getInterval } from '@ghostfolio/api/helper/portfolio.helper';
|
|
||||||
import { RedactValuesInResponseInterceptor } from '@ghostfolio/api/interceptors/redact-values-in-response/redact-values-in-response.interceptor';
|
import { RedactValuesInResponseInterceptor } from '@ghostfolio/api/interceptors/redact-values-in-response/redact-values-in-response.interceptor';
|
||||||
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor';
|
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor';
|
||||||
import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor';
|
import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor';
|
||||||
import { ApiService } from '@ghostfolio/api/services/api/api.service';
|
import { ApiService } from '@ghostfolio/api/services/api/api.service';
|
||||||
import { DataGatheringService } from '@ghostfolio/api/services/data-gathering/data-gathering.service';
|
import { DataGatheringService } from '@ghostfolio/api/services/data-gathering/data-gathering.service';
|
||||||
import { ImpersonationService } from '@ghostfolio/api/services/impersonation/impersonation.service';
|
import { ImpersonationService } from '@ghostfolio/api/services/impersonation/impersonation.service';
|
||||||
|
import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper';
|
||||||
import {
|
import {
|
||||||
DATA_GATHERING_QUEUE_PRIORITY_HIGH,
|
DATA_GATHERING_QUEUE_PRIORITY_HIGH,
|
||||||
HEADER_KEY_IMPERSONATION
|
HEADER_KEY_IMPERSONATION
|
||||||
@ -110,7 +110,7 @@ export class OrderController {
|
|||||||
let startDate: Date;
|
let startDate: Date;
|
||||||
|
|
||||||
if (dateRange) {
|
if (dateRange) {
|
||||||
({ endDate, startDate } = getInterval(dateRange));
|
({ endDate, startDate } = getIntervalFromDateRange(dateRange));
|
||||||
}
|
}
|
||||||
|
|
||||||
const filters = this.apiService.buildFiltersFromQueryParams({
|
const filters = this.apiService.buildFiltersFromQueryParams({
|
||||||
|
@ -4,13 +4,11 @@ import { PortfolioOrder } from '@ghostfolio/api/app/portfolio/interfaces/portfol
|
|||||||
import { TransactionPointSymbol } from '@ghostfolio/api/app/portfolio/interfaces/transaction-point-symbol.interface';
|
import { TransactionPointSymbol } from '@ghostfolio/api/app/portfolio/interfaces/transaction-point-symbol.interface';
|
||||||
import { TransactionPoint } from '@ghostfolio/api/app/portfolio/interfaces/transaction-point.interface';
|
import { TransactionPoint } from '@ghostfolio/api/app/portfolio/interfaces/transaction-point.interface';
|
||||||
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
|
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
|
||||||
import {
|
import { getFactor } from '@ghostfolio/api/helper/portfolio.helper';
|
||||||
getFactor,
|
|
||||||
getInterval
|
|
||||||
} from '@ghostfolio/api/helper/portfolio.helper';
|
|
||||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
|
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
|
||||||
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
|
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
|
||||||
import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces';
|
import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces';
|
||||||
|
import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper';
|
||||||
import { MAX_CHART_ITEMS } from '@ghostfolio/common/config';
|
import { MAX_CHART_ITEMS } from '@ghostfolio/common/config';
|
||||||
import {
|
import {
|
||||||
DATE_FORMAT,
|
DATE_FORMAT,
|
||||||
@ -133,7 +131,7 @@ export abstract class PortfolioCalculator {
|
|||||||
this.useCache = useCache;
|
this.useCache = useCache;
|
||||||
this.userId = userId;
|
this.userId = userId;
|
||||||
|
|
||||||
const { endDate, startDate } = getInterval(dateRange);
|
const { endDate, startDate } = getIntervalFromDateRange(dateRange);
|
||||||
|
|
||||||
this.endDate = endDate;
|
this.endDate = endDate;
|
||||||
this.startDate = startDate;
|
this.startDate = startDate;
|
||||||
@ -303,7 +301,7 @@ export abstract class PortfolioCalculator {
|
|||||||
const feeInBaseCurrency = item.fee.mul(
|
const feeInBaseCurrency = item.fee.mul(
|
||||||
exchangeRatesByCurrency[`${item.currency}${this.currency}`]?.[
|
exchangeRatesByCurrency[`${item.currency}${this.currency}`]?.[
|
||||||
lastTransactionPoint.date
|
lastTransactionPoint.date
|
||||||
]
|
] ?? 1
|
||||||
);
|
);
|
||||||
|
|
||||||
const marketPriceInBaseCurrency = (
|
const marketPriceInBaseCurrency = (
|
||||||
@ -433,7 +431,10 @@ export abstract class PortfolioCalculator {
|
|||||||
dateRange?: DateRange;
|
dateRange?: DateRange;
|
||||||
withDataDecimation?: boolean;
|
withDataDecimation?: boolean;
|
||||||
}): Promise<HistoricalDataItem[]> {
|
}): Promise<HistoricalDataItem[]> {
|
||||||
const { endDate, startDate } = getInterval(dateRange, this.getStartDate());
|
const { endDate, startDate } = getIntervalFromDateRange(
|
||||||
|
dateRange,
|
||||||
|
this.getStartDate()
|
||||||
|
);
|
||||||
|
|
||||||
const daysInMarket = differenceInDays(endDate, startDate) + 1;
|
const daysInMarket = differenceInDays(endDate, startDate) + 1;
|
||||||
const step = withDataDecimation
|
const step = withDataDecimation
|
||||||
|
@ -7,7 +7,6 @@ import {
|
|||||||
hasNotDefinedValuesInObject,
|
hasNotDefinedValuesInObject,
|
||||||
nullifyValuesInObject
|
nullifyValuesInObject
|
||||||
} from '@ghostfolio/api/helper/object.helper';
|
} from '@ghostfolio/api/helper/object.helper';
|
||||||
import { getInterval } from '@ghostfolio/api/helper/portfolio.helper';
|
|
||||||
import { RedactValuesInResponseInterceptor } from '@ghostfolio/api/interceptors/redact-values-in-response/redact-values-in-response.interceptor';
|
import { RedactValuesInResponseInterceptor } from '@ghostfolio/api/interceptors/redact-values-in-response/redact-values-in-response.interceptor';
|
||||||
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor';
|
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor';
|
||||||
import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor';
|
import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor';
|
||||||
@ -15,6 +14,7 @@ import { ApiService } from '@ghostfolio/api/services/api/api.service';
|
|||||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
|
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
|
||||||
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
|
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
|
||||||
import { ImpersonationService } from '@ghostfolio/api/services/impersonation/impersonation.service';
|
import { ImpersonationService } from '@ghostfolio/api/services/impersonation/impersonation.service';
|
||||||
|
import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper';
|
||||||
import {
|
import {
|
||||||
DEFAULT_CURRENCY,
|
DEFAULT_CURRENCY,
|
||||||
HEADER_KEY_IMPERSONATION
|
HEADER_KEY_IMPERSONATION
|
||||||
@ -247,7 +247,7 @@ export class PortfolioController {
|
|||||||
await this.impersonationService.validateImpersonationId(impersonationId);
|
await this.impersonationService.validateImpersonationId(impersonationId);
|
||||||
const userCurrency = this.request.user.Settings.settings.baseCurrency;
|
const userCurrency = this.request.user.Settings.settings.baseCurrency;
|
||||||
|
|
||||||
const { endDate, startDate } = getInterval(dateRange);
|
const { endDate, startDate } = getIntervalFromDateRange(dateRange);
|
||||||
|
|
||||||
const { activities } = await this.orderService.getOrders({
|
const { activities } = await this.orderService.getOrders({
|
||||||
endDate,
|
endDate,
|
||||||
|
@ -4,10 +4,7 @@ import { CashDetails } from '@ghostfolio/api/app/account/interfaces/cash-details
|
|||||||
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
||||||
import { OrderService } from '@ghostfolio/api/app/order/order.service';
|
import { OrderService } from '@ghostfolio/api/app/order/order.service';
|
||||||
import { UserService } from '@ghostfolio/api/app/user/user.service';
|
import { UserService } from '@ghostfolio/api/app/user/user.service';
|
||||||
import {
|
import { getFactor } from '@ghostfolio/api/helper/portfolio.helper';
|
||||||
getFactor,
|
|
||||||
getInterval
|
|
||||||
} from '@ghostfolio/api/helper/portfolio.helper';
|
|
||||||
import { AccountClusterRiskCurrentInvestment } from '@ghostfolio/api/models/rules/account-cluster-risk/current-investment';
|
import { AccountClusterRiskCurrentInvestment } from '@ghostfolio/api/models/rules/account-cluster-risk/current-investment';
|
||||||
import { AccountClusterRiskSingleAccount } from '@ghostfolio/api/models/rules/account-cluster-risk/single-account';
|
import { AccountClusterRiskSingleAccount } from '@ghostfolio/api/models/rules/account-cluster-risk/single-account';
|
||||||
import { CurrencyClusterRiskBaseCurrencyCurrentInvestment } from '@ghostfolio/api/models/rules/currency-cluster-risk/base-currency-current-investment';
|
import { CurrencyClusterRiskBaseCurrencyCurrentInvestment } from '@ghostfolio/api/models/rules/currency-cluster-risk/base-currency-current-investment';
|
||||||
@ -18,7 +15,10 @@ import { DataProviderService } from '@ghostfolio/api/services/data-provider/data
|
|||||||
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
|
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
|
||||||
import { ImpersonationService } from '@ghostfolio/api/services/impersonation/impersonation.service';
|
import { ImpersonationService } from '@ghostfolio/api/services/impersonation/impersonation.service';
|
||||||
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service';
|
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service';
|
||||||
import { getAnnualizedPerformancePercent } from '@ghostfolio/common/calculation-helper';
|
import {
|
||||||
|
getAnnualizedPerformancePercent,
|
||||||
|
getIntervalFromDateRange
|
||||||
|
} from '@ghostfolio/common/calculation-helper';
|
||||||
import {
|
import {
|
||||||
DEFAULT_CURRENCY,
|
DEFAULT_CURRENCY,
|
||||||
EMERGENCY_FUND_TAG_ID,
|
EMERGENCY_FUND_TAG_ID,
|
||||||
@ -912,7 +912,7 @@ export class PortfolioService {
|
|||||||
const userId = await this.getUserId(impersonationId, this.request.user.id);
|
const userId = await this.getUserId(impersonationId, this.request.user.id);
|
||||||
const user = await this.userService.user({ id: userId });
|
const user = await this.userService.user({ id: userId });
|
||||||
|
|
||||||
const { endDate } = getInterval(dateRange);
|
const { endDate } = getIntervalFromDateRange(dateRange);
|
||||||
|
|
||||||
const { activities } = await this.orderService.getOrders({
|
const { activities } = await this.orderService.getOrders({
|
||||||
endDate,
|
endDate,
|
||||||
@ -1089,7 +1089,7 @@ export class PortfolioService {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const { endDate } = getInterval(dateRange);
|
const { endDate } = getIntervalFromDateRange(dateRange);
|
||||||
|
|
||||||
const { activities } = await this.orderService.getOrders({
|
const { activities } = await this.orderService.getOrders({
|
||||||
endDate,
|
endDate,
|
||||||
|
@ -1,17 +1,4 @@
|
|||||||
import { resetHours } from '@ghostfolio/common/helper';
|
|
||||||
import { DateRange } from '@ghostfolio/common/types';
|
|
||||||
|
|
||||||
import { Type as ActivityType } from '@prisma/client';
|
import { Type as ActivityType } from '@prisma/client';
|
||||||
import {
|
|
||||||
endOfDay,
|
|
||||||
max,
|
|
||||||
subDays,
|
|
||||||
startOfMonth,
|
|
||||||
startOfWeek,
|
|
||||||
startOfYear,
|
|
||||||
subYears,
|
|
||||||
endOfYear
|
|
||||||
} from 'date-fns';
|
|
||||||
|
|
||||||
export function getFactor(activityType: ActivityType) {
|
export function getFactor(activityType: ActivityType) {
|
||||||
let factor: number;
|
let factor: number;
|
||||||
@ -30,61 +17,3 @@ export function getFactor(activityType: ActivityType) {
|
|||||||
|
|
||||||
return factor;
|
return factor;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getInterval(
|
|
||||||
aDateRange: DateRange,
|
|
||||||
portfolioStart = new Date(0)
|
|
||||||
) {
|
|
||||||
let endDate = endOfDay(new Date(Date.now()));
|
|
||||||
let startDate = portfolioStart;
|
|
||||||
|
|
||||||
switch (aDateRange) {
|
|
||||||
case '1d':
|
|
||||||
startDate = max([
|
|
||||||
startDate,
|
|
||||||
subDays(resetHours(new Date(Date.now())), 1)
|
|
||||||
]);
|
|
||||||
break;
|
|
||||||
case 'mtd':
|
|
||||||
startDate = max([
|
|
||||||
startDate,
|
|
||||||
subDays(startOfMonth(resetHours(new Date(Date.now()))), 1)
|
|
||||||
]);
|
|
||||||
break;
|
|
||||||
case 'wtd':
|
|
||||||
startDate = max([
|
|
||||||
startDate,
|
|
||||||
subDays(
|
|
||||||
startOfWeek(resetHours(new Date(Date.now())), { weekStartsOn: 1 }),
|
|
||||||
1
|
|
||||||
)
|
|
||||||
]);
|
|
||||||
break;
|
|
||||||
case 'ytd':
|
|
||||||
startDate = max([
|
|
||||||
startDate,
|
|
||||||
subDays(startOfYear(resetHours(new Date(Date.now()))), 1)
|
|
||||||
]);
|
|
||||||
break;
|
|
||||||
case '1y':
|
|
||||||
startDate = max([
|
|
||||||
startDate,
|
|
||||||
subYears(resetHours(new Date(Date.now())), 1)
|
|
||||||
]);
|
|
||||||
break;
|
|
||||||
case '5y':
|
|
||||||
startDate = max([
|
|
||||||
startDate,
|
|
||||||
subYears(resetHours(new Date(Date.now())), 5)
|
|
||||||
]);
|
|
||||||
break;
|
|
||||||
case 'max':
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// '2024', '2023', '2022', etc.
|
|
||||||
endDate = endOfYear(new Date(aDateRange));
|
|
||||||
startDate = max([startDate, new Date(aDateRange)]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { endDate, startDate };
|
|
||||||
}
|
|
||||||
|
@ -38,6 +38,7 @@
|
|||||||
<gf-treemap-chart
|
<gf-treemap-chart
|
||||||
class="mt-3"
|
class="mt-3"
|
||||||
cursor="pointer"
|
cursor="pointer"
|
||||||
|
[dateRange]="user?.settings?.dateRange"
|
||||||
[holdings]="holdings"
|
[holdings]="holdings"
|
||||||
(treemapChartClicked)="onSymbolClicked($event)"
|
(treemapChartClicked)="onSymbolClicked($event)"
|
||||||
/>
|
/>
|
||||||
|
@ -14,7 +14,7 @@ import {
|
|||||||
} from '@ghostfolio/common/helper';
|
} from '@ghostfolio/common/helper';
|
||||||
import { LineChartItem } from '@ghostfolio/common/interfaces';
|
import { LineChartItem } from '@ghostfolio/common/interfaces';
|
||||||
import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface';
|
import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface';
|
||||||
import { ColorScheme, DateRange, GroupBy } from '@ghostfolio/common/types';
|
import { ColorScheme, GroupBy } from '@ghostfolio/common/types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
@ -58,7 +58,6 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
|
|||||||
@Input() isInPercent = false;
|
@Input() isInPercent = false;
|
||||||
@Input() isLoading = false;
|
@Input() isLoading = false;
|
||||||
@Input() locale = getLocale();
|
@Input() locale = getLocale();
|
||||||
@Input() range: DateRange = 'max';
|
|
||||||
@Input() savingsRate = 0;
|
@Input() savingsRate = 0;
|
||||||
|
|
||||||
@ViewChild('chartCanvas') chartCanvas;
|
@ViewChild('chartCanvas') chartCanvas;
|
||||||
|
@ -282,7 +282,6 @@
|
|||||||
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
|
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
|
||||||
[isLoading]="isLoadingInvestmentChart"
|
[isLoading]="isLoadingInvestmentChart"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
[range]="user?.settings?.dateRange"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -340,7 +339,6 @@
|
|||||||
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
|
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
|
||||||
[isLoading]="isLoadingInvestmentTimelineChart"
|
[isLoading]="isLoadingInvestmentTimelineChart"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
[range]="user?.settings?.dateRange"
|
|
||||||
[savingsRate]="savingsRate"
|
[savingsRate]="savingsRate"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -377,7 +375,6 @@
|
|||||||
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
|
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
|
||||||
[isLoading]="isLoadingDividendTimelineChart"
|
[isLoading]="isLoadingDividendTimelineChart"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
[range]="user?.settings?.dateRange"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,19 @@
|
|||||||
import { Big } from 'big.js';
|
import { Big } from 'big.js';
|
||||||
|
import {
|
||||||
|
endOfDay,
|
||||||
|
endOfYear,
|
||||||
|
max,
|
||||||
|
startOfMonth,
|
||||||
|
startOfWeek,
|
||||||
|
startOfYear,
|
||||||
|
subDays,
|
||||||
|
subYears
|
||||||
|
} from 'date-fns';
|
||||||
import { isNumber } from 'lodash';
|
import { isNumber } from 'lodash';
|
||||||
|
|
||||||
|
import { resetHours } from './helper';
|
||||||
|
import { DateRange } from './types';
|
||||||
|
|
||||||
export function getAnnualizedPerformancePercent({
|
export function getAnnualizedPerformancePercent({
|
||||||
daysInMarket,
|
daysInMarket,
|
||||||
netPerformancePercentage
|
netPerformancePercentage
|
||||||
@ -18,3 +31,61 @@ export function getAnnualizedPerformancePercent({
|
|||||||
|
|
||||||
return new Big(0);
|
return new Big(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getIntervalFromDateRange(
|
||||||
|
aDateRange: DateRange,
|
||||||
|
portfolioStart = new Date(0)
|
||||||
|
) {
|
||||||
|
let endDate = endOfDay(new Date(Date.now()));
|
||||||
|
let startDate = portfolioStart;
|
||||||
|
|
||||||
|
switch (aDateRange) {
|
||||||
|
case '1d':
|
||||||
|
startDate = max([
|
||||||
|
startDate,
|
||||||
|
subDays(resetHours(new Date(Date.now())), 1)
|
||||||
|
]);
|
||||||
|
break;
|
||||||
|
case 'mtd':
|
||||||
|
startDate = max([
|
||||||
|
startDate,
|
||||||
|
subDays(startOfMonth(resetHours(new Date(Date.now()))), 1)
|
||||||
|
]);
|
||||||
|
break;
|
||||||
|
case 'wtd':
|
||||||
|
startDate = max([
|
||||||
|
startDate,
|
||||||
|
subDays(
|
||||||
|
startOfWeek(resetHours(new Date(Date.now())), { weekStartsOn: 1 }),
|
||||||
|
1
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
break;
|
||||||
|
case 'ytd':
|
||||||
|
startDate = max([
|
||||||
|
startDate,
|
||||||
|
subDays(startOfYear(resetHours(new Date(Date.now()))), 1)
|
||||||
|
]);
|
||||||
|
break;
|
||||||
|
case '1y':
|
||||||
|
startDate = max([
|
||||||
|
startDate,
|
||||||
|
subYears(resetHours(new Date(Date.now())), 1)
|
||||||
|
]);
|
||||||
|
break;
|
||||||
|
case '5y':
|
||||||
|
startDate = max([
|
||||||
|
startDate,
|
||||||
|
subYears(resetHours(new Date(Date.now())), 5)
|
||||||
|
]);
|
||||||
|
break;
|
||||||
|
case 'max':
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// '2024', '2023', '2022', etc.
|
||||||
|
endDate = endOfYear(new Date(aDateRange));
|
||||||
|
startDate = max([startDate, new Date(aDateRange)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { endDate, startDate };
|
||||||
|
}
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
import { getAnnualizedPerformancePercent } from '@ghostfolio/common/calculation-helper';
|
import {
|
||||||
|
getAnnualizedPerformancePercent,
|
||||||
|
getIntervalFromDateRange
|
||||||
|
} from '@ghostfolio/common/calculation-helper';
|
||||||
import {
|
import {
|
||||||
AssetProfileIdentifier,
|
AssetProfileIdentifier,
|
||||||
PortfolioPosition
|
PortfolioPosition
|
||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
|
import { DateRange } from '@ghostfolio/common/types';
|
||||||
|
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import {
|
import {
|
||||||
@ -23,7 +27,7 @@ import { ChartConfiguration } from 'chart.js';
|
|||||||
import { LinearScale } from 'chart.js';
|
import { LinearScale } from 'chart.js';
|
||||||
import { Chart } from 'chart.js';
|
import { Chart } from 'chart.js';
|
||||||
import { TreemapController, TreemapElement } from 'chartjs-chart-treemap';
|
import { TreemapController, TreemapElement } from 'chartjs-chart-treemap';
|
||||||
import { differenceInDays } from 'date-fns';
|
import { differenceInDays, max } from 'date-fns';
|
||||||
import { orderBy } from 'lodash';
|
import { orderBy } from 'lodash';
|
||||||
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||||
|
|
||||||
@ -41,6 +45,7 @@ export class GfTreemapChartComponent
|
|||||||
implements AfterViewInit, OnChanges, OnDestroy
|
implements AfterViewInit, OnChanges, OnDestroy
|
||||||
{
|
{
|
||||||
@Input() cursor: string;
|
@Input() cursor: string;
|
||||||
|
@Input() dateRange: DateRange;
|
||||||
@Input() holdings: PortfolioPosition[];
|
@Input() holdings: PortfolioPosition[];
|
||||||
|
|
||||||
@Output() treemapChartClicked = new EventEmitter<AssetProfileIdentifier>();
|
@Output() treemapChartClicked = new EventEmitter<AssetProfileIdentifier>();
|
||||||
@ -75,6 +80,8 @@ export class GfTreemapChartComponent
|
|||||||
private initialize() {
|
private initialize() {
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
|
|
||||||
|
const { endDate, startDate } = getIntervalFromDateRange(this.dateRange);
|
||||||
|
|
||||||
const data: ChartConfiguration['data'] = <any>{
|
const data: ChartConfiguration['data'] = <any>{
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
@ -82,8 +89,11 @@ export class GfTreemapChartComponent
|
|||||||
const annualizedNetPerformancePercentWithCurrencyEffect =
|
const annualizedNetPerformancePercentWithCurrencyEffect =
|
||||||
getAnnualizedPerformancePercent({
|
getAnnualizedPerformancePercent({
|
||||||
daysInMarket: differenceInDays(
|
daysInMarket: differenceInDays(
|
||||||
new Date(),
|
endDate,
|
||||||
ctx.raw._data.dateOfFirstActivity
|
max([
|
||||||
|
ctx.raw._data.dateOfFirstActivity ?? new Date(0),
|
||||||
|
startDate
|
||||||
|
])
|
||||||
),
|
),
|
||||||
netPerformancePercentage: new Big(
|
netPerformancePercentage: new Big(
|
||||||
ctx.raw._data.netPerformancePercentWithCurrencyEffect
|
ctx.raw._data.netPerformancePercentWithCurrencyEffect
|
||||||
|
@ -8,9 +8,9 @@
|
|||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
} @else {
|
} @else {
|
||||||
@if (marketState === 'closed' && range === '1d') {
|
@if (marketState === 'closed' && dateRange === '1d') {
|
||||||
<ion-icon class="text-muted" name="pause-circle-outline" [size]="size" />
|
<ion-icon class="text-muted" name="pause-circle-outline" [size]="size" />
|
||||||
} @else if (marketState === 'delayed' && range === '1d') {
|
} @else if (marketState === 'delayed' && dateRange === '1d') {
|
||||||
<ion-icon class="text-muted" name="time-outline" [size]="size" />
|
<ion-icon class="text-muted" name="time-outline" [size]="size" />
|
||||||
} @else if (value <= -0.0005) {
|
} @else if (value <= -0.0005) {
|
||||||
<ion-icon
|
<ion-icon
|
||||||
|
@ -19,9 +19,9 @@ import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
|||||||
templateUrl: './trend-indicator.component.html'
|
templateUrl: './trend-indicator.component.html'
|
||||||
})
|
})
|
||||||
export class GfTrendIndicatorComponent {
|
export class GfTrendIndicatorComponent {
|
||||||
|
@Input() dateRange: DateRange;
|
||||||
@Input() isLoading = false;
|
@Input() isLoading = false;
|
||||||
@Input() marketState: MarketState = 'open';
|
@Input() marketState: MarketState = 'open';
|
||||||
@Input() range: DateRange = 'max';
|
|
||||||
@Input() size: 'large' | 'medium' | 'small' = 'small';
|
@Input() size: 'large' | 'medium' | 'small' = 'small';
|
||||||
@Input() value = 0;
|
@Input() value = 0;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user