Skip to content

Commit

Permalink
feat: diagram components colors (onecx#630)
Browse files Browse the repository at this point in the history
* feat: diagram components allow passing colors

* feat: enchance diagram colors

* fix: fixed tests and default value
  • Loading branch information
markuczy authored Dec 10, 2024
1 parent 39230d6 commit 3d69020
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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'],
},
])
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
}
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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: [
Expand Down Expand Up @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<ocx-diagram
[data]="(diagramData$ | async) || []"
[fillMissingColors]="fillMissingColors"
[sumKey]="sumKey"
[diagramType]="diagramType"
(onDataSelect)="dataClicked($event)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,39 @@ export const WithDiagramTypeSelection = {
sumKey: 'Total',
},
}

export const WithCustomColors = {
render: Template,
args: {
diagramType: DiagramType.PIE,
data: mockData,
supportedDiagramTypes: [DiagramType.PIE, DiagramType.HORIZONTAL_BAR, DiagramType.VERTICAL_BAR],
column: {
id: 'fruitType',
type: ColumnType.STRING,
},
sumKey: 'Total',
colors: {
['Apple']: 'green',
['Banana']: 'yellow',
},
},
}

export const WithForcedCustomColors = {
render: Template,
args: {
diagramType: DiagramType.PIE,
data: mockData,
supportedDiagramTypes: [DiagramType.PIE, DiagramType.HORIZONTAL_BAR, DiagramType.VERTICAL_BAR],
column: {
id: 'fruitType',
type: ColumnType.STRING,
},
sumKey: 'Total',
colors: {
['Apple']: 'green',
},
fillMissingColors: true,
},
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ export interface GroupByCountDiagramComponentState {
export class GroupByCountDiagramComponent implements OnInit {
@Input() sumKey = 'SEARCH.SUMMARY_TITLE'
@Input() diagramType = DiagramType.PIE
/**
* 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
/**
* @deprecated Will be replaced by diagramType
*/
Expand Down Expand Up @@ -66,27 +72,37 @@ export class GroupByCountDiagramComponent implements OnInit {
this.columnField = value.id
}

private _colors$ = new BehaviorSubject<Record<string, string>>({})
@Input()
get colors(): Record<string, string> {
return this._colors$.getValue()
}
set colors(value: Record<string, string>) {
this._colors$.next(value)
}

@Output() dataSelected: EventEmitter<any> = new EventEmitter()
@Output() diagramTypeChanged: EventEmitter<DiagramType> = new EventEmitter()
@Output() componentStateChanged: EventEmitter<GroupByCountDiagramComponentState> = new EventEmitter()

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,
}))
)
)
Expand All @@ -105,7 +121,7 @@ export class GroupByCountDiagramComponent implements OnInit {
this.diagramType = newDiagramType
this.diagramTypeChanged.emit(newDiagramType)
this.componentStateChanged.emit({
activeDiagramType: newDiagramType
activeDiagramType: newDiagramType,
})
}
}
2 changes: 1 addition & 1 deletion libs/angular-accelerator/src/lib/model/diagram-data.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export type DiagramData = { label: string; value: number }
export type DiagramData = { label: string; value: number; backgroundColor?: string }

0 comments on commit 3d69020

Please sign in to comment.