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="stylesheet" href="/tracing/ui/tracks/ruler_track.css"> 9 10<link rel="import" href="/tracing/ui/base/draw_helpers.html"> 11<link rel="import" href="/tracing/ui/base/heading.html"> 12<link rel="import" href="/tracing/ui/base/ui.html"> 13<link rel="import" href="/tracing/ui/tracks/track.html"> 14 15<script> 16'use strict'; 17 18tr.exportTo('tr.ui.tracks', function() { 19 /** 20 * A track that displays the ruler. 21 * @constructor 22 * @extends {Track} 23 */ 24 var RulerTrack = tr.ui.b.define('ruler-track', tr.ui.tracks.Track); 25 26 var logOf10 = Math.log(10); 27 function log10(x) { 28 return Math.log(x) / logOf10; 29 } 30 31 RulerTrack.prototype = { 32 __proto__: tr.ui.tracks.Track.prototype, 33 34 decorate: function(viewport) { 35 tr.ui.tracks.Track.prototype.decorate.call(this, viewport); 36 this.classList.add('ruler-track'); 37 this.strings_secs_ = []; 38 this.strings_msecs_ = []; 39 this.strings_usecs_ = []; 40 this.strings_nsecs_ = []; 41 42 this.viewportChange_ = this.viewportChange_.bind(this); 43 viewport.addEventListener('change', this.viewportChange_); 44 45 var heading = document.createElement('tr-ui-heading'); 46 heading.arrowVisible = false; 47 this.appendChild(heading); 48 }, 49 50 detach: function() { 51 tr.ui.tracks.Track.prototype.detach.call(this); 52 this.viewport.removeEventListener('change', 53 this.viewportChange_); 54 }, 55 56 viewportChange_: function() { 57 if (this.viewport.interestRange.isEmpty) 58 this.classList.remove('tall-mode'); 59 else 60 this.classList.add('tall-mode'); 61 }, 62 63 draw: function(type, viewLWorld, viewRWorld) { 64 switch (type) { 65 case tr.ui.tracks.DrawType.GRID: 66 this.drawGrid_(viewLWorld, viewRWorld); 67 break; 68 case tr.ui.tracks.DrawType.MARKERS: 69 if (!this.viewport.interestRange.isEmpty) 70 this.viewport.interestRange.draw(this.context(), 71 viewLWorld, viewRWorld); 72 break; 73 } 74 }, 75 76 drawGrid_: function(viewLWorld, viewRWorld) { 77 var ctx = this.context(); 78 var pixelRatio = window.devicePixelRatio || 1; 79 80 var canvasBounds = ctx.canvas.getBoundingClientRect(); 81 var trackBounds = this.getBoundingClientRect(); 82 var width = canvasBounds.width * pixelRatio; 83 var height = trackBounds.height * pixelRatio; 84 85 var hasInterestRange = !this.viewport.interestRange.isEmpty; 86 87 var rulerHeight = hasInterestRange ? (height * 2) / 5 : height; 88 89 var vp = this.viewport; 90 var dt = vp.currentDisplayTransform; 91 92 var idealMajorMarkDistancePix = 150 * pixelRatio; 93 var idealMajorMarkDistanceWorld = 94 dt.xViewVectorToWorld(idealMajorMarkDistancePix); 95 96 var majorMarkDistanceWorld; 97 98 // The conservative guess is the nearest enclosing 0.1, 1, 10, 100, etc. 99 var conservativeGuess = 100 Math.pow(10, Math.ceil(log10(idealMajorMarkDistanceWorld))); 101 102 // Once we have a conservative guess, consider things that evenly add up 103 // to the conservative guess, e.g. 0.5, 0.2, 0.1 Pick the one that still 104 // exceeds the ideal mark distance. 105 var divisors = [10, 5, 2, 1]; 106 for (var i = 0; i < divisors.length; ++i) { 107 var tightenedGuess = conservativeGuess / divisors[i]; 108 if (dt.xWorldVectorToView(tightenedGuess) < idealMajorMarkDistancePix) 109 continue; 110 majorMarkDistanceWorld = conservativeGuess / divisors[i - 1]; 111 break; 112 } 113 114 var unit; 115 var unitDivisor; 116 var tickLabels = undefined; 117 if (majorMarkDistanceWorld < 0.0001) { 118 unit = 'ns'; 119 unitDivisor = 0.000001; 120 tickLabels = this.strings_nsecs_; 121 } else if (majorMarkDistanceWorld < 0.1) { 122 unit = 'us'; 123 unitDivisor = 0.001; 124 tickLabels = this.strings_usecs_; 125 } else if (majorMarkDistanceWorld < 100) { 126 unit = 'ms'; 127 unitDivisor = 1; 128 tickLabels = this.strings_msecs_; 129 } else { 130 unit = 's'; 131 unitDivisor = 1000; 132 tickLabels = this.strings_secs_; 133 } 134 135 var numTicksPerMajor = 5; 136 var minorMarkDistanceWorld = majorMarkDistanceWorld / numTicksPerMajor; 137 var minorMarkDistancePx = dt.xWorldVectorToView(minorMarkDistanceWorld); 138 139 var firstMajorMark = 140 Math.floor(viewLWorld / majorMarkDistanceWorld) * 141 majorMarkDistanceWorld; 142 143 var minorTickH = Math.floor(rulerHeight * 0.25); 144 145 ctx.save(); 146 147 var pixelRatio = window.devicePixelRatio || 1; 148 ctx.lineWidth = Math.round(pixelRatio); 149 150 // Apply subpixel translate to get crisp lines. 151 // http://www.mobtowers.com/html5-canvas-crisp-lines-every-time/ 152 var crispLineCorrection = (ctx.lineWidth % 2) / 2; 153 ctx.translate(crispLineCorrection, -crispLineCorrection); 154 155 ctx.fillStyle = 'rgb(0, 0, 0)'; 156 ctx.strokeStyle = 'rgb(0, 0, 0)'; 157 ctx.textAlign = 'left'; 158 ctx.textBaseline = 'top'; 159 160 ctx.font = (9 * pixelRatio) + 'px sans-serif'; 161 162 vp.majorMarkPositions = []; 163 164 // Each iteration of this loop draws one major mark 165 // and numTicksPerMajor minor ticks. 166 // 167 // Rendering can't be done in world space because canvas transforms 168 // affect line width. So, do the conversions manually. 169 ctx.beginPath(); 170 for (var curX = firstMajorMark; 171 curX < viewRWorld; 172 curX += majorMarkDistanceWorld) { 173 174 var curXView = Math.floor(dt.xWorldToView(curX)); 175 176 var unitValue = curX / unitDivisor; 177 var roundedUnitValue = Math.round(unitValue * 100000) / 100000; 178 179 if (!tickLabels[roundedUnitValue]) 180 tickLabels[roundedUnitValue] = roundedUnitValue + ' ' + unit; 181 ctx.fillText(tickLabels[roundedUnitValue], 182 curXView + (2 * pixelRatio), 0); 183 184 vp.majorMarkPositions.push(curXView); 185 186 // Major mark 187 tr.ui.b.drawLine(ctx, curXView, 0, curXView, rulerHeight); 188 189 // Minor marks 190 for (var i = 1; i < numTicksPerMajor; ++i) { 191 var xView = Math.floor(curXView + minorMarkDistancePx * i); 192 tr.ui.b.drawLine(ctx, 193 xView, rulerHeight - minorTickH, 194 xView, rulerHeight); 195 } 196 } 197 198 // Draw bottom bar. 199 ctx.strokeStyle = 'rgb(0, 0, 0)'; 200 tr.ui.b.drawLine(ctx, 0, height, width, height); 201 ctx.stroke(); 202 203 // Give distance between directly adjacent markers. 204 if (!hasInterestRange) 205 return; 206 207 // Draw middle bar. 208 tr.ui.b.drawLine(ctx, 0, rulerHeight, width, rulerHeight); 209 ctx.stroke(); 210 211 // Distance Variables. 212 var displayDistance; 213 var displayTextColor = 'rgb(0,0,0)'; 214 215 // Arrow Variables. 216 var arrowSpacing = 10 * pixelRatio; 217 var arrowColor = 'rgb(128,121,121)'; 218 var arrowPosY = rulerHeight * 1.75; 219 var arrowWidthView = 3 * pixelRatio; 220 var arrowLengthView = 10 * pixelRatio; 221 var spaceForArrowsView = 2 * (arrowWidthView + arrowSpacing); 222 223 ctx.textBaseline = 'middle'; 224 ctx.font = (14 * pixelRatio) + 'px sans-serif'; 225 var textPosY = arrowPosY; 226 227 var interestRange = vp.interestRange; 228 229 // If the range is zero, draw it's min timestamp next to the line. 230 if (interestRange.range === 0) { 231 var markerWorld = interestRange.min; 232 var markerView = dt.xWorldToView(markerWorld); 233 var displayValue = markerWorld / unitDivisor; 234 displayValue = Math.abs((Math.round(displayValue * 1000) / 1000)); 235 236 var textToDraw = displayValue + ' ' + unit; 237 var textLeftView = markerView + 4 * pixelRatio; 238 var textWidthView = ctx.measureText(textToDraw).width; 239 240 // Put text to the left in case it gets cut off. 241 if (textLeftView + textWidthView > width) 242 textLeftView = markerView - 4 * pixelRatio - textWidthView; 243 244 ctx.fillStyle = displayTextColor; 245 ctx.fillText(textToDraw, textLeftView, textPosY); 246 return; 247 } 248 249 var leftMarker = interestRange.min; 250 var rightMarker = interestRange.max; 251 252 var leftMarkerView = dt.xWorldToView(leftMarker); 253 var rightMarkerView = dt.xWorldToView(rightMarker); 254 255 var distanceBetweenMarkers = interestRange.range; 256 var distanceBetweenMarkersView = 257 dt.xWorldVectorToView(distanceBetweenMarkers); 258 var positionInMiddleOfMarkersView = 259 leftMarkerView + (distanceBetweenMarkersView / 2); 260 261 // Determine units. 262 if (distanceBetweenMarkers < 0.0001) { 263 unit = 'ns'; 264 unitDivisor = 0.000001; 265 } else if (distanceBetweenMarkers < 0.1) { 266 unit = 'us'; 267 unitDivisor = 0.001; 268 } else if (distanceBetweenMarkers < 100) { 269 unit = 'ms'; 270 unitDivisor = 1; 271 } else { 272 unit = 's'; 273 unitDivisor = 1000; 274 } 275 276 // Calculate display value to print. 277 displayDistance = distanceBetweenMarkers / unitDivisor; 278 var roundedDisplayDistance = 279 Math.abs((Math.round(displayDistance * 1000) / 1000)); 280 var textToDraw = roundedDisplayDistance + ' ' + unit; 281 var textWidthView = ctx.measureText(textToDraw).width; 282 var spaceForArrowsAndTextView = 283 textWidthView + spaceForArrowsView + arrowSpacing; 284 285 // Set text positions. 286 var textLeftView = positionInMiddleOfMarkersView - textWidthView / 2; 287 var textRightView = textLeftView + textWidthView; 288 289 if (spaceForArrowsAndTextView > distanceBetweenMarkersView) { 290 // Print the display distance text right of the 2 markers. 291 textLeftView = rightMarkerView + 2 * arrowSpacing; 292 293 // Put text to the left in case it gets cut off. 294 if (textLeftView + textWidthView > width) 295 textLeftView = leftMarkerView - 2 * arrowSpacing - textWidthView; 296 297 ctx.fillStyle = displayTextColor; 298 ctx.fillText(textToDraw, textLeftView, textPosY); 299 300 // Draw the arrows pointing from outside in and a line in between. 301 ctx.strokeStyle = arrowColor; 302 ctx.beginPath(); 303 tr.ui.b.drawLine(ctx, leftMarkerView, arrowPosY, rightMarkerView, 304 arrowPosY); 305 ctx.stroke(); 306 307 ctx.fillStyle = arrowColor; 308 tr.ui.b.drawArrow(ctx, 309 leftMarkerView - 1.5 * arrowSpacing, arrowPosY, 310 leftMarkerView, arrowPosY, 311 arrowLengthView, arrowWidthView); 312 tr.ui.b.drawArrow(ctx, 313 rightMarkerView + 1.5 * arrowSpacing, arrowPosY, 314 rightMarkerView, arrowPosY, 315 arrowLengthView, arrowWidthView); 316 317 } else if (spaceForArrowsView <= distanceBetweenMarkersView) { 318 var leftArrowStart; 319 var rightArrowStart; 320 if (spaceForArrowsAndTextView <= distanceBetweenMarkersView) { 321 // Print the display distance text. 322 ctx.fillStyle = displayTextColor; 323 ctx.fillText(textToDraw, textLeftView, textPosY); 324 325 leftArrowStart = textLeftView - arrowSpacing; 326 rightArrowStart = textRightView + arrowSpacing; 327 } else { 328 leftArrowStart = positionInMiddleOfMarkersView; 329 rightArrowStart = positionInMiddleOfMarkersView; 330 } 331 332 // Draw the arrows pointing inside out. 333 ctx.strokeStyle = arrowColor; 334 ctx.fillStyle = arrowColor; 335 tr.ui.b.drawArrow(ctx, 336 leftArrowStart, arrowPosY, 337 leftMarkerView, arrowPosY, 338 arrowLengthView, arrowWidthView); 339 tr.ui.b.drawArrow(ctx, 340 rightArrowStart, arrowPosY, 341 rightMarkerView, arrowPosY, 342 arrowLengthView, arrowWidthView); 343 } 344 345 ctx.restore(); 346 }, 347 348 /** 349 * Adds items intersecting the given range to a selection. 350 * @param {number} loVX Lower X bound of the interval to search, in 351 * viewspace. 352 * @param {number} hiVX Upper X bound of the interval to search, in 353 * viewspace. 354 * @param {number} loVY Lower Y bound of the interval to search, in 355 * viewspace. 356 * @param {number} hiVY Upper Y bound of the interval to search, in 357 * viewspace. 358 * @param {Selection} selection Selection to which to add results. 359 */ 360 addIntersectingEventsInRangeToSelection: function( 361 loVX, hiVX, loY, hiY, selection) { 362 // Does nothing. There's nothing interesting to pick on the ruler 363 // track. 364 }, 365 366 addAllEventsMatchingFilterToSelection: function(filter, selection) { 367 } 368 }; 369 370 return { 371 RulerTrack: RulerTrack 372 }; 373}); 374</script> 375