Skip to content

Commit 6834471

Browse files
committed
feat(charts): implementa chart do tipo column
1 parent 404cc71 commit 6834471

8 files changed

+194
-44
lines changed

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

+25
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,31 @@ const poChartDefaultHeight = 400;
2929
*
3030
* > Veja nosso [guia de uso para gráficos](/guides/guide-charts) para auxiliar na construção do seu gráfico,
3131
* informando em qual caso utilizar, o que devemos evitar e boas práticas relacionada a cores.
32+
*
33+
* #### Tokens customizáveis
34+
*
35+
* É possível alterar o estilo do componente usando os seguintes tokens (CSS):
36+
*
37+
* > Para maiores informações, acesse o guia [Personalizando o Tema Padrão com Tokens CSS](https://po-ui.io/guides/theme-customization).
38+
*
39+
* | Propriedade | Descrição | Valor Padrão |
40+
* |------------------------------------------|-------------------------------------------------------|---------------------------------------------------|
41+
* | **Header (.po-chart-header )** | | |
42+
* | `--background-color` | Cor de background do cabeçalho | `var(--color-neutral-light-00)` |
43+
* | `--color` | Cor da fonte do cabeçalho | `var(--color-neutral-dark-70)` |
44+
* | `--font-family` | Família tipográfica usada | `var(--font-family-theme)` |
45+
* | `--font-size-title` | Tamanho da fonte | `var(--font-size-default)` |
46+
* | `--font-size-icons` | Tamanho dos ícones | `var(--font-size-md)` |
47+
* | `--font-weight` | Peso da fonte | `var(--font-weight-bold)` |
48+
* | **Chart (.po-chart)** | | |
49+
* | `--background-color-grid` | Cor de background | `var(--color-neutral-light-00)` |
50+
* | `--color-grid` | Cor da fonte | `var(--color-neutral-light-20)` |
51+
* | `--font-family-grid` | Família tipográfica usada | `var(--font-family-theme)` |
52+
* | `--font-size-grid` | Tamanho da fonte | `var(--font-size-xs)` |
53+
* | `--font-weight-grid` | Peso da fonte | `var(--font-weight-normal)` |
54+
* | `--color-legend` | Cor da fonte da legenda | `var(--color-neutral-dark-70)` |
55+
* | `--border-radius-bar` | Contém o valor do raio dos cantos do elemento | `var(--border-radius-none)` |
56+
* | `--color-grid-hover` | Cor no estado hover | `var(--color-neutral-mid-60)` |
3257
*/
3358
@Directive()
3459
export abstract class PoChartNewBaseComponent {

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

+8-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,14 @@
3333
class="po-chart"
3434
[ngStyle]="headerHeight ? { height: height - headerHeight + 'px', 'margin-top': chartMarginTop } : null"
3535
></div>
36-
<div #tooltipElement id="custom-tooltip" [p-tooltip]="tooltipText" style="position: absolute"></div>
36+
<div
37+
#tooltipElement
38+
id="custom-tooltip"
39+
[p-tooltip]="tooltipText"
40+
[p-hide-arrow]="true"
41+
[p-inner-html]="true"
42+
[p-tooltip-position]="positionTooltip"
43+
></div>
3744
<po-popup #popup [p-actions]="popupActions" p-position="bottom" [p-target]="targetRef"> </po-popup>
3845
</div>
3946

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

+32-19
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,19 @@ describe('PoChartNewComponent', () => {
434434
event: { offsetX: 10, offsetY: 20 }
435435
};
436436
mouseoverCallback(mockParams);
437+
438+
const mockParamsBar = {
439+
seriesName: 'Exemplo',
440+
value: 100,
441+
name: 'Categoria X',
442+
seriesType: 'bar',
443+
seriesIndex: 0,
444+
event: { offsetX: 10, offsetY: 20 }
445+
};
446+
mouseoverCallback(mockParamsBar);
447+
448+
expect(component['positionTooltip']).toBe('top');
449+
437450
const clickCallback = component['chartInstance'].on.calls.argsFor(0)[1];
438451

439452
const mockParamsClick = {};
@@ -497,7 +510,21 @@ describe('PoChartNewComponent', () => {
497510

498511
mouseoverCallback(mockParams);
499512

500-
expect(component.tooltipText).toBe('Exemplo: 150');
513+
expect(component.tooltipText.replace(/\s/g, '')).toBe(
514+
'<b>Categoria Y</b><br>Exemplo:<b>150</b>'.replace(/\s/g, '')
515+
);
516+
517+
const mockParamsNoSeriesName = {
518+
value: 99,
519+
name: 'Categoria Sem Nome',
520+
seriesIndex: 0,
521+
seriesType: 'line',
522+
event: { offsetX: 5, offsetY: 10 }
523+
};
524+
525+
mouseoverCallback(mockParamsNoSeriesName);
526+
527+
expect(component.tooltipText.replace(/\s/g, '')).toBe('CategoriaSemNome<b>99</b>'.replace(/\s/g, ''));
501528
});
502529
});
503530

@@ -640,7 +667,8 @@ describe('PoChartNewComponent', () => {
640667
mockSeriesWithColor = [
641668
{ label: 'Serie 1', data: [1, 2, 3], type: PoChartType.Column, color: 'po-color-01' },
642669
{ label: 'Serie 2', data: [4, 5, 6], type: PoChartType.Line, color: 'po-color-02' },
643-
{ label: 'Serie 3', data: [7, 8, 9], color: '#123456' }
670+
{ label: 'Serie 3', data: [7, 8, 9], color: '#123456' },
671+
{ data: [10, 11, 12], color: '#abcdef' }
644672
];
645673

646674
spyOn(colorService, 'getColors').and.callFake((series: Array<any>) => series);
@@ -666,12 +694,13 @@ describe('PoChartNewComponent', () => {
666694

667695
const result = component['setSeries']();
668696

669-
expect(result.length).toBe(3);
697+
expect(result.length).toBe(4);
670698
expect(result[0].type).toBe('bar');
671699
expect(result[0].name).toBe('Serie 1');
672700
expect(result[0].itemStyle.color).toBe('#0000ff');
673701
expect(result[1].type).toBe('line');
674702
expect(result[2].type).toBe('bar');
703+
expect(result[3].name).toBe('');
675704
});
676705

677706
it('should return all types charts', () => {
@@ -725,22 +754,6 @@ describe('PoChartNewComponent', () => {
725754
expect(result[0].type).toBe('pie');
726755
});
727756

728-
it('should handle color variables and direct color values correctly', () => {
729-
component.series = mockSeriesWithColor;
730-
731-
const result = component['setSeries']();
732-
expect(result[0].itemStyle.color).toBe('#0000ff');
733-
expect(result[2].itemStyle.color).toBe('#123456');
734-
});
735-
736-
it('should set emphasis styles correctly', () => {
737-
component.series = mockSeriesWithColor;
738-
739-
const result = component['setSeries']();
740-
expect(result[0].emphasis.itemStyle.color).toBe('#0000ff');
741-
expect(result[0].emphasis.itemStyle.borderWidth).toBe(2);
742-
});
743-
744757
it('should handle dataLabel.fixed configuration', () => {
745758
component.series = mockSeriesWithColor;
746759
component.dataLabel = { fixed: true };

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

+45-19
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export class PoChartNewComponent extends PoChartNewBaseComponent implements Afte
7272
protected isExpanded = false;
7373
protected legendData: Array<{ name: string; color: string }> = [];
7474
protected headerHeight: number;
75+
protected positionTooltip = 'bottom';
7576
protected popupActions: Array<PoPopupAction> = [
7677
{
7778
label: this.literals.exportCSV,
@@ -94,6 +95,7 @@ export class PoChartNewComponent extends PoChartNewBaseComponent implements Afte
9495
];
9596
private chartInstance!: echarts.ECharts;
9697
private currentRenderer: 'svg' | 'canvas';
98+
private boundaryGap = false;
9799

98100
constructor(
99101
private el: ElementRef,
@@ -201,13 +203,20 @@ export class PoChartNewComponent extends PoChartNewBaseComponent implements Afte
201203

202204
this.chartInstance.on('mouseover', (params: any) => {
203205
if (!params.value) return;
204-
if (params.seriesType === 'line') {
206+
if (params.seriesType) {
205207
const divTooltipElement = this.el.nativeElement.querySelector('#custom-tooltip');
206208
if (divTooltipElement) {
207209
const chartElement = this.el.nativeElement.querySelector('#chart-id');
210+
if (params.seriesType === 'bar') {
211+
this.positionTooltip = 'top';
212+
}
213+
const customTooltipText = params.seriesName
214+
? `<b>${params.name}</b><br>
215+
${params.seriesName}: <b>${params.value}</b>`
216+
: `${params.name}<b>${params.value}</b>`;
208217
this.tooltipText = this.series[params.seriesIndex].tooltip
209218
? this.series[params.seriesIndex].tooltip
210-
: `${params.seriesName}: ${params.value}`;
219+
: customTooltipText;
211220
divTooltipElement.style.left = `${params.event.offsetX + chartElement.offsetLeft + 3}px`;
212221
divTooltipElement.style.top = `${chartElement.offsetTop + params.event.offsetY + 3}px`;
213222
this.poTooltip.toggleTooltipVisibility(true);
@@ -243,12 +252,12 @@ export class PoChartNewComponent extends PoChartNewBaseComponent implements Afte
243252
grid: {
244253
top: paddingTop,
245254
left: this.options?.axis?.paddingLeft || 48,
246-
right: 16,
255+
right: this.options?.axis?.paddingRight || 32,
247256
borderWidth: tokenBorderWidthSm
248257
},
249258
xAxis: {
250259
type: 'category',
251-
boundaryGap: false,
260+
boundaryGap: this.boundaryGap,
252261
data: this.categories,
253262
axisLabel: {
254263
fontFamily: this.getCSSVariable('--font-family-grid', '.po-chart'),
@@ -313,7 +322,7 @@ export class PoChartNewComponent extends PoChartNewBaseComponent implements Afte
313322
realtime: true,
314323
bottom: 'calc(100%)',
315324
height: 20,
316-
right: 16,
325+
right: this.options?.axis?.paddingRight || 32,
317326
xAxisIndex: [0]
318327
},
319328
{
@@ -387,11 +396,10 @@ export class PoChartNewComponent extends PoChartNewBaseComponent implements Afte
387396
const hasArea = this.type === 'area' || this.series.some(serie => serie.type === 'area');
388397
const newSeries: Array<any> = [...this.colorService.getColors<PoChartSerie>(this.series, true, hasArea)];
389398
const tokenBorderWidthMd = this.resolvePx('--border-width-md');
399+
const widthTypeColumn = this.resolvePx('--spacing-sm', '.po-chart');
390400

391401
return newSeries.map((serie, index) => {
392-
if (serie.label && typeof serie.label === 'string') {
393-
serie.name = serie.label;
394-
}
402+
serie.name = serie.label && typeof serie.label === 'string' ? serie.label : '';
395403

396404
if (!serie.type || serie.type === 'area' || serie.type === 'column') {
397405
!serie.type ? this.setTypeSerie(serie, this.type) : this.setTypeSerie(serie, serie.type);
@@ -402,22 +410,25 @@ export class PoChartNewComponent extends PoChartNewBaseComponent implements Afte
402410
: serie.color;
403411

404412
this.setSerieEmphasis(serie, colorVariable, tokenBorderWidthMd);
413+
this.setSerieTypeLine(serie, tokenBorderWidthMd, colorVariable);
405414
this.setSerieTypeArea(serie, index);
415+
this.setSerieTypeColumn(serie, widthTypeColumn, colorVariable);
406416

407-
const isTypeLine = this.type === 'line' || serie.type === 'line';
408-
if (isTypeLine) {
409-
serie.symbolSize = 8;
410-
serie.symbol = 'circle';
411-
}
417+
return serie;
418+
});
419+
}
420+
421+
private setSerieTypeLine(serie, tokenBorderWidthMd: number, color: string) {
422+
if (serie.type === 'line') {
423+
serie.symbolSize = 8;
424+
serie.symbol = 'circle';
412425
serie.itemStyle = {
413-
color:
414-
isTypeLine && !this.options?.fillPoints ? this.getCSSVariable('--color-neutral-light-00') : colorVariable,
415-
borderColor: colorVariable,
426+
color: !this.options?.fillPoints ? this.getCSSVariable('--color-neutral-light-00') : color,
427+
borderColor: color,
416428
borderWidth: tokenBorderWidthMd
417429
};
418-
serie.lineStyle = { color: colorVariable, width: tokenBorderWidthMd };
419-
return serie;
420-
});
430+
serie.lineStyle = { color: color, width: tokenBorderWidthMd };
431+
}
421432
}
422433

423434
private setSerieTypeArea(serie, index: number) {
@@ -434,6 +445,20 @@ export class PoChartNewComponent extends PoChartNewBaseComponent implements Afte
434445
}
435446
}
436447

448+
private setSerieTypeColumn(serie, width: number, color: string) {
449+
if (serie.isTypeColumn) {
450+
serie.barWidth = width;
451+
serie.itemStyle = {
452+
borderRadius: this.resolvePx('--border-radius-bar', '.po-chart'),
453+
color: color
454+
};
455+
serie.emphasis = {
456+
focus: 'series'
457+
};
458+
this.boundaryGap = true;
459+
}
460+
}
461+
437462
private setSerieEmphasis(serie, color: string, tokenBorder: number) {
438463
serie.emphasis = {
439464
itemStyle: {
@@ -469,6 +494,7 @@ export class PoChartNewComponent extends PoChartNewBaseComponent implements Afte
469494
serie.type = 'bar';
470495
break;
471496
case PoChartType.Column:
497+
serie.isTypeColumn = true;
472498
serie.type = 'bar';
473499
break;
474500
case PoChartType.Donut:

projects/ui/src/lib/components/po-chart/interfaces/po-chart-axis-options.interface.ts

+11
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,17 @@ export interface PoChartAxisOptions {
5959
*/
6060
paddingLeft?: number;
6161

62+
/**
63+
* @optional
64+
*
65+
* @description
66+
*
67+
* Permite aumentar ou diminuir o espaço o espaço direito do gráfico
68+
*
69+
* @default `32`
70+
*/
71+
paddingRight?: number;
72+
6273
/**
6374
* @optional
6475
*

projects/ui/src/lib/directives/po-tooltip/po-tooltip-base.directive.ts

+31
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,37 @@ export abstract class PoTooltipBaseDirective {
4747
*/
4848
@Input({ alias: 'p-append-in-body', transform: convertToBoolean }) appendInBody: boolean = false;
4949

50+
/**
51+
* @optional
52+
*
53+
* @description
54+
*
55+
* Controla a exibição da seta de indicação da tooltip.
56+
*
57+
* Quando `true`, a seta que aponta para o elemento alvo será ocultada.
58+
* Quando `false`, a seta será exibida normalmente.
59+
*
60+
* Essa propriedade é útil em cenários onde a seta não é necessária ou pode interferir no layout da aplicação.
61+
*
62+
* @default `false`
63+
*/
64+
@Input({ alias: 'p-hide-arrow', transform: convertToBoolean }) hideArrow: boolean = false;
65+
66+
/**
67+
* @optional
68+
*
69+
* @description
70+
*
71+
* Permite a renderização de conteúdo HTML dentro da tooltip.
72+
*
73+
* Quando `true`, o valor da propriedade `tooltip` será interpretado como HTML,
74+
* possibilitando a utilização de tags e elementos HTML dentro da tooltip.
75+
* Caso `false`, o conteúdo será tratado como texto puro.
76+
*
77+
* @default `false`
78+
*/
79+
@Input({ alias: 'p-inner-html', transform: convertToBoolean }) innerHtml: boolean = false;
80+
5081
protected _tooltipPosition?: string = 'bottom';
5182
protected tooltipContent;
5283

projects/ui/src/lib/directives/po-tooltip/po-tooltip.directive.spec.ts

+32-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Component } from '@angular/core';
1+
import { Component, Renderer2 } from '@angular/core';
22
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
33
import { By } from '@angular/platform-browser';
44

@@ -187,6 +187,33 @@ describe('PoTooltipDirective', () => {
187187
expect(directiveElement.nativeElement.querySelector('.po-tooltip-content')).toBeTruthy();
188188
});
189189

190+
it('should create tooltip with proper configurations', () => {
191+
const renderer = fixture.debugElement.injector.get(Renderer2);
192+
spyOn(renderer, 'setStyle').and.callThrough();
193+
spyOn(renderer, 'setProperty').and.callThrough();
194+
195+
directive.hideArrow = true;
196+
directive.innerHtml = true;
197+
directive.tooltip = '<b>HTML</b>';
198+
directive.createTooltip();
199+
200+
expect(renderer.setStyle).toHaveBeenCalledWith(jasmine.any(Object), 'display', 'none');
201+
202+
expect(renderer.setProperty).toHaveBeenCalledWith(jasmine.any(Object), 'innerHTML', '<b>HTML</b>');
203+
204+
(renderer.setStyle as jasmine.Spy).calls.reset();
205+
(renderer.setProperty as jasmine.Spy).calls.reset();
206+
207+
directive.hideArrow = false;
208+
directive.innerHtml = false;
209+
directive.tooltip = 'Plain text';
210+
directive.createTooltip();
211+
212+
expect(renderer.setStyle).not.toHaveBeenCalledWith(jasmine.any(Object), 'display', 'none');
213+
214+
expect(renderer.setProperty).not.toHaveBeenCalledWith(jasmine.any(Object), 'innerHTML', jasmine.any(String));
215+
});
216+
190217
it('onMouseEnter: should create tooltip ', fakeAsync(() => {
191218
directive.tooltip = 'TEXT';
192219
directive.tooltipContent = false;
@@ -377,13 +404,13 @@ describe('PoTooltipDirective', () => {
377404
expect(directive.tooltipContent).toBe(undefined);
378405
}));
379406

380-
it('should call update Text', () => {
407+
it('should call update Text and set innerHTML when innerHtml is true', () => {
381408
directive.lastTooltipText = 'abc';
382-
directive.tooltip = 'def';
383-
409+
directive.tooltip = '<b>def</b>';
410+
directive.innerHtml = true;
384411
directive.updateTextContent();
385412

386-
expect(directive.divContent.outerHTML.indexOf('def') > -1).toBeTruthy();
413+
expect(directive.divContent.innerHTML).toBe('<b>def</b>');
387414
});
388415

389416
it('should keep text without changes', () => {

0 commit comments

Comments
 (0)