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