xref: /aosp_15_r20/development/tools/motion/motion_test_watcher_app/src/app/visualization.ts (revision 90c8c64db3049935a07c6143d7fd006e26f8ecca)
1import { checkArgument, checkNotNull } from '../utils/preconditions';
2import { DataPoint, isNotFound } from './golden';
3import { VisualTimeline } from './visual-timeline';
4
5export interface Visualization {
6  height: number;
7  render(
8    timeline: VisualTimeline,
9    dataPoints: Array<DataPoint>,
10    canvas: HTMLCanvasElement,
11  ): void;
12}
13
14export const PROBE_COLORS = [
15  '#E51C23',
16  '#9C27B0',
17  '#5677FC',
18  '#00BCD4',
19  '#259B24',
20  '#CDDC39',
21  '#FFC107',
22  '#795548',
23  '#737373',
24];
25
26function lighten(hex: string): string {
27  hex = hex.replace(/[^0-9A-F]/gi, '');
28  var bigint = parseInt(hex, 16);
29  var r = (bigint >> 16) & 255;
30  var g = (bigint >> 8) & 255;
31  var b = bigint & 255;
32
33  return 'rgba(' + r + ',' + g + ',' + b + ',0.15)';
34}
35
36export class LineGraphVisualization implements Visualization {
37  color: string
38  fillColor: string
39
40  height: number = 96
41
42  constructor(
43    public minValue: number,
44    public maxValue: number,
45     color: string|null,
46  ) {
47    checkArgument(
48      minValue < maxValue,
49      `minValue ${minValue} >= maxValue ${maxValue}`,
50    );
51
52    this.color = color ?? PROBE_COLORS[0]
53    this.fillColor  = lighten(this.color);
54
55  }
56
57  render(
58    timeline: VisualTimeline,
59    dataPoints: Array<DataPoint>,
60    canvas: HTMLCanvasElement,
61  ) {
62    const cw = canvas.width;
63    const ch = 90;
64
65    const ctx = checkNotNull(canvas.getContext('2d'));
66    ctx.clearRect(0, 0, cw, canvas.height);
67    ctx.save();
68    ctx.translate(0, 5);
69
70    const min = this.minValue;
71    const max = this.maxValue;
72    const color = this.color;
73
74    for (let i = 0; i < dataPoints.length; i++) {
75      if (isNotFound(dataPoints[i])) {
76        continue;
77      }
78      const start = i;
79
80      while (i < dataPoints.length && !isNotFound(dataPoints[i])) {
81        i++;
82      }
83      const end = i;
84
85      const bottomLineWidth = 1;
86
87      const mid = min <=0 && max >= 0 ? 0 : (min > 0) ? min : max
88
89      const midY = (1 - (mid - min) / (max - min)) * ch
90
91      ctx.save();
92      try {
93        ctx.fillStyle = this.fillColor;
94        ctx.beginPath();
95        ctx.moveTo(timeline.frameToPx(start), midY);
96        for (let i = start; i < end; i++) {
97          const value = dataPoints.at(i);
98          if (typeof value !== 'number') {
99            continue;
100          }
101          ctx.lineTo(
102            timeline.frameToPx(i),
103            (1 - (value - min) / (max - min)) * ch,
104          );
105        }
106        ctx.lineTo(timeline.frameToPx(end - 1), midY);
107        ctx.fill();
108      } finally {
109        ctx.restore();
110      }
111      ctx.save();
112      try {
113        ctx.beginPath();
114        ctx.strokeStyle = color;
115        ctx.lineWidth = bottomLineWidth;
116
117        ctx.moveTo(timeline.frameToPx(start), midY);
118        ctx.lineTo(timeline.frameToPx(end - 1), midY);
119
120        ctx.stroke();
121      } finally {
122        ctx.restore();
123      }
124
125      ctx.fillStyle = color;
126
127      for (let i = start; i < end; i++) {
128        const value = dataPoints.at(i);
129        if (typeof value !== 'number') {
130          continue;
131        }
132        ctx.beginPath();
133
134        ctx.arc(
135          timeline.frameToPx(i),
136          (1 - (value - min) / (max - min)) * ch,
137          2,
138          0,
139          2 * Math.PI,
140        );
141
142        ctx.fill();
143      }
144
145      ctx.restore();
146    }
147  }
148}
149
150export class DataPointVisualization implements Visualization {
151  color: string = PROBE_COLORS[0];
152  height: number = 32
153
154  render(
155    timeline: VisualTimeline,
156    dataPoints: Array<DataPoint>,
157    canvas: HTMLCanvasElement,
158  ) {
159    const cw = canvas.width;
160
161    const ctx = checkNotNull(canvas.getContext('2d'));
162    ctx.clearRect(0, 0, cw, canvas.height);
163
164    ctx.fillStyle = this.color;
165
166    dataPoints.forEach((dataPoint, idx) => {
167      if (isNotFound(dataPoint)) return;
168      ctx.beginPath();
169
170      ctx.arc(
171        /* x */ timeline.frameToPx(idx),
172        /* y */ 15,
173        /* radius */ 5,
174        /* startAngle */ 0,
175        /* endAngle */ 2 * Math.PI,
176      );
177      ctx.fill();
178    });
179  }
180}
181