3 Copyright (c) 2013 The Chromium Authors. All rights reserved.
4 Use of this source code is governed by a BSD-style license that can be
5 found in the LICENSE file.
8 <link rel="stylesheet" href="/tracing/tracks/ruler_track.css">
10 <link rel="import" href="/tracing/constants.html">
11 <link rel="import" href="/tracing/tracks/track.html">
12 <link rel="import" href="/tracing/tracks/heading_track.html">
13 <link rel="import" href="/tracing/draw_helpers.html">
14 <link rel="import" href="/tvcm/ui.html">
19 tvcm.exportTo('tracing.tracks', function() {
21 * A track that displays the ruler.
23 * @extends {HeadingTrack}
25 var RulerTrack = tvcm.ui.define('ruler-track', tracing.tracks.HeadingTrack);
27 var logOf10 = Math.log(10);
29 return Math.log(x) / logOf10;
32 RulerTrack.prototype = {
33 __proto__: tracing.tracks.HeadingTrack.prototype,
35 decorate: function(viewport) {
36 tracing.tracks.HeadingTrack.prototype.decorate.call(this, viewport);
37 this.classList.add('ruler-track');
38 this.strings_secs_ = [];
39 this.strings_msecs_ = [];
41 this.viewportChange_ = this.viewportChange_.bind(this);
42 viewport.addEventListener('change', this.viewportChange_);
47 tracing.tracks.HeadingTrack.prototype.detach.call(this);
48 this.viewport.removeEventListener('change',
49 this.viewportChange_);
52 viewportChange_: function() {
53 if (this.viewport.interestRange.isEmpty)
54 this.classList.remove('tall-mode');
56 this.classList.add('tall-mode');
59 draw: function(type, viewLWorld, viewRWorld) {
61 case tracing.tracks.DrawType.SLICE:
62 this.drawSlices_(viewLWorld, viewRWorld);
64 case tracing.tracks.DrawType.MARKERS:
65 if (!this.viewport.interestRange.isEmpty)
66 this.viewport.interestRange.draw(this.context(),
67 viewLWorld, viewRWorld);
72 drawSlices_: function(viewLWorld, viewRWorld) {
73 var ctx = this.context();
74 var pixelRatio = window.devicePixelRatio || 1;
76 var bounds = ctx.canvas.getBoundingClientRect();
77 var width = bounds.width * pixelRatio;
78 var height = bounds.height * pixelRatio;
80 var hasInterestRange = !this.viewport.interestRange.isEmpty;
82 var rulerHeight = hasInterestRange ? (height * 2) / 5 : height;
84 var vp = this.viewport;
85 var dt = vp.currentDisplayTransform;
87 var idealMajorMarkDistancePix = 150 * pixelRatio;
88 var idealMajorMarkDistanceWorld =
89 dt.xViewVectorToWorld(idealMajorMarkDistancePix);
91 var majorMarkDistanceWorld;
93 // The conservative guess is the nearest enclosing 0.1, 1, 10, 100, etc.
94 var conservativeGuess =
95 Math.pow(10, Math.ceil(log10(idealMajorMarkDistanceWorld)));
97 // Once we have a conservative guess, consider things that evenly add up
98 // to the conservative guess, e.g. 0.5, 0.2, 0.1 Pick the one that still
99 // exceeds the ideal mark distance.
100 var divisors = [10, 5, 2, 1];
101 for (var i = 0; i < divisors.length; ++i) {
102 var tightenedGuess = conservativeGuess / divisors[i];
103 if (dt.xWorldVectorToView(tightenedGuess) < idealMajorMarkDistancePix)
105 majorMarkDistanceWorld = conservativeGuess / divisors[i - 1];
111 var tickLabels = undefined;
112 if (majorMarkDistanceWorld < 100) {
115 tickLabels = this.strings_msecs_;
119 tickLabels = this.strings_secs_;
122 var numTicksPerMajor = 5;
123 var minorMarkDistanceWorld = majorMarkDistanceWorld / numTicksPerMajor;
124 var minorMarkDistancePx = dt.xWorldVectorToView(minorMarkDistanceWorld);
127 Math.floor(viewLWorld / majorMarkDistanceWorld) *
128 majorMarkDistanceWorld;
130 var minorTickH = Math.floor(rulerHeight * 0.25);
134 var pixelRatio = window.devicePixelRatio || 1;
135 ctx.lineWidth = Math.round(pixelRatio);
137 // Apply subpixel translate to get crisp lines.
138 // http://www.mobtowers.com/html5-canvas-crisp-lines-every-time/
139 var crispLineCorrection = (ctx.lineWidth % 2) / 2;
140 ctx.translate(crispLineCorrection, -crispLineCorrection);
142 ctx.fillStyle = 'rgb(0, 0, 0)';
143 ctx.strokeStyle = 'rgb(0, 0, 0)';
144 ctx.textAlign = 'left';
145 ctx.textBaseline = 'top';
147 ctx.font = (9 * pixelRatio) + 'px sans-serif';
149 vp.majorMarkPositions = [];
151 // Each iteration of this loop draws one major mark
152 // and numTicksPerMajor minor ticks.
154 // Rendering can't be done in world space because canvas transforms
155 // affect line width. So, do the conversions manually.
157 for (var curX = firstMajorMark;
159 curX += majorMarkDistanceWorld) {
161 var curXView = Math.floor(dt.xWorldToView(curX));
163 var unitValue = curX / unitDivisor;
164 var roundedUnitValue = Math.floor(unitValue * 100000) / 100000;
166 if (!tickLabels[roundedUnitValue])
167 tickLabels[roundedUnitValue] = roundedUnitValue + ' ' + unit;
168 ctx.fillText(tickLabels[roundedUnitValue],
169 curXView + (2 * pixelRatio), 0);
171 vp.majorMarkPositions.push(curXView);
174 tracing.drawLine(ctx, curXView, 0, curXView, rulerHeight);
177 for (var i = 1; i < numTicksPerMajor; ++i) {
178 var xView = Math.floor(curXView + minorMarkDistancePx * i);
179 tracing.drawLine(ctx,
180 xView, rulerHeight - minorTickH,
186 ctx.strokeStyle = 'rgb(0, 0, 0)';
187 tracing.drawLine(ctx, 0, height, width, height);
190 // Give distance between directly adjacent markers.
191 if (!hasInterestRange)
195 tracing.drawLine(ctx, 0, rulerHeight, width, rulerHeight);
198 // Distance Variables.
200 var displayTextColor = 'rgb(0,0,0)';
203 var arrowSpacing = 10 * pixelRatio;
204 var arrowColor = 'rgb(128,121,121)';
205 var arrowPosY = rulerHeight * 1.75;
206 var arrowWidthView = 3 * pixelRatio;
207 var arrowLengthView = 10 * pixelRatio;
208 var spaceForArrowsView = 2 * (arrowWidthView + arrowSpacing);
210 ctx.textBaseline = 'middle';
211 ctx.font = (14 * pixelRatio) + 'px sans-serif';
212 var textPosY = arrowPosY;
214 var interestRange = vp.interestRange;
216 // If the range is zero, draw it's min timestamp next to the line.
217 if (interestRange.range === 0) {
218 var markerWorld = interestRange.min;
219 var markerView = dt.xWorldToView(markerWorld);
220 var displayValue = markerWorld / unitDivisor;
221 displayValue = Math.abs((Math.floor(displayValue * 1000) / 1000));
223 var textToDraw = displayValue + ' ' + unit;
224 var textLeftView = markerView + 4 * pixelRatio;
225 var textWidthView = ctx.measureText(textToDraw).width;
227 // Put text to the left in case it gets cut off.
228 if (textLeftView + textWidthView > width)
229 textLeftView = markerView - 4 * pixelRatio - textWidthView;
231 ctx.fillStyle = displayTextColor;
232 ctx.fillText(textToDraw, textLeftView, textPosY);
236 var leftMarker = interestRange.min;
237 var rightMarker = interestRange.max;
239 var leftMarkerView = dt.xWorldToView(leftMarker);
240 var rightMarkerView = dt.xWorldToView(rightMarker);
242 var distanceBetweenMarkers = interestRange.range;
243 var distanceBetweenMarkersView =
244 dt.xWorldVectorToView(distanceBetweenMarkers);
245 var positionInMiddleOfMarkersView =
246 leftMarkerView + (distanceBetweenMarkersView / 2);
249 if (distanceBetweenMarkers < 100) {
257 // Calculate display value to print.
258 displayDistance = distanceBetweenMarkers / unitDivisor;
259 var roundedDisplayDistance =
260 Math.abs((Math.floor(displayDistance * 1000) / 1000));
261 var textToDraw = roundedDisplayDistance + ' ' + unit;
262 var textWidthView = ctx.measureText(textToDraw).width;
263 var spaceForArrowsAndTextView =
264 textWidthView + spaceForArrowsView + arrowSpacing;
266 // Set text positions.
267 var textLeftView = positionInMiddleOfMarkersView - textWidthView / 2;
268 var textRightView = textLeftView + textWidthView;
270 if (spaceForArrowsAndTextView > distanceBetweenMarkersView) {
271 // Print the display distance text right of the 2 markers.
272 textLeftView = rightMarkerView + 2 * arrowSpacing;
274 // Put text to the left in case it gets cut off.
275 if (textLeftView + textWidthView > width)
276 textLeftView = leftMarkerView - 2 * arrowSpacing - textWidthView;
278 ctx.fillStyle = displayTextColor;
279 ctx.fillText(textToDraw, textLeftView, textPosY);
281 // Draw the arrows pointing from outside in and a line in between.
282 ctx.strokeStyle = arrowColor;
284 tracing.drawLine(ctx, leftMarkerView, arrowPosY, rightMarkerView,
288 ctx.fillStyle = arrowColor;
289 tracing.drawArrow(ctx,
290 leftMarkerView - 1.5 * arrowSpacing, arrowPosY,
291 leftMarkerView, arrowPosY,
292 arrowLengthView, arrowWidthView);
293 tracing.drawArrow(ctx,
294 rightMarkerView + 1.5 * arrowSpacing, arrowPosY,
295 rightMarkerView, arrowPosY,
296 arrowLengthView, arrowWidthView);
298 } else if (spaceForArrowsView <= distanceBetweenMarkersView) {
301 if (spaceForArrowsAndTextView <= distanceBetweenMarkersView) {
302 // Print the display distance text.
303 ctx.fillStyle = displayTextColor;
304 ctx.fillText(textToDraw, textLeftView, textPosY);
306 leftArrowStart = textLeftView - arrowSpacing;
307 rightArrowStart = textRightView + arrowSpacing;
309 leftArrowStart = positionInMiddleOfMarkersView;
310 rightArrowStart = positionInMiddleOfMarkersView;
313 // Draw the arrows pointing inside out.
314 ctx.strokeStyle = arrowColor;
315 ctx.fillStyle = arrowColor;
316 tracing.drawArrow(ctx,
317 leftArrowStart, arrowPosY,
318 leftMarkerView, arrowPosY,
319 arrowLengthView, arrowWidthView);
320 tracing.drawArrow(ctx,
321 rightArrowStart, arrowPosY,
322 rightMarkerView, arrowPosY,
323 arrowLengthView, arrowWidthView);
330 * Adds items intersecting the given range to a selection.
331 * @param {number} loVX Lower X bound of the interval to search, in
333 * @param {number} hiVX Upper X bound of the interval to search, in
335 * @param {number} loVY Lower Y bound of the interval to search, in
337 * @param {number} hiVY Upper Y bound of the interval to search, in
339 * @param {Selection} selection Selection to which to add results.
341 addIntersectingItemsInRangeToSelection: function(
342 loVX, hiVX, loY, hiY, selection) {
343 // Does nothing. There's nothing interesting to pick on the ruler
347 addAllObjectsMatchingFilterToSelection: function(filter, selection) {
352 RulerTrack: RulerTrack