• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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