From 3d69020398c46baae923b2def2d815f6ddf4dff0 Mon Sep 17 00:00:00 2001 From: markuczy <129275100+markuczy@users.noreply.github.com> Date: Tue, 10 Dec 2024 14:16:27 +0100 Subject: [PATCH] feat: diagram components colors (#630) * feat: diagram components allow passing colors * feat: enchance diagram colors * fix: fixed tests and default value --- .../diagram/diagram.component.spec.ts | 70 +++++++++++++++++++ .../diagram/diagram.component.stories.ts | 44 ++++++++++++ .../components/diagram/diagram.component.ts | 22 +++++- .../group-by-count-diagram.component.html | 1 + ...roup-by-count-diagram.component.stories.ts | 36 ++++++++++ .../group-by-count-diagram.component.ts | 26 +++++-- .../src/lib/model/diagram-data.ts | 2 +- 7 files changed, 194 insertions(+), 7 deletions(-) diff --git a/libs/angular-accelerator/src/lib/components/diagram/diagram.component.spec.ts b/libs/angular-accelerator/src/lib/components/diagram/diagram.component.spec.ts index 2060ce627..c07d6bf39 100644 --- a/libs/angular-accelerator/src/lib/components/diagram/diagram.component.spec.ts +++ b/libs/angular-accelerator/src/lib/components/diagram/diagram.component.spec.ts @@ -13,6 +13,7 @@ import { DiagramHarness, TestbedHarnessEnvironment } from '../../../../testing' import { DiagramType } from '../../model/diagram-type' import { DiagramComponent, DiagramLayouts } from './diagram.component' import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http' +import { ColorUtils } from '../../utils/colorutils' describe('DiagramComponent', () => { let translateService: TranslateService @@ -231,4 +232,73 @@ describe('DiagramComponent', () => { expect(await diagramTypeSelectButtonOptions[0].hasClass('p-highlight')).toBe(false) expect(await diagramTypeSelectButtonOptions[1].hasClass('p-highlight')).toBe(true) }) + + it('should interpolate colors by default', () => { + const mockResult = diagramData.map((v, i) => i.toString()) + jest.spyOn(ColorUtils, 'interpolateColors').mockReturnValue(mockResult) + + component.ngOnChanges() + + expect(component.chartData?.datasets).toEqual([ + { + data: diagramData.map((d) => d.value), + backgroundColor: mockResult, + }, + ]) + }) + + it('should use custom colors', () => { + component.data = [ + { label: 'test0', value: 1, backgroundColor: 'blue' }, + { label: 'test1', value: 2, backgroundColor: 'red' }, + ] + + component.ngOnChanges() + + expect(component.chartData?.datasets).toEqual([ + { + data: [1, 2], + backgroundColor: ['blue', 'red'], + }, + ]) + }) + it('should interpolate all colors if not all items have custom colors and filling missing colors is not allowed', () => { + const mockData = [ + { label: 'test0', value: 1, backgroundColor: 'blue' }, + { label: 'test1', value: 2 }, + ] + const mockResult = mockData.map((v, i) => i.toString()) + jest.spyOn(ColorUtils, 'interpolateColors').mockReturnValue(mockResult) + component.fillMissingColors = false + + component.data = mockData + + component.ngOnChanges() + + expect(component.chartData?.datasets).toEqual([ + { + data: [1, 2], + backgroundColor: ['0', '1'], + }, + ]) + }) + it('should use custom colors and interpolate undefined ones if custom colors are forced', () => { + const mockData = [ + { label: 'test0', value: 1, backgroundColor: 'blue' }, + { label: 'test1', value: 2 }, + ] + const mockResult = mockData.map((v, i) => i.toString()) + jest.spyOn(ColorUtils, 'interpolateColors').mockReturnValue(mockResult) + + component.data = mockData + + component.ngOnChanges() + + expect(component.chartData?.datasets).toEqual([ + { + data: [1, 2], + backgroundColor: ['blue', '0'], + }, + ]) + }) }) diff --git a/libs/angular-accelerator/src/lib/components/diagram/diagram.component.stories.ts b/libs/angular-accelerator/src/lib/components/diagram/diagram.component.stories.ts index ce2809d6e..e604c8d82 100644 --- a/libs/angular-accelerator/src/lib/components/diagram/diagram.component.stories.ts +++ b/libs/angular-accelerator/src/lib/components/diagram/diagram.component.stories.ts @@ -98,3 +98,47 @@ export const WithDiagramTypeSelection = { supportedDiagramTypes: [DiagramType.PIE, DiagramType.HORIZONTAL_BAR, DiagramType.VERTICAL_BAR], }, } + +const mockDataWithColors: DiagramData[] = [ + { + label: 'Apples', + value: 10, + backgroundColor: 'yellow', + }, + { + label: 'Bananas', + value: 7, + backgroundColor: 'orange', + }, + { + label: 'Oranges', + value: 3, + backgroundColor: 'red', + }, +] + +export const WithCustomColors = { + render: Template, + args: { + diagramType: DiagramType.PIE, + data: mockDataWithColors, + supportedDiagramTypes: [DiagramType.PIE, DiagramType.HORIZONTAL_BAR, DiagramType.VERTICAL_BAR], + }, +} + +export const WithForcedCustomColors = { + render: Template, + args: { + diagramType: DiagramType.PIE, + data: [ + ...mockData, + { + label: 'Peaches', + value: 2, + backgroundColor: 'Yellow', + }, + ], + supportedDiagramTypes: [DiagramType.PIE, DiagramType.HORIZONTAL_BAR, DiagramType.VERTICAL_BAR], + fillMissingColors: true, + }, +} diff --git a/libs/angular-accelerator/src/lib/components/diagram/diagram.component.ts b/libs/angular-accelerator/src/lib/components/diagram/diagram.component.ts index 339f62828..452d61c85 100644 --- a/libs/angular-accelerator/src/lib/components/diagram/diagram.component.ts +++ b/libs/angular-accelerator/src/lib/components/diagram/diagram.component.ts @@ -54,6 +54,12 @@ const allDiagramTypes: DiagramLayouts[] = [ export class DiagramComponent implements OnInit, OnChanges { @Input() data: DiagramData[] | undefined @Input() sumKey = 'OCX_DIAGRAM.SUM' + /** + * This property determines if diagram should generate the colors for the data that does not have any set. + * + * Setting this property to false will result in using the provided colors only if every data item has one. In the scenario where at least one item does not have a color set, diagram will generate all colors. + */ + @Input() fillMissingColors = true private _diagramType: DiagramType = DiagramType.PIE selectedDiagramType: DiagramLayouts | undefined public chartType: 'bar' | 'line' | 'scatter' | 'bubble' | 'pie' | 'doughnut' | 'polarArea' | 'radar' = 'pie' @@ -105,7 +111,7 @@ export class DiagramComponent implements OnInit, OnChanges { const inputData = this.data.map((diagramData) => diagramData.value) this.amountOfData = this.data.reduce((acc, current) => acc + current.value, 0) - const COLORS = interpolateColors(this.data.length, colorScale, colorRangeInfo) + const COLORS = this.generateColors(this.data, colorScale, colorRangeInfo) this.chartData = { labels: this.data.map((data) => data.label), datasets: [ @@ -136,6 +142,20 @@ export class DiagramComponent implements OnInit, OnChanges { } } + generateColors(data: DiagramData[], colorScale: any, colorRangeInfo: any) { + const dataColors = data.map((diagramData) => diagramData.backgroundColor) + if (dataColors.filter((v) => v !== undefined).length === data.length) { + return dataColors + } else if (this.fillMissingColors) { + // it is intended to generate more colors than needed, so interval for generated colors is same as amount of items on the diagram + const interpolatedColors = interpolateColors(dataColors.length, colorScale, colorRangeInfo) + let interpolatedIndex = 0 + return dataColors.map((color) => (color === undefined ? interpolatedColors[interpolatedIndex++] : color)) + } else { + return interpolateColors(data.length, colorScale, colorRangeInfo) + } + } + generateTotal(data: DiagramData[]): number { return data.reduce((acc, current) => acc + current.value, 0) } diff --git a/libs/angular-accelerator/src/lib/components/group-by-count-diagram/group-by-count-diagram.component.html b/libs/angular-accelerator/src/lib/components/group-by-count-diagram/group-by-count-diagram.component.html index 56631dba0..025cdfb00 100644 --- a/libs/angular-accelerator/src/lib/components/group-by-count-diagram/group-by-count-diagram.component.html +++ b/libs/angular-accelerator/src/lib/components/group-by-count-diagram/group-by-count-diagram.component.html @@ -1,5 +1,6 @@ >({}) + @Input() + get colors(): Record { + return this._colors$.getValue() + } + set colors(value: Record) { + this._colors$.next(value) + } + @Output() dataSelected: EventEmitter = new EventEmitter() @Output() diagramTypeChanged: EventEmitter = new EventEmitter() @Output() componentStateChanged: EventEmitter = new EventEmitter() @@ -73,20 +88,21 @@ export class GroupByCountDiagramComponent implements OnInit { constructor(private translateService: TranslateService) {} ngOnInit(): void { - this.diagramData$ = combineLatest([this._data$, this._columnField$, this._columnType$]).pipe( - mergeMap(([data, columnField, columnType]) => { + this.diagramData$ = combineLatest([this._data$, this._columnField$, this._columnType$, this._colors$]).pipe( + mergeMap(([data, columnField, columnType, colors]) => { const columnData = data.map((d) => ObjectUtils.resolveFieldData(d, columnField)) const occurrences = columnData.reduce((acc, current) => { return acc.some((e: { label: any }) => e.label === current) ? (acc.find((e: { label: any }) => e.label === current).value++, acc) - : [...acc, { label: current, value: 1 }] + : [...acc, { label: current, value: 1, backgroundColor: colors[current.toString()] }] }, []) if (columnType === ColumnType.TRANSLATION_KEY && occurrences.length > 0) { return this.translateService.get(occurrences.map((o: { label: any }) => o.label)).pipe( map((translations: { [x: string]: any }) => - occurrences.map((o: { label: string; value: any }) => ({ + occurrences.map((o: { label: string; value: any; backgroundColor: string | undefined }) => ({ label: translations[o.label], value: o.value, + backgroundColor: o.backgroundColor, })) ) ) @@ -105,7 +121,7 @@ export class GroupByCountDiagramComponent implements OnInit { this.diagramType = newDiagramType this.diagramTypeChanged.emit(newDiagramType) this.componentStateChanged.emit({ - activeDiagramType: newDiagramType + activeDiagramType: newDiagramType, }) } } diff --git a/libs/angular-accelerator/src/lib/model/diagram-data.ts b/libs/angular-accelerator/src/lib/model/diagram-data.ts index a8399760c..ba72f5932 100644 --- a/libs/angular-accelerator/src/lib/model/diagram-data.ts +++ b/libs/angular-accelerator/src/lib/model/diagram-data.ts @@ -1 +1 @@ -export type DiagramData = { label: string; value: number } +export type DiagramData = { label: string; value: number; backgroundColor?: string }