Browse Source

Feature/draw the performance chart of the overview page on the trading days and remove the samples of the day

pull/8267/head
mister-ideas 1 day ago
parent
commit
bccbca05d2
  1. 8
      CHANGELOG.md
  2. 46
      apps/client/src/app/components/home-overview/home-overview.component.ts
  3. 50
      apps/client/src/app/components/home-overview/intraday-samples.spec.ts
  4. 83
      apps/client/src/app/components/home-overview/intraday-samples.ts
  5. 143
      libs/common/src/lib/chart-helper.spec.ts
  6. 281
      libs/common/src/lib/chart-helper.ts
  7. 15
      libs/ui/src/lib/line-chart/line-chart.component.ts

8
CHANGELOG.md

@ -9,15 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Added a time axis to the performance chart on the overview page, drawing the current day from samples recorded on the device, which spans the whole current day, week, month or year and uses days, months or years as unit according to the selected date range
### Changed
- Hid the performance chart on the overview page if it has less than three data points
- Added a time axis to the performance chart on the overview page which only contains the trading days, spans the whole current year and uses hours, days, months or years as unit according to the selected date range
- Added a link to the holdings tab in the holdings table on the overview tab of the home page (experimental)
### Changed
- Hid the performance chart on the overview page if it has less than three data points
- Reduced the performance chart on the overview page to one point per week above 400 trading days
- Improved the market price in the benchmark detail dialog by adding the currency and reducing the precision on mobile
- Improved the style of the close button in the dialog footer component
- Improved the style of the close button in the dialog header component

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

