1<!DOCTYPE html> 2<!-- 3Copyright (c) 2013 The Chromium Authors. All rights reserved. 4Use of this source code is governed by a BSD-style license that can be 5found in the LICENSE file. 6--> 7 8<link rel="import" href="/tracing/ui/base/event_presenter.html"> 9<link rel="import" href="/tracing/base/sorted_array_utils.html"> 10<link rel="import" href="/tracing/ui/base/elided_cache.html"> 11 12<script> 13'use strict'; 14 15/** 16 * @fileoverview Provides various helper methods for drawing to a provided 17 * canvas. 18 */ 19tr.exportTo('tr.ui.b', function() { 20 var elidedTitleCache = new tr.ui.b.ElidedTitleCache(); 21 var ColorScheme = tr.b.ColorScheme; 22 var colorsAsStrings = ColorScheme.colorsAsStrings; 23 24 var EventPresenter = tr.ui.b.EventPresenter; 25 var blackColorId = ColorScheme.getColorIdForReservedName('black'); 26 27 /** 28 * This value is used to allow for consistent style UI elements. 29 * Thread time visualisation uses a smaller rectangle that has this height. 30 * @const 31 */ 32 var THIN_SLICE_HEIGHT = 4; 33 34 /** 35 * This value is used to for performance considerations when drawing large 36 * zoomed out traces that feature cpu time in the slices. If the waiting 37 * width is less than the threshold, we only draw the rectangle as a solid. 38 * @const 39 */ 40 var SLICE_WAITING_WIDTH_DRAW_THRESHOLD = 3; 41 42 /** 43 * If the slice has mostly been waiting to be scheduled on the cpu, the 44 * wall clock will be far greater than the cpu clock. Draw the slice 45 * only as an idle slice, if the active width is not thicker than the 46 * threshold. 47 * @const 48 */ 49 var SLICE_ACTIVE_WIDTH_DRAW_THRESHOLD = 1; 50 51 /** 52 * Should we elide text on trace labels? 53 * Without eliding, text that is too wide isn't drawn at all. 54 * Disable if you feel this causes a performance problem. 55 * This is a default value that can be overridden in tracks for testing. 56 * @const 57 */ 58 var SHOULD_ELIDE_TEXT = true; 59 60 /** 61 * Draw the define line into |ctx|. 62 * 63 * @param {Context} ctx The context to draw into. 64 * @param {float} x1 The start x position of the line. 65 * @param {float} y1 The start y position of the line. 66 * @param {float} x2 The end x position of the line. 67 * @param {float} y2 The end y position of the line. 68 */ 69 function drawLine(ctx, x1, y1, x2, y2) { 70 ctx.moveTo(x1, y1); 71 ctx.lineTo(x2, y2); 72 } 73 74 /** 75 * Draw the defined triangle into |ctx|. 76 * 77 * @param {Context} ctx The context to draw into. 78 * @param {float} x1 The first corner x. 79 * @param {float} y1 The first corner y. 80 * @param {float} x2 The second corner x. 81 * @param {float} y2 The second corner y. 82 * @param {float} x3 The third corner x. 83 * @param {float} y3 The third corner y. 84 */ 85 function drawTriangle(ctx, x1, y1, x2, y2, x3, y3) { 86 ctx.beginPath(); 87 ctx.moveTo(x1, y1); 88 ctx.lineTo(x2, y2); 89 ctx.lineTo(x3, y3); 90 ctx.closePath(); 91 } 92 93 /** 94 * Draw an arrow into |ctx|. 95 * 96 * @param {Context} ctx The context to draw into. 97 * @param {float} x1 The shaft x. 98 * @param {float} y1 The shaft y. 99 * @param {float} x2 The head x. 100 * @param {float} y2 The head y. 101 * @param {float} arrowLength The length of the head. 102 * @param {float} arrowWidth The width of the head. 103 */ 104 function drawArrow(ctx, x1, y1, x2, y2, arrowLength, arrowWidth) { 105 var dx = x2 - x1; 106 var dy = y2 - y1; 107 var len = Math.sqrt(dx * dx + dy * dy); 108 var perc = (len - arrowLength) / len; 109 var bx = x1 + perc * dx; 110 var by = y1 + perc * dy; 111 var ux = dx / len; 112 var uy = dy / len; 113 var ax = uy * arrowWidth; 114 var ay = -ux * arrowWidth; 115 116 ctx.beginPath(); 117 drawLine(ctx, x1, y1, x2, y2); 118 ctx.stroke(); 119 120 drawTriangle(ctx, 121 bx + ax, by + ay, 122 x2, y2, 123 bx - ax, by - ay); 124 ctx.fill(); 125 } 126 127 /** 128 * Draw the provided slices to the screen. 129 * 130 * Each of the elements in |slices| must provide the follow methods: 131 * * start 132 * * duration 133 * * colorId 134 * * selected 135 * 136 * @param {Context} ctx The canvas context. 137 * @param {TimelineDrawTransform} dt The draw transform. 138 * @param {float} viewLWorld The left most point of the world viewport. 139 * @param {float} viewRWorld The right most point of the world viewport. 140 * @param {float} viewHeight The height of the viewport. 141 * @param {Array} slices The slices to draw. 142 * @param {bool} async Whether the slices are drawn with async style. 143 */ 144 function drawSlices(ctx, dt, viewLWorld, viewRWorld, viewHeight, slices, 145 async) { 146 var pixelRatio = window.devicePixelRatio || 1; 147 var pixWidth = dt.xViewVectorToWorld(1); 148 var height = viewHeight * pixelRatio; 149 150 var darkRectHeight = THIN_SLICE_HEIGHT * pixelRatio; 151 152 // Not enough space for both colors, use light color only. 153 if (height < darkRectHeight) 154 darkRectHeight = 0; 155 156 var lightRectHeight = height - darkRectHeight; 157 158 // Begin rendering in world space. 159 ctx.save(); 160 dt.applyTransformToCanvas(ctx); 161 162 var rect = new tr.ui.b.FastRectRenderer( 163 ctx, 2 * pixWidth, 2 * pixWidth, colorsAsStrings); 164 rect.setYandH(0, height); 165 166 var lowSlice = tr.b.findLowIndexInSortedArray( 167 slices, 168 function(slice) { return slice.start + slice.duration; }, 169 viewLWorld); 170 171 var hadTopLevel = false; 172 173 for (var i = lowSlice; i < slices.length; ++i) { 174 var slice = slices[i]; 175 var x = slice.start; 176 if (x > viewRWorld) 177 break; 178 179 var w = pixWidth; 180 if (slice.duration > 0) { 181 w = Math.max(slice.duration, 0.000001); 182 if (w < pixWidth) 183 w = pixWidth; 184 } 185 186 var colorId = EventPresenter.getSliceColorId(slice); 187 var alpha = EventPresenter.getSliceAlpha(slice, async); 188 var lightAlpha = alpha * 0.70; 189 190 if (async && slice.isTopLevel) { 191 rect.setYandH(3, height - 3); 192 hadTopLevel = true; 193 } else { 194 rect.setYandH(0, height); 195 } 196 197 // If cpuDuration is available, draw rectangles proportional to the 198 // amount of cpu time taken. 199 if (!slice.cpuDuration) { 200 // No cpuDuration available, draw using only one alpha. 201 rect.fillRect(x, w, colorId, alpha); 202 continue; 203 } 204 205 var activeWidth = w * (slice.cpuDuration / slice.duration); 206 var waitingWidth = w - activeWidth; 207 208 // Check if we have enough screen space to draw the whole slice, with 209 // both color tones. 210 // 211 // Truncate the activeWidth to 0 if it is less than 'threshold' pixels. 212 if (activeWidth < SLICE_ACTIVE_WIDTH_DRAW_THRESHOLD * pixWidth) { 213 activeWidth = 0; 214 waitingWidth = w; 215 } 216 217 // Truncate the waitingWidth to 0 if it is less than 'threshold' pixels. 218 if (waitingWidth < SLICE_WAITING_WIDTH_DRAW_THRESHOLD * pixWidth) { 219 activeWidth = w; 220 waitingWidth = 0; 221 } 222 223 // We now draw the two rectangles making up the event slice. 224 // NOTE: The if statements are necessary for performance considerations. 225 // We do not want to force draws, if the width of the rectangle is 0. 226 // 227 // First draw the solid color, representing the 'active' part. 228 if (activeWidth > 0) { 229 rect.fillRect(x, activeWidth, colorId, alpha); 230 } 231 232 // Next draw the two toned 'idle' part. 233 // NOTE: Substracting pixWidth and drawing one extra pixel is done to 234 // prevent drawing artifacts. Without it, the two parts of the slice, 235 // ('active' and 'idle') may appear split apart. 236 if (waitingWidth > 0) { 237 // First draw the light toned top part. 238 rect.setYandH(0, lightRectHeight); 239 rect.fillRect(x + activeWidth - pixWidth, 240 waitingWidth + pixWidth, colorId, lightAlpha); 241 // Then the solid bottom half. 242 rect.setYandH(lightRectHeight, darkRectHeight); 243 rect.fillRect(x + activeWidth - pixWidth, 244 waitingWidth + pixWidth, colorId, alpha); 245 // Reset for the next slice. 246 rect.setYandH(0, height); 247 } 248 } 249 rect.flush(); 250 251 if (async && hadTopLevel) { 252 // Draw a top border over async slices in order to visually separate 253 // them from events above it. 254 // See https://github.com/google/trace-viewer/issues/725. 255 rect.setYandH(2, 1); 256 for (var i = lowSlice; i < slices.length; ++i) { 257 var slice = slices[i]; 258 var x = slice.start; 259 if (x > viewRWorld) 260 break; 261 262 if (!slice.isTopLevel) 263 continue; 264 265 var w = pixWidth; 266 if (slice.duration > 0) { 267 w = Math.max(slice.duration, 0.000001); 268 if (w < pixWidth) 269 w = pixWidth; 270 } 271 272 rect.fillRect(x, w, blackColorId, 0.7); 273 } 274 rect.flush(); 275 } 276 277 ctx.restore(); 278 } 279 280 /** 281 * Draw the provided instant slices as lines to the screen. 282 * 283 * Each of the elements in |slices| must provide the follow methods: 284 * * start 285 * * duration with value of 0. 286 * * colorId 287 * * selected 288 * 289 * @param {Context} ctx The canvas context. 290 * @param {TimelineDrawTransform} dt The draw transform. 291 * @param {float} viewLWorld The left most point of the world viewport. 292 * @param {float} viewRWorld The right most point of the world viewport. 293 * @param {float} viewHeight The height of the viewport. 294 * @param {Array} slices The slices to draw. 295 * @param {Numer} lineWidthInPixels The width of the lines. 296 */ 297 function drawInstantSlicesAsLines( 298 ctx, dt, viewLWorld, viewRWorld, viewHeight, slices, lineWidthInPixels) { 299 var pixelRatio = window.devicePixelRatio || 1; 300 var height = viewHeight * pixelRatio; 301 302 var pixWidth = dt.xViewVectorToWorld(1); 303 304 // Begin rendering in world space. 305 ctx.save(); 306 ctx.lineWidth = pixWidth * lineWidthInPixels * pixelRatio; 307 dt.applyTransformToCanvas(ctx); 308 ctx.beginPath(); 309 310 var lowSlice = tr.b.findLowIndexInSortedArray( 311 slices, 312 function(slice) { return slice.start; }, 313 viewLWorld); 314 315 for (var i = lowSlice; i < slices.length; ++i) { 316 var slice = slices[i]; 317 var x = slice.start; 318 if (x > viewRWorld) 319 break; 320 321 ctx.strokeStyle = EventPresenter.getInstantSliceColor(slice); 322 323 ctx.beginPath(); 324 ctx.moveTo(x, 0); 325 ctx.lineTo(x, height); 326 ctx.stroke(); 327 } 328 ctx.restore(); 329 } 330 331 /** 332 * Draws the labels for the given slices. 333 * 334 * The |slices| array must contain objects with the following API: 335 * * start 336 * * duration 337 * * title 338 * * didNotFinish (optional) 339 * 340 * @param {Context} ctx The graphics context. 341 * @param {TimelineDrawTransform} dt The draw transform. 342 * @param {float} viewLWorld The left most point of the world viewport. 343 * @param {float} viewRWorld The right most point of the world viewport. 344 * @param {Array} slices The slices to label. 345 * @param {bool} async Whether the slice labels are drawn with async style. 346 * @param {float} fontSize The font size. 347 * @param {float} yOffset The font offset. 348 */ 349 function drawLabels(ctx, dt, viewLWorld, viewRWorld, slices, async, 350 fontSize, yOffset) { 351 var pixelRatio = window.devicePixelRatio || 1; 352 var pixWidth = dt.xViewVectorToWorld(1); 353 354 ctx.save(); 355 356 ctx.textAlign = 'center'; 357 ctx.textBaseline = 'top'; 358 ctx.font = (fontSize * pixelRatio) + 'px sans-serif'; 359 360 if (async) 361 ctx.font = 'italic ' + ctx.font; 362 363 var cY = yOffset * pixelRatio; 364 365 var lowSlice = tr.b.findLowIndexInSortedArray( 366 slices, 367 function(slice) { return slice.start + slice.duration; }, 368 viewLWorld); 369 370 // Don't render text until until it is 20px wide 371 var quickDiscardThresshold = pixWidth * 20; 372 for (var i = lowSlice; i < slices.length; ++i) { 373 var slice = slices[i]; 374 if (slice.start > viewRWorld) 375 break; 376 377 if (slice.duration <= quickDiscardThresshold) 378 continue; 379 380 var title = slice.title + 381 (slice.didNotFinish ? ' (Did Not Finish)' : ''); 382 383 var drawnTitle = title; 384 var drawnWidth = elidedTitleCache.labelWidth(ctx, drawnTitle); 385 var fullLabelWidth = elidedTitleCache.labelWidthWorld( 386 ctx, drawnTitle, pixWidth); 387 if (SHOULD_ELIDE_TEXT && fullLabelWidth > slice.duration) { 388 var elidedValues = elidedTitleCache.get( 389 ctx, pixWidth, 390 drawnTitle, drawnWidth, 391 slice.duration); 392 drawnTitle = elidedValues.string; 393 drawnWidth = elidedValues.width; 394 } 395 396 if (drawnWidth * pixWidth < slice.duration) { 397 ctx.fillStyle = EventPresenter.getTextColor(slice); 398 var cX = dt.xWorldToView(slice.start + 0.5 * slice.duration); 399 ctx.fillText(drawnTitle, cX, cY, drawnWidth); 400 } 401 } 402 ctx.restore(); 403 } 404 405 return { 406 drawSlices: drawSlices, 407 drawInstantSlicesAsLines: drawInstantSlicesAsLines, 408 drawLabels: drawLabels, 409 410 drawLine: drawLine, 411 drawTriangle: drawTriangle, 412 drawArrow: drawArrow, 413 414 elidedTitleCache_: elidedTitleCache, 415 416 THIN_SLICE_HEIGHT: THIN_SLICE_HEIGHT 417 }; 418}); 419</script> 420