2 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
3 * Copyright (C) 2008, 2009 Anthony Ricaud <rik@webkit.org>
4 * Copyright (C) 2009 Google Inc. All rights reserved.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
16 * its contributors may be used to endorse or promote products derived
17 * from this software without specific prior written permission.
19 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
20 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 WebInspector.TimelineGrid = function()
36 this.element = document.createElement("div");
38 this._dividersElement = this.element.createChild("div", "resources-dividers");
40 this._gridHeaderElement = document.createElement("div");
41 this._gridHeaderElement.id = "timeline-grid-header";
42 this._eventDividersElement = this._gridHeaderElement.createChild("div", "resources-event-dividers");
43 this._dividersLabelBarElement = this._gridHeaderElement.createChild("div", "resources-dividers-label-bar");
44 this.element.appendChild(this._gridHeaderElement);
46 this._leftCurtainElement = this.element.createChild("div", "timeline-cpu-curtain-left");
47 this._rightCurtainElement = this.element.createChild("div", "timeline-cpu-curtain-right");
51 * @param {!WebInspector.TimelineGrid.Calculator} calculator
52 * @param {number} clientWidth
53 * @return {!{offsets: !Array.<number>, precision: number}}
55 WebInspector.TimelineGrid.calculateDividerOffsets = function(calculator, clientWidth)
57 const minGridSlicePx = 64; // minimal distance between grid lines.
58 const gridFreeZoneAtLeftPx = 50;
60 var dividersCount = clientWidth / minGridSlicePx;
61 var gridSliceTime = calculator.boundarySpan() / dividersCount;
62 var pixelsPerTime = clientWidth / calculator.boundarySpan();
64 // Align gridSliceTime to a nearest round value.
65 // We allow spans that fit into the formula: span = (1|2|5)x10^n,
66 // e.g.: ... .1 .2 .5 1 2 5 10 20 50 ...
67 // After a span has been chosen make grid lines at multiples of the span.
69 var logGridSliceTime = Math.ceil(Math.log(gridSliceTime) / Math.LN10);
70 gridSliceTime = Math.pow(10, logGridSliceTime);
71 if (gridSliceTime * pixelsPerTime >= 5 * minGridSlicePx)
72 gridSliceTime = gridSliceTime / 5;
73 if (gridSliceTime * pixelsPerTime >= 2 * minGridSlicePx)
74 gridSliceTime = gridSliceTime / 2;
76 var firstDividerTime = Math.ceil((calculator.minimumBoundary() - calculator.zeroTime()) / gridSliceTime) * gridSliceTime + calculator.zeroTime();
77 var lastDividerTime = calculator.maximumBoundary();
78 // Add some extra space past the right boundary as the rightmost divider label text
79 // may be partially shown rather than just pop up when a new rightmost divider gets into the view.
80 if (calculator.paddingLeft() > 0)
81 lastDividerTime = lastDividerTime + minGridSlicePx / pixelsPerTime;
82 dividersCount = Math.ceil((lastDividerTime - firstDividerTime) / gridSliceTime);
84 var skipLeftmostDividers = calculator.paddingLeft() === 0;
90 for (var i = 0; i < dividersCount; ++i) {
91 var left = calculator.computePosition(firstDividerTime + gridSliceTime * i);
92 if (skipLeftmostDividers && left < gridFreeZoneAtLeftPx)
94 offsets.push(firstDividerTime + gridSliceTime * i);
97 return {offsets: offsets, precision: Math.max(0, -Math.floor(Math.log(gridSliceTime * 1.01) / Math.LN10))};
101 * @param {!Object} canvas
102 * @param {!WebInspector.TimelineGrid.Calculator} calculator
103 * @param {?Array.<number>=} dividerOffsets
105 WebInspector.TimelineGrid.drawCanvasGrid = function(canvas, calculator, dividerOffsets)
107 var context = canvas.getContext("2d");
109 var ratio = window.devicePixelRatio;
110 context.scale(ratio, ratio);
111 var printDeltas = !!dividerOffsets;
112 var width = canvas.width / window.devicePixelRatio;
113 var height = canvas.height / window.devicePixelRatio;
115 if (!dividerOffsets) {
116 var dividersData = WebInspector.TimelineGrid.calculateDividerOffsets(calculator, width);
117 dividerOffsets = dividersData.offsets;
118 precision = dividersData.precision;
121 context.fillStyle = "rgba(255, 255, 255, 0.5)";
122 context.fillRect(0, 0, width, 15);
124 context.fillStyle = "#333";
125 context.strokeStyle = "rgba(0, 0, 0, 0.1)";
126 context.textBaseline = "hanging";
127 context.font = (printDeltas ? "italic bold 11px " : " 11px ") + WebInspector.fontFamily();
128 context.lineWidth = 1;
130 context.translate(0.5, 0.5);
131 const minWidthForTitle = 60;
132 var lastPosition = 0;
135 var paddingRight = 4;
137 for (var i = 0; i < dividerOffsets.length; ++i) {
138 time = dividerOffsets[i];
139 var position = calculator.computePosition(time);
141 if (position - lastPosition > minWidthForTitle) {
142 if (!printDeltas || i !== 0) {
143 var text = printDeltas ? calculator.formatTime(calculator.zeroTime() + time - lastTime) : calculator.formatTime(time, precision);
144 var textWidth = context.measureText(text).width;
145 var textPosition = printDeltas ? (position + lastPosition - textWidth) / 2 : position - textWidth - paddingRight;
146 context.fillText(text, textPosition, paddingTop);
149 context.moveTo(position, 0);
150 context.lineTo(position, height);
153 lastPosition = position;
158 WebInspector.TimelineGrid.prototype = {
159 get dividersElement()
161 return this._dividersElement;
164 get dividersLabelBarElement()
166 return this._dividersLabelBarElement;
169 removeDividers: function()
171 this._dividersElement.removeChildren();
172 this._dividersLabelBarElement.removeChildren();
176 * @param {!WebInspector.TimelineGrid.Calculator} calculator
177 * @param {?Array.<number>=} dividerOffsets
178 * @param {boolean=} printDeltas
181 updateDividers: function(calculator, dividerOffsets, printDeltas)
184 if (!dividerOffsets) {
185 var dividersData = WebInspector.TimelineGrid.calculateDividerOffsets(calculator, this._dividersElement.clientWidth);
186 dividerOffsets = dividersData.offsets;
187 precision = dividersData.precision;
191 var dividersElementClientWidth = this._dividersElement.clientWidth;
193 // Reuse divider elements and labels.
194 var divider = /** @type {?Element} */ (this._dividersElement.firstChild);
195 var dividerLabelBar = /** @type {?Element} */ (this._dividersLabelBarElement.firstChild);
197 const minWidthForTitle = 60;
198 var lastPosition = 0;
200 for (var i = 0; i < dividerOffsets.length; ++i) {
202 divider = document.createElement("div");
203 divider.className = "resources-divider";
204 this._dividersElement.appendChild(divider);
206 dividerLabelBar = document.createElement("div");
207 dividerLabelBar.className = "resources-divider";
208 var label = document.createElement("div");
209 label.className = "resources-divider-label";
210 dividerLabelBar._labelElement = label;
211 dividerLabelBar.appendChild(label);
212 this._dividersLabelBarElement.appendChild(dividerLabelBar);
215 var time = dividerOffsets[i];
216 var position = calculator.computePosition(time);
217 if (position - lastPosition > minWidthForTitle)
218 dividerLabelBar._labelElement.textContent = printDeltas ? calculator.formatTime(time - lastTime) : calculator.formatTime(time, precision);
220 dividerLabelBar._labelElement.textContent = "";
223 dividerLabelBar._labelElement.style.width = Math.ceil(position - lastPosition) + "px";
225 dividerLabelBar._labelElement.style.removeProperty("width");
227 lastPosition = position;
229 var percentLeft = 100 * position / dividersElementClientWidth;
230 divider.style.left = percentLeft + "%";
231 dividerLabelBar.style.left = percentLeft + "%";
233 divider = /** @type {?Element} */ (divider.nextSibling);
234 dividerLabelBar = /** @type {?Element} */ (dividerLabelBar.nextSibling);
239 var nextDivider = divider.nextSibling;
240 this._dividersElement.removeChild(divider);
241 divider = nextDivider;
243 while (dividerLabelBar) {
244 var nextDivider = dividerLabelBar.nextSibling;
245 this._dividersLabelBarElement.removeChild(dividerLabelBar);
246 dividerLabelBar = nextDivider;
251 addEventDivider: function(divider)
253 this._eventDividersElement.appendChild(divider);
256 addEventDividers: function(dividers)
258 this._gridHeaderElement.removeChild(this._eventDividersElement);
259 for (var i = 0; i < dividers.length; ++i) {
261 this._eventDividersElement.appendChild(dividers[i]);
263 this._gridHeaderElement.appendChild(this._eventDividersElement);
266 removeEventDividers: function()
268 this._eventDividersElement.removeChildren();
271 hideEventDividers: function()
273 this._eventDividersElement.classList.add("hidden");
276 showEventDividers: function()
278 this._eventDividersElement.classList.remove("hidden");
281 hideDividers: function()
283 this._dividersElement.classList.add("hidden");
286 showDividers: function()
288 this._dividersElement.classList.remove("hidden");
291 hideCurtains: function()
293 this._leftCurtainElement.classList.add("hidden");
294 this._rightCurtainElement.classList.add("hidden");
298 * @param {number} gapOffset
299 * @param {number} gapWidth
301 showCurtains: function(gapOffset, gapWidth)
303 this._leftCurtainElement.style.width = gapOffset + "px";
304 this._leftCurtainElement.classList.remove("hidden");
305 this._rightCurtainElement.style.left = (gapOffset + gapWidth) + "px";
306 this._rightCurtainElement.classList.remove("hidden");
309 setScrollAndDividerTop: function(scrollTop, dividersTop)
311 this._dividersLabelBarElement.style.top = scrollTop + "px";
312 this._eventDividersElement.style.top = scrollTop + "px";
313 this._leftCurtainElement.style.top = scrollTop + "px";
314 this._rightCurtainElement.style.top = scrollTop + "px";
321 WebInspector.TimelineGrid.Calculator = function() { }
323 WebInspector.TimelineGrid.Calculator.prototype = {
327 paddingLeft: function() { },
330 * @param {number} time
333 computePosition: function(time) { },
336 * @param {number} time
337 * @param {number=} precision
340 formatTime: function(time, precision) { },
342 /** @return {number} */
343 minimumBoundary: function() { },
345 /** @return {number} */
346 zeroTime: function() { },
348 /** @return {number} */
349 maximumBoundary: function() { },
351 /** @return {number} */
352 boundarySpan: function() { }