|
|
|
@ -16,6 +16,7 @@ import { LinearScale } from 'chart.js';
|
|
|
|
|
import { ArcElement } from 'chart.js';
|
|
|
|
|
import { DoughnutController } from 'chart.js';
|
|
|
|
|
import { Chart } from 'chart.js';
|
|
|
|
|
import * as Color from 'color';
|
|
|
|
|
|
|
|
|
|
@Component({
|
|
|
|
|
selector: 'gf-portfolio-proportion-chart',
|
|
|
|
@ -28,7 +29,7 @@ export class PortfolioProportionChartComponent
|
|
|
|
|
{
|
|
|
|
|
@Input() baseCurrency: Currency;
|
|
|
|
|
@Input() isInPercent: boolean;
|
|
|
|
|
@Input() key: string;
|
|
|
|
|
@Input() keys: string[];
|
|
|
|
|
@Input() locale: string;
|
|
|
|
|
@Input() maxItems?: number;
|
|
|
|
|
@Input() positions: {
|
|
|
|
@ -65,24 +66,54 @@ export class PortfolioProportionChartComponent
|
|
|
|
|
private initialize() {
|
|
|
|
|
this.isLoading = true;
|
|
|
|
|
const chartData: {
|
|
|
|
|
[symbol: string]: { color?: string; value: number };
|
|
|
|
|
[symbol: string]: {
|
|
|
|
|
color?: string;
|
|
|
|
|
subCategory: { [symbol: string]: { value: number } };
|
|
|
|
|
value: number;
|
|
|
|
|
};
|
|
|
|
|
} = {};
|
|
|
|
|
|
|
|
|
|
Object.keys(this.positions).forEach((symbol) => {
|
|
|
|
|
if (this.positions[symbol][this.key]) {
|
|
|
|
|
if (chartData[this.positions[symbol][this.key]]) {
|
|
|
|
|
chartData[this.positions[symbol][this.key]].value +=
|
|
|
|
|
if (this.positions[symbol][this.keys[0]]) {
|
|
|
|
|
if (chartData[this.positions[symbol][this.keys[0]]]) {
|
|
|
|
|
chartData[this.positions[symbol][this.keys[0]]].value +=
|
|
|
|
|
this.positions[symbol].value;
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
chartData[this.positions[symbol][this.keys[0]]].subCategory[
|
|
|
|
|
this.positions[symbol][this.keys[1]]
|
|
|
|
|
]
|
|
|
|
|
) {
|
|
|
|
|
chartData[this.positions[symbol][this.keys[0]]].subCategory[
|
|
|
|
|
this.positions[symbol][this.keys[1]]
|
|
|
|
|
].value += this.positions[symbol].value;
|
|
|
|
|
} else {
|
|
|
|
|
chartData[this.positions[symbol][this.keys[0]]].subCategory[
|
|
|
|
|
this.positions[symbol][this.keys[1]] ?? UNKNOWN_KEY
|
|
|
|
|
] = { value: this.positions[symbol].value };
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
chartData[this.positions[symbol][this.key]] = {
|
|
|
|
|
chartData[this.positions[symbol][this.keys[0]]] = {
|
|
|
|
|
subCategory: {},
|
|
|
|
|
value: this.positions[symbol].value
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (this.positions[symbol][this.keys[1]]) {
|
|
|
|
|
chartData[this.positions[symbol][this.keys[0]]].subCategory = {
|
|
|
|
|
[this.positions[symbol][this.keys[1]]]: {
|
|
|
|
|
value: this.positions[symbol].value
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (chartData[UNKNOWN_KEY]) {
|
|
|
|
|
chartData[UNKNOWN_KEY].value += this.positions[symbol].value;
|
|
|
|
|
} else {
|
|
|
|
|
chartData[UNKNOWN_KEY] = {
|
|
|
|
|
subCategory: this.keys[1]
|
|
|
|
|
? { [this.keys[1]]: { value: 0 } }
|
|
|
|
|
: undefined,
|
|
|
|
|
value: this.positions[symbol].value
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
@ -107,13 +138,17 @@ export class PortfolioProportionChartComponent
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!unknownItem) {
|
|
|
|
|
const index = chartDataSorted.push([UNKNOWN_KEY, { value: 0 }]);
|
|
|
|
|
const index = chartDataSorted.push([
|
|
|
|
|
UNKNOWN_KEY,
|
|
|
|
|
{ subCategory: {}, value: 0 }
|
|
|
|
|
]);
|
|
|
|
|
unknownItem = chartDataSorted[index];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rest.forEach((restItem) => {
|
|
|
|
|
if (unknownItem?.[1]) {
|
|
|
|
|
unknownItem[1] = {
|
|
|
|
|
subCategory: {},
|
|
|
|
|
value: unknownItem[1].value + restItem[1].value
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
@ -141,21 +176,53 @@ export class PortfolioProportionChartComponent
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const backgroundColorSubCategory: string[] = [];
|
|
|
|
|
const dataSubCategory: number[] = [];
|
|
|
|
|
const labelSubCategory: string[] = [];
|
|
|
|
|
|
|
|
|
|
chartDataSorted.forEach(([, item]) => {
|
|
|
|
|
let lightnessRatio = 0.2;
|
|
|
|
|
|
|
|
|
|
Object.keys(item.subCategory).forEach((subCategory) => {
|
|
|
|
|
backgroundColorSubCategory.push(
|
|
|
|
|
Color(item.color).lighten(lightnessRatio).hex()
|
|
|
|
|
);
|
|
|
|
|
dataSubCategory.push(item.subCategory[subCategory].value);
|
|
|
|
|
labelSubCategory.push(subCategory);
|
|
|
|
|
|
|
|
|
|
lightnessRatio += 0.1;
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const datasets = [
|
|
|
|
|
{
|
|
|
|
|
backgroundColor: chartDataSorted.map(([, item]) => {
|
|
|
|
|
return item.color;
|
|
|
|
|
}),
|
|
|
|
|
borderWidth: 0,
|
|
|
|
|
data: chartDataSorted.map(([, item]) => {
|
|
|
|
|
return item.value;
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
let labels = chartDataSorted.map(([label]) => {
|
|
|
|
|
return label;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (this.keys[1]) {
|
|
|
|
|
datasets.unshift({
|
|
|
|
|
backgroundColor: backgroundColorSubCategory,
|
|
|
|
|
borderWidth: 0,
|
|
|
|
|
data: dataSubCategory
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
labels = labelSubCategory.concat(labels);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const data = {
|
|
|
|
|
datasets: [
|
|
|
|
|
{
|
|
|
|
|
backgroundColor: chartDataSorted.map(([, item]) => {
|
|
|
|
|
return item.color;
|
|
|
|
|
}),
|
|
|
|
|
borderWidth: 0,
|
|
|
|
|
data: chartDataSorted.map(([, item]) => {
|
|
|
|
|
return item.value;
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
labels: chartDataSorted.map(([label]) => {
|
|
|
|
|
return label;
|
|
|
|
|
})
|
|
|
|
|
datasets,
|
|
|
|
|
labels
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (this.chartCanvas) {
|
|
|
|
@ -166,13 +233,16 @@ export class PortfolioProportionChartComponent
|
|
|
|
|
this.chart = new Chart(this.chartCanvas.nativeElement, {
|
|
|
|
|
data,
|
|
|
|
|
options: {
|
|
|
|
|
cutout: '70%',
|
|
|
|
|
plugins: {
|
|
|
|
|
legend: { display: false },
|
|
|
|
|
tooltip: {
|
|
|
|
|
callbacks: {
|
|
|
|
|
label: (context) => {
|
|
|
|
|
const label =
|
|
|
|
|
context.label === UNKNOWN_KEY ? 'Other' : context.label;
|
|
|
|
|
const labelIndex =
|
|
|
|
|
(data.datasets[context.datasetIndex - 1]?.data?.length ??
|
|
|
|
|
0) + context.dataIndex;
|
|
|
|
|
const label = context.chart.data.labels[labelIndex];
|
|
|
|
|
|
|
|
|
|
if (this.isInPercent) {
|
|
|
|
|
const value = 100 * <number>context.raw;
|
|
|
|
|