From 9bc773e8fced31a72ce4624546ede79e994c7f96 Mon Sep 17 00:00:00 2001 From: markuczy Date: Wed, 4 Dec 2024 13:45:25 +0100 Subject: [PATCH 1/3] feat: diagram components allow passing colors --- .../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..bda921426 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', () => { + 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: ['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.forceCustomColors = true + + 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..af777fb47 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], + forceCustomColors: 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..5ffdbc409 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' + /** + * Set this property to true if custom colors should be forced to be applied. **It is recommended to set the backgroundColor property for every data item to ensure appropriate color scheme on the diagram.** + * + * Custom colors are applied only if every data item has backgroundColor property set. + */ + @Input() forceCustomColors = false 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.forceCustomColors) { + // 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..3c46c1f95 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 } From e9d6d18a569130bdc32d6a8d7f8028c0ab15e1dd Mon Sep 17 00:00:00 2001 From: markuczy Date: Mon, 9 Dec 2024 16:40:47 +0100 Subject: [PATCH 2/3] feat: enchance diagram colors --- .../src/lib/components/diagram/diagram.component.spec.ts | 2 +- .../lib/components/diagram/diagram.component.stories.ts | 2 +- .../src/lib/components/diagram/diagram.component.ts | 8 ++++---- .../group-by-count-diagram.component.html | 2 +- .../group-by-count-diagram.component.stories.ts | 2 +- .../group-by-count-diagram.component.ts | 6 +++--- 6 files changed, 11 insertions(+), 11 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 bda921426..a021c0a78 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 @@ -288,7 +288,7 @@ describe('DiagramComponent', () => { ] const mockResult = mockData.map((v, i) => i.toString()) jest.spyOn(ColorUtils, 'interpolateColors').mockReturnValue(mockResult) - component.forceCustomColors = true + component.fillMissingColors = true component.data = mockData 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 af777fb47..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 @@ -139,6 +139,6 @@ export const WithForcedCustomColors = { }, ], supportedDiagramTypes: [DiagramType.PIE, DiagramType.HORIZONTAL_BAR, DiagramType.VERTICAL_BAR], - forceCustomColors: true, + 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 5ffdbc409..452d61c85 100644 --- a/libs/angular-accelerator/src/lib/components/diagram/diagram.component.ts +++ b/libs/angular-accelerator/src/lib/components/diagram/diagram.component.ts @@ -55,11 +55,11 @@ export class DiagramComponent implements OnInit, OnChanges { @Input() data: DiagramData[] | undefined @Input() sumKey = 'OCX_DIAGRAM.SUM' /** - * Set this property to true if custom colors should be forced to be applied. **It is recommended to set the backgroundColor property for every data item to ensure appropriate color scheme on the diagram.** + * This property determines if diagram should generate the colors for the data that does not have any set. * - * Custom colors are applied only if every data item has backgroundColor property 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() forceCustomColors = false + @Input() fillMissingColors = true private _diagramType: DiagramType = DiagramType.PIE selectedDiagramType: DiagramLayouts | undefined public chartType: 'bar' | 'line' | 'scatter' | 'bubble' | 'pie' | 'doughnut' | 'polarArea' | 'radar' = 'pie' @@ -146,7 +146,7 @@ export class DiagramComponent implements OnInit, OnChanges { const dataColors = data.map((diagramData) => diagramData.backgroundColor) if (dataColors.filter((v) => v !== undefined).length === data.length) { return dataColors - } else if (this.forceCustomColors) { + } 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 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 3c46c1f95..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,6 +1,6 @@ Date: Mon, 9 Dec 2024 17:48:26 +0100 Subject: [PATCH 3/3] fix: fixed tests and default value --- .../src/lib/components/diagram/diagram.component.spec.ts | 4 ++-- .../group-by-count-diagram.component.ts | 2 +- 2 files changed, 3 insertions(+), 3 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 a021c0a78..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 @@ -262,13 +262,14 @@ describe('DiagramComponent', () => { }, ]) }) - it('should interpolate all colors if not all items have custom colors', () => { + 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 @@ -288,7 +289,6 @@ describe('DiagramComponent', () => { ] const mockResult = mockData.map((v, i) => i.toString()) jest.spyOn(ColorUtils, 'interpolateColors').mockReturnValue(mockResult) - component.fillMissingColors = true component.data = mockData diff --git a/libs/angular-accelerator/src/lib/components/group-by-count-diagram/group-by-count-diagram.component.ts b/libs/angular-accelerator/src/lib/components/group-by-count-diagram/group-by-count-diagram.component.ts index 345944168..69b08e219 100644 --- a/libs/angular-accelerator/src/lib/components/group-by-count-diagram/group-by-count-diagram.component.ts +++ b/libs/angular-accelerator/src/lib/components/group-by-count-diagram/group-by-count-diagram.component.ts @@ -23,7 +23,7 @@ export class GroupByCountDiagramComponent implements OnInit { * * 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 = false + @Input() fillMissingColors = true /** * @deprecated Will be replaced by diagramType */