Browse Source

Task/refactor line charts to share chart configuration (#7438)

* Refactor line chart components to share common chart configuration

* Update changelog
pull/7456/head^2
Thomas Kaul 1 week ago
committed by GitHub
parent
commit
6318c064af
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 103
      apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts
  3. 101
      apps/client/src/app/components/investment-chart/investment-chart.component.ts
  4. 110
      libs/common/src/lib/chart-helper.ts
  5. 28
      libs/ui/src/lib/chart/chart.options.ts
  6. 1
      libs/ui/src/lib/chart/index.ts
  7. 81
      libs/ui/src/lib/line-chart/line-chart.component.ts

1
CHANGELOG.md

@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Moved the tags to the overview tab of the account detail dialog (experimental)
- Moved the tags to the overview tab of the holding detail dialog
- Refactored the line chart components to share the common chart configuration
### Fixed

103
apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts

@ -1,20 +1,21 @@
import {
getTooltipOptions,
getVerticalHoverLinePlugin
getChartBorderColor,
getChartElementsOptions,
getTimeAxisOptions,
getValueAxisOptions,
getVerticalHoverLinePlugin,
getZeroLineAnnotation
} from '@ghostfolio/common/chart-helper';
import { primaryColorRgb, secondaryColorRgb } from '@ghostfolio/common/config';
import {
getBackgroundColor,
getDateFormatString,
getLocale,
getTextColor,
parseDate
} from '@ghostfolio/common/helper';
import { getLocale, parseDate } from '@ghostfolio/common/helper';
import { LineChartItem, User } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { internalRoutes } from '@ghostfolio/common/routes/routes';
import { ColorScheme } from '@ghostfolio/common/types';
import { registerChartConfiguration } from '@ghostfolio/ui/chart';
import {
getTimeSeriesTooltipOptions,
registerChartConfiguration
} from '@ghostfolio/ui/chart';
import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator';
import {
@ -166,28 +167,13 @@ export class GfBenchmarkComparatorComponent implements OnChanges, OnDestroy {
data,
options: {
animation: false,
elements: {
line: {
tension: 0
},
point: {
hoverBackgroundColor: getBackgroundColor(this.colorScheme()),
hoverRadius: 2,
radius: 0
}
},
elements: getChartElementsOptions(this.colorScheme()),
interaction: { intersect: false, mode: 'index' },
maintainAspectRatio: true,
plugins: {
annotation: {
annotations: {
yAxis: {
borderColor: `rgba(${getTextColor(this.colorScheme())}, 0.1)`,
borderWidth: 1,
scaleID: 'y',
type: 'line',
value: 0
}
yAxis: getZeroLineAnnotation(this.colorScheme())
}
},
legend: {
@ -195,54 +181,21 @@ export class GfBenchmarkComparatorComponent implements OnChanges, OnDestroy {
},
tooltip: this.getTooltipPluginConfiguration(),
verticalHoverLine: {
color: `rgba(${getTextColor(this.colorScheme())}, 0.1)`
color: getChartBorderColor(this.colorScheme())
}
},
responsive: true,
scales: {
x: {
border: {
color: `rgba(${getTextColor(this.colorScheme())}, 0.1)`,
width: 1
},
display: true,
grid: {
display: false
},
type: 'time',
time: {
tooltipFormat: getDateFormatString(this.locale()),
unit: 'year'
}
},
y: {
border: {
width: 0
},
display: true,
grid: {
color: ({ scale, tick }) => {
if (
tick.value === 0 ||
tick.value === scale.max ||
tick.value === scale.min
) {
return `rgba(${getTextColor(this.colorScheme())}, 0.1)`;
}
return 'transparent';
}
},
position: 'right',
ticks: {
callback: (value: number) => {
return `${value.toFixed(2)} %`;
},
display: true,
mirror: true,
z: 1
}
x: getTimeAxisOptions({
colorScheme: this.colorScheme(),
locale: this.locale()
}),
y: getValueAxisOptions({
colorScheme: this.colorScheme(),
tickCallback: (tickValue) => {
return `${Number(tickValue).toFixed(2)} %`;
}
})
}
},
plugins: [
@ -255,16 +208,10 @@ export class GfBenchmarkComparatorComponent implements OnChanges, OnDestroy {
}
private getTooltipPluginConfiguration(): Partial<TooltipOptions<'line'>> {
return {
...getTooltipOptions({
return getTimeSeriesTooltipOptions<'line'>({
colorScheme: this.colorScheme(),
locale: this.locale(),
unit: '%'
}),
mode: 'index',
position: 'top',
xAlign: 'center',
yAlign: 'bottom'
};
});
}
}

101
apps/client/src/app/components/investment-chart/investment-chart.component.ts

@ -1,20 +1,21 @@
import {
getTooltipOptions,
getChartBorderColor,
getChartElementsOptions,
getTimeAxisOptions,
getValueAxisOptions,
getVerticalHoverLinePlugin,
getZeroLineAnnotation,
transformTickToAbbreviation
} from '@ghostfolio/common/chart-helper';
import { primaryColorRgb, secondaryColorRgb } from '@ghostfolio/common/config';
import {
getBackgroundColor,
getDateFormatString,
getLocale,
getTextColor,
parseDate
} from '@ghostfolio/common/helper';
import { getLocale, parseDate } from '@ghostfolio/common/helper';
import { LineChartItem } from '@ghostfolio/common/interfaces';
import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface';
import { ColorScheme, GroupBy } from '@ghostfolio/common/types';
import { registerChartConfiguration } from '@ghostfolio/ui/chart';
import {
getTimeSeriesTooltipOptions,
registerChartConfiguration
} from '@ghostfolio/ui/chart';
import {
ChangeDetectionStrategy,
@ -175,16 +176,7 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy {
data: chartData,
options: {
animation: false,
elements: {
line: {
tension: 0
},
point: {
hoverBackgroundColor: getBackgroundColor(this.colorScheme),
hoverRadius: 2,
radius: 0
}
},
elements: getChartElementsOptions(this.colorScheme),
interaction: { intersect: false, mode: 'index' },
maintainAspectRatio: true,
plugins: {
@ -212,13 +204,7 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy {
value: this.savingsRate
}
: undefined,
yAxis: {
borderColor: `rgba(${getTextColor(this.colorScheme)}, 0.1)`,
borderWidth: 1,
scaleID: 'y',
type: 'line',
value: 0
}
yAxis: getZeroLineAnnotation(this.colorScheme)
}
},
legend: {
@ -226,54 +212,23 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy {
},
tooltip: this.getTooltipPluginConfiguration(),
verticalHoverLine: {
color: `rgba(${getTextColor(this.colorScheme)}, 0.1)`
color: getChartBorderColor(this.colorScheme)
}
},
responsive: true,
scales: {
x: {
border: {
color: `rgba(${getTextColor(this.colorScheme)}, 0.1)`,
width: this.groupBy ? 0 : 1
},
display: true,
grid: {
display: false
},
type: 'time',
time: {
tooltipFormat: getDateFormatString(this.locale),
unit: 'year'
}
},
y: {
border: {
display: false
},
x: getTimeAxisOptions({
borderWidth: this.groupBy ? 0 : 1,
colorScheme: this.colorScheme,
locale: this.locale
}),
y: getValueAxisOptions({
colorScheme: this.colorScheme,
display: !this.isInPercentage,
grid: {
color: ({ scale, tick }) => {
if (
tick.value === 0 ||
tick.value === scale.max ||
tick.value === scale.min
) {
return `rgba(${getTextColor(this.colorScheme)}, 0.1)`;
}
return 'transparent';
}
},
position: 'right',
ticks: {
callback: (value: number) => {
return transformTickToAbbreviation(value);
},
display: true,
mirror: true,
z: 1
}
tickCallback: (tickValue) => {
return transformTickToAbbreviation(Number(tickValue));
}
})
}
},
plugins: [
@ -289,19 +244,13 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy {
private getTooltipPluginConfiguration(): Partial<
TooltipOptions<'bar' | 'line'>
> {
return {
...getTooltipOptions({
return getTimeSeriesTooltipOptions<'bar' | 'line'>({
colorScheme: this.colorScheme,
currency: this.isInPercentage ? undefined : this.currency,
groupBy: this.groupBy,
locale: this.isInPercentage ? undefined : this.locale,
unit: this.isInPercentage ? '%' : undefined
}),
mode: 'index',
position: 'top',
xAlign: 'center',
yAlign: 'bottom'
};
});
}
private isInFuture<T>(aContext: ScriptableLineSegmentContext, aValue: T) {

110
libs/common/src/lib/chart-helper.ts

@ -1,13 +1,17 @@
import type { ElementRef } from '@angular/core';
import type {
Chart,
ChartOptions,
ChartType,
ControllerDatasetOptions,
Plugin,
Point,
ScaleOptions,
Tick,
TooltipOptions,
TooltipPosition
} from 'chart.js';
import type { AnnotationOptions } from 'chartjs-plugin-annotation';
import { format } from 'date-fns';
import {
@ -15,6 +19,7 @@ import {
DATE_FORMAT_MONTHLY,
DATE_FORMAT_YEARLY,
getBackgroundColor,
getDateFormatString,
getLocale,
getTextColor
} from './helper';
@ -36,6 +41,53 @@ export function formatGroupedDate({
return format(date, DATE_FORMAT);
}
export function getChartBorderColor(colorScheme: ColorScheme) {
return `rgba(${getTextColor(colorScheme)}, 0.1)`;
}
export function getChartElementsOptions(
colorScheme: ColorScheme
): ChartOptions<'bar' | 'line'>['elements'] {
return {
line: {
tension: 0
},
point: {
hoverBackgroundColor: getBackgroundColor(colorScheme),
hoverRadius: 2,
radius: 0
}
};
}
export function getTimeAxisOptions({
borderWidth = 1,
colorScheme,
display = true,
locale = getLocale()
}: {
borderWidth?: number;
colorScheme: ColorScheme;
display?: boolean;
locale?: string;
}): ScaleOptions<'time'> {
return {
display,
border: {
color: getChartBorderColor(colorScheme),
width: borderWidth
},
grid: {
display: false
},
time: {
tooltipFormat: getDateFormatString(locale),
unit: 'year'
},
type: 'time'
};
}
export function getTooltipOptions<T extends ChartType>({
colorScheme,
currency = '',
@ -53,7 +105,7 @@ export function getTooltipOptions<T extends ChartType>({
backgroundColor: getBackgroundColor(colorScheme),
bodyColor: `rgb(${getTextColor(colorScheme)})`,
borderWidth: 1,
borderColor: `rgba(${getTextColor(colorScheme)}, 0.1)`,
borderColor: getChartBorderColor(colorScheme),
// @ts-expect-error: no need to set all attributes in callbacks
callbacks: {
label: (context) => {
@ -116,6 +168,50 @@ export function getTooltipPositionerMapTop(
};
}
export function getValueAxisOptions({
colorScheme,
display = true,
highlightedValues = [],
tickCallback
}: {
colorScheme: ColorScheme;
display?: boolean;
highlightedValues?: number[];
tickCallback: (
tickValue: number | string,
index: number,
ticks: Tick[]
) => string;
}): ScaleOptions<'linear'> {
return {
display,
border: {
display: false
},
grid: {
color: ({ scale, tick }) => {
if (
tick.value === 0 ||
tick.value === scale.max ||
tick.value === scale.min ||
highlightedValues.includes(tick.value)
) {
return getChartBorderColor(colorScheme);
}
return 'transparent';
}
},
position: 'right',
ticks: {
display,
callback: tickCallback,
mirror: true,
z: 1
}
};
}
export function getVerticalHoverLinePlugin<T extends 'line' | 'bar'>(
chartCanvas: ElementRef<HTMLCanvasElement>,
colorScheme: ColorScheme
@ -152,6 +248,18 @@ export function getVerticalHoverLinePlugin<T extends 'line' | 'bar'>(
};
}
export function getZeroLineAnnotation(
colorScheme: ColorScheme
): AnnotationOptions<'line'> {
return {
borderColor: getChartBorderColor(colorScheme),
borderWidth: 1,
scaleID: 'y',
type: 'line',
value: 0
};
}
export function transformTickToAbbreviation(value: number) {
if (value === 0) {
return '0';

28
libs/ui/src/lib/chart/chart.options.ts

@ -0,0 +1,28 @@
import { getTooltipOptions } from '@ghostfolio/common/chart-helper';
import { ColorScheme, GroupBy } from '@ghostfolio/common/types';
import type { TooltipOptions } from 'chart.js';
import './chart.registry';
export function getTimeSeriesTooltipOptions<T extends 'bar' | 'line'>({
colorScheme,
currency,
groupBy,
locale,
unit
}: {
colorScheme: ColorScheme;
currency?: string;
groupBy?: GroupBy;
locale?: string;
unit?: string;
}): Partial<TooltipOptions<T>> {
return {
...getTooltipOptions<T>({ colorScheme, currency, groupBy, locale, unit }),
mode: 'index',
position: 'top',
xAlign: 'center',
yAlign: 'bottom'
};
}

1
libs/ui/src/lib/chart/index.ts

@ -1 +1,2 @@
export * from './chart.options';
export * from './chart.registry';

81
libs/ui/src/lib/line-chart/line-chart.component.ts

@ -1,14 +1,11 @@
import {
getTooltipOptions,
getChartBorderColor,
getTimeAxisOptions,
getValueAxisOptions,
getVerticalHoverLinePlugin
} from '@ghostfolio/common/chart-helper';
import { primaryColorRgb, secondaryColorRgb } from '@ghostfolio/common/config';
import {
getBackgroundColor,
getDateFormatString,
getLocale,
getTextColor
} from '@ghostfolio/common/helper';
import { getBackgroundColor, getLocale } from '@ghostfolio/common/helper';
import { LineChartItem } from '@ghostfolio/common/interfaces';
import { ColorScheme } from '@ghostfolio/common/types';
@ -38,7 +35,10 @@ import {
import 'chartjs-adapter-date-fns';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { registerChartConfiguration } from '../chart';
import {
getTimeSeriesTooltipOptions,
registerChartConfiguration
} from '../chart';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
@ -209,49 +209,21 @@ export class GfLineChartComponent
},
tooltip: this.getTooltipPluginConfiguration(),
verticalHoverLine: {
color: `rgba(${getTextColor(this.colorScheme)}, 0.1)`
color: getChartBorderColor(this.colorScheme)
}
},
scales: {
x: {
border: {
color: `rgba(${getTextColor(this.colorScheme)}, 0.1)`
},
x: getTimeAxisOptions({
colorScheme: this.colorScheme,
display: this.showXAxis,
grid: {
display: false
},
time: {
tooltipFormat: getDateFormatString(this.locale),
unit: 'year'
},
type: 'time'
},
locale: this.locale
}),
y: {
border: {
width: 0
},
...getValueAxisOptions({
colorScheme: this.colorScheme,
display: this.showYAxis,
grid: {
color: ({ scale, tick }) => {
if (
tick.value === 0 ||
tick.value === scale.max ||
tick.value === scale.min ||
tick.value === this.yMax ||
tick.value === this.yMin
) {
return `rgba(${getTextColor(this.colorScheme)}, 0.1)`;
}
return 'transparent';
}
},
max: this.yMax,
min: this.yMin,
position: 'right',
ticks: {
callback: (tickValue, index, ticks) => {
highlightedValues: [this.yMax, this.yMin],
tickCallback: (tickValue, index, ticks) => {
if (index === 0 || index === ticks.length - 1) {
// Only print last and first legend entry
@ -274,11 +246,10 @@ export class GfLineChartComponent
}
return '';
},
display: this.showYAxis,
mirror: true,
z: 1
},
}
}),
max: this.yMax,
min: this.yMin,
type: 'linear'
}
},
@ -321,17 +292,11 @@ export class GfLineChartComponent
}
private getTooltipPluginConfiguration(): Partial<TooltipOptions<'line'>> {
return {
...getTooltipOptions({
return getTimeSeriesTooltipOptions<'line'>({
colorScheme: this.colorScheme,
currency: this.currency,
locale: this.locale,
unit: this.unit
}),
mode: 'index',
position: 'top',
xAlign: 'center',
yAlign: 'bottom'
};
});
}
}

Loading…
Cancel
Save