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'use strict'; 6 7base.requireStylesheet('tracks.counter_track'); 8 9base.require('tracks.canvas_based_track'); 10base.require('color_scheme'); 11base.require('ui'); 12 13base.exportTo('tracing.tracks', function() { 14 15 var palette = tracing.getColorPalette(); 16 17 /** 18 * A track that displays a Counter object. 19 * @constructor 20 * @extends {CanvasBasedTrack} 21 */ 22 23 var CounterTrack = 24 tracing.ui.define(tracing.tracks.CanvasBasedTrack); 25 26 CounterTrack.prototype = { 27 28 __proto__: tracing.tracks.CanvasBasedTrack.prototype, 29 30 decorate: function() { 31 this.classList.add('counter-track'); 32 this.addControlButtonElements_(false); 33 this.selectedSamples_ = {}; 34 this.categoryFilter_ = new tracing.Filter(); 35 }, 36 37 /** 38 * Called by all the addToSelection functions on the created selection 39 * hit objects. Override this function on parent classes to add 40 * context-specific information to the hit. 41 */ 42 decorateHit: function(hit) { 43 }, 44 45 get counter() { 46 return this.counter_; 47 }, 48 49 set counter(counter) { 50 this.counter_ = counter; 51 this.invalidate(); 52 this.updateVisibility_(); 53 }, 54 55 set categoryFilter(v) { 56 this.categoryFilter_ = v; 57 this.updateVisibility_(); 58 }, 59 60 /** 61 * @return {Object} A sparse, mutable map from sample index to bool. Samples 62 * indices the map that are true are drawn as selected. Callers that mutate 63 * the map must manually call invalidate on the track to trigger a redraw. 64 */ 65 get selectedSamples() { 66 return this.selectedSamples_; 67 }, 68 69 updateVisibility_: function() { 70 this.visible = (this.counter_ && 71 this.categoryFilter_.matchCounter(this.counter_)); 72 }, 73 74 redraw: function() { 75 var ctr = this.counter_; 76 var ctx = this.ctx_; 77 var canvasW = this.canvas_.width; 78 var canvasH = this.canvas_.height; 79 80 ctx.clearRect(0, 0, canvasW, canvasH); 81 82 // Culling parametrs. 83 var vp = this.viewport_; 84 var pixWidth = vp.xViewVectorToWorld(1); 85 var viewLWorld = vp.xViewToWorld(0); 86 var viewRWorld = vp.xViewToWorld(canvasW); 87 88 // Give the viewport a chance to draw onto this canvas. 89 vp.drawUnderContent(ctx, viewLWorld, viewRWorld, canvasH); 90 91 // Drop sampels that are less than skipDistancePix apart. 92 var skipDistancePix = 1; 93 var skipDistanceWorld = vp.xViewVectorToWorld(skipDistancePix); 94 95 // Begin rendering in world space. 96 ctx.save(); 97 vp.applyTransformToCanvas(ctx); 98 99 // Figure out where drawing should begin. 100 var numSeries = ctr.numSeries; 101 var numSamples = ctr.numSamples; 102 var startIndex = tracing.findLowIndexInSortedArray(ctr.timestamps, 103 function(x) { 104 return x; 105 }, 106 viewLWorld); 107 startIndex = startIndex - 1 > 0 ? startIndex - 1 : 0; 108 109 // Draw indices one by one until we fall off the viewRWorld. 110 var yScale = canvasH / ctr.maxTotal; 111 for (var seriesIndex = ctr.numSeries - 1; 112 seriesIndex >= 0; seriesIndex--) { 113 var colorId = ctr.seriesColors[seriesIndex]; 114 ctx.fillStyle = palette[colorId]; 115 ctx.beginPath(); 116 117 // Set iLast and xLast such that the first sample we draw is the 118 // startIndex sample. 119 var iLast = startIndex - 1; 120 var xLast = iLast >= 0 ? ctr.timestamps[iLast] - skipDistanceWorld : -1; 121 var yLastView = canvasH; 122 123 // Iterate over samples from iLast onward until we either fall off the 124 // viewRWorld or we run out of samples. To avoid drawing too much, after 125 // drawing a sample at xLast, skip subsequent samples that are less than 126 // skipDistanceWorld from xLast. 127 var hasMoved = false; 128 while (true) { 129 var i = iLast + 1; 130 if (i >= numSamples) { 131 ctx.lineTo(xLast, yLastView); 132 ctx.lineTo(xLast + 8 * pixWidth, yLastView); 133 ctx.lineTo(xLast + 8 * pixWidth, canvasH); 134 break; 135 } 136 137 var x = ctr.timestamps[i]; 138 139 var y = ctr.totals[i * numSeries + seriesIndex]; 140 var yView = canvasH - (yScale * y); 141 142 if (x > viewRWorld) { 143 ctx.lineTo(x, yLastView); 144 ctx.lineTo(x, canvasH); 145 break; 146 } 147 148 if (i + 1 < numSamples) { 149 var xNext = ctr.timestamps[i + 1]; 150 if (xNext - xLast <= skipDistanceWorld && xNext < viewRWorld) { 151 iLast = i; 152 continue; 153 } 154 } 155 156 if (!hasMoved) { 157 ctx.moveTo(viewLWorld, canvasH); 158 hasMoved = true; 159 } 160 if (x - xLast < skipDistanceWorld) { 161 // We know that xNext > xLast + skipDistanceWorld, so we can 162 // safely move this sample's x over that much without passing 163 // xNext. This ensure that the previous sample is visible when 164 // zoomed out very far. 165 x = xLast + skipDistanceWorld; 166 } 167 ctx.lineTo(x, yLastView); 168 ctx.lineTo(x, yView); 169 iLast = i; 170 xLast = x; 171 yLastView = yView; 172 } 173 ctx.closePath(); 174 ctx.fill(); 175 } 176 ctx.fillStyle = 'rgba(255, 0, 0, 1)'; 177 for (var i in this.selectedSamples_) { 178 if (!this.selectedSamples_[i]) 179 continue; 180 181 var x = ctr.timestamps[i]; 182 for (var seriesIndex = ctr.numSeries - 1; 183 seriesIndex >= 0; seriesIndex--) { 184 var y = ctr.totals[i * numSeries + seriesIndex]; 185 var yView = canvasH - (yScale * y); 186 ctx.fillRect(x - pixWidth, yView - 1, 3 * pixWidth, 3); 187 } 188 } 189 ctx.restore(); 190 191 // Give the viewport a chance to draw over this canvas. 192 vp.drawOverContent(ctx, viewLWorld, viewRWorld, canvasH); 193 }, 194 195 /** 196 * Adds items intersecting a point to a selection. 197 * @param {number} vX X location to search at, in viewspace. 198 * @param {number} vY Y location to search at, in viewspace. 199 * @param {Selection} selection Selection to which to add hits. 200 * @return {boolean} true if a slice was found, otherwise false. 201 */ 202 addIntersectingItemsToSelection: function(vX, vY, selection) { 203 var clientRect = this.getBoundingClientRect(); 204 if (vY < clientRect.top || vY >= clientRect.bottom) 205 return false; 206 207 var pixelRatio = window.devicePixelRatio || 1; 208 var wX = this.viewport_.xViewVectorToWorld(vX * devicePixelRatio); 209 210 var ctr = this.counter_; 211 if (vX < this.counter_.timestamps[0]) 212 return false; 213 var i = tracing.findLowIndexInSortedArray(ctr.timestamps, 214 function(x) { return x; }, 215 wX); 216 if (i < 0 || i >= ctr.timestamps.length) 217 return false; 218 219 // Sample i is going to either be exactly at wX or slightly above it, 220 // E.g. asking for 7.5 in [7,8] gives i=1. So bump i back by 1 if needed. 221 if (i > 0 && wX > this.counter_.timestamps[i - 1]) 222 i--; 223 224 // Some preliminaries. 225 var canvasH = this.getBoundingClientRect().height; 226 var yScale = canvasH / ctr.maxTotal; 227 228 /* 229 // Figure out which sample we hit 230 var seriesIndexHit; 231 for (var seriesIndex = 0; seriesIndex < ctr.numSeries; seriesIndex++) { 232 var y = ctr.totals[i * ctr.numSeries + seriesIndex]; 233 var yView = canvasH - (yScale * y) + clientRect.top; 234 if (wY >= yView) { 235 seriesIndexHit = seriesIndex; 236 break; 237 } 238 } 239 if (seriesIndexHit === undefined) 240 return false; 241 */ 242 var hit = selection.addCounterSample(this, this.counter, i); 243 this.decorateHit(hit); 244 return true; 245 }, 246 247 /** 248 * Adds items intersecting the given range to a selection. 249 * @param {number} loVX Lower X bound of the interval to search, in 250 * viewspace. 251 * @param {number} hiVX Upper X bound of the interval to search, in 252 * viewspace. 253 * @param {number} loVY Lower Y bound of the interval to search, in 254 * viewspace. 255 * @param {number} hiVY Upper Y bound of the interval to search, in 256 * viewspace. 257 * @param {Selection} selection Selection to which to add hits. 258 */ 259 addIntersectingItemsInRangeToSelection: function( 260 loVX, hiVX, loVY, hiVY, selection) { 261 262 var clientRect = this.getBoundingClientRect(); 263 var a = Math.max(loVY, clientRect.top); 264 var b = Math.min(hiVY, clientRect.bottom); 265 if (a > b) 266 return; 267 268 var ctr = this.counter_; 269 270 var pixelRatio = window.devicePixelRatio || 1; 271 var loWX = this.viewport_.xViewToWorld(loVX * pixelRatio); 272 var hiWX = this.viewport_.xViewToWorld(hiVX * pixelRatio); 273 274 var iLo = tracing.findLowIndexInSortedArray(ctr.timestamps, 275 function(x) { return x; }, 276 loWX); 277 var iHi = tracing.findLowIndexInSortedArray(ctr.timestamps, 278 function(x) { return x; }, 279 hiWX); 280 281 // Sample i is going to either be exactly at wX or slightly above it, 282 // E.g. asking for 7.5 in [7,8] gives i=1. So bump i back by 1 if needed. 283 if (iLo > 0 && loWX > ctr.timestamps[iLo - 1]) 284 iLo--; 285 if (iHi > 0 && hiWX > ctr.timestamps[iHi - 1]) 286 iHi--; 287 288 // Iterate over every sample intersecting.. 289 for (var i = iLo; i <= iHi; i++) { 290 if (i >= ctr.timestamps.length) 291 continue; 292 293 // TODO(nduca): Pick the seriesIndexHit based on the loY - hiY values. 294 var hit = selection.addCounterSample(this, this.counter, i); 295 this.decorateHit(hit); 296 } 297 }, 298 299 addAllObjectsMatchingFilterToSelection: function(filter, selection) { 300 } 301 }; 302 303 return { 304 CounterTrack: CounterTrack 305 }; 306}); 307