Browse Source

Feature/show the whole current period on the time axis of the overview chart

pull/8267/head
mister-ideas 3 days ago
parent
commit
6b7f4093c3
  1. 5
      apps/client/src/app/components/home-overview/home-overview.component.ts
  2. 53
      libs/common/src/lib/chart-helper.spec.ts
  3. 69
      libs/common/src/lib/chart-helper.ts
  4. 2
      libs/ui/src/lib/line-chart/line-chart.component.ts

5
apps/client/src/app/components/home-overview/home-overview.component.ts

@ -63,7 +63,10 @@ export class GfHomeOverviewComponent implements OnInit {
// A line through only two points (e.g. the close of the previous day and // A line through only two points (e.g. the close of the previous day and
// now) is drawn as a misleading slope across the whole width // now) is drawn as a misleading slope across the whole width
protected readonly hasEnoughChartData = computed(() => { protected readonly hasEnoughChartData = computed(() => {
return (this.historicalDataItems()?.length ?? 0) >= MINIMUM_CHART_ITEMS; const dateRange = this.user()?.settings?.dateRange;
const minimum = dateRange === '1d' ? 1 : MINIMUM_CHART_ITEMS;
return (this.historicalDataItems()?.length ?? 0) >= minimum;
}); });
protected readonly holdings = signal<PortfolioPosition[] | undefined>( protected readonly holdings = signal<PortfolioPosition[] | undefined>(
undefined undefined

53
libs/common/src/lib/chart-helper.spec.ts

@ -1,8 +1,8 @@
import { getTimeAxisUnit } from './chart-helper'; import { getTimeAxisBounds, getTimeAxisUnit } from './chart-helper';
describe('getTimeAxisUnit', () => { describe('getTimeAxisUnit', () => {
it.each([ it.each([
['1d', 'day'], ['1d', 'hour'],
['wtd', 'day'], ['wtd', 'day'],
['mtd', 'day'], ['mtd', 'day'],
['ytd', 'month'], ['ytd', 'month'],
@ -15,3 +15,52 @@ describe('getTimeAxisUnit', () => {
expect(getTimeAxisUnit(dateRange)).toBe(unit); expect(getTimeAxisUnit(dateRange)).toBe(unit);
}); });
}); });
describe('getTimeAxisBounds', () => {
const reference = new Date(2026, 9, 21, 15, 30);
it('spans the whole current year for ytd', () => {
const { max, min } = getTimeAxisBounds('ytd', reference);
expect(new Date(min).toDateString()).toBe(
new Date(2026, 0, 1).toDateString()
);
expect(new Date(max).toDateString()).toBe(
new Date(2026, 11, 31).toDateString()
);
});
it('spans the whole current month for mtd', () => {
const { max, min } = getTimeAxisBounds('mtd', reference);
expect(new Date(min).getDate()).toBe(1);
expect(new Date(max).getDate()).toBe(31);
});
it('spans the whole current week for wtd', () => {
const { max, min } = getTimeAxisBounds('wtd', reference);
expect(new Date(min).getDay()).toBe(1);
expect(new Date(max).getDay()).toBe(0);
});
it('spans the whole current day for 1d', () => {
const { max, min } = getTimeAxisBounds('1d', reference);
expect(new Date(min).getHours()).toBe(0);
expect(new Date(max).getHours()).toBe(23);
});
it('spans a past year', () => {
const { max, min } = getTimeAxisBounds('2024', reference);
expect(new Date(min).getFullYear()).toBe(2024);
expect(new Date(max).getMonth()).toBe(11);
});
it('does not fix the bounds of rolling ranges', () => {
expect(getTimeAxisBounds('1y', reference)).toEqual({});
expect(getTimeAxisBounds('5y', reference)).toEqual({});
expect(getTimeAxisBounds('max', reference)).toEqual({});
});
});

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

@ -12,7 +12,17 @@ import type {
TooltipPosition TooltipPosition
} from 'chart.js'; } from 'chart.js';
import type { AnnotationOptions } from 'chartjs-plugin-annotation'; import type { AnnotationOptions } from 'chartjs-plugin-annotation';
import { format } from 'date-fns'; import {
endOfDay,
endOfMonth,
endOfWeek,
endOfYear,
format,
startOfDay,
startOfMonth,
startOfWeek,
startOfYear
} from 'date-fns';
import { import {
DATE_FORMAT, DATE_FORMAT,
@ -67,9 +77,10 @@ export function getChartElementsOptions(
*/ */
export function getTimeAxisUnit( export function getTimeAxisUnit(
aDateRange?: DateRange aDateRange?: DateRange
): 'day' | 'month' | 'year' { ): 'day' | 'hour' | 'month' | 'year' {
switch (aDateRange) { switch (aDateRange) {
case '1d': case '1d':
return 'hour';
case 'mtd': case 'mtd':
case 'wtd': case 'wtd':
return 'day'; return 'day';
@ -83,18 +94,68 @@ export function getTimeAxisUnit(
} }
} }
/**
* Returns the bounds of the time axis for the date ranges which have a fixed
* end (the current week, month or year), so that the chart shows the whole
* period with the part still to come left empty. Other date ranges (rolling
* periods and `max`) end at the latest data point.
*/
export function getTimeAxisBounds(
aDateRange?: DateRange,
aReferenceDate = new Date()
): { max?: number; min?: number } {
switch (aDateRange) {
case '1d':
return {
max: endOfDay(aReferenceDate).getTime(),
min: startOfDay(aReferenceDate).getTime()
};
case 'wtd':
return {
max: endOfWeek(aReferenceDate, { weekStartsOn: 1 }).getTime(),
min: startOfWeek(aReferenceDate, { weekStartsOn: 1 }).getTime()
};
case 'mtd':
return {
max: endOfMonth(aReferenceDate).getTime(),
min: startOfMonth(aReferenceDate).getTime()
};
case 'ytd':
return {
max: endOfYear(aReferenceDate).getTime(),
min: startOfYear(aReferenceDate).getTime()
};
default: {
if (typeof aDateRange === 'string' && /^\d{4}$/.test(aDateRange)) {
const year = new Date(Number(aDateRange), 0, 1);
return {
max: endOfYear(year).getTime(),
min: startOfYear(year).getTime()
};
}
return {};
}
}
}
export function getTimeAxisOptions({ export function getTimeAxisOptions({
borderWidth = 1, borderWidth = 1,
colorScheme, colorScheme,
display = true, display = true,
locale = getLocale(), locale = getLocale(),
max,
min,
unit = 'year' unit = 'year'
}: { }: {
borderWidth?: number; borderWidth?: number;
colorScheme?: ColorScheme; colorScheme?: ColorScheme;
display?: boolean; display?: boolean;
locale?: string; locale?: string;
unit?: 'day' | 'month' | 'year'; max?: number;
min?: number;
unit?: 'day' | 'hour' | 'month' | 'year';
}): ScaleOptions<'time'> { }): ScaleOptions<'time'> {
return { return {
display, display,
@ -105,6 +166,8 @@ export function getTimeAxisOptions({
grid: { grid: {
display: false display: false
}, },
max,
min,
ticks: { ticks: {
autoSkip: true, autoSkip: true,
maxRotation: 0, maxRotation: 0,

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

@ -1,5 +1,6 @@
import { import {
getChartBorderColor, getChartBorderColor,
getTimeAxisBounds,
getTimeAxisOptions, getTimeAxisOptions,
getTimeAxisUnit, getTimeAxisUnit,
getValueAxisOptions, getValueAxisOptions,
@ -239,6 +240,7 @@ export class GfLineChartComponent
colorScheme: this.colorScheme, colorScheme: this.colorScheme,
display: this.showXAxis, display: this.showXAxis,
locale: this.locale, locale: this.locale,
...getTimeAxisBounds(this.dateRange),
unit: getTimeAxisUnit(this.dateRange) unit: getTimeAxisUnit(this.dateRange)
}), }),
y: { y: {

Loading…
Cancel
Save