@ -2,6 +2,7 @@ import { GfPortfolioPerformanceComponent } from '@ghostfolio/client/components/p
import { LayoutService } from '@ghostfolio/client/core/layout.service';
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
import { UserService } from '@ghostfolio/client/services/user/user.service';
import { getTradingDayItems } from '@ghostfolio/common/chart-helper';
import {
DEFAULT_CURRENCY,
DEFAULT_DATE_RANGE,
@ -12,7 +13,7 @@ import { isCashPosition } from '@ghostfolio/common/helper';
import {
AssetProfileIdentifier,
HistoricalDataItem,
LineChartItem,
NullableLineChartItem,
PortfolioPerformance,
PortfolioPosition,
User
@ -43,11 +44,6 @@ import { addIcons } from 'ionicons';
import { arrowForwardOutline } from 'ionicons/icons';
import { DeviceDetectorService } from 'ngx-device-detector';
import {
getIntradayChartItems,
recordIntradaySample
} from './intraday-samples';
const MINIMUM_CHART_ITEMS = 3;
@Component({
@ -69,15 +65,18 @@ export class GfHomeOverviewComponent implements OnInit {
protected readonly errors = signal<AssetProfileIdentifier[]>([]);
protected readonly hasImpersonationId = signal(false);
protected readonly defaultDateRange = DEFAULT_DATE_RANGE;
protected readonly historicalDataItems = signal<LineChartItem[] | null>(null);
protected readonly historicalDataItems = signal<
NullableLineChartItem[] | null
>(null);
// 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
protected readonly hasEnoughChartData = computed(() => {
const dateRange = this.user()?.settings?.dateRange;
const minimum = dateRange === '1d' ? 1 : MINIMUM_CHART_ITEMS;
return (this.historicalDataItems()?.length ?? 0) >= minimum;
return (
(this.historicalDataItems() ?? []).filter(({ value }) => {
return value !== null;
}).length >= MINIMUM_CHART_ITEMS
);
});
protected readonly holdings = signal<PortfolioPosition[] | undefined>(
undefined
@ -197,28 +196,21 @@ export class GfHomeOverviewComponent implements OnInit {
chart?: HistoricalDataItem[];
dateRange?: DateRange;
}) {
const items =
chart?.map(({ date, netPerformanceInPercentageWithCurrencyEffect }) => {
// Only the trading days, and the remaining ones of the current year
this.historicalDataItems.set(
getTradingDayItems({
dateRange,
items:
chart?.map(
({ date, netPerformanceInPercentageWithCurrencyEffect }) => {
return {
date,
value: (netPerformanceInPercentageWithCurrencyEffect ?? 0) * 100
};
}) ?? null;
const latest = items?.at(-1);
if ((dateRange ?? DEFAULT_DATE_RANGE) === '1d' && latest) {
// There is only one value per day on the server: draw the day from the
// close of the previous day to the samples recorded on this device
this.historicalDataItems.set(
getIntradayChartItems({
baseline: items?.[0]?.value ?? 0,
samples: recordIntradaySample({ value: latest.value })
}
) ?? []
})
);
} else {
this.historicalDataItems.set(items);
}
}
private update() {

50
apps/client/src/app/components/home-overview/intraday-samples.spec.ts

@ -1,50 +0,0 @@
import {
getIntradayChartItems,
INTRADAY_SAMPLE_INTERVAL_MS,
loadIntradaySamples,
recordIntradaySample
} from './intraday-samples';
describe('intraday samples', () => {
const now = new Date(2026, 9, 21, 10, 0, 0);
beforeEach(() => {
localStorage.clear();
});
it('records at most one sample per minute', () => {
recordIntradaySample({ now, value: 1 });
recordIntradaySample({ now: new Date(now.getTime() + 5_000), value: 2 });
expect(loadIntradaySamples(now)).toEqual([
{ timestamp: now.getTime(), value: 2 }
]);
recordIntradaySample({
now: new Date(now.getTime() + INTRADAY_SAMPLE_INTERVAL_MS),
value: 3
});
expect(loadIntradaySamples(now)).toHaveLength(2);
});
it('removes the samples of previous days', () => {
recordIntradaySample({ now: new Date(2026, 9, 20, 15, 0), value: 1 });
recordIntradaySample({ now, value: 2 });
expect(loadIntradaySamples(new Date(2026, 9, 20))).toEqual([]);
expect(loadIntradaySamples(now)).toHaveLength(1);
});
it('starts the chart of the day with the baseline at midnight', () => {
const items = getIntradayChartItems({
now,
baseline: 0,
samples: [{ timestamp: now.getTime(), value: 0.5 }]
});
expect(items).toHaveLength(2);
expect(new Date(items[0].date).getHours()).toBe(0);
expect(items[1].value).toBe(0.5);
});
});

83
apps/client/src/app/components/home-overview/intraday-samples.ts

@ -1,83 +0,0 @@
import { LineChartItem } from '@ghostfolio/common/interfaces';
import { format, startOfDay } from 'date-fns';
export const INTRADAY_SAMPLES_KEY_PREFIX = 'gf-intraday-';
export const INTRADAY_SAMPLE_INTERVAL_MS = 60_000;
export interface IntradaySample {
timestamp: number;
value: number;
}
function getKey(aDate: Date) {
return `${INTRADAY_SAMPLES_KEY_PREFIX}${format(aDate, 'yyyy-MM-dd')}`;
}
export function loadIntradaySamples(aDate = new Date()): IntradaySample[] {
try {
const samples = JSON.parse(localStorage.getItem(getKey(aDate)) ?? '[]');
return Array.isArray(samples) ? samples : [];
} catch {
return [];
}
}
/**
* Stores the current value of the day (at most one sample per minute) in the
* local storage of the device, because only a single value per day is stored
* on the server. Samples of previous days are removed.
*/
export function recordIntradaySample({
now = new Date(),
value
}: {
now?: Date;
value: number;
}): IntradaySample[] {
const samples = loadIntradaySamples(now);
const last = samples.at(-1);
if (!last || now.getTime() - last.timestamp >= INTRADAY_SAMPLE_INTERVAL_MS) {
samples.push({ value, timestamp: now.getTime() });
} else {
// Keep the latest value of the current minute
last.value = value;
}
try {
for (let index = localStorage.length - 1; index >= 0; index--) {
const key = localStorage.key(index);
if (key?.startsWith(INTRADAY_SAMPLES_KEY_PREFIX) && key !== getKey(now)) {
localStorage.removeItem(key);
}
}
localStorage.setItem(getKey(now), JSON.stringify(samples));
} catch {}
return samples;
}
/**
* Builds the chart of the current day: the baseline (the close of the previous
* day) at the start of the day followed by the recorded samples of the day.
*/
export function getIntradayChartItems({
baseline,
now = new Date(),
samples
}: {
baseline: number;
now?: Date;
samples: IntradaySample[];
}): LineChartItem[] {
return [
{ date: startOfDay(now).toISOString(), value: baseline },
...samples.map(({ timestamp, value }) => {
return { value, date: new Date(timestamp).toISOString() };
})
];
}

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

@ -1,4 +1,11 @@
import { getTimeAxisBounds, getTimeAxisUnit } from './chart-helper';
import { addDays, format } from 'date-fns';
import {
getTimeAxisUnit,
getTimeSeriesTickCallback,
getTradingDayItems
} from './chart-helper';
import { DATE_FORMAT } from './helper';
describe('getTimeAxisUnit', () => {
it.each([
@ -6,61 +13,129 @@ describe('getTimeAxisUnit', () => {
['wtd', 'day'],
['mtd', 'day'],
['ytd', 'month'],
['1y', 'month'],
['1y', false],
['2025', 'month'],
['5y', 'year'],
['max', 'year'],
['5y', false],
['max', false],
[undefined, 'year']
])('uses the unit of %s', (dateRange, unit) => {
expect(getTimeAxisUnit(dateRange)).toBe(unit);
});
});
describe('getTimeAxisBounds', () => {
const reference = new Date(2026, 9, 21, 15, 30);
describe('getTradingDayItems', () => {
// Thursday
const now = new Date(2026, 11, 24);
it('spans the whole current year for ytd', () => {
const { max, min } = getTimeAxisBounds('ytd', reference);
it('skips the weekends', () => {
const items = getTradingDayItems({
dateRange: '1y',
now,
items: [
{ date: '2026-12-18', value: 1 },
{ date: '2026-12-19', value: 1 },
{ date: '2026-12-20', value: 1 },
{ date: '2026-12-21', value: 2 }
]
});
expect(new Date(min).toDateString()).toBe(
new Date(2026, 0, 1).toDateString()
);
expect(new Date(max).toDateString()).toBe(
new Date(2026, 11, 31).toDateString()
);
expect(items.map(({ date }) => date)).toEqual(['2026-12-18', '2026-12-21']);
});
it('spans the whole current month for mtd', () => {
const { max, min } = getTimeAxisBounds('mtd', reference);
it('adds the remaining trading days of the year', () => {
const items = getTradingDayItems({
dateRange: 'ytd',
now,
items: [{ date: '2026-12-24', value: 1 }]
});
expect(new Date(min).getDate()).toBe(1);
expect(new Date(max).getDate()).toBe(31);
expect(items).toEqual([
{ date: '2026-12-24', value: 1 },
{ date: '2026-12-25', value: null },
{ date: '2026-12-28', value: null },
{ date: '2026-12-29', value: null },
{ date: '2026-12-30', value: null },
{ date: '2026-12-31', value: null }
]);
});
});
describe('getTimeSeriesTickCallback', () => {
it('labels the first point of each hour', () => {
const ticks = [
new Date(2026, 9, 22, 9, 0),
new Date(2026, 9, 22, 9, 30),
new Date(2026, 9, 22, 10, 0),
new Date(2026, 9, 22, 10, 30)
].map((date) => {
return { value: date.getTime() };
});
const callback = getTimeSeriesTickCallback('hour');
it('spans the whole current week for wtd', () => {
const { max, min } = getTimeAxisBounds('wtd', reference);
expect(
ticks.map(({ value }, index) => {
return callback(value, index, ticks);
})
).toEqual(['09:00', '', '10:00', '']);
});
expect(new Date(min).getDay()).toBe(1);
expect(new Date(max).getDay()).toBe(0);
it('labels the start of each trading day, also after a post-market past midnight', () => {
const ticks = [
new Date(2026, 9, 22, 21, 0),
new Date(2026, 9, 22, 23, 0),
new Date(2026, 9, 23, 1, 0),
new Date(2026, 9, 23, 9, 0)
].map((date) => {
return { value: date.getTime() };
});
it('spans the whole current day for 1d', () => {
const { max, min } = getTimeAxisBounds('1d', reference);
const callback = getTimeSeriesTickCallback('day');
expect(
ticks.map(({ value }, index) => {
return callback(value, index, ticks);
})
).toEqual(['Thu 22', '', '', 'Fri 23']);
});
expect(new Date(min).getHours()).toBe(0);
expect(new Date(max).getHours()).toBe(23);
it('thins out the labels', () => {
const ticks = Array.from({ length: 20 }, (_, index) => {
return { value: new Date(2026, 0, 1 + index).getTime() };
});
it('spans a past year', () => {
const { max, min } = getTimeAxisBounds('2024', reference);
const callback = getTimeSeriesTickCallback('day', 5);
const labels = ticks
.map(({ value }, index) => {
return callback(value, index, ticks);
})
.filter(Boolean);
expect(new Date(min).getFullYear()).toBe(2024);
expect(new Date(max).getMonth()).toBe(11);
expect(labels.length).toBeLessThanOrEqual(5);
});
});
describe('getTradingDayItems with many items', () => {
it('keeps one point per week (the last trading day)', () => {
const items = Array.from({ length: 28 }, (_, index) => {
return {
date: format(addDays(new Date(2026, 0, 5), index), DATE_FORMAT),
value: index
};
});
const result = getTradingDayItems({ dateRange: '1y', items, maxItems: 10 });
it('does not fix the bounds of rolling ranges', () => {
expect(getTimeAxisBounds('1y', reference)).toEqual({});
expect(getTimeAxisBounds('5y', reference)).toEqual({});
expect(getTimeAxisBounds('max', reference)).toEqual({});
expect(
result.map(({ date }) => {
return date;
})
).toEqual([
'2026-01-05',
'2026-01-09',
'2026-01-16',
'2026-01-23',
'2026-01-30'
]);
});
});

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

@ -12,18 +12,9 @@ import type {
TooltipPosition
} from 'chart.js';
import type { AnnotationOptions } from 'chartjs-plugin-annotation';
import {
endOfDay,
endOfMonth,
endOfWeek,
endOfYear,
format,
startOfDay,
startOfMonth,
startOfWeek,
startOfYear
} from 'date-fns';
import { addDays, format, startOfWeek } from 'date-fns';
import { secondaryColorRgb } from './config';
import {
DATE_FORMAT,
DATE_FORMAT_MONTHLY,
@ -31,7 +22,8 @@ import {
getBackgroundColor,
getDateFormatString,
getLocale,
getTextColor
getTextColor,
parseDate
} from './helper';
import { ColorScheme, DateRange, GroupBy } from './types';
@ -51,6 +43,21 @@ export function formatGroupedDate({
return format(date, DATE_FORMAT);
}
// Colors for the lines of the compared benchmarks, the portfolio uses the
// primary color
const BENCHMARK_COLORS = [
`rgb(${secondaryColorRgb.r}, ${secondaryColorRgb.g}, ${secondaryColorRgb.b})`,
'rgb(255, 159, 64)',
'rgb(75, 192, 112)',
'rgb(153, 102, 255)',
'rgb(255, 99, 132)',
'rgb(201, 203, 207)'
];
export function getBenchmarkColor(index: number) {
return BENCHMARK_COLORS[index % BENCHMARK_COLORS.length];
}
export function getChartBorderColor(colorScheme?: ColorScheme) {
return `rgba(${getTextColor(colorScheme)}, 0.1)`;
}
@ -75,69 +82,196 @@ export function getChartElementsOptions(
* length of the date range: days up to a month, months up to a year and years
* above
*/
/**
* Returns the daily items of the trading days (Monday to Friday) and, for a
* date range with a fixed end (the current year), the remaining trading days
* without a value, so that the chart shows the whole period
*/
function getWeekKey(aDate?: Date) {
return aDate
? format(startOfWeek(aDate, { weekStartsOn: 1 }), DATE_FORMAT)
: '';
}
export function getTradingDayItems<T extends { date: string }>({
dateRange,
items,
maxItems = 400,
now = new Date()
}: {
dateRange?: DateRange;
items: T[];
/** Above, one point per week (the last trading day) is kept */
maxItems?: number;
now?: Date;
}): (T | { date: string; value: null })[] {
const isWeekend = (aDate: Date) => {
return aDate.getDay() === 0 || aDate.getDay() === 6;
};
const result: (T | { date: string; value: null })[] = items.filter(
({ date }) => {
const parsedDate = parseDate(date);
return !parsedDate || !isWeekend(parsedDate);
}
);
if (result.length > maxItems) {
const weekly = result.filter(({ date }, index) => {
const next = result[index + 1];
return (
index === 0 ||
!next ||
getWeekKey(parseDate(date)) !== getWeekKey(parseDate(next.date))
);
});
result.splice(0, result.length, ...weekly);
}
let end: Date | undefined;
let start: Date | undefined;
if (dateRange === 'ytd') {
end = new Date(now.getFullYear(), 11, 31);
start = new Date(now.getFullYear(), 0, 1);
} else if (/^\d{4}$/.test(dateRange ?? '')) {
end = new Date(Number(dateRange), 11, 31);
start = new Date(Number(dateRange), 0, 1);
}
const firstDate = result[0] ? parseDate(result[0].date) : undefined;
if (start && firstDate && firstDate < start) {
// The baseline (the close of the previous year) is shown at the start
result[0] = { ...result[0], date: format(start, DATE_FORMAT) };
}
const lastDate = items.at(-1)?.date;
const last = lastDate ? parseDate(lastDate) : undefined;
if (end && last) {
for (
let date = addDays(last, 1);
date.getTime() <= end.getTime();
date = addDays(date, 1)
) {
if (!isWeekend(date)) {
result.push({ date: format(date, DATE_FORMAT), value: null });
}
}
}
return result;
}
export function getTimeAxisUnit(
aDateRange?: DateRange
): 'day' | 'hour' | 'month' | 'year' {
): 'day' | 'hour' | 'month' | 'year' | false {
switch (aDateRange) {
case undefined:
return 'year';
case '1d':
return 'hour';
case 'mtd':
case 'wtd':
return 'day';
case '1y':
case '5y':
case 'max':
case undefined:
return 'year';
// Chosen by the chart according to the length of the period
return false;
default:
// 'ytd', '1y' and single years
// 'ytd' and single years
return 'month';
}
}
const TIME_SERIES_LABEL_FORMATS = {
day: 'EEE d',
hour: 'HH:mm',
month: 'MMM',
year: 'yyyy'
} as const;
/**
* 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.
* Returns the labels of an axis with equidistant points (without the closed
* markets): a label at the first point of a period (hour, day, month or
* year), thinned out to at most a few labels
*/
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);
export function getTimeSeriesTickCallback(
aUnit: 'day' | 'hour' | 'month' | 'year' | false,
aMaxLabels = 7
) {
let cache: { key: string; labels: Map<number, string> } | undefined;
return {
max: endOfYear(year).getTime(),
min: startOfYear(year).getTime()
return (
_value: number | string,
index: number,
ticks: { value: number }[]
) => {
const key = `${ticks.length}-${ticks[0]?.value}-${ticks.at(-1)?.value}`;
if (cache?.key !== key) {
const span = (ticks.at(-1)?.value ?? 0) - (ticks[0]?.value ?? 0);
const unit =
aUnit ||
(span > 3 * 365 * 24 * 60 * 60 * 1000
? 'year'
: span > 45 * 24 * 60 * 60 * 1000
? 'month'
: 'day');
const getPeriod = (aTime: number) => {
const date = new Date(aTime);
switch (unit) {
case 'hour':
return `${date.toDateString()} ${date.getHours()}`;
case 'month':
return `${date.getFullYear()}-${date.getMonth()}`;
default:
return `${date.getFullYear()}`;
}
};
// The first point of each period
const starts: number[] = [];
ticks.forEach(({ value }, tickIndex) => {
const previous = ticks[tickIndex - 1]?.value;
if (
tickIndex === 0 ||
(unit === 'day'
? // A trading day starts after the closed market (the post-market
// can last after midnight)
value - previous > 2 * 60 * 60 * 1000
: getPeriod(value) !== getPeriod(previous))
) {
starts.push(tickIndex);
}
});
return {};
const step = Math.max(1, Math.ceil(starts.length / aMaxLabels));
const labels = new Map<number, string>();
starts.forEach((tickIndex, startIndex) => {
if (startIndex % step === 0) {
labels.set(
tickIndex,
format(ticks[tickIndex].value, TIME_SERIES_LABEL_FORMATS[unit])
);
}
});
cache = { key, labels };
}
return cache.labels.get(index) ?? '';
};
}
export function getTimeAxisOptions({
@ -145,18 +279,19 @@ export function getTimeAxisOptions({
colorScheme,
display = true,
locale = getLocale(),
max,
min,
unit = 'year'
type = 'time',
unit = 'year',
withTime = false
}: {
borderWidth?: number;
colorScheme?: ColorScheme;
display?: boolean;
locale?: string;
max?: number;
min?: number;
unit?: 'day' | 'hour' | 'month' | 'year';
}): ScaleOptions<'time'> {
type?: 'time' | 'timeseries';
unit?: 'day' | 'hour' | 'month' | 'year' | false;
/** Whether the points are intraday (the tooltip shows the time) */
withTime?: boolean;
}): ScaleOptions<'time' | 'timeseries'> {
return {
display,
border: {
@ -166,18 +301,36 @@ export function getTimeAxisOptions({
grid: {
display: false
},
max,
min,
ticks: {
ticks:
type === 'timeseries'
? {
// A label at the first point of an hour, day, month or year
autoSkip: false,
callback: getTimeSeriesTickCallback(unit),
maxRotation: 0,
source: 'data'
}
: {
autoSkip: true,
maxRotation: 0,
maxTicksLimit: 6
},
time: {
tooltipFormat: unit === 'hour' ? 'HH:mm' : getDateFormatString(locale),
tooltipFormat: withTime
? unit === 'hour'
? 'HH:mm'
: `${getDateFormatString(locale)} HH:mm`
: getDateFormatString(locale),
displayFormats: {
day: 'EEE d',
hour: 'HH:mm',
month: unit === 'month' ? 'MMM' : 'MMM yy',
week: 'd MMM',
year: 'yyyy'
},
unit
},
type: 'time'
type
};
}

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

@ -1,6 +1,5 @@
import {
getChartBorderColor,
getTimeAxisBounds,
getTimeAxisOptions,
getTimeAxisUnit,
getValueAxisOptions,
@ -8,10 +7,7 @@ import {
} from '@ghostfolio/common/chart-helper';
import { primaryColorRgb, secondaryColorRgb } from '@ghostfolio/common/config';
import { getBackgroundColor, getLocale } from '@ghostfolio/common/helper';
import {
LineChartItem,
NullableLineChartItem
} from '@ghostfolio/common/interfaces';
import { NullableLineChartItem } from '@ghostfolio/common/interfaces';
import { ColorScheme, DateRange } from '@ghostfolio/common/types';
import {
@ -36,6 +32,7 @@ import {
LineElement,
PointElement,
TimeScale,
TimeSeriesScale,
Tooltip,
type TooltipOptions
} from 'chart.js';
@ -63,7 +60,7 @@ export class GfLineChartComponent
@Input() colorScheme?: ColorScheme;
@Input() currency?: string;
@Input() dateRange?: DateRange;
@Input() historicalDataItems: LineChartItem[] | null;
@Input() historicalDataItems: NullableLineChartItem[] | null;
@Input() isAnimated = false;
@Input() label: string;
@Input() locale = getLocale();
@ -93,6 +90,7 @@ export class GfLineChartComponent
PointElement,
LinearScale,
TimeScale,
TimeSeriesScale,
Tooltip
);
@ -143,7 +141,7 @@ export class GfLineChartComponent
this.isLoading = true;
const benchmarkPrices: (number | null)[] = [];
const labels: string[] = [];
const marketPrices: number[] = [];
const marketPrices: (number | null)[] = [];
this.historicalDataItems?.forEach((historicalDataItem, index) => {
benchmarkPrices.push(this.benchmarkDataItems?.[index]?.value ?? null);
@ -295,7 +293,8 @@ export class GfLineChartComponent
colorScheme: this.colorScheme,
display: this.showXAxis,
locale: this.locale,
...getTimeAxisBounds(this.dateRange),
// Equidistant points: the closed markets (nights, weekends) are skipped
type: this.dateRange ? 'timeseries' : 'time',
unit: getTimeAxisUnit(this.dateRange)
});
}

Loading…
Cancel
Save