Skip to content

Commit f0447d0

Browse files
committed
Dependency: Make node clickable, for navigation
1 parent cd6b6b6 commit f0447d0

5 files changed

Lines changed: 77 additions & 37 deletions

File tree

src/app/component/activity-description/activity-description.component.html

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,10 @@ <h1>
6262
</mat-panel-title>
6363
</mat-expansion-panel-header>
6464
<div *ngIf="currentActivity?.name">
65-
<app-dependency-graph [activityName]="currentActivity?.name || ''"></app-dependency-graph>
65+
<app-dependency-graph
66+
[activityName]="currentActivity?.name || ''"
67+
(activityClicked)="onActivityClicked($event)">
68+
</app-dependency-graph>
6669
</div>
6770
</mat-expansion-panel>
6871

src/app/component/activity-description/activity-description.component.spec.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { HttpClient, HttpHandler } from '@angular/common/http';
22
import { ComponentFixture, TestBed } from '@angular/core/testing';
33
import { RouterTestingModule } from '@angular/router/testing';
44
import { ActivatedRoute } from '@angular/router';
5+
import { of } from 'rxjs';
56

67
import { ActivityDescriptionComponent } from './activity-description.component';
78
import { LoaderService } from 'src/app/service/loader/data-loader.service';
@@ -12,13 +13,11 @@ import { isEmptyObj } from 'src/app/util/util';
1213

