diff --git a/CHANGELOG.md b/CHANGELOG.md
index 98a0320e..ddffe90a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,8 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
+### Added
+
+- Added a vertical hover line to inspect data points in the line chart component
+
### Changed
+- Improved the tooltips of the chart components (content and style)
- Simplified the pricing page
## 1.153.0 - 27.05.2022
diff --git a/apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.component.html b/apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.component.html
index 7264be84..c3d905be 100644
--- a/apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.component.html
+++ b/apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.component.html
@@ -2,8 +2,10 @@
{{ itemByMonth.key }}
diff --git a/apps/client/src/app/components/home-market/home-market.html b/apps/client/src/app/components/home-market/home-market.html
index b55103bf..d509a641 100644
--- a/apps/client/src/app/components/home-market/home-market.html
+++ b/apps/client/src/app/components/home-market/home-market.html
@@ -7,11 +7,13 @@
diff --git a/apps/client/src/app/components/home-overview/home-overview.html b/apps/client/src/app/components/home-overview/home-overview.html
index 7f804d99..e82ef7f5 100644
--- a/apps/client/src/app/components/home-overview/home-overview.html
+++ b/apps/client/src/app/components/home-overview/home-overview.html
@@ -6,6 +6,7 @@
+ getTooltipPositionerMapTop(this.chart, position);
}
public ngOnChanges() {
@@ -98,6 +112,7 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
data: this.investments.map((position) => {
return position.investment;
}),
+ label: 'Investment',
segment: {
borderColor: (context: unknown) =>
this.isInFuture(
@@ -114,6 +129,9 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
if (this.chartCanvas) {
if (this.chart) {
this.chart.data = data;
+ this.chart.options.plugins.tooltip = (
+ this.getTooltipPluginConfiguration()
+ );
this.chart.update();
} else {
this.chart = new Chart(this.chartCanvas.nativeElement, {
@@ -124,13 +142,20 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
tension: 0
},
point: {
+ hoverBackgroundColor: getBackgroundColor(),
+ hoverRadius: 2,
radius: 0
}
},
+ interaction: { intersect: false, mode: 'index' },
maintainAspectRatio: true,
- plugins: {
+ plugins: {
legend: {
display: false
+ },
+ tooltip: this.getTooltipPluginConfiguration(),
+ verticalHoverLine: {
+ color: `rgba(${getTextColor()}, 0.1)`
}
},
responsive: true,
@@ -138,16 +163,21 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
x: {
display: true,
grid: {
+ borderColor: `rgba(${getTextColor()}, 0.1)`,
+ color: `rgba(${getTextColor()}, 0.8)`,
display: false
},
type: 'time',
time: {
+ tooltipFormat: getDateFormatString(this.locale),
unit: 'year'
}
},
y: {
display: !this.isInPercent,
grid: {
+ borderColor: `rgba(${getTextColor()}, 0.1)`,
+ color: `rgba(${getTextColor()}, 0.8)`,
display: false
},
ticks: {
@@ -161,6 +191,7 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
}
}
},
+ plugins: [getVerticalHoverLinePlugin(this.chartCanvas)],
type: 'line'
});
@@ -169,6 +200,19 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
}
}
+ private getTooltipPluginConfiguration() {
+ return {
+ ...getTooltipOptions(
+ this.isInPercent ? undefined : this.currency,
+ this.isInPercent ? undefined : this.locale
+ ),
+ mode: 'index',
+ position: 'top',
+ xAlign: 'center',
+ yAlign: 'bottom'
+ };
+ }
+
private isInFuture(aContext: any, aValue: T) {
return isAfter(new Date(aContext?.p1?.parsed?.x), new Date())
? aValue
diff --git a/apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html b/apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html
index 64bb56f3..59a8e4e1 100644
--- a/apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html
+++ b/apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html
@@ -23,7 +23,9 @@
class="mb-4"
benchmarkLabel="Average Unit Price"
[benchmarkDataItems]="benchmarkDataItems"
+ [currency]="SymbolProfile?.currency"
[historicalDataItems]="historicalDataItems"
+ [locale]="data.locale"
[showGradient]="true"
[showXAxis]="true"
[showYAxis]="true"
diff --git a/apps/client/src/app/pages/portfolio/analysis/analysis-page.html b/apps/client/src/app/pages/portfolio/analysis/analysis-page.html
index 361110d7..4364a8e8 100644
--- a/apps/client/src/app/pages/portfolio/analysis/analysis-page.html
+++ b/apps/client/src/app/pages/portfolio/analysis/analysis-page.html
@@ -2,21 +2,17 @@
Analysis
-
-
- Investment Timeline
-
-
-
-
-
+
+
Investment Timeline
+
+
diff --git a/libs/common/src/lib/chart-helper.ts b/libs/common/src/lib/chart-helper.ts
new file mode 100644
index 00000000..d2c68af2
--- /dev/null
+++ b/libs/common/src/lib/chart-helper.ts
@@ -0,0 +1,83 @@
+import { Chart, TooltipPosition } from 'chart.js';
+
+import { getBackgroundColor, getTextColor } from './helper';
+
+export function getTooltipOptions(currency = '', locale = '') {
+ return {
+ backgroundColor: getBackgroundColor(),
+ bodyColor: `rgb(${getTextColor()})`,
+ borderWidth: 1,
+ borderColor: `rgba(${getTextColor()}, 0.1)`,
+ callbacks: {
+ label: (context) => {
+ let label = context.dataset.label || '';
+ if (label) {
+ label += ': ';
+ }
+ if (context.parsed.y !== null) {
+ if (currency) {
+ label += `${context.parsed.y.toLocaleString(locale, {
+ maximumFractionDigits: 2,
+ minimumFractionDigits: 2
+ })} ${currency}`;
+ } else {
+ label += context.parsed.y.toFixed(2);
+ }
+ }
+ return label;
+ }
+ },
+ caretSize: 0,
+ cornerRadius: 2,
+ footerColor: `rgb(${getTextColor()})`,
+ itemSort: (a, b) => {
+ // Reverse order
+ return b.datasetIndex - a.datasetIndex;
+ },
+ titleColor: `rgb(${getTextColor()})`,
+ usePointStyle: true
+ };
+}
+
+export function getTooltipPositionerMapTop(
+ chart: Chart,
+ position: TooltipPosition
+) {
+ if (!position) {
+ return false;
+ }
+ return {
+ x: position.x,
+ y: chart.chartArea.top
+ };
+}
+
+export function getVerticalHoverLinePlugin(chartCanvas) {
+ return {
+ afterDatasetsDraw: (chart, x, options) => {
+ const active = chart.getActiveElements();
+
+ if (!active || active.length === 0) {
+ return;
+ }
+
+ const color = options.color || `rgb(${getTextColor()})`;
+ const width = options.width || 1;
+
+ const {
+ chartArea: { bottom, top }
+ } = chart;
+ const xValue = active[0].element.x;
+
+ const context = chartCanvas.nativeElement.getContext('2d');
+ context.lineWidth = width;
+ context.strokeStyle = color;
+
+ context.beginPath();
+ context.moveTo(xValue, top);
+ context.lineTo(xValue, bottom);
+ context.stroke();
+ },
+ id: 'verticalHoverLine'
+ };
+}
diff --git a/libs/ui/src/lib/fire-calculator/fire-calculator.component.ts b/libs/ui/src/lib/fire-calculator/fire-calculator.component.ts
index 933d1899..00c83482 100644
--- a/libs/ui/src/lib/fire-calculator/fire-calculator.component.ts
+++ b/libs/ui/src/lib/fire-calculator/fire-calculator.component.ts
@@ -13,6 +13,7 @@ import {
ViewChild
} from '@angular/core';
import { FormBuilder, FormControl } from '@angular/forms';
+import { getTooltipOptions } from '@ghostfolio/common/chart-helper';
import { primaryColorRgb } from '@ghostfolio/common/config';
import { transformTickToAbbreviation } from '@ghostfolio/common/helper';
import {
@@ -182,10 +183,7 @@ export class FireCalculatorComponent
options: {
plugins: {
tooltip: {
- itemSort: (a, b) => {
- // Reverse order
- return b.datasetIndex - a.datasetIndex;
- },
+ ...getTooltipOptions(),
mode: 'index',
callbacks: {
footer: (items) => {
diff --git a/libs/ui/src/lib/line-chart/line-chart.component.ts b/libs/ui/src/lib/line-chart/line-chart.component.ts
index d2c185b1..bcf004ed 100644
--- a/libs/ui/src/lib/line-chart/line-chart.component.ts
+++ b/libs/ui/src/lib/line-chart/line-chart.component.ts
@@ -10,8 +10,17 @@ import {
OnDestroy,
ViewChild
} from '@angular/core';
+import {
+ getTooltipOptions,
+ getTooltipPositionerMapTop,
+ getVerticalHoverLinePlugin
+} from '@ghostfolio/common/chart-helper';
import { primaryColorRgb, secondaryColorRgb } from '@ghostfolio/common/config';
-import { getBackgroundColor } from '@ghostfolio/common/helper';
+import {
+ getBackgroundColor,
+ getDateFormatString,
+ getTextColor
+} from '@ghostfolio/common/helper';
import {
Chart,
Filler,
@@ -19,7 +28,8 @@ import {
LineElement,
LinearScale,
PointElement,
- TimeScale
+ TimeScale,
+ Tooltip
} from 'chart.js';
import { LineChartItem } from './interfaces/line-chart.interface';
@@ -33,7 +43,9 @@ import { LineChartItem } from './interfaces/line-chart.interface';
export class LineChartComponent implements AfterViewInit, OnChanges, OnDestroy {
@Input() benchmarkDataItems: LineChartItem[] = [];
@Input() benchmarkLabel = '';
+ @Input() currency: string;
@Input() historicalDataItems: LineChartItem[];
+ @Input() locale: string;
@Input() showGradient = false;
@Input() showLegend = false;
@Input() showLoader = true;
@@ -57,8 +69,12 @@ export class LineChartComponent implements AfterViewInit, OnChanges, OnDestroy {
LineElement,
PointElement,
LinearScale,
- TimeScale
+ TimeScale,
+ Tooltip
);
+
+ Tooltip.positioners['top'] = (elements, position) =>
+ getTooltipPositionerMapTop(this.chart, position);
}
public ngAfterViewInit() {
@@ -142,26 +158,43 @@ export class LineChartComponent implements AfterViewInit, OnChanges, OnDestroy {
if (this.chartCanvas) {
if (this.chart) {
this.chart.data = data;
+ this.chart.options.plugins.tooltip = (
+ this.getTooltipPluginConfiguration()
+ );
this.chart.update();
} else {
this.chart = new Chart(this.chartCanvas.nativeElement, {
data,
options: {
animation: false,
- plugins: {
+ elements: {
+ point: {
+ hoverBackgroundColor: getBackgroundColor(),
+ hoverRadius: 2
+ }
+ },
+ interaction: { intersect: false, mode: 'index' },
+ plugins: {
legend: {
align: 'start',
display: this.showLegend,
position: 'bottom'
+ },
+ tooltip: this.getTooltipPluginConfiguration(),
+ verticalHoverLine: {
+ color: `rgba(${getTextColor()}, 0.1)`
}
},
scales: {
x: {
display: this.showXAxis,
grid: {
+ borderColor: `rgba(${getTextColor()}, 0.1)`,
+ color: `rgba(${getTextColor()}, 0.8)`,
display: false
},
time: {
+ tooltipFormat: getDateFormatString(this.locale),
unit: 'year'
},
type: 'time'
@@ -169,6 +202,8 @@ export class LineChartComponent implements AfterViewInit, OnChanges, OnDestroy {
y: {
display: this.showYAxis,
grid: {
+ borderColor: `rgba(${getTextColor()}, 0.1)`,
+ color: `rgba(${getTextColor()}, 0.8)`,
display: false
},
max: this.yMax,
@@ -204,6 +239,7 @@ export class LineChartComponent implements AfterViewInit, OnChanges, OnDestroy {
},
spanGaps: true
},
+ plugins: [getVerticalHoverLinePlugin(this.chartCanvas)],
type: 'line'
});
}
@@ -211,4 +247,14 @@ export class LineChartComponent implements AfterViewInit, OnChanges, OnDestroy {
this.isLoading = false;
}
+
+ private getTooltipPluginConfiguration() {
+ return {
+ ...getTooltipOptions(this.currency, this.locale),
+ mode: 'index',
+ position: 'top',
+ xAlign: 'center',
+ yAlign: 'bottom'
+ };
+ }
}
diff --git a/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts b/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts
index 78e56a8c..389e0967 100644
--- a/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts
+++ b/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts
@@ -10,6 +10,7 @@ import {
Output,
ViewChild
} from '@angular/core';
+import { getTooltipOptions } from '@ghostfolio/common/chart-helper';
import { UNKNOWN_KEY } from '@ghostfolio/common/config';
import { getTextColor } from '@ghostfolio/common/helper';
import { PortfolioPosition, UniqueAsset } from '@ghostfolio/common/interfaces';
@@ -255,8 +256,9 @@ export class PortfolioProportionChartComponent
if (this.chartCanvas) {
if (this.chart) {
this.chart.data = data;
- this.chart.options.plugins.tooltip =
- this.getTooltipPluginConfiguration(data);
+ this.chart.options.plugins.tooltip = (
+ this.getTooltipPluginConfiguration(data)
+ );
this.chart.update();
} else {
this.chart = new Chart(this.chartCanvas.nativeElement, {
@@ -339,6 +341,7 @@ export class PortfolioProportionChartComponent
private getTooltipPluginConfiguration(data: ChartConfiguration['data']) {
return {
+ ...getTooltipOptions(this.baseCurrency, this.locale),
callbacks: {
label: (context) => {
const labelIndex =