Browse Source

Bugfix/light color scheme in chart helpers (#7975)

* Fix colors of charts if appearance is set to light and OS uses dark mode

* Update changelog
pull/7983/head
Thomas Kaul 1 week ago
committed by GitHub
parent
commit
435aeeb1f7
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 8
      apps/client/src/app/app.component.ts
  3. 7
      apps/client/src/app/components/admin-market-data/asset-profile-dialog-host/asset-profile-dialog-host.component.ts
  4. 2
      apps/client/src/app/components/admin-market-data/asset-profile-dialog/interfaces/interfaces.ts
  5. 22
      apps/client/src/app/components/investment-chart/investment-chart.component.ts
  6. 14
      libs/common/src/lib/chart-helper.ts
  7. 3
      libs/common/src/lib/config.ts
  8. 35
      libs/common/src/lib/helper.spec.ts
  9. 20
      libs/common/src/lib/helper.ts
  10. 2
      libs/ui/src/lib/chart/chart.options.ts
  11. 8
      libs/ui/src/lib/line-chart/line-chart.component.ts
  12. 6
      libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts
  13. 4
      libs/ui/src/lib/treemap-chart/treemap-chart.component.stories.ts

1
CHANGELOG.md

@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Fixed the colors of the investment chart component (dark mode)
- Fixed the colors of the charts if the appearance is set to light and the operating system uses dark mode
- Fixed the allocation column of the top holdings of ETFs on the allocations page after a change of the user settings
- Fixed the value column of the top holdings of ETFs on the allocations page in the _Presenter View_
- Fixed the projected sustainable retirement income on the _FIRE_ page for a projected total amount of zero (experimental)

8
apps/client/src/app/app.component.ts

@ -1,4 +1,4 @@
import { getCssVariable } from '@ghostfolio/common/helper';
import { getCssVariable, isDarkColorScheme } from '@ghostfolio/common/helper';
import {
AssetProfileIdentifier,
InfoItem,
@ -260,11 +260,7 @@ export class GfAppComponent implements OnInit {
}
private initializeTheme(userPreferredColorScheme?: ColorScheme) {
const isDarkTheme = userPreferredColorScheme
? userPreferredColorScheme === 'DARK'
: window.matchMedia('(prefers-color-scheme: dark)').matches;
this.toggleTheme(isDarkTheme);
this.toggleTheme(isDarkColorScheme(userPreferredColorScheme));
// Default chart styles
Chart.defaults.font.family = getCssVariable('--font-family-sans-serif');

7
apps/client/src/app/components/admin-market-data/asset-profile-dialog-host/asset-profile-dialog-host.component.ts

@ -1,8 +1,5 @@
import { UserService } from '@ghostfolio/client/services/user/user.service';
import {
DEFAULT_COLOR_SCHEME,
DEFAULT_LOCALE
} from '@ghostfolio/common/config';
import { DEFAULT_LOCALE } from '@ghostfolio/common/config';
import { AssetProfileIdentifier, User } from '@ghostfolio/common/interfaces';
import { internalRoutes } from '@ghostfolio/common/routes/routes';
import { DataSource } from '@ghostfolio/prisma/enums';
@ -150,7 +147,7 @@ export class GfAssetProfileDialogHostComponent implements OnDestroy, OnInit {
data: {
dataSource,
symbol,
colorScheme: user?.settings.colorScheme ?? DEFAULT_COLOR_SCHEME,
colorScheme: user?.settings?.colorScheme,
deviceType: this.deviceType(),
locale: user?.settings?.locale ?? DEFAULT_LOCALE
} satisfies AssetProfileDialogParams,

2
apps/client/src/app/components/admin-market-data/asset-profile-dialog/interfaces/interfaces.ts

@ -2,7 +2,7 @@ import { ColorScheme } from '@ghostfolio/common/types';
import { DataSource } from '@ghostfolio/prisma/enums';
export interface AssetProfileDialogParams {
colorScheme: ColorScheme;
colorScheme?: ColorScheme;
dataSource: DataSource;
deviceType: string;
locale: string;

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

@ -7,11 +7,7 @@ import {
getZeroLineAnnotation,
transformTickToAbbreviation
} from '@ghostfolio/common/chart-helper';
import {
DEFAULT_COLOR_SCHEME,
primaryColorRgb,
secondaryColorRgb
} from '@ghostfolio/common/config';
import { primaryColorRgb, secondaryColorRgb } from '@ghostfolio/common/config';
import { getLocale, parseDate } from '@ghostfolio/common/helper';
import { LineChartItem } from '@ghostfolio/common/interfaces';
import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface';
@ -174,15 +170,13 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy {
this.chart.update();
} else {
const colorScheme = this.colorScheme ?? DEFAULT_COLOR_SCHEME;
this.chart = new Chart<'bar' | 'line'>(
this.chartCanvas().nativeElement,
{
data: chartData,
options: {
animation: false,
elements: getChartElementsOptions(colorScheme),
elements: getChartElementsOptions(this.colorScheme),
interaction: { intersect: false, mode: 'index' },
maintainAspectRatio: true,
plugins: {
@ -210,7 +204,7 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy {
value: this.savingsRate
}
: undefined,
yAxis: getZeroLineAnnotation(colorScheme)
yAxis: getZeroLineAnnotation(this.colorScheme)
}
},
legend: {
@ -218,18 +212,18 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy {
},
tooltip: this.getTooltipPluginConfiguration(),
verticalHoverLine: {
color: getChartBorderColor(colorScheme)
color: getChartBorderColor(this.colorScheme)
}
},
responsive: true,
scales: {
x: getTimeAxisOptions({
colorScheme,
borderWidth: this.groupBy ? 0 : 1,
colorScheme: this.colorScheme,
locale: this.locale
}),
y: getValueAxisOptions({
colorScheme,
colorScheme: this.colorScheme,
display: !this.isInPercentage,
tickCallback: (tickValue) => {
return transformTickToAbbreviation(Number(tickValue));
@ -238,7 +232,7 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy {
}
},
plugins: [
getVerticalHoverLinePlugin(this.chartCanvas(), colorScheme)
getVerticalHoverLinePlugin(this.chartCanvas(), this.colorScheme)
],
type: this.groupBy ? 'bar' : 'line'
}
@ -251,7 +245,7 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy {
TooltipOptions<'bar' | 'line'>
> {
return getTimeSeriesTooltipOptions<'bar' | 'line'>({
colorScheme: this.colorScheme ?? DEFAULT_COLOR_SCHEME,
colorScheme: this.colorScheme,
currency: this.isInPercentage ? undefined : this.currency,
groupBy: this.groupBy,
locale: this.isInPercentage ? undefined : this.locale,

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

@ -41,12 +41,12 @@ export function formatGroupedDate({
return format(date, DATE_FORMAT);
}
export function getChartBorderColor(colorScheme: ColorScheme) {
export function getChartBorderColor(colorScheme?: ColorScheme) {
return `rgba(${getTextColor(colorScheme)}, 0.1)`;
}
export function getChartElementsOptions(
colorScheme: ColorScheme
colorScheme?: ColorScheme
): ChartOptions<'bar' | 'line'>['elements'] {
return {
line: {
@ -67,7 +67,7 @@ export function getTimeAxisOptions({
locale = getLocale()
}: {
borderWidth?: number;
colorScheme: ColorScheme;
colorScheme?: ColorScheme;
display?: boolean;
locale?: string;
}): ScaleOptions<'time'> {
@ -95,7 +95,7 @@ export function getTooltipOptions<T extends ChartType>({
locale = getLocale(),
unit = ''
}: {
colorScheme: ColorScheme;
colorScheme?: ColorScheme;
currency?: string;
groupBy?: GroupBy;
locale?: string;
@ -174,7 +174,7 @@ export function getValueAxisOptions({
highlightedValues = [],
tickCallback
}: {
colorScheme: ColorScheme;
colorScheme?: ColorScheme;
display?: boolean;
highlightedValues?: number[];
tickCallback: (
@ -214,7 +214,7 @@ export function getValueAxisOptions({
export function getVerticalHoverLinePlugin<T extends 'line' | 'bar'>(
chartCanvas: ElementRef<HTMLCanvasElement>,
colorScheme: ColorScheme
colorScheme?: ColorScheme
): Plugin<T, { color: string; width: number }> {
return {
afterDatasetsDraw: (chart, _, options) => {
@ -249,7 +249,7 @@ export function getVerticalHoverLinePlugin<T extends 'line' | 'bar'>(
}
export function getZeroLineAnnotation(
colorScheme: ColorScheme
colorScheme?: ColorScheme
): AnnotationOptions<'line'> {
return {
borderColor: getChartBorderColor(colorScheme),

3
libs/common/src/lib/config.ts

@ -8,7 +8,7 @@ import {
import { JobOptions, JobStatus } from 'bull';
import ms from 'ms';
import { ColorScheme, DateRange } from './types';
import { DateRange } from './types';
export const ghostfolioPrefix = 'GF';
@ -100,7 +100,6 @@ export const PORTFOLIO_SNAPSHOT_COMPUTATION_QUEUE_PRIORITY_LOW =
export const STATISTICS_GATHERING_QUEUE = 'STATISTICS_GATHERING_QUEUE';
export const DEFAULT_COLOR_SCHEME: ColorScheme = 'LIGHT';
export const DEFAULT_CURRENCY = 'USD';
export const DEFAULT_DATE_FORMAT_MONTH_YEAR = 'MMM yyyy';
export const DEFAULT_DATE_RANGE: DateRange = 'max';

35
libs/common/src/lib/helper.spec.ts

@ -13,6 +13,7 @@ import {
isAccountExcluded,
isCurrency,
isCurrencySymbol,
isDarkColorScheme,
isSplitRatio,
isValidCurrencyCode,
isValidCustomAssetProfileSymbol,
@ -348,6 +349,40 @@ describe('Helper', () => {
});
});
describe('Is dark color scheme', () => {
const mockPrefersColorSchemeDark = (matches: boolean) => {
window.matchMedia = jest.fn(() => {
return { matches } as MediaQueryList;
});
};
afterEach(() => {
Reflect.deleteProperty(window, 'matchMedia');
});
it('Dark color scheme with operating system in light mode', () => {
mockPrefersColorSchemeDark(false);
expect(isDarkColorScheme('DARK')).toEqual(true);
});
it('Light color scheme with operating system in dark mode', () => {
mockPrefersColorSchemeDark(true);
expect(isDarkColorScheme('LIGHT')).toEqual(false);
});
it('Auto color scheme with operating system in dark mode', () => {
mockPrefersColorSchemeDark(true);
expect(isDarkColorScheme()).toEqual(true);
expect(isDarkColorScheme(null)).toEqual(true);
});
it('Auto color scheme with operating system in light mode', () => {
mockPrefersColorSchemeDark(false);
expect(isDarkColorScheme()).toEqual(false);
expect(isDarkColorScheme(null)).toEqual(false);
});
});
describe('Is split ratio', () => {
it('Forward split', () => {
expect(isSplitRatio({ denominator: 1, numerator: 2 })).toEqual(true);

20
libs/common/src/lib/helper.ts

@ -345,12 +345,9 @@ export function getAssetProfileIdentifier({
return `${dataSource}-${symbol}`;
}
export function getBackgroundColor(aColorScheme: ColorScheme) {
export function getBackgroundColor(aColorScheme?: ColorScheme) {
return getCssVariable(
aColorScheme === 'DARK' ||
window.matchMedia('(prefers-color-scheme: dark)').matches
? '--dark-background'
: '--light-background'
isDarkColorScheme(aColorScheme) ? '--dark-background' : '--light-background'
);
}
@ -531,10 +528,9 @@ export function getSum(aArray: Big[]) {
return new Big(0);
}
export function getTextColor(aColorScheme: ColorScheme) {
export function getTextColor(aColorScheme?: ColorScheme) {
const cssVariable = getCssVariable(
aColorScheme === 'DARK' ||
window.matchMedia('(prefers-color-scheme: dark)').matches
isDarkColorScheme(aColorScheme)
? '--light-primary-text'
: '--dark-primary-text'
);
@ -612,6 +608,14 @@ export function isCurrencySymbol(aSymbol: string) {
);
}
export function isDarkColorScheme(aColorScheme?: ColorScheme | null) {
if (aColorScheme) {
return aColorScheme === 'DARK';
}
return window.matchMedia('(prefers-color-scheme: dark)').matches;
}
export function isDerivedCurrency(aCurrency: string) {
if (aCurrency === 'USX') {
return true;

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

@ -12,7 +12,7 @@ export function getTimeSeriesTooltipOptions<T extends 'bar' | 'line'>({
locale,
unit
}: {
colorScheme: ColorScheme;
colorScheme?: ColorScheme;
currency?: string;
groupBy?: GroupBy;
locale?: string;

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

@ -4,11 +4,7 @@ import {
getValueAxisOptions,
getVerticalHoverLinePlugin
} from '@ghostfolio/common/chart-helper';
import {
DEFAULT_COLOR_SCHEME,
primaryColorRgb,
secondaryColorRgb
} from '@ghostfolio/common/config';
import { primaryColorRgb, secondaryColorRgb } from '@ghostfolio/common/config';
import { getBackgroundColor, getLocale } from '@ghostfolio/common/helper';
import { LineChartItem } from '@ghostfolio/common/interfaces';
import { ColorScheme } from '@ghostfolio/common/types';
@ -56,7 +52,7 @@ export class GfLineChartComponent
{
@Input() benchmarkDataItems: LineChartItem[] = [];
@Input() benchmarkLabel = '';
@Input() colorScheme: ColorScheme = DEFAULT_COLOR_SCHEME;
@Input() colorScheme?: ColorScheme;
@Input() currency: string;
@Input() historicalDataItems: LineChartItem[];
@Input() isAnimated = false;

6
libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts

@ -1,5 +1,5 @@
import { getTooltipOptions } from '@ghostfolio/common/chart-helper';
import { DEFAULT_COLOR_SCHEME, UNKNOWN_KEY } from '@ghostfolio/common/config';
import { UNKNOWN_KEY } from '@ghostfolio/common/config';
import { getLocale, getSum, getTextColor } from '@ghostfolio/common/helper';
import { PortfolioPosition } from '@ghostfolio/common/interfaces';
import { ColorScheme } from '@ghostfolio/common/types';
@ -124,7 +124,7 @@ export class GfPortfolioProportionChartComponent
};
} = {};
const textColor = getTextColor(this.colorScheme ?? DEFAULT_COLOR_SCHEME);
const textColor = getTextColor(this.colorScheme);
this.colorMap = {
[this.OTHER_KEY]: `rgba(${textColor}, 0.24)`,
@ -448,7 +448,7 @@ export class GfPortfolioProportionChartComponent
): Partial<TooltipOptions<'doughnut'>> {
return {
...getTooltipOptions({
colorScheme: this.colorScheme ?? DEFAULT_COLOR_SCHEME,
colorScheme: this.colorScheme,
currency: this.baseCurrency,
locale: this.locale
}),

4
libs/ui/src/lib/treemap-chart/treemap-chart.component.stories.ts

@ -1,5 +1,3 @@
import { DEFAULT_COLOR_SCHEME } from '@ghostfolio/common/config';
import { CommonModule } from '@angular/common';
import { moduleMetadata } from '@storybook/angular';
import type { Meta, StoryObj } from '@storybook/angular';
@ -38,7 +36,7 @@ export const Default: Story = {
args: {
holdings,
baseCurrency: 'USD',
colorScheme: DEFAULT_COLOR_SCHEME,
colorScheme: 'LIGHT',
cursor: undefined,
dateRange: 'mtd',
locale: 'en-US'

Loading…
Cancel
Save