1import { PreviewService } from './../../service/preview.service'; 2import { 3 AfterViewInit, 4 Component, 5 Input, 6 ViewChild, 7 OnChanges, 8 SimpleChanges, 9 ElementRef, 10 Output, 11 EventEmitter, 12} from '@angular/core'; 13import { MotionGoldenData, MotionGoldenFeature } from '../../model/golden'; 14import { Visualization, DataPoint } from './visualization'; 15import { LineGraphVisualization } from './line-graph-visualization'; 16import * as d3 from 'd3'; 17import { NgIf } from '@angular/common'; 18import { MatIconModule } from '@angular/material/icon'; 19 20@Component({ 21 selector: 'app-graph', 22 imports: [NgIf, MatIconModule], 23 templateUrl: './graph.component.html', 24 styleUrl: './graph.component.css', 25}) 26export class GraphComponent implements AfterViewInit, OnChanges { 27 constructor(private previewService: PreviewService) {} 28 29 @Input() expectedData: MotionGoldenData | undefined; 30 @Input() actualData: MotionGoldenData | undefined; 31 @Input() featureName: string | undefined; 32 @Input() isExpanded: boolean = false; 33 @Output() expand = new EventEmitter<string>(); 34 35 @ViewChild('chartContainer', { static: true }) 36 chartContainer!: ElementRef<HTMLDivElement>; 37 graphId: string = ''; 38 39 private svg!: d3.Selection<SVGSVGElement, unknown, null, undefined>; 40 private width!: number; 41 private height!: number; 42 private data: DataPoint[] = []; 43 private visualization!: Visualization; 44 45 ngAfterViewInit(): void { 46 this.graphId = `graph-${this.featureName}-${Date.now()}`; 47 this.width = this.chartContainer.nativeElement.offsetWidth; 48 this.height = this.chartContainer.nativeElement.offsetHeight; 49 this.createChart(); 50 } 51 52 ngOnChanges(changes: SimpleChanges): void { 53 if ( 54 changes['actualData'] || 55 changes['expectedData'] || 56 changes['featureName'] 57 ) { 58 this.graphId = `graph-${this.featureName}-${Date.now()}`; 59 this.updateData(); 60 this.createChart(); 61 } 62 } 63 64 private updateData(): void { 65 this.data = []; 66 const actualFeature = 67 this.featureName && this.actualData 68 ? this.actualData.features.find((f) => f.name === this.featureName) 69 : undefined; 70 const expectedFeature = 71 this.featureName && this.expectedData 72 ? this.expectedData.features.find((f) => f.name === this.featureName) 73 : undefined; 74 75 this.visualization = this.createVisualization(actualFeature); 76 77 if (this.visualization instanceof LineGraphVisualization) { 78 this.createLineChartData(actualFeature, expectedFeature); 79 } else { 80 } 81 } 82 83 private createLineChartData( 84 actualFeature: MotionGoldenFeature | undefined, 85 expectedFeature: MotionGoldenFeature | undefined 86 ) { 87 const combinedLength = Math.max( 88 actualFeature?.data_points?.length || 0, 89 expectedFeature?.data_points?.length || 0 90 ); 91 92 for (let i = 0; i < combinedLength; i++) { 93 const actualDataPoint = 94 actualFeature?.data_points && actualFeature.data_points[i]; 95 const expectedDataPoint = 96 expectedFeature?.data_points && expectedFeature.data_points[i]; 97 98 let x = -1; 99 if (this.actualData?.frame_ids[i] === 'after') { 100 x = (this.actualData?.frame_ids[i - 1] as number) + 50; 101 } else if (this.actualData?.frame_ids[i] === 'before') { 102 x = (this.actualData?.frame_ids[i + 1] as number) - 50; 103 } else { 104 x = this.actualData?.frame_ids[i] as number; 105 } 106 107 const newPoint: DataPoint = { x }; 108 if (actualDataPoint && typeof actualDataPoint === 'number') { 109 newPoint.actualValue = actualDataPoint; 110 } 111 if (expectedDataPoint && typeof expectedDataPoint === 'number') { 112 newPoint.expectedValue = expectedDataPoint; 113 } 114 115 this.data.push(newPoint); 116 } 117 } 118 119 private createVisualization( 120 actualFeature: MotionGoldenFeature | undefined, 121 expectedFeature: MotionGoldenFeature | undefined = undefined 122 ): Visualization { 123 const name = actualFeature?.name; 124 const type = actualFeature?.type; 125 126 if ( 127 actualFeature && 128 ['float', 'int', 'dp', 'dpOffset', 'dpSize', 'offset'].includes( 129 type || '' 130 ) 131 ) { 132 const numericValues = 133 actualFeature.data_points?.filter( 134 (it): it is number => typeof it === 'number' 135 ) || []; 136 const minValue = Math.min(...numericValues) ?? 0; 137 let maxValue = Math.max(...numericValues) ?? 1; 138 139 if (minValue === maxValue) { 140 maxValue += 1; 141 } 142 143 return new LineGraphVisualization( 144 minValue, 145 maxValue, 146 this.graphId, 147 this.previewService 148 ); 149 } 150 return new LineGraphVisualization(0, 1, this.graphId, this.previewService); 151 } 152 153 private createChart(): void { 154 this.chartContainer.nativeElement.innerHTML = ''; 155 this.svg = d3 156 .select(this.chartContainer.nativeElement) 157 .append('svg') 158 .attr('width', this.width) 159 .attr('height', this.height); 160 this.visualization.render(this.svg, this.data, this.width, this.height); 161 } 162} 163