Skip to content

Commit 9a4dc70

Browse files
committed
feat(charts): implementa chart do tipo donut
1 parent daea44c commit 9a4dc70

File tree

5 files changed

+262
-55
lines changed

5 files changed

+262
-55
lines changed

projects/ui/src/lib/components/po-chart-new/po-chart-grid-utils.ts

Lines changed: 66 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { EChartsOption } from 'echarts/dist/echarts.esm';
22
import { PoChartNewComponent } from './po-chart-new.component';
3+
import { PoChartSerie } from '../po-chart/interfaces/po-chart-serie.interface';
4+
import { PoChartType } from '../po-chart/enums/po-chart-type.enum';
35

46
const gridPaddingValues = {
57
paddingBottomWithTopLegend: 48,
@@ -16,6 +18,7 @@ const gridPaddingValues = {
1618
} as const;
1719

1820
export class PoChartGridUtils {
21+
private isTypeDonut = false;
1922
constructor(private readonly component: PoChartNewComponent) {}
2023

2124
setGridOption(options: EChartsOption) {
@@ -157,39 +160,87 @@ export class PoChartGridUtils {
157160
}
158161
}
159162

160-
setListTypePie() {
161-
let radius = '85%';
163+
setSerieTypeDonutPie(serie: any, color: string) {
164+
if (this.component.listTypePieDonut?.length) {
165+
const borderWidth = this.resolvePx('--border-width-sm');
166+
const borderColor = this.component.getCSSVariable('--border-color', '.po-chart');
167+
const seriePie = {
168+
name: serie.name,
169+
value: serie.data,
170+
itemStyle: {
171+
borderWidth: borderWidth,
172+
borderColor: borderColor,
173+
color: color,
174+
borderRadius: this.component.options?.borderRadius
175+
},
176+
label: { show: this.isTypeDonut && this.component.options?.textCenterGraph }
177+
};
178+
this.component.listTypePieDonut[0].data.push(seriePie);
179+
}
180+
}
181+
182+
setListTypeDonutPie(type: PoChartType) {
183+
if (type === PoChartType.Donut) {
184+
this.isTypeDonut = true;
185+
this.component.itemsTypeDonut = this.normalizeToPercentage(this.component.series);
186+
}
187+
let radiusHorizontal = '85%';
188+
let radiusVertical = '55%';
162189
let positionHorizontal;
163190
if (this.component.options?.legend === false) {
164-
radius = '95%';
191+
radiusHorizontal = '95%';
192+
radiusVertical = '65%';
165193
positionHorizontal = '50%';
166194
} else {
167195
positionHorizontal = this.component.options?.legendVerticalPosition === 'top' ? '54%' : '46%';
168196
}
169-
this.component.listTypePie = [
197+
198+
if (this.component.options?.innerRadius) {
199+
radiusVertical = this.getAdjustedRadius(radiusVertical, this.component.options.innerRadius);
200+
}
201+
const radius = this.isTypeDonut ? [radiusVertical, radiusHorizontal] : radiusHorizontal;
202+
this.component.listTypePieDonut = [
170203
{
171204
type: 'pie',
172205
center: ['50%', positionHorizontal],
173206
radius: radius,
207+
label: {
208+
show: !!(this.isTypeDonut && this.component.options?.textCenterGraph),
209+
position: 'center',
210+
formatter: this.component.options?.textCenterGraph,
211+
fontFamily: this.component.getCSSVariable('--font-family-hightlight-value', '.po-chart'),
212+
fontSize: this.resolvePx('--font-size-hightlight-value', '.po-chart'),
213+
color: this.component.getCSSVariable('--color-hightlight-value', '.po-chart'),
214+
fontWeight: Number(this.component.getCSSVariable('--font-weight-hightlight-value', '.po-chart'))
215+
},
174216
emphasis: { focus: 'self' },
175217
data: [],
176218
blur: { itemStyle: { opacity: 0.4 } }
177219
}
178220
];
179221
}
180222

181-
setSerieTypePie(serie: any, color: string) {
182-
if (this.component.listTypePie?.length) {
183-
const borderWidth = this.resolvePx('--border-width-sm');
184-
const borderColor = this.component.getCSSVariable('--border-color', '.po-chart');
185-
const seriePie = {
186-
name: serie.name,
187-
value: serie.data,
188-
itemStyle: { borderWidth: borderWidth, borderColor: borderColor, color: color },
189-
label: { show: false }
190-
};
191-
this.component.listTypePie[0].data.push(seriePie);
223+
private getAdjustedRadius(radius: string, innerRadius: number): string {
224+
const radiusValue = parseFloat(radius);
225+
if (innerRadius >= 100) {
226+
return radius;
192227
}
228+
const adjusted = radiusValue * (innerRadius / 100);
229+
return `${adjusted}%`;
230+
}
231+
232+
private normalizeToPercentage(series: Array<PoChartSerie>) {
233+
const total =
234+
series
235+
.map(item => item.data)
236+
.filter((value): value is number => typeof value === 'number')
237+
.reduce((sum, value) => sum + value, 0) || 1;
238+
239+
return series.map(item => ({
240+
label: item.label,
241+
data: item.data,
242+
valuePercentage: +(((item.data as number) / total) * 100).toFixed(2)
243+
}));
193244
}
194245

195246
resolvePx(size: string, selector?: string): number {

projects/ui/src/lib/components/po-chart-new/po-chart-grid.utils.spec.ts

Lines changed: 77 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { PoChartType } from '../po-chart/enums/po-chart-type.enum';
2+
import { PoChartOptions } from '../po-chart/interfaces/po-chart-options.interface';
13
import { PoChartGridUtils } from './po-chart-grid-utils';
24
import { EChartsOption } from 'echarts';
35

@@ -87,18 +89,29 @@ describe('PoChartGridUtils', () => {
8789
});
8890

8991
describe('setListPie', () => {
92+
const labelProperties = {
93+
show: false,
94+
position: 'center',
95+
formatter: undefined,
96+
fontFamily: '',
97+
fontSize: undefined,
98+
color: '',
99+
fontWeight: 0
100+
};
101+
90102
it('should set pie config with radius 95% and center 50% 50% when legend is false', () => {
91103
utils['component'].options = { legend: false } as any;
92104

93-
utils.setListTypePie();
105+
utils.setListTypeDonutPie(PoChartType.Pie);
94106

95-
expect(utils['component'].listTypePie).toEqual([
107+
expect(utils['component'].listTypePieDonut).toEqual([
96108
{
97109
type: 'pie',
98110
center: ['50%', '50%'],
99111
radius: '95%',
100112
emphasis: { focus: 'self' },
101113
data: [],
114+
label: labelProperties,
102115
blur: { itemStyle: { opacity: 0.4 } }
103116
}
104117
]);
@@ -107,15 +120,16 @@ describe('PoChartGridUtils', () => {
107120
it('should set pie config with center 50% 54% when legendVerticalPosition is top', () => {
108121
utils['component'].options = { legend: true, legendVerticalPosition: 'top' } as any;
109122

110-
utils.setListTypePie();
123+
utils.setListTypeDonutPie(PoChartType.Pie);
111124

112-
expect(utils['component'].listTypePie).toEqual([
125+
expect(utils['component'].listTypePieDonut).toEqual([
113126
{
114127
type: 'pie',
115128
center: ['50%', '54%'],
116129
radius: '85%',
117130
emphasis: { focus: 'self' },
118131
data: [],
132+
label: labelProperties,
119133
blur: { itemStyle: { opacity: 0.4 } }
120134
}
121135
]);
@@ -124,15 +138,72 @@ describe('PoChartGridUtils', () => {
124138
it('should set pie config with center 50% 46% when legendVerticalPosition is not top', () => {
125139
utils['component'].options = { legend: true, legendVerticalPosition: 'bottom' } as any;
126140

127-
utils.setListTypePie();
141+
utils.setListTypeDonutPie(PoChartType.Pie);
128142

129-
expect(utils['component'].listTypePie).toEqual([
143+
expect(utils['component'].listTypePieDonut).toEqual([
130144
{
131145
type: 'pie',
132146
center: ['50%', '46%'],
133147
radius: '85%',
134148
emphasis: { focus: 'self' },
135149
data: [],
150+
label: labelProperties,
151+
blur: { itemStyle: { opacity: 0.4 } }
152+
}
153+
]);
154+
});
155+
});
156+
157+
describe('setListDonut', () => {
158+
const labelProperties = {
159+
show: true,
160+
position: 'center',
161+
formatter: 'test',
162+
fontFamily: '',
163+
fontSize: undefined,
164+
color: '',
165+
fontWeight: 0
166+
};
167+
168+
it('should set donut config if innerRadius is 100', () => {
169+
utils['component'].options = { innerRadius: 100, textCenterGraph: 'test' } as PoChartOptions;
170+
utils['component'].series = [
171+
{ label: 'Serie 1', data: 10 },
172+
{ label: 'Serie 2', data: 30 }
173+
];
174+
175+
utils.setListTypeDonutPie(PoChartType.Donut);
176+
177+
expect(utils['component'].listTypePieDonut).toEqual([
178+
{
179+
type: 'pie',
180+
center: ['50%', '46%'],
181+
radius: ['55%', '85%'],
182+
emphasis: { focus: 'self' },
183+
data: [],
184+
label: labelProperties,
185+
blur: { itemStyle: { opacity: 0.4 } }
186+
}
187+
]);
188+
});
189+
190+
it('should set donut config if innerRadius is 80', () => {
191+
utils['component'].options = { innerRadius: 80, textCenterGraph: 'test' } as PoChartOptions;
192+
utils['component'].series = [
193+
{ label: 'Serie 1', data: 10 },
194+
{ label: 'Serie 2', data: 30 }
195+
];
196+
197+
utils.setListTypeDonutPie(PoChartType.Donut);
198+
199+
expect(utils['component'].listTypePieDonut).toEqual([
200+
{
201+
type: 'pie',
202+
center: ['50%', '46%'],
203+
radius: ['44%', '85%'],
204+
emphasis: { focus: 'self' },
205+
data: [],
206+
label: labelProperties,
136207
blur: { itemStyle: { opacity: 0.4 } }
137208
}
138209
]);

projects/ui/src/lib/components/po-chart-new/po-chart-new.component.spec.ts

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,57 @@ describe('PoChartNewComponent', () => {
512512
expect(component.seriesClick.emit).not.toHaveBeenCalled();
513513
});
514514

515+
it('should emit seriesHover event when clicking on the chart if type is Donut', () => {
516+
const tooltipElement = document.createElement('div');
517+
tooltipElement.id = 'custom-tooltip';
518+
document.body.appendChild(tooltipElement);
519+
const chartElement = document.createElement('div');
520+
chartElement.id = 'chart-id';
521+
document.body.appendChild(chartElement);
522+
component['el'] = {
523+
nativeElement: document
524+
};
525+
526+
component['chartInstance'] = {
527+
on: jasmine.createSpy('on')
528+
} as any;
529+
530+
spyOn(component.seriesHover, 'emit');
531+
spyOn(component.seriesClick, 'emit');
532+
533+
component.series = [{ label: 'Colombia', data: 300, color: 'color-10' }];
534+
component.itemsTypeDonut = [{ label: 'Colombia', data: 300, valuePercentage: '80%' }];
535+
536+
component['initEChartsEvents']();
537+
538+
expect(component['chartInstance'].on).toHaveBeenCalledWith('mouseover', jasmine.any(Function));
539+
540+
const mouseoverCallback = component['chartInstance'].on.calls.argsFor(1)[1];
541+
542+
const mockParams = {
543+
value: 300,
544+
name: 'Colombia',
545+
seriesType: 'pie',
546+
seriesIndex: 0,
547+
event: { offsetX: 10, offsetY: 20 }
548+
};
549+
mouseoverCallback(mockParams);
550+
551+
expect(component['positionTooltip']).toBe('top');
552+
553+
const clickCallback = component['chartInstance'].on.calls.argsFor(0)[1];
554+
555+
const mockParamsClick = {};
556+
clickCallback(mockParamsClick);
557+
558+
expect(component.tooltipText).toBe(`Colombia: <b>80%%</b>`);
559+
expect(component.seriesHover.emit).toHaveBeenCalledWith({
560+
label: 'Colombia',
561+
data: 300
562+
});
563+
expect(component.seriesClick.emit).not.toHaveBeenCalled();
564+
});
565+
515566
it('should hide the tooltip when leaving the chart (mouseout)', () => {
516567
component['chartInstance'] = {
517568
on: jasmine.createSpy('on')
@@ -845,12 +896,23 @@ describe('PoChartNewComponent', () => {
845896
expect(result[1].type).toBe('bar');
846897
});
847898

848-
it('should return type Donut', () => {
899+
it('should return type Donut if data is not valid', () => {
849900
component.type = PoChartType.Donut;
850901
component.series = [{ label: 'Serie 1', data: [7, 8, 9] }];
851902

852903
const result = component['setSeries']();
853-
expect(result[0].type).toBe('donut');
904+
expect(result[0].type).toBe('pie');
905+
});
906+
907+
it('should return type Donut', () => {
908+
component.type = PoChartType.Donut;
909+
component.series = [
910+
{ label: 'Serie 1', data: 10 },
911+
{ label: 'Serie 2', data: 30 }
912+
];
913+
914+
const result = component['setSeries']();
915+
expect(result[0].type).toBe('pie');
854916
});
855917

856918
it('should return type Pie', () => {
@@ -1201,7 +1263,7 @@ describe('PoChartNewComponent', () => {
12011263
expect(mockChartInstance.getDataURL).toHaveBeenCalledWith({
12021264
type: 'jpeg',
12031265
pixelRatio: 2,
1204-
backgroundColor: 'white'
1266+
backgroundColor: '#ffffff'
12051267
});
12061268
expect(component['configureImageCanvas']).toHaveBeenCalledWith('jpeg', mockImage);
12071269
});

0 commit comments

Comments
 (0)