This repository was archived by the owner on Mar 31, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathpie-chart.d3.ts
More file actions
122 lines (101 loc) · 3.76 KB
/
Copy pathpie-chart.d3.ts
File metadata and controls
122 lines (101 loc) · 3.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import * as math from 'mathjs';
import * as d3 from 'd3';
import { BaseD3, RenderOptions as BaseRenderOptions } from './base.d3';
import { Observable } from 'rxjs';
import { PieChartDatum } from '../components/pie-chart/pie-chart.component';
import { CategoricalPoint } from '../datasets/metas/types';
export interface LegendItemStyle {
colorMap: string[];
}
export interface RenderOptions<Datum extends PieChartDatum> extends BaseRenderOptions {
data$: Observable<Datum[]>;
}
export class PieChartD3 extends BaseD3<RenderOptions<PieChartDatum>> {
static fontSizeSmall = 12;
static fontSizeMedium = 14;
static defaultLegendItemStyle: LegendItemStyle = {
colorMap: [
'#2F5EC4',
'#4285F4',
'#93D5ED',
'#DCF7FA',
],
};
protected dataG: d3.Selection<SVGGElement, unknown, null, undefined>;
protected pie = d3.pie<CategoricalPoint>().value(point => point.y).sort(null);
protected arc = d3.arc().outerRadius(200).innerRadius(140);
protected legendG: d3.Selection<SVGGElement, unknown, null, undefined>;
render() {
super.render();
const { data$ } = this.renderOptions;
this.renderData();
data$
.pipe(this.takeUntilCleared())
.subscribe(data => {
this.updateData(data);
this.updateLegend(data);
});
}
protected renderData() {
this.dataG = this.svg
.append('g')
.attr('class', 'xy_chart-data');
}
protected updateData(data: PieChartDatum[]) {
this.svg.selectAll('.slice').remove();
if (data.length > 0) {
// TODO: Support rendering multiple datum in the same chart
const { points, style = {} } = data[0];
const { colorMap } = { ...PieChartD3.defaultLegendItemStyle, ...style };
const colorScale = d3.scaleOrdinal(colorMap);
const { height, width } = this.renderOptions;
const xCenter = width * 2 / 5;
const yCenter = height / 2;
this.svg
.selectAll('.slice')
.data(this.pie(points))
.enter()
.append('path')
.attr('class', 'slice')
.attr('fill', ({ data: point }) => colorScale(point.x))
.attr('transform', `translate(${xCenter}, ${yCenter})`)
.attr('d', this.arc as any)
.attr('stroke', 'white')
.attr('stroke-width', '6px');
}
}
protected updateLegend(data: PieChartDatum[]) {
const { points, style = {} } = data[0];
const { colorMap } = { ...PieChartD3.defaultLegendItemStyle, ...style };
const colorScale = d3.scaleOrdinal(colorMap);
const { height, width } = this.renderOptions;
const legendLabelSize = PieChartD3.fontSizeMedium;
const left = width * 0.7;
const top = height / 2 - legendLabelSize * points.length / 2;
const totalYSum = math.sum(points.map(({ y }) => y));
const getPercentageText = (yValue: number) => `${(yValue / totalYSum * 100).toFixed(2)}%`;
this.legendG = this.svg
.append('g')
.attr('transform', `translate(${left},${top})`);
this.legendG
.selectAll(null)
.data(this.pie(points))
.enter()
.append('rect')
.attr('y', ({ index }) => legendLabelSize * index * 1.8)
.attr('width', legendLabelSize)
.attr('height', legendLabelSize)
.attr('fill', ({ data: point }) => colorScale(point.x))
.attr('stroke', 'grey')
.style('stroke-width', '1px');
this.legendG
.selectAll(null)
.data(this.pie(points))
.enter()
.append('text')
.text(({ data: point }) => `${point.x} (${getPercentageText(point.y)})`)
.attr('x', legendLabelSize * 1.2)
.attr('y', ({ index }) => legendLabelSize * index * 1.8 + legendLabelSize)
.style('font-size', `${legendLabelSize}px`);
}
}