Skip to content

Commit e30f7cb

Browse files
committed
feat(charts): implementa chart do tipo pie
1 parent 7056918 commit e30f7cb

File tree

5 files changed

+249
-74
lines changed

5 files changed

+249
-74
lines changed

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

+38
Original file line numberDiff line numberDiff line change
@@ -134,10 +134,48 @@ export class PoChartGridUtils {
134134
color: color
135135
};
136136
serie.emphasis = { focus: 'series' };
137+
serie.blur = {
138+
itemStyle: { opacity: 0.4 }
139+
};
137140
this.component.boundaryGap = true;
138141
}
139142
}
140143

144+
setListTypePie() {
145+
let radius = '85%';
146+
let positionHorizontal;
147+
if (this.component.options?.legend === false) {
148+
radius = '95%';
149+
positionHorizontal = '50%';
150+
} else {
151+
positionHorizontal = this.component.options?.legendVerticalPosition === 'top' ? '54%' : '46%';
152+
}
153+
this.component.listTypePie = [
154+
{
155+
type: 'pie',
156+
center: ['50%', positionHorizontal],
157+
radius: radius,
158+
emphasis: { focus: 'self' },
159+
data: [],
160+
blur: { itemStyle: { opacity: 0.4 } }
161+
}
162+
];
163+
}
164+
165+
setSerieTypePie(serie: any, color: string) {
166+
if (this.component.listTypePie?.length) {
167+
const borderWidth = this.resolvePx('--border-width-sm');
168+
const borderColor = this.component.getCSSVariable('--border-color', '.po-chart');
169+
const seriePie = {
170+
name: serie.name,
171+
value: serie.data,
172+
itemStyle: { borderWidth: borderWidth, borderColor: borderColor, color: color },
173+
label: { show: false }
174+
};
175+
this.component.listTypePie[0].data.push(seriePie);
176+
}
177+
}
178+
141179
resolvePx(size: string, selector?: string): number {
142180
const token = this.component.getCSSVariable(size, selector);
143181
if (token.endsWith('px')) {

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

+53
Original file line numberDiff line numberDiff line change
@@ -85,4 +85,57 @@ describe('PoChartGridUtils', () => {
8585
expect(option.xAxis['axisLabel'].overflow).toBe('break');
8686
});
8787
});
88+
89+
describe('setListPie', () => {
90+
it('should set pie config with radius 95% and center 50% 50% when legend is false', () => {
91+
utils['component'].options = { legend: false } as any;
92+
93+
utils.setListTypePie();
94+
95+
expect(utils['component'].listTypePie).toEqual([
96+
{
97+
type: 'pie',
98+
center: ['50%', '50%'],
99+
radius: '95%',
100+
emphasis: { focus: 'self' },
101+
data: [],
102+
blur: { itemStyle: { opacity: 0.4 } }
103+
}
104+
]);
105+
});
106+
107+
it('should set pie config with center 50% 54% when legendVerticalPosition is top', () => {
108+
utils['component'].options = { legend: true, legendVerticalPosition: 'top' } as any;
109+
110+
utils.setListTypePie();
111+
112+
expect(utils['component'].listTypePie).toEqual([
113+
{
114+
type: 'pie',
115+
center: ['50%', '54%'],
116+
radius: '85%',
117+
emphasis: { focus: 'self' },
118+
data: [],
119+
blur: { itemStyle: { opacity: 0.4 } }
120+
}
121+
]);
122+
});
123+
124+
it('should set pie config with center 50% 46% when legendVerticalPosition is not top', () => {
125+
utils['component'].options = { legend: true, legendVerticalPosition: 'bottom' } as any;
126+
127+
utils.setListTypePie();
128+
129+
expect(utils['component'].listTypePie).toEqual([
130+
{
131+
type: 'pie',
132+
center: ['50%', '46%'],
133+
radius: '85%',
134+
emphasis: { focus: 'self' },
135+
data: [],
136+
blur: { itemStyle: { opacity: 0.4 } }
137+
}
138+
]);
139+
});
140+
});
88141
});

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

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
<div *ngIf="series.length" [class.expanded]="isExpanded" id="chart-container">
22
<div class="po-chart-header">
3-
<div class="po-chart-header-title po-lg-9 po-md-8 po-sm-6" style="padding: 0">
4-
<strong> {{ title }} </strong>
3+
<div
4+
class="po-chart-header-title po-lg-9 po-md-8 po-sm-6"
5+
[p-tooltip]="tooltipTitle"
6+
(mouseover)="showTooltipTitle($event)"
7+
>
8+
{{ title }}
59
</div>
610

