Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: diagram components colors #630

Merged
merged 4 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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', () => {
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.fillMissingColors = true

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 = false
/**
* @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 }
Loading