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