diff --git a/libs/client-d3-charts/package.json b/libs/client-d3-charts/package.json index 21f81bdc..c1c235b0 100644 --- a/libs/client-d3-charts/package.json +++ b/libs/client-d3-charts/package.json @@ -1,6 +1,6 @@ { "name": "@rfprodz/client-d3-charts", - "version": "1.3.16", + "version": "1.3.17", "description": "Angular chart components based on D3JS (https://d3js.org).", "keywords": [ "angular-charts", diff --git a/libs/client-d3-charts/src/lib/components/chart-examples-bar/chart-examples-bar.component.html b/libs/client-d3-charts/src/lib/components/chart-examples-bar/chart-examples-bar.component.html new file mode 100644 index 00000000..fea8c58d --- /dev/null +++ b/libs/client-d3-charts/src/lib/components/chart-examples-bar/chart-examples-bar.component.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/libs/client-d3-charts/src/lib/components/chart-examples-bar/chart-examples-bar.component.scss b/libs/client-d3-charts/src/lib/components/chart-examples-bar/chart-examples-bar.component.scss new file mode 100644 index 00000000..e477add8 --- /dev/null +++ b/libs/client-d3-charts/src/lib/components/chart-examples-bar/chart-examples-bar.component.scss @@ -0,0 +1,13 @@ +:host { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: center; + + .container { + display: flex; + flex: 1 1 auto; + flex-wrap: wrap; + width: 100%; + } +} diff --git a/libs/client-d3-charts/src/lib/components/chart-examples-bar/chart-examples-bar.component.spec.ts b/libs/client-d3-charts/src/lib/components/chart-examples-bar/chart-examples-bar.component.spec.ts new file mode 100644 index 00000000..dd69e40f --- /dev/null +++ b/libs/client-d3-charts/src/lib/components/chart-examples-bar/chart-examples-bar.component.spec.ts @@ -0,0 +1,33 @@ +import { ComponentFixture, TestBed, TestModuleMetadata } from '@angular/core/testing'; +import { firstValueFrom } from 'rxjs'; + +import { AppChartExamplesBarComponent } from './chart-examples-bar.component'; + +describe('AppChartExamplesBarComponent', () => { + const testBedConfig: TestModuleMetadata = { + declarations: [AppChartExamplesBarComponent], + }; + + let fixture: ComponentFixture; + let component: AppChartExamplesBarComponent; + + beforeEach(async () => { + await TestBed.configureTestingModule(testBedConfig).compileComponents(); + fixture = TestBed.createComponent(AppChartExamplesBarComponent); + component = fixture.debugElement.componentInstance; + fixture.detectChanges(); + }); + + it('should be defined', () => { + expect(component).toBeDefined(); + }); + + it('the chart options should have expected structure', async () => { + const config = await firstValueFrom(component.barChartConfig$); + expect(config.options).toEqual({ + chartTitle: 'Example bar chart', + xAxisTitle: 'long x axis title', + yAxisTitle: 'long y axis title', + }); + }); +}); diff --git a/libs/client-d3-charts/src/lib/components/chart-examples-bar/chart-examples-bar.component.ts b/libs/client-d3-charts/src/lib/components/chart-examples-bar/chart-examples-bar.component.ts new file mode 100644 index 00000000..dbdff076 --- /dev/null +++ b/libs/client-d3-charts/src/lib/components/chart-examples-bar/chart-examples-bar.component.ts @@ -0,0 +1,56 @@ +import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { first, map, switchMap, timer } from 'rxjs'; + +import { IBarChartOptions, TBarChartData } from '../../interfaces/bar-chart.interface'; + +/** + * BAr chart examples. + */ +@Component({ + selector: 'app-chart-examples-bar', + templateUrl: './chart-examples-bar.component.html', + styleUrls: ['./chart-examples-bar.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AppChartExamplesBarComponent { + /** + * Sample bar chart data. + */ + private get barChartData() { + return [ + { title: 'one', value: 1 }, + { title: 'two', value: 2 }, + { title: 'three', value: 3 }, + { title: 'four', value: 4 }, + { title: 'five', value: 5 }, + ]; + } + + private readonly breakpoint$ = this.breakpointObserver + .observe([Breakpoints.XSmall, Breakpoints.Small, Breakpoints.Medium, Breakpoints.Large, Breakpoints.XLarge]) + .pipe(map(result => Object.keys(result.breakpoints).find(item => result.breakpoints[item]) ?? 'unknown')); + + public readonly barChartConfig$ = this.breakpoint$.pipe( + switchMap(() => { + const timeout = 100; + return timer(timeout).pipe( + first(), + map(() => ({ data: this.barChartData, options: this.barChartOptions() })), + ); + }), + ); + + constructor(private readonly breakpointObserver: BreakpointObserver) {} + + /** + * Example bar chart options. + */ + private barChartOptions() { + return >{ + chartTitle: 'Example bar chart', + xAxisTitle: 'long x axis title', + yAxisTitle: 'long y axis title', + }; + } +} diff --git a/libs/client-d3-charts/src/lib/components/chart-examples-force-directed/chart-examples-force-directed.component.html b/libs/client-d3-charts/src/lib/components/chart-examples-force-directed/chart-examples-force-directed.component.html new file mode 100644 index 00000000..150eff04 --- /dev/null +++ b/libs/client-d3-charts/src/lib/components/chart-examples-force-directed/chart-examples-force-directed.component.html @@ -0,0 +1,7 @@ +
+ +
diff --git a/libs/client-d3-charts/src/lib/components/chart-examples-force-directed/chart-examples-force-directed.component.scss b/libs/client-d3-charts/src/lib/components/chart-examples-force-directed/chart-examples-force-directed.component.scss new file mode 100644 index 00000000..e477add8 --- /dev/null +++ b/libs/client-d3-charts/src/lib/components/chart-examples-force-directed/chart-examples-force-directed.component.scss @@ -0,0 +1,13 @@ +:host { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: center; + + .container { + display: flex; + flex: 1 1 auto; + flex-wrap: wrap; + width: 100%; + } +} diff --git a/libs/client-d3-charts/src/lib/components/chart-examples-force-directed/chart-examples-force-directed.component.spec.ts b/libs/client-d3-charts/src/lib/components/chart-examples-force-directed/chart-examples-force-directed.component.spec.ts new file mode 100644 index 00000000..1a1d8035 --- /dev/null +++ b/libs/client-d3-charts/src/lib/components/chart-examples-force-directed/chart-examples-force-directed.component.spec.ts @@ -0,0 +1,31 @@ +import { ComponentFixture, TestBed, TestModuleMetadata } from '@angular/core/testing'; +import { firstValueFrom } from 'rxjs'; + +import { AppChartExamplesForceDirectedComponent } from './chart-examples-force-directed.component'; + +describe('AppChartExamplesForceDirectedComponent', () => { + const testBedConfig: TestModuleMetadata = { + declarations: [AppChartExamplesForceDirectedComponent], + }; + + let fixture: ComponentFixture; + let component: AppChartExamplesForceDirectedComponent; + + beforeEach(async () => { + await TestBed.configureTestingModule(testBedConfig).compileComponents(); + fixture = TestBed.createComponent(AppChartExamplesForceDirectedComponent); + component = fixture.debugElement.componentInstance; + fixture.detectChanges(); + }); + + it('should be defined', () => { + expect(component).toBeDefined(); + }); + + it('the chart options should have expected structure', async () => { + const config = await firstValueFrom(component.forceDirectedChartConfig$); + expect(config.options).toEqual({ + chartTitle: 'Example force directed chart', + }); + }); +}); diff --git a/libs/client-d3-charts/src/lib/components/chart-examples-force-directed/chart-examples-force-directed.component.ts b/libs/client-d3-charts/src/lib/components/chart-examples-force-directed/chart-examples-force-directed.component.ts new file mode 100644 index 00000000..251c9037 --- /dev/null +++ b/libs/client-d3-charts/src/lib/components/chart-examples-force-directed/chart-examples-force-directed.component.ts @@ -0,0 +1,90 @@ +import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { first, map, switchMap, timer } from 'rxjs'; + +import { + IForceDirectedChartData, + IForceDirectedChartOptions, + IForceDirectedGraphEntity, +} from '../../interfaces/force-directed-chart.interface'; + +@Component({ + selector: 'app-chart-examples-force-directed', + templateUrl: './chart-examples-force-directed.component.html', + styleUrls: ['./chart-examples-force-directed.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AppChartExamplesForceDirectedComponent { + /** + * Sample force directed chart data. + */ + private get forceDirectedChartData() { + const input = { + domains: ['first', 'second', 'third'], + entities: [ + { name: 'one', domains: ['first'], img: '' }, + { name: 'two', domains: ['second'], img: '' }, + { name: 'three', domains: ['third'], img: '' }, + { name: 'four', domains: ['first', 'second'], img: '' }, + { name: 'five', domains: ['second'], img: '' }, + { name: 'six', domains: ['third', 'second'], img: '' }, + { name: 'seven', domains: ['second'], img: '' }, + { name: 'eight', domains: ['third'], img: '' }, + ], + }; + const domains: IForceDirectedChartData['domains'] = input.domains.map((name, index) => ({ index, name, value: 1 })); + const entities: IForceDirectedChartData['entities'] = input.entities.map((app, index) => ({ + index: index, + name: app.name, + domains: [...app.domains], + img: app.img, + linksCount: app.domains.length, + })); + const nodes: IForceDirectedGraphEntity[] = [...entities]; + const links: IForceDirectedChartData['links'] = entities + .map(entity => { + return entity.domains.map(domain => { + const source = domains.find(value => domain === value.name)?.index ?? -1; + const target = entity.index; + return { source, target }; + }); + }) + .reduce((accumulator, item) => (Array.isArray(item) ? [...accumulator, ...item] : [...accumulator, item]), []) + .filter(link => link.source !== -1 && link.target !== -1 && typeof link.target !== 'undefined'); + const chartData: IForceDirectedChartData = { + domains, + entities: entities.map(item => ({ + ...item, + linksCount: links.reduce((acc, link) => (link.target === item.index ? acc + 1 : acc), 0), + })), + links, + nodes, + }; + return chartData; + } + + private readonly breakpoint$ = this.breakpointObserver + .observe([Breakpoints.XSmall, Breakpoints.Small, Breakpoints.Medium, Breakpoints.Large, Breakpoints.XLarge]) + .pipe(map(result => Object.keys(result.breakpoints).find(item => result.breakpoints[item]) ?? 'unknown')); + + public readonly forceDirectedChartConfig$ = this.breakpoint$.pipe( + switchMap(() => { + const timeout = 100; + return timer(timeout).pipe( + first(), + map(() => ({ data: this.forceDirectedChartData, options: this.forceDirectedChartOptions() })), + ); + }), + ); + + constructor(private readonly breakpointObserver: BreakpointObserver) {} + + /** + * Example force directed chart options. + */ + private forceDirectedChartOptions() { + return >{ + chartTitle: 'Example force directed chart', + }; + } +} diff --git a/libs/client-d3-charts/src/lib/components/chart-examples-line/chart-examples-line.component.html b/libs/client-d3-charts/src/lib/components/chart-examples-line/chart-examples-line.component.html new file mode 100644 index 00000000..a99fc1e5 --- /dev/null +++ b/libs/client-d3-charts/src/lib/components/chart-examples-line/chart-examples-line.component.html @@ -0,0 +1,13 @@ +
+ + + + + + + +
diff --git a/libs/client-d3-charts/src/lib/components/chart-examples-line/chart-examples-line.component.scss b/libs/client-d3-charts/src/lib/components/chart-examples-line/chart-examples-line.component.scss new file mode 100644 index 00000000..e477add8 --- /dev/null +++ b/libs/client-d3-charts/src/lib/components/chart-examples-line/chart-examples-line.component.scss @@ -0,0 +1,13 @@ +:host { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: center; + + .container { + display: flex; + flex: 1 1 auto; + flex-wrap: wrap; + width: 100%; + } +} diff --git a/libs/client-d3-charts/src/lib/components/chart-examples-line/chart-examples-line.component.spec.ts b/libs/client-d3-charts/src/lib/components/chart-examples-line/chart-examples-line.component.spec.ts new file mode 100644 index 00000000..a2a7196c --- /dev/null +++ b/libs/client-d3-charts/src/lib/components/chart-examples-line/chart-examples-line.component.spec.ts @@ -0,0 +1,34 @@ +import { ComponentFixture, TestBed, TestModuleMetadata } from '@angular/core/testing'; +import { firstValueFrom } from 'rxjs'; + +import { AppChartExamplesLineComponent } from './chart-examples-line.component'; + +describe('AppChartExamplesLineComponent', () => { + const testBedConfig: TestModuleMetadata = { + declarations: [AppChartExamplesLineComponent], + }; + + let fixture: ComponentFixture; + let component: AppChartExamplesLineComponent; + + beforeEach(async () => { + await TestBed.configureTestingModule(testBedConfig).compileComponents(); + fixture = TestBed.createComponent(AppChartExamplesLineComponent); + component = fixture.debugElement.componentInstance; + fixture.detectChanges(); + }); + + it('should be defined', () => { + expect(component).toBeDefined(); + }); + + it('the chart options should have expected structure', async () => { + const config = await firstValueFrom(component.lineChartConfig$); + expect(config.options).toEqual({ + chartTitle: 'Example line chart, date format default', + dateFormat: 'default', + xAxisTitle: 'Date range', + yAxisTitle: 'Value range', + }); + }); +}); diff --git a/libs/client-d3-charts/src/lib/components/chart-examples-line/chart-examples-line.component.ts b/libs/client-d3-charts/src/lib/components/chart-examples-line/chart-examples-line.component.ts new file mode 100644 index 00000000..0c007fbd --- /dev/null +++ b/libs/client-d3-charts/src/lib/components/chart-examples-line/chart-examples-line.component.ts @@ -0,0 +1,96 @@ +import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { first, map, switchMap, timer } from 'rxjs'; + +import { ILineChartOptions, TDateFormat, TLineChartData } from '../../interfaces/line-chart.interface'; + +/** + * Line chart examples. + */ +@Component({ + selector: 'app-chart-examples-line', + templateUrl: './chart-examples-line.component.html', + styleUrls: ['./chart-examples-line.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AppChartExamplesLineComponent { + /** + * Sample line chart data. + */ + private get lineChartData() { + return [ + [ + { timestamp: this.randomTimestamp(), value: this.randomValue() }, + { timestamp: this.randomTimestamp(), value: this.randomValue() }, + { timestamp: this.randomTimestamp(), value: this.randomValue() }, + { timestamp: this.randomTimestamp(), value: this.randomValue() }, + { timestamp: this.randomTimestamp(), value: this.randomValue() }, + { timestamp: this.randomTimestamp(), value: this.randomValue() }, + { timestamp: this.randomTimestamp(), value: this.randomValue() }, + ].sort((a, b) => a.timestamp - b.timestamp), + [ + { timestamp: this.randomTimestamp(), value: this.randomValue() }, + { timestamp: this.randomTimestamp(), value: this.randomValue() }, + { timestamp: this.randomTimestamp(), value: this.randomValue() }, + { timestamp: this.randomTimestamp(), value: this.randomValue() }, + { timestamp: this.randomTimestamp(), value: this.randomValue() }, + { timestamp: this.randomTimestamp(), value: this.randomValue() }, + { timestamp: this.randomTimestamp(), value: this.randomValue() }, + ].sort((a, b) => a.timestamp - b.timestamp), + [ + { timestamp: this.randomTimestamp(), value: this.randomValue() }, + { timestamp: this.randomTimestamp(), value: this.randomValue() }, + { timestamp: this.randomTimestamp(), value: this.randomValue() }, + { timestamp: this.randomTimestamp(), value: this.randomValue() }, + { timestamp: this.randomTimestamp(), value: this.randomValue() }, + { timestamp: this.randomTimestamp(), value: this.randomValue() }, + { timestamp: this.randomTimestamp(), value: this.randomValue() }, + ].sort((a, b) => a.timestamp - b.timestamp), + ]; + } + + private readonly breakpoint$ = this.breakpointObserver + .observe([Breakpoints.XSmall, Breakpoints.Small, Breakpoints.Medium, Breakpoints.Large, Breakpoints.XLarge]) + .pipe(map(result => Object.keys(result.breakpoints).find(item => result.breakpoints[item]) ?? 'unknown')); + + public readonly lineChartConfig$ = this.breakpoint$.pipe( + switchMap(() => { + const timeout = 100; + return timer(timeout).pipe( + first(), + map(() => ({ + data: this.lineChartData, + options: this.lineChartOptions(), + optionsDateDdMmYy: this.lineChartOptions('dd/mm/yy'), + optionsDateDdMmYyyy: this.lineChartOptions('dd/mm/yyyy'), + optionsDateMmYyyy: this.lineChartOptions('mm/yyyy'), + })), + ); + }), + ); + + constructor(private readonly breakpointObserver: BreakpointObserver) {} + + private randomValue(range?: number) { + const defaultRange = 100; + return Math.floor(Math.random() * (range ?? defaultRange) + 1); + } + + private randomTimestamp(range?: number) { + const defaultRange = 100000000; + return Math.floor(Math.random() * (range ?? defaultRange) + new Date().getTime()); + } + + /** + * Example line chart options. + * @param dateFormat date format + */ + private lineChartOptions(dateFormat: TDateFormat = 'default') { + return >{ + chartTitle: `Example line chart, date format ${dateFormat}`, + xAxisTitle: 'Date range', + yAxisTitle: 'Value range', + dateFormat, + }; + } +} diff --git a/libs/client-d3-charts/src/lib/components/chart-examples-pie/chart-examples-pie.component.html b/libs/client-d3-charts/src/lib/components/chart-examples-pie/chart-examples-pie.component.html new file mode 100644 index 00000000..97ab5ce4 --- /dev/null +++ b/libs/client-d3-charts/src/lib/components/chart-examples-pie/chart-examples-pie.component.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/libs/client-d3-charts/src/lib/components/chart-examples-pie/chart-examples-pie.component.scss b/libs/client-d3-charts/src/lib/components/chart-examples-pie/chart-examples-pie.component.scss new file mode 100644 index 00000000..e477add8 --- /dev/null +++ b/libs/client-d3-charts/src/lib/components/chart-examples-pie/chart-examples-pie.component.scss @@ -0,0 +1,13 @@ +:host { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: center; + + .container { + display: flex; + flex: 1 1 auto; + flex-wrap: wrap; + width: 100%; + } +} diff --git a/libs/client-d3-charts/src/lib/components/chart-examples-pie/chart-examples-pie.component.spec.ts b/libs/client-d3-charts/src/lib/components/chart-examples-pie/chart-examples-pie.component.spec.ts new file mode 100644 index 00000000..c7996a1f --- /dev/null +++ b/libs/client-d3-charts/src/lib/components/chart-examples-pie/chart-examples-pie.component.spec.ts @@ -0,0 +1,31 @@ +import { ComponentFixture, TestBed, TestModuleMetadata } from '@angular/core/testing'; +import { firstValueFrom } from 'rxjs'; + +import { AppChartExamplesPieComponent } from './chart-examples-pie.component'; + +describe('AppChartExamplesPieComponent', () => { + const testBedConfig: TestModuleMetadata = { + declarations: [AppChartExamplesPieComponent], + }; + + let fixture: ComponentFixture; + let component: AppChartExamplesPieComponent; + + beforeEach(async () => { + await TestBed.configureTestingModule(testBedConfig).compileComponents(); + fixture = TestBed.createComponent(AppChartExamplesPieComponent); + component = fixture.debugElement.componentInstance; + fixture.detectChanges(); + }); + + it('should be defined', () => { + expect(component).toBeDefined(); + }); + + it('the chart options should have expected structure', async () => { + const config = await firstValueFrom(component.pieChartConfig$); + expect(config.options).toEqual({ + chartTitle: 'Example pie chart', + }); + }); +}); diff --git a/libs/client-d3-charts/src/lib/components/chart-examples-pie/chart-examples-pie.component.ts b/libs/client-d3-charts/src/lib/components/chart-examples-pie/chart-examples-pie.component.ts new file mode 100644 index 00000000..f5bfc4d7 --- /dev/null +++ b/libs/client-d3-charts/src/lib/components/chart-examples-pie/chart-examples-pie.component.ts @@ -0,0 +1,55 @@ +import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { first, map, switchMap, timer } from 'rxjs'; + +import { IPieChartDataNode, IPieChartOptions } from '../../interfaces/pie-chart.interface'; + +/** + * Pie chart examples. + */ +@Component({ + selector: 'app-chart-examples-pie', + templateUrl: './chart-examples-pie.component.html', + styleUrls: ['./chart-examples-pie.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AppChartExamplesPieComponent { + /** + * Sample pie chart data. + */ + private get pieChartData() { + return [ + { key: 'one', y: 1 }, + { key: 'two', y: 2 }, + { key: 'three', y: 3 }, + { key: 'four', y: 4 }, + { key: 'five', y: 5 }, + { key: 'six', y: 6 }, + ]; + } + + private readonly breakpoint$ = this.breakpointObserver + .observe([Breakpoints.XSmall, Breakpoints.Small, Breakpoints.Medium, Breakpoints.Large, Breakpoints.XLarge]) + .pipe(map(result => Object.keys(result.breakpoints).find(item => result.breakpoints[item]) ?? 'unknown')); + + public readonly pieChartConfig$ = this.breakpoint$.pipe( + switchMap(() => { + const timeout = 100; + return timer(timeout).pipe( + first(), + map(() => ({ data: this.pieChartData, options: this.pieChartOptions() })), + ); + }), + ); + + constructor(private readonly breakpointObserver: BreakpointObserver) {} + + /** + * Example pie chart options. + */ + private pieChartOptions() { + return >{ + chartTitle: 'Example pie chart', + }; + } +} diff --git a/libs/client-d3-charts/src/lib/components/chart-examples-radar/chart-examples-radar.component.html b/libs/client-d3-charts/src/lib/components/chart-examples-radar/chart-examples-radar.component.html new file mode 100644 index 00000000..a3e35709 --- /dev/null +++ b/libs/client-d3-charts/src/lib/components/chart-examples-radar/chart-examples-radar.component.html @@ -0,0 +1,7 @@ +
+ + + + + +
diff --git a/libs/client-d3-charts/src/lib/components/chart-examples-radar/chart-examples-radar.component.scss b/libs/client-d3-charts/src/lib/components/chart-examples-radar/chart-examples-radar.component.scss new file mode 100644 index 00000000..e477add8 --- /dev/null +++ b/libs/client-d3-charts/src/lib/components/chart-examples-radar/chart-examples-radar.component.scss @@ -0,0 +1,13 @@ +:host { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: center; + + .container { + display: flex; + flex: 1 1 auto; + flex-wrap: wrap; + width: 100%; + } +} diff --git a/libs/client-d3-charts/src/lib/components/chart-examples-radar/chart-examples-radar.component.spec.ts b/libs/client-d3-charts/src/lib/components/chart-examples-radar/chart-examples-radar.component.spec.ts new file mode 100644 index 00000000..28b3ff9b --- /dev/null +++ b/libs/client-d3-charts/src/lib/components/chart-examples-radar/chart-examples-radar.component.spec.ts @@ -0,0 +1,31 @@ +import { ComponentFixture, TestBed, TestModuleMetadata } from '@angular/core/testing'; +import { firstValueFrom } from 'rxjs'; + +import { AppChartExamplesRadaraComponent } from './chart-examples-radar.component'; + +describe('AppChartExamplesRadaraComponent', () => { + const testBedConfig: TestModuleMetadata = { + declarations: [AppChartExamplesRadaraComponent], + }; + + let fixture: ComponentFixture; + let component: AppChartExamplesRadaraComponent; + + beforeEach(async () => { + await TestBed.configureTestingModule(testBedConfig).compileComponents(); + fixture = TestBed.createComponent(AppChartExamplesRadaraComponent); + component = fixture.debugElement.componentInstance; + fixture.detectChanges(); + }); + + it('should be defined', () => { + expect(component).toBeDefined(); + }); + + it('the chart options should have expected structure', async () => { + const config = await firstValueFrom(component.radarChartConfig$); + expect(config.options).toEqual({ + chartTitle: 'Example radar chart', + }); + }); +}); diff --git a/libs/client-d3-charts/src/lib/components/chart-examples-radar/chart-examples-radar.component.ts b/libs/client-d3-charts/src/lib/components/chart-examples-radar/chart-examples-radar.component.ts new file mode 100644 index 00000000..98c84d24 --- /dev/null +++ b/libs/client-d3-charts/src/lib/components/chart-examples-radar/chart-examples-radar.component.ts @@ -0,0 +1,71 @@ +import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { first, map, switchMap, timer } from 'rxjs'; + +import { IRadarChartDataNode, IRadarChartOptions } from '../../interfaces/radar-chart.interface'; + +/** + * Radar chart examples. + */ +@Component({ + selector: 'app-chart-examples-radar', + templateUrl: './chart-examples-radar.component.html', + styleUrls: ['./chart-examples-radar.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AppChartExamplesRadaraComponent { + /** + * Sample radar chart data. + */ + private get radarChartData() { + return [ + [ + { axis: 'one', value: 1, unit: 'x' }, + { axis: 'two', value: 2, unit: 'x' }, + { axis: 'three', value: 3, unit: 'x' }, + { axis: 'four', value: 4, unit: 'x' }, + { axis: 'five', value: 5, unit: 'x' }, + { axis: 'six', value: 6, unit: 'x' }, + { axis: 'seven', value: 7, unit: 'x' }, + { axis: 'eight', value: 8, unit: 'x' }, + { axis: 'nine (long labels are wrapped)', value: 9, unit: 'x' }, + ], + [ + { axis: 'one', value: 9, unit: 'y' }, + { axis: 'two', value: 8, unit: 'y' }, + { axis: 'three', value: 7, unit: 'y' }, + { axis: 'four', value: 6, unit: 'y' }, + { axis: 'five', value: 5, unit: 'y' }, + { axis: 'six', value: 4, unit: 'y' }, + { axis: 'seven', value: 3, unit: 'y' }, + { axis: 'eight', value: 2, unit: 'y' }, + { axis: 'nine (long labels are wrapped)', value: 1, unit: 'y' }, + ], + ]; + } + + private readonly breakpoint$ = this.breakpointObserver + .observe([Breakpoints.XSmall, Breakpoints.Small, Breakpoints.Medium, Breakpoints.Large, Breakpoints.XLarge]) + .pipe(map(result => Object.keys(result.breakpoints).find(item => result.breakpoints[item]) ?? 'unknown')); + + public readonly radarChartConfig$ = this.breakpoint$.pipe( + switchMap(() => { + const timeout = 100; + return timer(timeout).pipe( + first(), + map(() => ({ data: this.radarChartData, options: this.radarChartOptions() })), + ); + }), + ); + + constructor(private readonly breakpointObserver: BreakpointObserver) {} + + /** + * Example radar chart options. + */ + private radarChartOptions() { + return >{ + chartTitle: 'Example radar chart', + }; + } +} diff --git a/libs/client-d3-charts/src/lib/components/chart-examples/chart-examples.component.html b/libs/client-d3-charts/src/lib/components/chart-examples/chart-examples.component.html index f510de00..aa115692 100644 --- a/libs/client-d3-charts/src/lib/components/chart-examples/chart-examples.component.html +++ b/libs/client-d3-charts/src/lib/components/chart-examples/chart-examples.component.html @@ -1,45 +1,17 @@ -
- -
+
-
- - - - - - - -
+
-
- - - - - -
+
-
- -
+
-
- -
+ diff --git a/libs/client-d3-charts/src/lib/components/chart-examples/chart-examples.component.spec.ts b/libs/client-d3-charts/src/lib/components/chart-examples/chart-examples.component.spec.ts index 2453b10c..81af31e2 100644 --- a/libs/client-d3-charts/src/lib/components/chart-examples/chart-examples.component.spec.ts +++ b/libs/client-d3-charts/src/lib/components/chart-examples/chart-examples.component.spec.ts @@ -1,10 +1,12 @@ +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed, TestModuleMetadata } from '@angular/core/testing'; import { AppChartExamplesComponent } from './chart-examples.component'; -describe('AppGlobalProgressBarComponent', () => { +describe('AppChartExamplesComponent', () => { const testBedConfig: TestModuleMetadata = { declarations: [AppChartExamplesComponent], + schemas: [CUSTOM_ELEMENTS_SCHEMA], }; let fixture: ComponentFixture; @@ -20,39 +22,4 @@ describe('AppGlobalProgressBarComponent', () => { it('should be defined', () => { expect(component).toBeDefined(); }); - - it('barChartOptions should have expected structure', () => { - expect(component.barChartOptions()).toEqual({ - chartTitle: 'Example bar chart', - xAxisTitle: 'long x axis title', - yAxisTitle: 'long y axis title', - }); - }); - - it('lineChartOptions should have expected structure', () => { - expect(component.lineChartOptions()).toEqual({ - chartTitle: 'Example line chart, date format default', - dateFormat: 'default', - xAxisTitle: 'Date range', - yAxisTitle: 'Value range', - }); - }); - - it('radarChartOptions should have expected structure', () => { - expect(component.radarChartOptions()).toEqual({ - chartTitle: 'Example radar chart', - }); - }); - - it('pieChartOptions should have expected structure', () => { - expect(component.pieChartOptions()).toEqual({ - chartTitle: 'Example pie chart', - }); - }); - - it('forceDirectedChartOptions should have expected structure', () => { - expect(component.forceDirectedChartOptions()).toEqual({ - chartTitle: 'Example force directed chart', - }); - }); }); diff --git a/libs/client-d3-charts/src/lib/components/chart-examples/chart-examples.component.ts b/libs/client-d3-charts/src/lib/components/chart-examples/chart-examples.component.ts index bc199312..e4a92651 100644 --- a/libs/client-d3-charts/src/lib/components/chart-examples/chart-examples.component.ts +++ b/libs/client-d3-charts/src/lib/components/chart-examples/chart-examples.component.ts @@ -1,16 +1,4 @@ -import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout'; import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { first, map, switchMap, timer } from 'rxjs'; - -import { IBarChartOptions, TBarChartData } from '../../interfaces/bar-chart.interface'; -import { - IForceDirectedChartData, - IForceDirectedChartOptions, - IForceDirectedGraphEntity, -} from '../../interfaces/force-directed-chart.interface'; -import { ILineChartOptions, TDateFormat, TLineChartData } from '../../interfaces/line-chart.interface'; -import { IPieChartDataNode, IPieChartOptions } from '../../interfaces/pie-chart.interface'; -import { IRadarChartDataNode, IRadarChartOptions } from '../../interfaces/radar-chart.interface'; @Component({ selector: 'app-chart-examples', @@ -18,264 +6,4 @@ import { IRadarChartDataNode, IRadarChartOptions } from '../../interfaces/radar- styleUrls: ['./chart-examples.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class AppChartExamplesComponent { - /** - * Sample bar chart data. - */ - public get barChartData() { - return [ - { title: 'one', value: 1 }, - { title: 'two', value: 2 }, - { title: 'three', value: 3 }, - { title: 'four', value: 4 }, - { title: 'five', value: 5 }, - ]; - } - - /** - * Sample line chart data. - */ - public get lineChartData() { - return [ - [ - { timestamp: this.randomTimestamp(), value: this.randomValue() }, - { timestamp: this.randomTimestamp(), value: this.randomValue() }, - { timestamp: this.randomTimestamp(), value: this.randomValue() }, - { timestamp: this.randomTimestamp(), value: this.randomValue() }, - { timestamp: this.randomTimestamp(), value: this.randomValue() }, - { timestamp: this.randomTimestamp(), value: this.randomValue() }, - { timestamp: this.randomTimestamp(), value: this.randomValue() }, - ].sort((a, b) => a.timestamp - b.timestamp), - [ - { timestamp: this.randomTimestamp(), value: this.randomValue() }, - { timestamp: this.randomTimestamp(), value: this.randomValue() }, - { timestamp: this.randomTimestamp(), value: this.randomValue() }, - { timestamp: this.randomTimestamp(), value: this.randomValue() }, - { timestamp: this.randomTimestamp(), value: this.randomValue() }, - { timestamp: this.randomTimestamp(), value: this.randomValue() }, - { timestamp: this.randomTimestamp(), value: this.randomValue() }, - ].sort((a, b) => a.timestamp - b.timestamp), - [ - { timestamp: this.randomTimestamp(), value: this.randomValue() }, - { timestamp: this.randomTimestamp(), value: this.randomValue() }, - { timestamp: this.randomTimestamp(), value: this.randomValue() }, - { timestamp: this.randomTimestamp(), value: this.randomValue() }, - { timestamp: this.randomTimestamp(), value: this.randomValue() }, - { timestamp: this.randomTimestamp(), value: this.randomValue() }, - { timestamp: this.randomTimestamp(), value: this.randomValue() }, - ].sort((a, b) => a.timestamp - b.timestamp), - ]; - } - - /** - * Sample radar chart data. - */ - public get radarChartData() { - return [ - [ - { axis: 'one', value: 1, unit: 'x' }, - { axis: 'two', value: 2, unit: 'x' }, - { axis: 'three', value: 3, unit: 'x' }, - { axis: 'four', value: 4, unit: 'x' }, - { axis: 'five', value: 5, unit: 'x' }, - { axis: 'six', value: 6, unit: 'x' }, - { axis: 'seven', value: 7, unit: 'x' }, - { axis: 'eight', value: 8, unit: 'x' }, - { axis: 'nine (long labels are wrapped)', value: 9, unit: 'x' }, - ], - [ - { axis: 'one', value: 9, unit: 'y' }, - { axis: 'two', value: 8, unit: 'y' }, - { axis: 'three', value: 7, unit: 'y' }, - { axis: 'four', value: 6, unit: 'y' }, - { axis: 'five', value: 5, unit: 'y' }, - { axis: 'six', value: 4, unit: 'y' }, - { axis: 'seven', value: 3, unit: 'y' }, - { axis: 'eight', value: 2, unit: 'y' }, - { axis: 'nine (long labels are wrapped)', value: 1, unit: 'y' }, - ], - ]; - } - - /** - * Sample pie chart data. - */ - public get pieChartData() { - return [ - { key: 'one', y: 1 }, - { key: 'two', y: 2 }, - { key: 'three', y: 3 }, - { key: 'four', y: 4 }, - { key: 'five', y: 5 }, - { key: 'six', y: 6 }, - ]; - } - - /** - * Sample force directed chart data. - */ - public get forceDirectedChartData() { - const input = { - domains: ['first', 'second', 'third'], - entities: [ - { name: 'one', domains: ['first'], img: '' }, - { name: 'two', domains: ['second'], img: '' }, - { name: 'three', domains: ['third'], img: '' }, - { name: 'four', domains: ['first', 'second'], img: '' }, - { name: 'five', domains: ['second'], img: '' }, - { name: 'six', domains: ['third', 'second'], img: '' }, - { name: 'seven', domains: ['second'], img: '' }, - { name: 'eight', domains: ['third'], img: '' }, - ], - }; - const domains: IForceDirectedChartData['domains'] = input.domains.map((name, index) => ({ index, name, value: 1 })); - const entities: IForceDirectedChartData['entities'] = input.entities.map((app, index) => ({ - index: index, - name: app.name, - domains: [...app.domains], - img: app.img, - linksCount: 0, - })); - const nodes: IForceDirectedGraphEntity[] = [...entities]; - const links: IForceDirectedChartData['links'] = entities - .map(entity => { - return entity.domains.map(domain => { - const source = domains.find(value => domain === value.name)?.index ?? -1; - const target = entity.index; - return { source, target }; - }); - }) - .reduce((accumulator, item) => (Array.isArray(item) ? [...accumulator, ...item] : [...accumulator, item]), []) - .filter(link => link.source !== -1 && link.target !== -1 && typeof link.target !== 'undefined'); - const chartData: IForceDirectedChartData = { - domains, - entities: entities.map(item => ({ - ...item, - linksCount: links.reduce((acc, link) => (link.target === item.index ? acc + 1 : acc), 0), - })), - links, - nodes, - }; - return chartData; - } - - private readonly breakpoint$ = this.breakpointObserver - .observe([Breakpoints.XSmall, Breakpoints.Small, Breakpoints.Medium, Breakpoints.Large, Breakpoints.XLarge]) - .pipe(map(result => Object.keys(result.breakpoints).find(item => result.breakpoints[item]) ?? 'unknown')); - - public readonly barChartConfig$ = this.breakpoint$.pipe( - switchMap(() => - timer(this.timeout).pipe( - first(), - map(() => ({ data: this.barChartData, options: this.barChartOptions() })), - ), - ), - ); - - public readonly lineChartConfig$ = this.breakpoint$.pipe( - switchMap(() => - timer(this.timeout).pipe( - first(), - map(() => ({ - data: this.lineChartData, - options: this.lineChartOptions(), - optionsDateDdMmYy: this.lineChartOptions('dd/mm/yy'), - optionsDateDdMmYyyy: this.lineChartOptions('dd/mm/yyyy'), - optionsDateMmYyyy: this.lineChartOptions('mm/yyyy'), - })), - ), - ), - ); - - public readonly radarChartConfig$ = this.breakpoint$.pipe( - switchMap(() => - timer(this.timeout).pipe( - first(), - map(() => ({ data: this.radarChartData, options: this.radarChartOptions() })), - ), - ), - ); - - public readonly pieChartConfig$ = this.breakpoint$.pipe( - switchMap(() => - timer(this.timeout).pipe( - first(), - map(() => ({ data: this.pieChartData, options: this.pieChartOptions() })), - ), - ), - ); - - public readonly forceDirectedChartConfig$ = this.breakpoint$.pipe( - switchMap(() => - timer(this.timeout).pipe( - first(), - map(() => ({ data: this.forceDirectedChartData, options: this.forceDirectedChartOptions() })), - ), - ), - ); - - constructor(private readonly breakpointObserver: BreakpointObserver) {} - - private readonly timeout = 100; - - public randomValue(range?: number) { - const defaultRange = 100; - return Math.floor(Math.random() * (range ?? defaultRange) + 1); - } - - public randomTimestamp(range?: number) { - const defaultRange = 100000000; - return Math.floor(Math.random() * (range ?? defaultRange) + new Date().getTime()); - } - - /** - * Example bar chart options. - */ - public barChartOptions() { - return >{ - chartTitle: 'Example bar chart', - xAxisTitle: 'long x axis title', - yAxisTitle: 'long y axis title', - }; - } - - /** - * Example line chart options. - * @param dateFormat date format - */ - public lineChartOptions(dateFormat: TDateFormat = 'default') { - return >{ - chartTitle: `Example line chart, date format ${dateFormat}`, - xAxisTitle: 'Date range', - yAxisTitle: 'Value range', - dateFormat, - }; - } - - /** - * Example radar chart options. - */ - public radarChartOptions() { - return >{ - chartTitle: 'Example radar chart', - }; - } - - /** - * Example pie chart options. - */ - public pieChartOptions() { - return >{ - chartTitle: 'Example pie chart', - }; - } - - /** - * Example force directed chart options. - */ - public forceDirectedChartOptions() { - return >{ - chartTitle: 'Example force directed chart', - }; - } -} +export class AppChartExamplesComponent {} diff --git a/libs/client-d3-charts/src/lib/d3-charts.module.ts b/libs/client-d3-charts/src/lib/d3-charts.module.ts index 7b0dfc1d..61b786fe 100644 --- a/libs/client-d3-charts/src/lib/d3-charts.module.ts +++ b/libs/client-d3-charts/src/lib/d3-charts.module.ts @@ -3,6 +3,11 @@ import { NgModule } from '@angular/core'; import { AppBarChartComponent } from './components/bar-chart/bar-chart.component'; import { AppChartExamplesComponent } from './components/chart-examples/chart-examples.component'; +import { AppChartExamplesBarComponent } from './components/chart-examples-bar/chart-examples-bar.component'; +import { AppChartExamplesForceDirectedComponent } from './components/chart-examples-force-directed/chart-examples-force-directed.component'; +import { AppChartExamplesLineComponent } from './components/chart-examples-line/chart-examples-line.component'; +import { AppChartExamplesPieComponent } from './components/chart-examples-pie/chart-examples-pie.component'; +import { AppChartExamplesRadaraComponent } from './components/chart-examples-radar/chart-examples-radar.component'; import { AppForceDirectedChartComponent } from './components/force-directed-chart/force-directed-chart.component'; import { AppLineChartComponent } from './components/line-chart/line-chart.component'; import { AppPieChartComponent } from './components/pie-chart/pie-chart.component'; @@ -17,6 +22,11 @@ import { AppRadarChartComponent } from './components/radar-chart/radar-chart.com AppBarChartComponent, AppLineChartComponent, AppChartExamplesComponent, + AppChartExamplesLineComponent, + AppChartExamplesRadaraComponent, + AppChartExamplesBarComponent, + AppChartExamplesPieComponent, + AppChartExamplesForceDirectedComponent, ], exports: [ AppPieChartComponent, diff --git a/libs/client-d3-charts/src/lib/util/force-directed-chart.util.ts b/libs/client-d3-charts/src/lib/util/force-directed-chart.util.ts index a8a9b271..42016df1 100644 --- a/libs/client-d3-charts/src/lib/util/force-directed-chart.util.ts +++ b/libs/client-d3-charts/src/lib/util/force-directed-chart.util.ts @@ -245,20 +245,15 @@ const createNodes = ( force: d3.Simulation, config: IForceDirectedChartOptions, ) => { + const base = 5; return svg .selectAll('.node') .data(data.nodes) .enter() .append('circle') .attr('class', 'node') - .attr('r', val => { - const base = 5; - return base + (val.value ?? 0) + (val.linksCount ?? 0) * 2; - }) - .style('stroke-width', val => { - const base = 5; - return base + (val.value ?? 0) + (val.linksCount ?? 0) * 2; - }) + .attr('r', val => base + (val.value ?? 1) * (val.linksCount ?? 1)) + .style('stroke-width', val => base + (val.value ?? 1) + (val.linksCount ?? 1)) .style('fill', val => (typeof val.img === 'undefined' || val.img === '' ? '#f00000' : `url(${val.img})`)) .call( d3