1314
let mockLoaderService: MockLoaderService;
1415
let mockActivatedRoute = {
15-
snapshot: {
16-
queryParams: { uuid: '00000000-1111-1111-1111-0000000000000' },
17-
},
16+
queryParams: of({ uuid: '00000000-1111-1111-1111-0000000000000' }),
1817
};
1918
let mockData = {
2019
'Dim 1': {
21-
'SubDim 1.1': {
20+
'SubDim-1.1': {
2221
'Activity 111': {
2322
uuid: '00000000-1111-1111-1111-0000000000000',
2423
level: 1,
@@ -77,19 +76,19 @@ describe('ActivityDescriptionComponent', () => {
7776
expect(isEmptyObj(component.currentActivity)).toBeFalsy();
7877
});
7978

80-
it('check if header is being generated', () => {
81-
const testDimension = 'Dim 1';
82-
const testSubDimension = 'SubDim 1.1';
79+
it('check if header is being generated', async () => {
80+
const testSubDimension = 'SubDim-1.1';
8381

82+
await fixture.whenStable();
8483
fixture.detectChanges();
84+
8585
const HTMLElement: HTMLElement = fixture.nativeElement;
8686
const heading = HTMLElement.querySelector('h1')!;
8787

88-
expect(heading?.textContent).toContain(testDimension);
8988
expect(heading?.textContent).toContain(testSubDimension);
9089
});
9190

92-
it('check if content is displayed', () => {
91+
it('check if content is displayed', async () => {
9392
// console.log(`${perfNow()}: ActivityDescription: "check if content is displayed"`);
9493
const testUUID = '00000000-1111-1111-1111-0000000000000';
9594
const testDesc = 'Description 111';
@@ -99,6 +98,7 @@ describe('ActivityDescriptionComponent', () => {
9998
const testComments = 'Comments 111';
10099
const testImplementationGuide = 'Implementation Guide 111';
101100

101+
await fixture.whenStable();
102102
fixture.detectChanges();
103103
const HTMLElement: HTMLElement = fixture.nativeElement;
104104

src/app/component/activity-description/activity-description.component.ts

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { Component, ViewChildren, QueryList, OnInit } from '@angular/core';
22
import { MatAccordion } from '@angular/material/expansion';
3-
import { ActivatedRoute } from '@angular/router';
3+
import { ActivatedRoute, Router } from '@angular/router';
44
import { LoaderService } from '../../service/loader/data-loader.service';
5-
import { Activity } from '../../model/activity-store';
5+
import { Activity, ActivityStore } from '../../model/activity-store';
66
import { DataStore } from 'src/app/model/data-store';
77

88
@Component({
@@ -23,20 +23,29 @@ export class ActivityDescriptionComponent implements OnInit {
2323
openCREVersion: string = 'OpenCRE';
2424
@ViewChildren(MatAccordion) accordion!: QueryList<MatAccordion>;
2525

26-
constructor(private route: ActivatedRoute, private loader: LoaderService) {}
26+
constructor(
27+
private route: ActivatedRoute,
28+
private loader: LoaderService,
29+
private router: Router
30+
) {}
2731

2832
ngOnInit() {
29-
let uuid: string = this.route.snapshot.queryParams['uuid'];
30-
let name: string = this.route.snapshot.queryParams['name'];
33+
this.route.queryParams.subscribe(params => {
34+
const uuid: string = params['uuid'];
35+
const name: string = params['name'];
36+
this.loadActivity(uuid, name);
37+
});
38+
}
3139

32-
// Load data
40+
loadActivity(uuid?: string, name?: string) {
3341
this.loader
3442
.load()
3543
.then((dataStore: DataStore) => {
36-
// Find the activity with matching UUID (or potentially name)
37-
if (!dataStore.activityStore) throw Error('TODO: Must handle these');
38-
39-
let activity: Activity = dataStore.activityStore.getActivity(uuid, name);
44+
if (!dataStore.activityStore) throw Error('DateStore not loaded');
45+
// Ensure uuid and name are strings (fallback to empty string if undefined)
46+
const uuidStr = uuid ?? '';
47+
const nameStr = name ?? '';
48+
let activity: Activity = dataStore.activityStore.getActivity(uuidStr, nameStr);
4049
if (!activity) {
4150
throw new Error('Activity not found');
4251
}
@@ -59,6 +68,22 @@ export class ActivityDescriptionComponent implements OnInit {
5968
});
6069
}
6170

71+
onActivityClicked(activityName: string) {
72+
// Find the activity by name and update the view without reloading the page
73+
const activityStore: ActivityStore = this.loader.datastore?.activityStore as ActivityStore;
74+
const activity: Activity = activityStore?.getActivityByName(activityName) as Activity;
75+
76+
if (activity) {
77+
// Update the URL query params (SPA style)
78+
this.router.navigate([], {
79+
relativeTo: this.route,
80+
queryParams: { uuid: activity.uuid },
81+
queryParamsHandling: 'merge',
82+
});
83+
this.loadActivity(activity.uuid, activity.name);
84+
}
85+
}
86+
6287
// Expand all function
6388
openAll(): void {
6489
this.accordion.forEach(element => {

src/app/component/dependency-graph/dependency-graph.component.ts

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { Activity } from 'src/app/model/activity-store';
55
import { DataStore } from 'src/app/model/data-store';
66
import { ThemeService } from 'src/app/service/theme.service';
77

8+
import { Output, EventEmitter } from '@angular/core';
9+
810
export interface graphNodes {
911
id: string;
1012
relativeLevel: number;
@@ -47,6 +49,8 @@ export class DependencyGraphComponent implements OnInit, OnChanges {
4749

4850
@Input() activityName: string = '';
4951

52+
@Output() activityClicked = new EventEmitter<string>();
53+
5054
constructor(private loader: LoaderService, private themeService: ThemeService) {
5155
this.theme = this.themeService.getTheme();
5256
this.setThemeColors(this.theme);
@@ -55,7 +59,6 @@ export class DependencyGraphComponent implements OnInit, OnChanges {
5559
ngOnInit(): void {
5660
this.loader.load().then((dataStore: DataStore) => {
5761
this.dataStore = dataStore;
58-
console.log('Dep-graph: Setting datastore');
5962
if (!dataStore.activityStore) {
6063
throw Error('No activity store loaded');
6164
}
@@ -69,7 +72,6 @@ export class DependencyGraphComponent implements OnInit, OnChanges {
6972
}
7073

7174
ngOnChanges(changes: SimpleChanges): void {
72-
console.log(changes);
7375
if (this.dataStore?.activityStore) {
7476
if (changes?.hasOwnProperty('activityName')) {
7577
this.populateGraph(changes['activityName'].currentValue);
@@ -157,11 +159,12 @@ export class DependencyGraphComponent implements OnInit, OnChanges {
157159
}
158160

159161
initX(d: any): number {
160-
let col: number = 8;
161-
if (d.activityCount > col && d.activityCount < col * 2) {
162-
col = Math.ceil(d.activityCount / 2);
162+
let colSize: number = 8;
163+
if (d.activityCount > colSize && d.activityCount <= colSize * 2.5) {
164+
let colCount: number = Math.ceil(d.activityCount / colSize);
165+
colSize = Math.ceil(d.activityCount / colCount);
163166
}
164-
return d.relativeLevel * Math.ceil(d.activityIndex / col) * 300;
167+
return d.relativeLevel * Math.ceil(d.activityIndex / colSize) * 300;
165168
}
166169
initY(d: any): number {
167170
return d.relativeLevel * 30;
@@ -176,15 +179,8 @@ export class DependencyGraphComponent implements OnInit, OnChanges {
176179
this.simulation = d3
177180
.forceSimulation()
178181
// .alphaMin(0.11)
179-
.force(
180-
'link',
181-
d3.forceLink().id(function (d: any) {
182-
return d.id;
183-
}).strength(0.1)
184-
)
185-
.force('x', d3.forceX((d: any) => { return self.initX(d) }).strength(5)
186-
)
187-
// .force('y', d3.forceY( this.initY ).strength(5))
182+
.force('link', d3.forceLink().id((d: any) => { return d.id; }).strength(0.1))
183+
.force('x', d3.forceX((d: any) => { return self.initX(d) }).strength(5))
188184
.force('charge', d3.forceManyBody().strength(-30))
189185
.force('collide', d3.forceCollide((d: any) => 30))
190186
.force('center', d3.forceCenter(0, 0));
@@ -222,7 +218,24 @@ export class DependencyGraphComponent implements OnInit, OnChanges {
222218
.selectAll('g')
223219
.data(this.graphData['nodes'])
224220
.enter()
225-
.append('g');
221+
.append('g')
222+
.on('click', (event: MouseEvent, d: any) => {
223+
if (d.relativeLevel != 0) {
224+
this.activityClicked.emit(d.id);
225+
}
226+
})
227+
.on('mouseover', (event: MouseEvent, d: any) => {
228+
if (this.activityClicked.observed) {
229+
if (d.relativeLevel != 0) {
230+
d3.select(event.currentTarget as Element).style('cursor', 'pointer');
231+
} else {
232+
d3.select(event.currentTarget as Element).style('cursor', 'default');
233+
}
234+
}
235+
})
236+
.on('mouseout', (event: MouseEvent, d: any) => {
237+
d3.select(event.currentTarget as Element).style('cursor', 'default');
238+
});
226239

227240
const rectHeight = 30;
228241
const rectRx = 10;

src/app/model/activity-store.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,6 @@ export class ActivityStore {
257257
for (let i = 0; i < activity.dependsOn.length; i++) {
258258
if (activity.dependsOn[i].match(UUID)) {
259259
if (activityByUuid.hasOwnProperty(activity.dependsOn[i])) {
260-
console.log(`Replaces ${activity.dependsOn[i]} with ${activityByUuid[activity.dependsOn[i]].name}`);
261260
activity.dependsOn[i] = activityByUuid[activity.dependsOn[i]].name;
262261
}
263262
}

0 commit comments

Comments
 (0)