| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 | |
| 6 /** | |
| 7 * @fileoverview Renders an array of slices into the provided div, | |
| 8 * using a child canvas element. Uses a FastRectRenderer to draw only | |
| 9 * the visible slices. | |
| 10 */ | |
| 11 cr.define('tracing', function() { | |
| 12 | |
| 13 var pallette = tracing.getPallette(); | |
| 14 var highlightIdBoost = tracing.getPalletteHighlightIdBoost(); | |
| 15 | |
| 16 // TODO(jrg): possibly obsoleted with the elided string cache. | |
| 17 // Consider removing. | |
| 18 var textWidthMap = { }; | |
| 19 function quickMeasureText(ctx, text) { | |
| 20 var w = textWidthMap[text]; | |
| 21 if (!w) { | |
| 22 w = ctx.measureText(text).width; | |
| 23 textWidthMap[text] = w; | |
| 24 } | |
| 25 return w; | |
| 26 } | |
| 27 | |
| 28 /** | |
| 29 * Cache for elided strings. | |
| 30 * Moved from the ElidedTitleCache protoype to a "global" for speed | |
| 31 * (variable reference is 100x faster). | |
| 32 * key: String we wish to elide. | |
| 33 * value: Another dict whose key is width | |
| 34 * and value is an ElidedStringWidthPair. | |
| 35 */ | |
| 36 var elidedTitleCacheDict = {}; | |
| 37 | |
| 38 /** | |
| 39 * A generic track that contains other tracks as its children. | |
| 40 * @constructor | |
| 41 */ | |
| 42 var TimelineContainerTrack = cr.ui.define('div'); | |
| 43 TimelineContainerTrack.prototype = { | |
| 44 __proto__: HTMLDivElement.prototype, | |
| 45 | |
| 46 decorate: function() { | |
| 47 this.tracks_ = []; | |
| 48 }, | |
| 49 | |
| 50 detach: function() { | |
| 51 for (var i = 0; i < this.tracks_.length; i++) | |
| 52 this.tracks_[i].detach(); | |
| 53 }, | |
| 54 | |
| 55 get viewport() { | |
| 56 return this.viewport_; | |
| 57 }, | |
| 58 | |
| 59 set viewport(v) { | |
| 60 this.viewport_ = v; | |
| 61 for (var i = 0; i < this.tracks_.length; i++) | |
| 62 this.tracks_[i].viewport = v; | |
| 63 this.updateChildTracks_(); | |
| 64 }, | |
| 65 | |
| 66 get firstCanvas() { | |
| 67 if (this.tracks_.length) | |
| 68 return this.tracks_[0].firstCanvas; | |
| 69 return undefined; | |
| 70 }, | |
| 71 | |
| 72 /** | |
| 73 * Adds items intersecting a point to a selection. | |
| 74 * @param {number} wX X location to search at, in worldspace. | |
| 75 * @param {number} wY Y location to search at, in offset space. | |
| 76 * offset space. | |
| 77 * @param {TimelineSelection} selection Selection to which to add hits. | |
| 78 * @return {boolean} true if a slice was found, otherwise false. | |
| 79 */ | |
| 80 addIntersectingItemsToSelection: function(wX, wY, selection) { | |
| 81 for (var i = 0; i < this.tracks_.length; i++) { | |
| 82 var trackClientRect = this.tracks_[i].getBoundingClientRect(); | |
| 83 if (wY >= trackClientRect.top && wY < trackClientRect.bottom) | |
| 84 this.tracks_[i].addIntersectingItemsToSelection(wX, wY, selection); | |
| 85 } | |
| 86 return false; | |
| 87 }, | |
| 88 | |
| 89 /** | |
| 90 * Adds items intersecting the given range to a selection. | |
| 91 * @param {number} loWX Lower X bound of the interval to search, in | |
| 92 * worldspace. | |
| 93 * @param {number} hiWX Upper X bound of the interval to search, in | |
| 94 * worldspace. | |
| 95 * @param {number} loY Lower Y bound of the interval to search, in | |
| 96 * offset space. | |
| 97 * @param {number} hiY Upper Y bound of the interval to search, in | |
| 98 * offset space. | |
| 99 * @param {TimelineSelection} selection Selection to which to add hits. | |
| 100 */ | |
| 101 addIntersectingItemsInRangeToSelection: function( | |
| 102 loWX, hiWX, loY, hiY, selection) { | |
| 103 for (var i = 0; i < this.tracks_.length; i++) { | |
| 104 var trackClientRect = this.tracks_[i].getBoundingClientRect(); | |
| 105 var a = Math.max(loY, trackClientRect.top); | |
| 106 var b = Math.min(hiY, trackClientRect.bottom); | |
| 107 if (a <= b) | |
| 108 this.tracks_[i].addIntersectingItemsInRangeToSelection( | |
| 109 loWX, hiWX, loY, hiY, selection); | |
| 110 } | |
| 111 }, | |
| 112 | |
| 113 addAllObjectsMatchingFilterToSelection: function(filter, selection) { | |
| 114 for (var i = 0; i < this.tracks_.length; i++) | |
| 115 this.tracks_[i].addAllObjectsMatchingFilterToSelection( | |
| 116 filter, selection); | |
| 117 } | |
| 118 }; | |
| 119 | |
| 120 function addControlButtonElements(el, canCollapse) { | |
| 121 var closeEl = document.createElement('div'); | |
| 122 closeEl.classList.add('timeline-track-button'); | |
| 123 closeEl.classList.add('timeline-track-close-button'); | |
| 124 closeEl.textContent = String.fromCharCode(215); // × | |
| 125 closeEl.addEventListener('click', function() { | |
| 126 el.style.display = 'None'; | |
| 127 }); | |
| 128 el.appendChild(closeEl); | |
| 129 | |
| 130 if (canCollapse) { | |
| 131 var collapseEl = document.createElement('div'); | |
| 132 collapseEl.classList.add('timeline-track-button'); | |
| 133 collapseEl.classList.add('timeline-track-collapse-button'); | |
| 134 var minus = '\u2212'; // minus sign; | |
| 135 var plus = '\u002b'; // plus sign; | |
| 136 collapseEl.textContent = minus; | |
| 137 var collapsed = false; | |
| 138 collapseEl.addEventListener('click', function() { | |
| 139 collapsed = !collapsed; | |
| 140 el.collapsedDidChange(collapsed); | |
| 141 collapseEl.textContent = collapsed ? plus : minus; | |
| 142 }); | |
| 143 el.appendChild(collapseEl); | |
| 144 } | |
| 145 } | |
| 146 | |
| 147 /** | |
| 148 * Visualizes a TimelineThread using a series of of TimelineSliceTracks. | |
| 149 * @constructor | |
| 150 */ | |
| 151 var TimelineThreadTrack = cr.ui.define(TimelineContainerTrack); | |
| 152 TimelineThreadTrack.prototype = { | |
| 153 __proto__: TimelineContainerTrack.prototype, | |
| 154 | |
| 155 decorate: function() { | |
| 156 this.classList.add('timeline-thread-track'); | |
| 157 }, | |
| 158 | |
| 159 get thread() { | |
| 160 return this.thread_; | |
| 161 }, | |
| 162 | |
| 163 set thread(thread) { | |
| 164 this.thread_ = thread; | |
| 165 this.updateChildTracks_(); | |
| 166 }, | |
| 167 | |
| 168 get tooltip() { | |
| 169 return this.tooltip_; | |
| 170 }, | |
| 171 | |
| 172 set tooltip(value) { | |
| 173 this.tooltip_ = value; | |
| 174 this.updateChildTracks_(); | |
| 175 }, | |
| 176 | |
| 177 get heading() { | |
| 178 return this.heading_; | |
| 179 }, | |
| 180 | |
| 181 set heading(h) { | |
| 182 this.heading_ = h; | |
| 183 this.updateChildTracks_(); | |
| 184 }, | |
| 185 | |
| 186 get headingWidth() { | |
| 187 return this.headingWidth_; | |
| 188 }, | |
| 189 | |
| 190 set headingWidth(width) { | |
| 191 this.headingWidth_ = width; | |
| 192 this.updateChildTracks_(); | |
| 193 }, | |
| 194 | |
| 195 addTrack_: function(slices) { | |
| 196 var track = new TimelineSliceTrack(); | |
| 197 track.heading = ''; | |
| 198 track.slices = slices; | |
| 199 track.headingWidth = this.headingWidth_; | |
| 200 track.viewport = this.viewport_; | |
| 201 | |
| 202 this.tracks_.push(track); | |
| 203 this.appendChild(track); | |
| 204 return track; | |
| 205 }, | |
| 206 | |
| 207 updateChildTracks_: function() { | |
| 208 this.detach(); | |
| 209 this.textContent = ''; | |
| 210 this.tracks_ = []; | |
| 211 if (this.thread_) { | |
| 212 if (this.thread_.cpuSlices) { | |
| 213 var track = this.addTrack_(this.thread_.cpuSlices); | |
| 214 track.height = '4px'; | |
| 215 track.decorateHit = function(hit) { | |
| 216 hit.thread = this.thread_; | |
| 217 } | |
| 218 } | |
| 219 | |
| 220 if (this.thread_.asyncSlices.length) { | |
| 221 var subRows = this.thread_.asyncSlices.subRows; | |
| 222 for (var srI = 0; srI < subRows.length; srI++) { | |
| 223 var track = this.addTrack_(subRows[srI]); | |
| 224 track.decorateHit = function(hit) { | |
| 225 // TODO(simonjam): figure out how to associate subSlice hits back | |
| 226 // to their parent slice. | |
| 227 } | |
| 228 track.asyncStyle = true; | |
| 229 } | |
| 230 } | |
| 231 | |
| 232 for (var srI = 0; srI < this.thread_.subRows.length; srI++) { | |
| 233 var track = this.addTrack_(this.thread_.subRows[srI]); | |
| 234 track.decorateHit = function(hit) { | |
| 235 hit.thread = this.thread_; | |
| 236 } | |
| 237 } | |
| 238 | |
| 239 if (this.tracks_.length > 0) { | |
| 240 if (this.thread_.cpuSlices) { | |
| 241 this.tracks_[1].heading = this.heading_; | |
| 242 this.tracks_[1].tooltip = this.tooltip_; | |
| 243 } else { | |
| 244 this.tracks_[0].heading = this.heading_; | |
| 245 this.tracks_[0].tooltip = this.tooltip_; | |
| 246 } | |
| 247 } | |
| 248 } | |
| 249 addControlButtonElements(this, this.tracks_.length >= 4); | |
| 250 }, | |
| 251 | |
| 252 collapsedDidChange: function(collapsed) { | |
| 253 if (collapsed) { | |
| 254 var h = parseInt(this.tracks_[0].height); | |
| 255 for (var i = 0; i < this.tracks_.length; ++i) { | |
| 256 if (h > 2) { | |
| 257 this.tracks_[i].height = Math.floor(h) + 'px'; | |
| 258 } else { | |
| 259 this.tracks_[i].style.display = 'None'; | |
| 260 } | |
| 261 h = h * 0.5; | |
| 262 } | |
| 263 } else { | |
| 264 for (var i = 0; i < this.tracks_.length; ++i) { | |
| 265 this.tracks_[i].height = this.tracks_[0].height; | |
| 266 this.tracks_[i].style.display = ''; | |
| 267 } | |
| 268 } | |
| 269 } | |
| 270 }; | |
| 271 | |
| 272 /** | |
| 273 * Visualizes a TimelineCpu using a series of of TimelineSliceTracks. | |
| 274 * @constructor | |
| 275 */ | |
| 276 var TimelineCpuTrack = cr.ui.define(TimelineContainerTrack); | |
| 277 TimelineCpuTrack.prototype = { | |
| 278 __proto__: TimelineContainerTrack.prototype, | |
| 279 | |
| 280 decorate: function() { | |
| 281 this.classList.add('timeline-thread-track'); | |
| 282 }, | |
| 283 | |
| 284 get cpu() { | |
| 285 return this.cpu_; | |
| 286 }, | |
| 287 | |
| 288 set cpu(cpu) { | |
| 289 this.cpu_ = cpu; | |
| 290 this.updateChildTracks_(); | |
| 291 }, | |
| 292 | |
| 293 get tooltip() { | |
| 294 return this.tooltip_; | |
| 295 }, | |
| 296 | |
| 297 set tooltip(value) { | |
| 298 this.tooltip_ = value; | |
| 299 this.updateChildTracks_(); | |
| 300 }, | |
| 301 | |
| 302 get heading() { | |
| 303 return this.heading_; | |
| 304 }, | |
| 305 | |
| 306 set heading(h) { | |
| 307 this.heading_ = h; | |
| 308 this.updateChildTracks_(); | |
| 309 }, | |
| 310 | |
| 311 get headingWidth() { | |
| 312 return this.headingWidth_; | |
| 313 }, | |
| 314 | |
| 315 set headingWidth(width) { | |
| 316 this.headingWidth_ = width; | |
| 317 this.updateChildTracks_(); | |
| 318 }, | |
| 319 | |
| 320 updateChildTracks_: function() { | |
| 321 this.detach(); | |
| 322 this.textContent = ''; | |
| 323 this.tracks_ = []; | |
| 324 if (this.cpu_) { | |
| 325 var track = new TimelineSliceTrack(); | |
| 326 track.slices = this.cpu_.slices; | |
| 327 track.headingWidth = this.headingWidth_; | |
| 328 track.viewport = this.viewport_; | |
| 329 | |
| 330 this.tracks_.push(track); | |
| 331 this.appendChild(track); | |
| 332 | |
| 333 this.tracks_[0].heading = this.heading_; | |
| 334 this.tracks_[0].tooltip = this.tooltip_; | |
| 335 } | |
| 336 addControlButtonElements(this, false); | |
| 337 } | |
| 338 }; | |
| 339 | |
| 340 /** | |
| 341 * A canvas-based track constructed. Provides the basic heading and | |
| 342 * invalidation-managment infrastructure. Subclasses must implement drawing | |
| 343 * and picking code. | |
| 344 * @constructor | |
| 345 * @extends {HTMLDivElement} | |
| 346 */ | |
| 347 var CanvasBasedTrack = cr.ui.define('div'); | |
| 348 | |
| 349 CanvasBasedTrack.prototype = { | |
| 350 __proto__: HTMLDivElement.prototype, | |
| 351 | |
| 352 decorate: function() { | |
| 353 this.className = 'timeline-canvas-based-track'; | |
| 354 this.slices_ = null; | |
| 355 | |
| 356 this.headingDiv_ = document.createElement('div'); | |
| 357 this.headingDiv_.className = 'timeline-canvas-based-track-title'; | |
| 358 this.appendChild(this.headingDiv_); | |
| 359 | |
| 360 this.canvasContainer_ = document.createElement('div'); | |
| 361 this.canvasContainer_.className = | |
| 362 'timeline-canvas-based-track-canvas-container'; | |
| 363 this.appendChild(this.canvasContainer_); | |
| 364 this.canvas_ = document.createElement('canvas'); | |
| 365 this.canvas_.className = 'timeline-canvas-based-track-canvas'; | |
| 366 this.canvasContainer_.appendChild(this.canvas_); | |
| 367 | |
| 368 this.ctx_ = this.canvas_.getContext('2d'); | |
| 369 }, | |
| 370 | |
| 371 detach: function() { | |
| 372 if (this.viewport_) | |
| 373 this.viewport_.removeEventListener('change', | |
| 374 this.viewportChangeBoundToThis_); | |
| 375 }, | |
| 376 | |
| 377 set headingWidth(width) { | |
| 378 this.headingDiv_.style.width = width; | |
| 379 }, | |
| 380 | |
| 381 get heading() { | |
| 382 return this.headingDiv_.textContent; | |
| 383 }, | |
| 384 | |
| 385 set heading(text) { | |
| 386 this.headingDiv_.textContent = text; | |
| 387 }, | |
| 388 | |
| 389 set tooltip(text) { | |
| 390 this.headingDiv_.title = text; | |
| 391 }, | |
| 392 | |
| 393 get viewport() { | |
| 394 return this.viewport_; | |
| 395 }, | |
| 396 | |
| 397 set viewport(v) { | |
| 398 this.viewport_ = v; | |
| 399 if (this.viewport_) | |
| 400 this.viewport_.removeEventListener('change', | |
| 401 this.viewportChangeBoundToThis_); | |
| 402 this.viewport_ = v; | |
| 403 if (this.viewport_) { | |
| 404 this.viewportChangeBoundToThis_ = this.viewportChange_.bind(this); | |
| 405 this.viewport_.addEventListener('change', | |
| 406 this.viewportChangeBoundToThis_); | |
| 407 } | |
| 408 this.invalidate(); | |
| 409 }, | |
| 410 | |
| 411 viewportChange_: function() { | |
| 412 this.invalidate(); | |
| 413 }, | |
| 414 | |
| 415 invalidate: function() { | |
| 416 if (this.rafPending_) | |
| 417 return; | |
| 418 webkitRequestAnimationFrame(function() { | |
| 419 this.rafPending_ = false; | |
| 420 if (!this.viewport_) | |
| 421 return; | |
| 422 | |
| 423 var style = window.getComputedStyle(this.canvasContainer_); | |
| 424 var style_width = parseInt(style.width); | |
| 425 var style_height = parseInt(style.height); | |
| 426 if (this.canvas_.width != style_width) | |
| 427 this.canvas_.width = style_width; | |
| 428 if (this.canvas_.height != style_height) | |
| 429 this.canvas_.height = style_height; | |
| 430 | |
| 431 this.redraw(); | |
| 432 }.bind(this), this); | |
| 433 this.rafPending_ = true; | |
| 434 }, | |
| 435 | |
| 436 get firstCanvas() { | |
| 437 return this.canvas_; | |
| 438 } | |
| 439 | |
| 440 }; | |
| 441 | |
| 442 /** | |
| 443 * A pair representing an elided string and world-coordinate width | |
| 444 * to draw it. | |
| 445 * @constructor | |
| 446 */ | |
| 447 function ElidedStringWidthPair(string, width) { | |
| 448 this.string = string; | |
| 449 this.width = width; | |
| 450 } | |
| 451 | |
| 452 /** | |
| 453 * A cache for elided strings. | |
| 454 * @constructor | |
| 455 */ | |
| 456 function ElidedTitleCache() { | |
| 457 } | |
| 458 | |
| 459 ElidedTitleCache.prototype = { | |
| 460 /** | |
| 461 * Return elided text. | |
| 462 * @param {track} A timeline slice track or other object that defines | |
| 463 * functions labelWidth() and labelWidthWorld(). | |
| 464 * @param {pixWidth} Pixel width. | |
| 465 * @param {title} Original title text. | |
| 466 * @param {width} Drawn width in world coords. | |
| 467 * @param {sliceDuration} Where the title must fit (in world coords). | |
| 468 * @return {ElidedStringWidthPair} Elided string and width. | |
| 469 */ | |
| 470 get: function(track, pixWidth, title, width, sliceDuration) { | |
| 471 var elidedDict = elidedTitleCacheDict[title]; | |
| 472 if (!elidedDict) { | |
| 473 elidedDict = {}; | |
| 474 elidedTitleCacheDict[title] = elidedDict; | |
| 475 } | |
| 476 var elidedDictForPixWidth = elidedDict[pixWidth]; | |
| 477 if (!elidedDictForPixWidth) { | |
| 478 elidedDict[pixWidth] = {}; | |
| 479 elidedDictForPixWidth = elidedDict[pixWidth]; | |
| 480 } | |
| 481 var stringWidthPair = elidedDictForPixWidth[sliceDuration]; | |
| 482 if (stringWidthPair === undefined) { | |
| 483 var newtitle = title; | |
| 484 var elided = false; | |
| 485 while (track.labelWidthWorld(newtitle, pixWidth) > sliceDuration) { | |
| 486 newtitle = newtitle.substring(0, newtitle.length * 0.75); | |
| 487 elided = true; | |
| 488 } | |
| 489 if (elided && newtitle.length > 3) | |
| 490 newtitle = newtitle.substring(0, newtitle.length - 3) + '...'; | |
| 491 stringWidthPair = new ElidedStringWidthPair( | |
| 492 newtitle, | |
| 493 track.labelWidth(newtitle)); | |
| 494 elidedDictForPixWidth[sliceDuration] = stringWidthPair; | |
| 495 } | |
| 496 return stringWidthPair; | |
| 497 } | |
| 498 }; | |
| 499 | |
| 500 /** | |
| 501 * A track that displays an array of TimelineSlice objects. | |
| 502 * @constructor | |
| 503 * @extends {CanvasBasedTrack} | |
| 504 */ | |
| 505 | |
| 506 var TimelineSliceTrack = cr.ui.define(CanvasBasedTrack); | |
| 507 | |
| 508 TimelineSliceTrack.prototype = { | |
| 509 | |
| 510 __proto__: CanvasBasedTrack.prototype, | |
| 511 | |
| 512 /** | |
| 513 * Should we elide text on trace labels? | |
| 514 * Without eliding, text that is too wide isn't drawn at all. | |
| 515 * Disable if you feel this causes a performance problem. | |
| 516 * This is a default value that can be overridden in tracks for testing. | |
| 517 * @const | |
| 518 */ | |
| 519 SHOULD_ELIDE_TEXT: true, | |
| 520 | |
| 521 decorate: function() { | |
| 522 this.classList.add('timeline-slice-track'); | |
| 523 this.elidedTitleCache = new ElidedTitleCache(); | |
| 524 this.asyncStyle_ = false; | |
| 525 }, | |
| 526 | |
| 527 /** | |
| 528 * Called by all the addToSelection functions on the created selection | |
| 529 * hit objects. Override this function on parent classes to add | |
| 530 * context-specific information to the hit. | |
| 531 */ | |
| 532 decorateHit: function(hit) { | |
| 533 }, | |
| 534 | |
| 535 get asyncStyle() { | |
| 536 return this.asyncStyle_; | |
| 537 }, | |
| 538 | |
| 539 set asyncStyle(v) { | |
| 540 this.asyncStyle_ = !!v; | |
| 541 this.invalidate(); | |
| 542 }, | |
| 543 | |
| 544 get slices() { | |
| 545 return this.slices_; | |
| 546 }, | |
| 547 | |
| 548 set slices(slices) { | |
| 549 this.slices_ = slices; | |
| 550 this.invalidate(); | |
| 551 }, | |
| 552 | |
| 553 get height() { | |
| 554 return window.getComputedStyle(this).height; | |
| 555 }, | |
| 556 | |
| 557 set height(height) { | |
| 558 this.style.height = height; | |
| 559 this.invalidate(); | |
| 560 }, | |
| 561 | |
| 562 labelWidth: function(title) { | |
| 563 return quickMeasureText(this.ctx_, title) + 2; | |
| 564 }, | |
| 565 | |
| 566 labelWidthWorld: function(title, pixWidth) { | |
| 567 return this.labelWidth(title) * pixWidth; | |
| 568 }, | |
| 569 | |
| 570 redraw: function() { | |
| 571 var ctx = this.ctx_; | |
| 572 var canvasW = this.canvas_.width; | |
| 573 var canvasH = this.canvas_.height; | |
| 574 | |
| 575 ctx.clearRect(0, 0, canvasW, canvasH); | |
| 576 | |
| 577 // Culling parameters. | |
| 578 var vp = this.viewport_; | |
| 579 var pixWidth = vp.xViewVectorToWorld(1); | |
| 580 var viewLWorld = vp.xViewToWorld(0); | |
| 581 var viewRWorld = vp.xViewToWorld(canvasW); | |
| 582 | |
| 583 // Draw grid without a transform because the scale | |
| 584 // affects line width. | |
| 585 if (vp.gridEnabled) { | |
| 586 var x = vp.gridTimebase; | |
| 587 ctx.beginPath(); | |
| 588 while (x < viewRWorld) { | |
| 589 if (x >= viewLWorld) { | |
| 590 // Do conversion to viewspace here rather than on | |
| 591 // x to avoid precision issues. | |
| 592 var vx = vp.xWorldToView(x); | |
| 593 ctx.moveTo(vx, 0); | |
| 594 ctx.lineTo(vx, canvasH); | |
| 595 } | |
| 596 x += vp.gridStep; | |
| 597 } | |
| 598 ctx.strokeStyle = 'rgba(255,0,0,0.25)'; | |
| 599 ctx.stroke(); | |
| 600 } | |
| 601 | |
| 602 // Begin rendering in world space. | |
| 603 ctx.save(); | |
| 604 vp.applyTransformToCanavs(ctx); | |
| 605 | |
| 606 // Slices. | |
| 607 if (this.asyncStyle_) | |
| 608 ctx.globalAlpha = 0.25; | |
| 609 var tr = new tracing.FastRectRenderer(ctx, viewLWorld, 2 * pixWidth, | |
| 610 2 * pixWidth, viewRWorld, pallette); | |
| 611 tr.setYandH(0, canvasH); | |
| 612 var slices = this.slices_; | |
| 613 for (var i = 0; i < slices.length; ++i) { | |
| 614 var slice = slices[i]; | |
| 615 var x = slice.start; | |
| 616 // Less than 0.001 causes short events to disappear when zoomed in. | |
| 617 var w = Math.max(slice.duration, 0.001); | |
| 618 var colorId = slice.selected ? | |
| 619 slice.colorId + highlightIdBoost : | |
| 620 slice.colorId; | |
| 621 | |
| 622 if (w < pixWidth) | |
| 623 w = pixWidth; | |
| 624 if (slice.duration > 0) { | |
| 625 tr.fillRect(x, w, colorId); | |
| 626 } else { | |
| 627 // Instant: draw a triangle. If zoomed too far, collapse | |
| 628 // into the FastRectRenderer. | |
| 629 if (pixWidth > 0.001) { | |
| 630 tr.fillRect(x, pixWidth, colorId); | |
| 631 } else { | |
| 632 ctx.fillStyle = pallette[colorId]; | |
| 633 ctx.beginPath(); | |
| 634 ctx.moveTo(x - (4 * pixWidth), canvasH); | |
| 635 ctx.lineTo(x, 0); | |
| 636 ctx.lineTo(x + (4 * pixWidth), canvasH); | |
| 637 ctx.closePath(); | |
| 638 ctx.fill(); | |
| 639 } | |
| 640 } | |
| 641 } | |
| 642 tr.flush(); | |
| 643 ctx.restore(); | |
| 644 | |
| 645 // Labels. | |
| 646 if (canvasH > 8) { | |
| 647 ctx.textAlign = 'center'; | |
| 648 ctx.textBaseline = 'top'; | |
| 649 ctx.font = '10px sans-serif'; | |
| 650 ctx.strokeStyle = 'rgb(0,0,0)'; | |
| 651 ctx.fillStyle = 'rgb(0,0,0)'; | |
| 652 // Don't render text until until it is 20px wide | |
| 653 var quickDiscardThresshold = pixWidth * 20; | |
| 654 var shouldElide = this.SHOULD_ELIDE_TEXT; | |
| 655 for (var i = 0; i < slices.length; ++i) { | |
| 656 var slice = slices[i]; | |
| 657 if (slice.duration > quickDiscardThresshold) { | |
| 658 var title = slice.title; | |
| 659 if (slice.didNotFinish) { | |
| 660 title += ' (Did Not Finish)'; | |
| 661 } | |
| 662 var drawnTitle = title; | |
| 663 var drawnWidth = this.labelWidth(drawnTitle); | |
| 664 if (shouldElide && | |
| 665 this.labelWidthWorld(drawnTitle, pixWidth) > slice.duration) { | |
| 666 var elidedValues = this.elidedTitleCache.get( | |
| 667 this, pixWidth, | |
| 668 drawnTitle, drawnWidth, | |
| 669 slice.duration); | |
| 670 drawnTitle = elidedValues.string; | |
| 671 drawnWidth = elidedValues.width; | |
| 672 } | |
| 673 if (drawnWidth * pixWidth < slice.duration) { | |
| 674 var cX = vp.xWorldToView(slice.start + 0.5 * slice.duration); | |
| 675 ctx.fillText(drawnTitle, cX, 2.5, drawnWidth); | |
| 676 } | |
| 677 } | |
| 678 } | |
| 679 } | |
| 680 }, | |
| 681 | |
| 682 /** | |
| 683 * Finds slices intersecting the given interval. | |
| 684 * @param {number} wX X location to search at, in worldspace. | |
| 685 * @param {number} wY Y location to search at, in offset space. | |
| 686 * offset space. | |
| 687 * @param {TimelineSelection} selection Selection to which to add hits. | |
| 688 * @return {boolean} true if a slice was found, otherwise false. | |
| 689 */ | |
| 690 addIntersectingItemsToSelection: function(wX, wY, selection) { | |
| 691 var clientRect = this.getBoundingClientRect(); | |
| 692 if (wY < clientRect.top || wY >= clientRect.bottom) | |
| 693 return false; | |
| 694 var x = tracing.findLowIndexInSortedIntervals(this.slices_, | |
| 695 function(x) { return x.start; }, | |
| 696 function(x) { return x.duration; }, | |
| 697 wX); | |
| 698 if (x >= 0 && x < this.slices_.length) { | |
| 699 var hit = selection.addSlice(this, this.slices_[x]); | |
| 700 this.decorateHit(hit); | |
| 701 return true; | |
| 702 } | |
| 703 return false; | |
| 704 }, | |
| 705 | |
| 706 /** | |
| 707 * Adds items intersecting the given range to a selection. | |
| 708 * @param {number} loWX Lower X bound of the interval to search, in | |
| 709 * worldspace. | |
| 710 * @param {number} hiWX Upper X bound of the interval to search, in | |
| 711 * worldspace. | |
| 712 * @param {number} loY Lower Y bound of the interval to search, in | |
| 713 * offset space. | |
| 714 * @param {number} hiY Upper Y bound of the interval to search, in | |
| 715 * offset space. | |
| 716 * @param {TimelineSelection} selection Selection to which to add hits. | |
| 717 */ | |
| 718 addIntersectingItemsInRangeToSelection: function( | |
| 719 loWX, hiWX, loY, hiY, selection) { | |
| 720 var clientRect = this.getBoundingClientRect(); | |
| 721 var a = Math.max(loY, clientRect.top); | |
| 722 var b = Math.min(hiY, clientRect.bottom); | |
| 723 if (a > b) | |
| 724 return; | |
| 725 | |
| 726 var that = this; | |
| 727 function onPickHit(slice) { | |
| 728 var hit = selection.addSlice(that, slice); | |
| 729 that.decorateHit(hit); | |
| 730 } | |
| 731 tracing.iterateOverIntersectingIntervals(this.slices_, | |
| 732 function(x) { return x.start; }, | |
| 733 function(x) { return x.duration; }, | |
| 734 loWX, hiWX, | |
| 735 onPickHit); | |
| 736 }, | |
| 737 | |
| 738 /** | |
| 739 * Find the index for the given slice. | |
| 740 * @return {index} Index of the given slice, or undefined. | |
| 741 * @private | |
| 742 */ | |
| 743 indexOfSlice_: function(slice) { | |
| 744 var index = tracing.findLowIndexInSortedArray(this.slices_, | |
| 745 function(x) { return x.start; }, | |
| 746 slice.start); | |
| 747 while (index < this.slices_.length && | |
| 748 slice.start == this.slices_[index].start && | |
| 749 slice.colorId != this.slices_[index].colorId) { | |
| 750 index++; | |
| 751 } | |
| 752 return index < this.slices_.length ? index : undefined; | |
| 753 }, | |
| 754 | |
| 755 /** | |
| 756 * Add the item to the left or right of the provided hit, if any, to the | |
| 757 * selection. | |
| 758 * @param {slice} The current slice. | |
| 759 * @param {Number} offset Number of slices away from the hit to look. | |
| 760 * @param {TimelineSelection} selection The selection to add a hit to, | |
| 761 * if found. | |
| 762 * @return {boolean} Whether a hit was found. | |
| 763 * @private | |
| 764 */ | |
| 765 addItemNearToProvidedHitToSelection: function(hit, offset, selection) { | |
| 766 if (!hit.slice) | |
| 767 return false; | |
| 768 | |
| 769 var index = this.indexOfSlice_(hit.slice); | |
| 770 if (index === undefined) | |
| 771 return false; | |
| 772 | |
| 773 var newIndex = index + offset; | |
| 774 if (newIndex < 0 || newIndex >= this.slices_.length) | |
| 775 return false; | |
| 776 | |
| 777 var hit = selection.addSlice(this, this.slices_[newIndex]); | |
| 778 this.decorateHit(hit); | |
| 779 return true; | |
| 780 }, | |
| 781 | |
| 782 addAllObjectsMatchingFilterToSelection: function(filter, selection) { | |
| 783 for (var i = 0; i < this.slices_.length; ++i) { | |
| 784 if (filter.matchSlice(this.slices_[i])) { | |
| 785 var hit = selection.addSlice(this, this.slices_[i]); | |
| 786 this.decorateHit(hit); | |
| 787 } | |
| 788 } | |
| 789 } | |
| 790 }; | |
| 791 | |
| 792 /** | |
| 793 * A track that displays the viewport size and scale. | |
| 794 * @constructor | |
| 795 * @extends {CanvasBasedTrack} | |
| 796 */ | |
| 797 | |
| 798 var TimelineViewportTrack = cr.ui.define(CanvasBasedTrack); | |
| 799 | |
| 800 var logOf10 = Math.log(10); | |
| 801 function log10(x) { | |
| 802 return Math.log(x) / logOf10; | |
| 803 } | |
| 804 | |
| 805 TimelineViewportTrack.prototype = { | |
| 806 | |
| 807 __proto__: CanvasBasedTrack.prototype, | |
| 808 | |
| 809 decorate: function() { | |
| 810 this.classList.add('timeline-viewport-track'); | |
| 811 this.strings_secs_ = []; | |
| 812 this.strings_msecs_ = []; | |
| 813 }, | |
| 814 | |
| 815 redraw: function() { | |
| 816 var ctx = this.ctx_; | |
| 817 var canvasW = this.canvas_.width; | |
| 818 var canvasH = this.canvas_.height; | |
| 819 | |
| 820 ctx.clearRect(0, 0, canvasW, canvasH); | |
| 821 | |
| 822 // Culling parametrs. | |
| 823 var vp = this.viewport_; | |
| 824 var pixWidth = vp.xViewVectorToWorld(1); | |
| 825 var viewLWorld = vp.xViewToWorld(0); | |
| 826 var viewRWorld = vp.xViewToWorld(canvasW); | |
| 827 | |
| 828 var idealMajorMarkDistancePix = 150; | |
| 829 var idealMajorMarkDistanceWorld = | |
| 830 vp.xViewVectorToWorld(idealMajorMarkDistancePix); | |
| 831 | |
| 832 // The conservative guess is the nearest enclosing 0.1, 1, 10, 100, etc | |
| 833 var conservativeGuess = | |
| 834 Math.pow(10, Math.ceil(log10(idealMajorMarkDistanceWorld))); | |
| 835 | |
| 836 // Once we have a conservative guess, consider things that evenly add up | |
| 837 // to the conservative guess, e.g. 0.5, 0.2, 0.1 Pick the one that still | |
| 838 // exceeds the ideal mark distance. | |
| 839 var divisors = [10, 5, 2, 1]; | |
| 840 for (var i = 0; i < divisors.length; ++i) { | |
| 841 var tightenedGuess = conservativeGuess / divisors[i]; | |
| 842 if (vp.xWorldVectorToView(tightenedGuess) < idealMajorMarkDistancePix) | |
| 843 continue; | |
| 844 majorMarkDistanceWorld = conservativeGuess / divisors[i - 1]; | |
| 845 break; | |
| 846 } | |
| 847 var tickLabels = undefined; | |
| 848 if (majorMarkDistanceWorld < 100) { | |
| 849 unit = 'ms'; | |
| 850 unitDivisor = 1; | |
| 851 tickLabels = this.strings_msecs_; | |
| 852 } else { | |
| 853 unit = 's'; | |
| 854 unitDivisor = 1000; | |
| 855 tickLabels = this.strings_secs_; | |
| 856 } | |
| 857 | |
| 858 var numTicksPerMajor = 5; | |
| 859 var minorMarkDistanceWorld = majorMarkDistanceWorld / numTicksPerMajor; | |
| 860 var minorMarkDistancePx = vp.xWorldVectorToView(minorMarkDistanceWorld); | |
| 861 | |
| 862 var firstMajorMark = | |
| 863 Math.floor(viewLWorld / majorMarkDistanceWorld) * | |
| 864 majorMarkDistanceWorld; | |
| 865 | |
| 866 var minorTickH = Math.floor(canvasH * 0.25); | |
| 867 | |
| 868 ctx.fillStyle = 'rgb(0, 0, 0)'; | |
| 869 ctx.strokeStyle = 'rgb(0, 0, 0)'; | |
| 870 ctx.textAlign = 'left'; | |
| 871 ctx.textBaseline = 'top'; | |
| 872 ctx.font = '9px sans-serif'; | |
| 873 | |
| 874 // Each iteration of this loop draws one major mark | |
| 875 // and numTicksPerMajor minor ticks. | |
| 876 // | |
| 877 // Rendering can't be done in world space because canvas transforms | |
| 878 // affect line width. So, do the conversions manually. | |
| 879 for (var curX = firstMajorMark; | |
| 880 curX < viewRWorld; | |
| 881 curX += majorMarkDistanceWorld) { | |
| 882 | |
| 883 var curXView = Math.floor(vp.xWorldToView(curX)); | |
| 884 | |
| 885 var unitValue = curX / unitDivisor; | |
| 886 var roundedUnitValue = Math.floor(unitValue * 100000) / 100000; | |
| 887 if (!tickLabels[roundedUnitValue]) | |
| 888 tickLabels[roundedUnitValue] = roundedUnitValue + ' ' + unit; | |
| 889 ctx.fillText(tickLabels[roundedUnitValue], curXView + 2, 0); | |
| 890 ctx.beginPath(); | |
| 891 | |
| 892 // Major mark | |
| 893 ctx.moveTo(curXView, 0); | |
| 894 ctx.lineTo(curXView, canvasW); | |
| 895 | |
| 896 // Minor marks | |
| 897 for (var i = 1; i < numTicksPerMajor; ++i) { | |
| 898 var xView = Math.floor(curXView + minorMarkDistancePx * i); | |
| 899 ctx.moveTo(xView, canvasH - minorTickH); | |
| 900 ctx.lineTo(xView, canvasH); | |
| 901 } | |
| 902 | |
| 903 ctx.stroke(); | |
| 904 } | |
| 905 }, | |
| 906 | |
| 907 /** | |
| 908 * Adds items intersecting a point to a selection. | |
| 909 * @param {number} wX X location to search at, in worldspace. | |
| 910 * @param {number} wY Y location to search at, in offset space. | |
| 911 * offset space. | |
| 912 * @param {TimelineSelection} selection Selection to which to add hits. | |
| 913 * @return {boolean} true if a slice was found, otherwise false. | |
| 914 */ | |
| 915 addIntersectingItemsToSelection: function(wX, wY, selection) { | |
| 916 // Does nothing. There's nothing interesting to pick on the viewport | |
| 917 // track. | |
| 918 }, | |
| 919 | |
| 920 /** | |
| 921 * Adds items intersecting the given range to a selection. | |
| 922 * @param {number} loWX Lower X bound of the interval to search, in | |
| 923 * worldspace. | |
| 924 * @param {number} hiWX Upper X bound of the interval to search, in | |
| 925 * worldspace. | |
| 926 * @param {number} loY Lower Y bound of the interval to search, in | |
| 927 * offset space. | |
| 928 * @param {number} hiY Upper Y bound of the interval to search, in | |
| 929 * offset space. | |
| 930 * @param {TimelineSelection} selection Selection to which to add hits. | |
| 931 */ | |
| 932 addIntersectingItemsInRangeToSelection: function( | |
| 933 loWX, hiWX, loY, hiY, selection) { | |
| 934 // Does nothing. There's nothing interesting to pick on the viewport | |
| 935 // track. | |
| 936 }, | |
| 937 | |
| 938 addAllObjectsMatchingFilterToSelection: function(filter, selection) { | |
| 939 } | |
| 940 | |
| 941 }; | |
| 942 | |
| 943 /** | |
| 944 * A track that displays a TimelineCounter object. | |
| 945 * @constructor | |
| 946 * @extends {CanvasBasedTrack} | |
| 947 */ | |
| 948 | |
| 949 var TimelineCounterTrack = cr.ui.define(CanvasBasedTrack); | |
| 950 | |
| 951 TimelineCounterTrack.prototype = { | |
| 952 | |
| 953 __proto__: CanvasBasedTrack.prototype, | |
| 954 | |
| 955 decorate: function() { | |
| 956 this.classList.add('timeline-counter-track'); | |
| 957 addControlButtonElements(this, false); | |
| 958 this.selectedSamples_ = {}; | |
| 959 }, | |
| 960 | |
| 961 /** | |
| 962 * Called by all the addToSelection functions on the created selection | |
| 963 * hit objects. Override this function on parent classes to add | |
| 964 * context-specific information to the hit. | |
| 965 */ | |
| 966 decorateHit: function(hit) { | |
| 967 }, | |
| 968 | |
| 969 get counter() { | |
| 970 return this.counter_; | |
| 971 }, | |
| 972 | |
| 973 set counter(counter) { | |
| 974 this.counter_ = counter; | |
| 975 this.invalidate(); | |
| 976 }, | |
| 977 | |
| 978 /** | |
| 979 * @return {Object} A sparce, mutable map from sample index to bool. Samples | |
| 980 * indices the map that are true are drawn as selected. Callers that mutate | |
| 981 * the map must manually call invalidate on the track to trigger a redraw. | |
| 982 */ | |
| 983 get selectedSamples() { | |
| 984 return this.selectedSamples_; | |
| 985 }, | |
| 986 | |
| 987 redraw: function() { | |
| 988 var ctr = this.counter_; | |
| 989 var ctx = this.ctx_; | |
| 990 var canvasW = this.canvas_.width; | |
| 991 var canvasH = this.canvas_.height; | |
| 992 | |
| 993 ctx.clearRect(0, 0, canvasW, canvasH); | |
| 994 | |
| 995 // Culling parametrs. | |
| 996 var vp = this.viewport_; | |
| 997 var pixWidth = vp.xViewVectorToWorld(1); | |
| 998 var viewLWorld = vp.xViewToWorld(0); | |
| 999 var viewRWorld = vp.xViewToWorld(canvasW); | |
| 1000 | |
| 1001 // Drop sampels that are less than skipDistancePix apart. | |
| 1002 var skipDistancePix = 1; | |
| 1003 var skipDistanceWorld = vp.xViewVectorToWorld(skipDistancePix); | |
| 1004 | |
| 1005 // Begin rendering in world space. | |
| 1006 ctx.save(); | |
| 1007 vp.applyTransformToCanavs(ctx); | |
| 1008 | |
| 1009 // Figure out where drawing should begin. | |
| 1010 var numSeries = ctr.numSeries; | |
| 1011 var numSamples = ctr.numSamples; | |
| 1012 var startIndex = tracing.findLowIndexInSortedArray(ctr.timestamps, | |
| 1013 function() { | |
| 1014 }, | |
| 1015 viewLWorld); | |
| 1016 | |
| 1017 // Draw indices one by one until we fall off the viewRWorld. | |
| 1018 var yScale = canvasH / ctr.maxTotal; | |
| 1019 for (var seriesIndex = ctr.numSeries - 1; | |
| 1020 seriesIndex >= 0; seriesIndex--) { | |
| 1021 var colorId = ctr.seriesColors[seriesIndex]; | |
| 1022 ctx.fillStyle = pallette[colorId]; | |
| 1023 ctx.beginPath(); | |
| 1024 | |
| 1025 // Set iLast and xLast such that the first sample we draw is the | |
| 1026 // startIndex sample. | |
| 1027 var iLast = startIndex - 1; | |
| 1028 var xLast = iLast >= 0 ? ctr.timestamps[iLast] - skipDistanceWorld : -1; | |
| 1029 var yLastView = canvasH; | |
| 1030 | |
| 1031 // Iterate over samples from iLast onward until we either fall off the | |
| 1032 // viewRWorld or we run out of samples. To avoid drawing too much, after | |
| 1033 // drawing a sample at xLast, skip subsequent samples that are less than | |
| 1034 // skipDistanceWorld from xLast. | |
| 1035 var hasMoved = false; | |
| 1036 while (true) { | |
| 1037 var i = iLast + 1; | |
| 1038 if (i >= numSamples) { | |
| 1039 ctx.lineTo(xLast, yLastView); | |
| 1040 ctx.lineTo(xLast + 8 * pixWidth, yLastView); | |
| 1041 ctx.lineTo(xLast + 8 * pixWidth, canvasH); | |
| 1042 break; | |
| 1043 } | |
| 1044 | |
| 1045 var x = ctr.timestamps[i]; | |
| 1046 | |
| 1047 var y = ctr.totals[i * numSeries + seriesIndex]; | |
| 1048 var yView = canvasH - (yScale * y); | |
| 1049 | |
| 1050 if (x > viewRWorld) { | |
| 1051 ctx.lineTo(x, yLastView); | |
| 1052 ctx.lineTo(x, canvasH); | |
| 1053 break; | |
| 1054 } | |
| 1055 | |
| 1056 if (x - xLast < skipDistanceWorld) { | |
| 1057 iLast = i; | |
| 1058 continue; | |
| 1059 } | |
| 1060 | |
| 1061 if (!hasMoved) { | |
| 1062 ctx.moveTo(viewLWorld, canvasH); | |
| 1063 hasMoved = true; | |
| 1064 } | |
| 1065 ctx.lineTo(x, yLastView); | |
| 1066 ctx.lineTo(x, yView); | |
| 1067 iLast = i; | |
| 1068 xLast = x; | |
| 1069 yLastView = yView; | |
| 1070 } | |
| 1071 ctx.closePath(); | |
| 1072 ctx.fill(); | |
| 1073 } | |
| 1074 ctx.fillStyle = 'rgba(255, 0, 0, 1)'; | |
| 1075 for (var i in this.selectedSamples_) { | |
| 1076 if (!this.selectedSamples_[i]) | |
| 1077 continue; | |
| 1078 | |
| 1079 var x = ctr.timestamps[i]; | |
| 1080 for (var seriesIndex = ctr.numSeries - 1; | |
| 1081 seriesIndex >= 0; seriesIndex--) { | |
| 1082 var y = ctr.totals[i * numSeries + seriesIndex]; | |
| 1083 var yView = canvasH - (yScale * y); | |
| 1084 ctx.fillRect(x - pixWidth, yView - 1, 3 * pixWidth, 3); | |
| 1085 } | |
| 1086 } | |
| 1087 ctx.restore(); | |
| 1088 }, | |
| 1089 | |
| 1090 /** | |
| 1091 * Adds items intersecting a point to a selection. | |
| 1092 * @param {number} wX X location to search at, in worldspace. | |
| 1093 * @param {number} wY Y location to search at, in offset space. | |
| 1094 * offset space. | |
| 1095 * @param {TimelineSelection} selection Selection to which to add hits. | |
| 1096 * @return {boolean} true if a slice was found, otherwise false. | |
| 1097 */ | |
| 1098 addIntersectingItemsToSelection: function(wX, wY, selection) { | |
| 1099 var clientRect = this.getBoundingClientRect(); | |
| 1100 if (wY < clientRect.top || wY >= clientRect.bottom) | |
| 1101 return false; | |
| 1102 var ctr = this.counter_; | |
| 1103 if (wX < this.counter_.timestamps[0]) | |
| 1104 return false; | |
| 1105 var i = tracing.findLowIndexInSortedArray(ctr.timestamps, | |
| 1106 function(x) { return x; }, | |
| 1107 wX); | |
| 1108 if (i < 0 || i >= ctr.timestamps.length) | |
| 1109 return false; | |
| 1110 | |
| 1111 // Sample i is going to either be exactly at wX or slightly above it, | |
| 1112 // E.g. asking for 7.5 in [7,8] gives i=1. So bump i back by 1 if needed. | |
| 1113 if (i > 0 && wX > this.counter_.timestamps[i - 1]) | |
| 1114 i--; | |
| 1115 | |
| 1116 // Some preliminaries. | |
| 1117 var canvasH = this.getBoundingClientRect().height; | |
| 1118 var yScale = canvasH / ctr.maxTotal; | |
| 1119 | |
| 1120 /* | |
| 1121 // Figure out which sample we hit | |
| 1122 var seriesIndexHit; | |
| 1123 for (var seriesIndex = 0; seriesIndex < ctr.numSeries; seriesIndex++) { | |
| 1124 var y = ctr.totals[i * ctr.numSeries + seriesIndex]; | |
| 1125 var yView = canvasH - (yScale * y) + clientRect.top; | |
| 1126 if (wY >= yView) { | |
| 1127 seriesIndexHit = seriesIndex; | |
| 1128 break; | |
| 1129 } | |
| 1130 } | |
| 1131 if (seriesIndexHit === undefined) | |
| 1132 return false; | |
| 1133 */ | |
| 1134 var hit = selection.addCounterSample(this, this.counter, i); | |
| 1135 this.decorateHit(hit); | |
| 1136 return true; | |
| 1137 }, | |
| 1138 | |
| 1139 /** | |
| 1140 * Adds items intersecting the given range to a selection. | |
| 1141 * @param {number} loWX Lower X bound of the interval to search, in | |
| 1142 * worldspace. | |
| 1143 * @param {number} hiWX Upper X bound of the interval to search, in | |
| 1144 * worldspace. | |
| 1145 * @param {number} loY Lower Y bound of the interval to search, in | |
| 1146 * offset space. | |
| 1147 * @param {number} hiY Upper Y bound of the interval to search, in | |
| 1148 * offset space. | |
| 1149 * @param {TimelineSelection} selection Selection to which to add hits. | |
| 1150 */ | |
| 1151 addIntersectingItemsInRangeToSelection: function( | |
| 1152 loWX, hiWX, loY, hiY, selection) { | |
| 1153 | |
| 1154 var clientRect = this.getBoundingClientRect(); | |
| 1155 var a = Math.max(loY, clientRect.top); | |
| 1156 var b = Math.min(hiY, clientRect.bottom); | |
| 1157 if (a > b) | |
| 1158 return; | |
| 1159 | |
| 1160 var ctr = this.counter_; | |
| 1161 | |
| 1162 var iLo = tracing.findLowIndexInSortedArray(ctr.timestamps, | |
| 1163 function(x) { return x; }, | |
| 1164 loWX); | |
| 1165 var iHi = tracing.findLowIndexInSortedArray(ctr.timestamps, | |
| 1166 function(x) { return x; }, | |
| 1167 hiWX); | |
| 1168 | |
| 1169 // Sample i is going to either be exactly at wX or slightly above it, | |
| 1170 // E.g. asking for 7.5 in [7,8] gives i=1. So bump i back by 1 if needed. | |
| 1171 if (iLo > 0 && loWX > ctr.timestamps[iLo - 1]) | |
| 1172 iLo--; | |
| 1173 if (iHi > 0 && hiWX > ctr.timestamps[iHi - 1]) | |
| 1174 iHi--; | |
| 1175 | |
| 1176 // Iterate over every sample intersecting.. | |
| 1177 for (var i = iLo; i <= iHi; i++) { | |
| 1178 if (i >= ctr.timestamps.length) | |
| 1179 continue; | |
| 1180 | |
| 1181 // TODO(nduca): Pick the seriesIndexHit based on the loY - hiY values. | |
| 1182 var hit = selection.addCounterSample(this, this.counter, i); | |
| 1183 this.decorateHit(hit); | |
| 1184 } | |
| 1185 }, | |
| 1186 | |
| 1187 addAllObjectsMatchingFilterToSelection: function(filter, selection) { | |
| 1188 } | |
| 1189 | |
| 1190 }; | |
| 1191 | |
| 1192 return { | |
| 1193 TimelineCounterTrack: TimelineCounterTrack, | |
| 1194 TimelineSliceTrack: TimelineSliceTrack, | |
| 1195 TimelineThreadTrack: TimelineThreadTrack, | |
| 1196 TimelineViewportTrack: TimelineViewportTrack, | |
| 1197 TimelineCpuTrack: TimelineCpuTrack | |
| 1198 }; | |
| 1199 }); | |
| OLD | NEW |