• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright 2024 Google LLC
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17import { checkArgument, checkNotNull } from '../util/preconditions';
18import { DataPoint, isNotFound } from './golden';
19import { VisualTimeline } from './visual-timeline';
20
21export interface Visualization {
22  height: number;
23  render(
24    timeline: VisualTimeline,
25    dataPoints: Array<DataPoint>,
26    canvas: HTMLCanvasElement
27  ): void;
28}
29
30export const PROBE_COLORS = [
31  '#E51C23',
32  '#9C27B0',
33  '#5677FC',
34  '#00BCD4',
35  '#259B24',
36  '#CDDC39',
37  '#FFC107',
38  '#795548',
39  '#737373',
40];
41
42function lighten(hex: string): string {
43  hex = hex.replace(/[^0-9A-F]/gi, '');
44  var bigint = parseInt(hex, 16);
45  var r = (bigint >> 16) & 255;
46  var g = (bigint >> 8) & 255;
47  var b = bigint & 255;
48
49  return 'rgba(' + r + ',' + g + ',' + b + ',0.15)';
50}
51
52export class LineGraphVisualization implements Visualization {
53  color: string;
54  fillColor: string;
55
56  height: number = 96;
57
58  constructor(
59    public minValue: number,
60    public maxValue: number,
61    color: string | null
62  ) {
63    checkArgument(
64      minValue < maxValue,
65      `minValue ${minValue} >= maxValue ${maxValue}`
66    );
67
68    this.color = color ?? PROBE_COLORS[0];
69    this.fillColor = lighten(this.color);
70  }
71
72  render(
73    timeline: VisualTimeline,
74    dataPoints: Array<DataPoint>,
75    canvas: HTMLCanvasElement
76  ) {
77    const cw = canvas.width;
78    const ch = 90;
79
80    const ctx = checkNotNull(canvas.getContext('2d'));
81    ctx.clearRect(0, 0, cw, canvas.height);
82    ctx.save();
83    ctx.translate(0, 5);
84
85    const min = this.minValue;
86    const max = this.maxValue;
87    const color = this.color;
88
89    for (let i = 0; i < dataPoints.length; i++) {
90      if (isNotFound(dataPoints[i])) {
91        continue;
92      }
93      const start = i;
94
95      while (i < dataPoints.length && !isNotFound(dataPoints[i])) {
96        i++;
97      }
98      const end = i;
99
100      const bottomLineWidth = 1;
101
102      const mid = min <= 0 && max >= 0 ? 0 : min > 0 ? min : max;
103
104      const midY = (1 - (mid - min) / (max - min)) * ch;
105
106      ctx.save();
107      try {
108        ctx.fillStyle = this.fillColor;
109        ctx.beginPath();
110        ctx.moveTo(timeline.frameToPx(start), midY);
111        for (let i = start; i < end; i++) {
112          const value = dataPoints.at(i);
113          if (typeof value !== 'number') {
114            continue;
115          }
116          ctx.lineTo(
117            timeline.frameToPx(i),
118            (1 - (value - min) / (max - min)) * ch
119          );
120        }
121        ctx.lineTo(timeline.frameToPx(end - 1), midY);
122        ctx.fill();
123      } finally {
124        ctx.restore();
125      }
126      ctx.save();
127      try {
128        ctx.beginPath();
129        ctx.strokeStyle = color;
130        ctx.lineWidth = bottomLineWidth;
131
132        ctx.moveTo(timeline.frameToPx(start), midY);
133        ctx.lineTo(timeline.frameToPx(end - 1), midY);
134
135        ctx.stroke();
136      } finally {
137        ctx.restore();
138      }
139
140      ctx.fillStyle = color;
141
142      for (let i = start; i < end; i++) {
143        const value = dataPoints.at(i);
144        if (typeof value !== 'number') {
145          continue;
146        }
147        ctx.beginPath();
148
149        ctx.arc(
150          timeline.frameToPx(i),
151          (1 - (value - min) / (max - min)) * ch,
152          2,
153          0,
154          2 * Math.PI
155        );
156
157        ctx.fill();
158      }
159
160      ctx.restore();
161    }
162  }
163}
164
165export class DataPointVisualization implements Visualization {
166  color: string = PROBE_COLORS[0];
167  height: number = 32;
168
169  render(
170    timeline: VisualTimeline,
171    dataPoints: Array<DataPoint>,
172    canvas: HTMLCanvasElement
173  ) {
174    const cw = canvas.width;
175
176    const ctx = checkNotNull(canvas.getContext('2d'));
177    ctx.clearRect(0, 0, cw, canvas.height);
178
179    ctx.fillStyle = this.color;
180
181    dataPoints.forEach((dataPoint, idx) => {
182      if (isNotFound(dataPoint)) return;
183      ctx.beginPath();
184
185      ctx.arc(
186        /* x */ timeline.frameToPx(idx),
187        /* y */ 15,
188        /* radius */ 5,
189        /* startAngle */ 0,
190        /* endAngle */ 2 * Math.PI
191      );
192      ctx.fill();
193    });
194  }
195}
196