|
|
@ -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; |
|
|
|