711
<div class="po-chart-header-actions po-lg-3 po-md-4 po-sm-6">

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

+87-46
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,29 @@ describe('PoChartNewComponent', () => {
122122
expect(component).toBeTruthy();
123123
});
124124

125+
it('should show tooltip only when content overflows', () => {
126+
const event: any = {
127+
target: document.createElement('div')
128+
};
129+
130+
const element = event.target as HTMLElement;
131+
132+
// Simula overflow
133+
Object.defineProperty(element, 'scrollWidth', { value: 150, configurable: true });
134+
Object.defineProperty(element, 'offsetWidth', { value: 100, configurable: true });
135+
136+
component.title = 'Long Title';
137+
component.showTooltipTitle(event);
138+
expect(component['tooltipTitle']).toBe('Long Title');
139+
140+
// Simula conteúdo sem overflow
141+
Object.defineProperty(element, 'scrollWidth', { value: 80 });
142+
Object.defineProperty(element, 'offsetWidth', { value: 100 });
143+
144+
component.showTooltipTitle(event);
145+
expect(component['tooltipTitle']).toBeUndefined();
146+
});
147+
125148
describe('Lifecycle hooks:', () => {
126149
it('ngAfterViewInit: should initialize echarts', () => {
127150
spyOn(component, <any>'initECharts');
@@ -399,6 +422,35 @@ describe('PoChartNewComponent', () => {
399422
expect(component.seriesHover.emit).not.toHaveBeenCalled();
400423
});
401424

425+
it('should emit seriesClick event when clicking on the chart if params.seriesName is undefined', () => {
426+
component['chartInstance'] = {
427+
on: jasmine.createSpy('on')
428+
} as any;
429+
430+
spyOn(component.seriesClick, 'emit');
431+
spyOn(component.seriesHover, 'emit');
432+
433+
component['initEChartsEvents']();
434+
435+
expect(component['chartInstance'].on).toHaveBeenCalledWith('click', jasmine.any(Function));
436+
437+
const clickCallback = component['chartInstance'].on.calls.argsFor(0)[1];
438+
439+
const mockParams = { value: 100, name: 'Name X' };
440+
clickCallback(mockParams);
441+
442+
const mouseoverCallback = component['chartInstance'].on.calls.argsFor(1)[1];
443+
444+
const mockParamsMouse = {};
445+
mouseoverCallback(mockParamsMouse);
446+
447+
expect(component.seriesClick.emit).toHaveBeenCalledWith({
448+
label: 'Name X',
449+
data: 100
450+
});
451+
expect(component.seriesHover.emit).not.toHaveBeenCalled();
452+
});
453+
402454
it('should emit seriesHover event when hovering over a series', () => {
403455
const tooltipElement = document.createElement('div');
404456
tooltipElement.id = 'custom-tooltip';
@@ -465,7 +517,7 @@ describe('PoChartNewComponent', () => {
465517
on: jasmine.createSpy('on')
466518
} as any;
467519

468-
spyOn(component.poTooltip, 'toggleTooltipVisibility');
520+
spyOn(component.poTooltip.last, 'toggleTooltipVisibility');
469521

470522
component['initEChartsEvents']();
471523

@@ -475,7 +527,7 @@ describe('PoChartNewComponent', () => {
475527

476528
mouseoutCallback();
477529

478-
expect(component.poTooltip.toggleTooltipVisibility).toHaveBeenCalledWith(false);
530+
expect(component.poTooltip.last.toggleTooltipVisibility).toHaveBeenCalledWith(false);
479531
});
480532

481533
it('should set tooltipText as "seriesName: value" when tooltip is not defined', () => {
@@ -524,7 +576,7 @@ describe('PoChartNewComponent', () => {
524576

525577
mouseoverCallback(mockParamsNoSeriesName);
526578

527-
expect(component.tooltipText.replace(/\s/g, '')).toBe('CategoriaSemNome<b>99</b>'.replace(/\s/g, ''));
579+
expect(component.tooltipText.replace(/\s/g, '')).toBe('CategoriaSemNome:<b>99</b>'.replace(/\s/g, ''));
528580
});
529581
});
530582

@@ -735,9 +787,11 @@ describe('PoChartNewComponent', () => {
735787

736788
const result = component['setSeries']();
737789

738-
expect(result.length).toBe(2);
790+
expect(result.length).toBe(1);
739791
expect(result[0].type).toBe('pie');
740-
expect(result[0].name).toBe('Serie 1');
792+
expect(result[0].data[0].name).toBe('Serie 1');
793+
expect(result[0].data[1].name).toBe('Serie 2');
794+
expect(result[0].data.length).toBe(2);
741795
});
742796

743797
it('should transform series correctly with default configurations', () => {
@@ -1013,6 +1067,33 @@ describe('PoChartNewComponent', () => {
10131067

10141068
expect(component['setTableColumns']).not.toHaveBeenCalled();
10151069
});
1070+
1071+
it('should set Series if type is Pie', () => {
1072+
component.type = PoChartType.Pie;
1073+
component.series = [
1074+
{ data: 80, label: 'Pie Value 1' },
1075+
{ data: 20, label: 'Pie Value 2' }
1076+
];
1077+
component['chartInstance'] = {
1078+
getOption: jasmine.createSpy('getOption').and.returnValue({
1079+
series: [
1080+
{
1081+
name: 'Série A',
1082+
data: [
1083+
{ name: 'Pie Value 1', value: 80 },
1084+
{ name: 'Pie Value 2', value: 20 }
1085+
]
1086+
}
1087+
]
1088+
})
1089+
} as any;
1090+
spyOn(component as any, 'setTableColumns');
1091+
1092+
component['setTableProperties']();
1093+
1094+
expect(component['setTableColumns']).not.toHaveBeenCalled();
1095+
expect(component['itemsTable']).toEqual([{ 'Série': '-', 'Pie Value 1': 80, 'Pie Value 2': 20 }]);
1096+
});
10161097
});
10171098

10181099
describe('setTableColumns:', () => {
@@ -1055,6 +1136,7 @@ describe('PoChartNewComponent', () => {
10551136
{ serie: 'Série 1', valor1: 10, valor2: undefined },
10561137
{ serie: 'Série 2', valor1: 30 }
10571138
];
1139+
component['columnsTable'] = [{ property: 'serie' }, { property: 'valor1' }, { property: 'valor2' }];
10581140
component.options = {} as any;
10591141

10601142
component['downloadCsv']();
@@ -1192,47 +1274,6 @@ describe('PoChartNewComponent', () => {
11921274
expect(component['setHeaderProperties']).not.toHaveBeenCalled();
11931275
});
11941276

1195-
// it('should create and download a PNG image correctly', done => {
1196-
// const chartElement = document.createElement('div');
1197-
// chartElement.style.width = '800px';
1198-
// chartElement.style.height = '600px';
1199-
1200-
// const headerElement = document.createElement('div');
1201-
// headerElement.style.height = '50px';
1202-
1203-
// const mockImage = new Image();
1204-
// const canvas = document.createElement('canvas');
1205-
// const ctx = canvas.getContext('2d');
1206-
// spyOn(canvas, 'getContext').and.returnValue(ctx);
1207-
1208-
// const link = document.createElement('a');
1209-
// spyOn(document, 'createElement').and.callFake((tag: string) => {
1210-
// if (tag === 'canvas') return canvas;
1211-
// if (tag === 'a') return link;
1212-
// return document.createElement(tag);
1213-
// });
1214-
1215-
// spyOn(canvas, 'toDataURL').and.returnValue('data:image/png;base64,fakeImageData');
1216-
// spyOn(link, 'click');
1217-
1218-
// component['configureImageCanvas']('png', mockImage);
1219-
1220-
// setTimeout(() => {
1221-
// mockImage.onload?.(new Event('load'));
1222-
// }, 100);
1223-
1224-
// setTimeout(() => {
1225-
// try {
1226-
// expect(link.href).toBe('data:image/png;base64,fakeImageData');
1227-
// expect(link.download).toBe('grafico-exportado.png');
1228-
// expect(link.click).toHaveBeenCalled();
1229-
// done();
1230-
// } catch (error) {
1231-
// done.fail(error);
1232-
// }
1233-
// }, 300);
1234-
// });
1235-
12361277
it('should create and download a PNG image correctly', done => {
12371278
const chartElement = document.createElement('div');
12381279
chartElement.style.width = '800px';

0 commit comments

Comments
 (0)