2 * Copyright (C) 2012 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 * @extends {WebInspector.SplitView}
34 * @param {string} title
35 * @param {!WebInspector.TimelineModeViewDelegate} delegate
36 * @param {!WebInspector.TimelineModel} model
38 WebInspector.CountersGraph = function(title, delegate, model)
40 WebInspector.SplitView.call(this, true, false);
42 this.element.id = "memory-graphs-container";
44 this._delegate = delegate;
46 this._calculator = new WebInspector.TimelineCalculator(this._model);
48 this._graphsContainer = this.mainElement();
49 this._createCurrentValuesBar();
50 this._canvasView = new WebInspector.VBoxWithResizeCallback(this._resize.bind(this));
51 this._canvasView.show(this._graphsContainer);
52 this._canvasContainer = this._canvasView.element;
53 this._canvasContainer.id = "memory-graphs-canvas-container";
54 this._canvas = this._canvasContainer.createChild("canvas");
55 this._canvas.id = "memory-counters-graph";
57 this._canvasContainer.addEventListener("mouseover", this._onMouseMove.bind(this), true);
58 this._canvasContainer.addEventListener("mousemove", this._onMouseMove.bind(this), true);
59 this._canvasContainer.addEventListener("mouseout", this._onMouseOut.bind(this), true);
60 this._canvasContainer.addEventListener("click", this._onClick.bind(this), true);
61 // We create extra timeline grid here to reuse its event dividers.
62 this._timelineGrid = new WebInspector.TimelineGrid();
63 this._canvasContainer.appendChild(this._timelineGrid.dividersElement);
66 this.sidebarElement().createChild("div", "sidebar-tree sidebar-tree-section").textContent = title;
71 WebInspector.CountersGraph.prototype = {
72 _createCurrentValuesBar: function()
74 this._currentValuesBar = this._graphsContainer.createChild("div");
75 this._currentValuesBar.id = "counter-values-bar";
79 * @param {string} uiName
80 * @param {string} uiValueTemplate
81 * @param {string} color
82 * @return {!WebInspector.CountersGraph.Counter}
84 createCounter: function(uiName, uiValueTemplate, color)
86 var counter = new WebInspector.CountersGraph.Counter();
87 this._counters.push(counter);
88 this._counterUI.push(new WebInspector.CountersGraph.CounterUI(this, uiName, uiValueTemplate, color, counter));
93 * @return {!WebInspector.View}
106 for (var i = 0; i < this._counters.length; ++i) {
107 this._counters[i].reset();
108 this._counterUI[i].reset();
115 var parentElement = this._canvas.parentElement;
116 this._canvas.width = parentElement.clientWidth * window.devicePixelRatio;
117 this._canvas.height = parentElement.clientHeight * window.devicePixelRatio;
118 var timelinePaddingLeft = 15;
119 this._calculator.setDisplayWindow(timelinePaddingLeft, this._canvas.width);
124 * @param {number} startTime
125 * @param {number} endTime
127 setWindowTimes: function(startTime, endTime)
129 this._calculator.setWindow(startTime, endTime);
130 this.scheduleRefresh();
133 scheduleRefresh: function()
135 WebInspector.invokeOnceAfterBatchUpdate(this, this.refresh);
140 for (var i = 0; i < this._counters.length; ++i) {
141 this._counters[i]._calculateVisibleIndexes(this._calculator);
142 this._counters[i]._calculateXValues(this._canvas.width);
146 for (var i = 0; i < this._counterUI.length; i++)
147 this._counterUI[i]._drawGraph(this._canvas);
151 * @param {?Event} event
153 _onClick: function(event)
155 var x = event.x - this._canvasContainer.totalOffsetLeft();
156 var minDistance = Infinity;
158 for (var i = 0; i < this._counterUI.length; ++i) {
159 var counterUI = this._counterUI[i];
160 if (!counterUI.counter.times.length)
162 var index = counterUI._recordIndexAt(x);
163 var distance = Math.abs(x * window.devicePixelRatio - counterUI.counter.x[index]);
164 if (distance < minDistance) {
165 minDistance = distance;
166 bestTime = counterUI.counter.times[index];
169 if (bestTime !== undefined)
170 this._revealRecordAt(bestTime);
174 * @param {number} time
176 _revealRecordAt: function(time)
180 * @param {!WebInspector.TimelineModel.Record} record
182 * @this {WebInspector.CountersGraph}
184 function findRecordToReveal(record)
186 if (!this._model.isVisible(record))
188 if (record.startTime() <= time && time <= record.endTime()) {
189 recordToReveal = record;
192 // If there is no record containing the time than use the latest one before that time.
193 if (!recordToReveal || record.endTime() < time && recordToReveal.endTime() < record.endTime())
194 recordToReveal = record;
197 this._model.forAllRecords(null, findRecordToReveal.bind(this));
198 this._delegate.select(recordToReveal ? WebInspector.TimelineSelection.fromRecord(recordToReveal) : null);
202 * @param {?Event} event
204 _onMouseOut: function(event)
206 delete this._markerXPosition;
207 this._clearCurrentValueAndMarker();
210 _clearCurrentValueAndMarker: function()
212 for (var i = 0; i < this._counterUI.length; i++)
213 this._counterUI[i]._clearCurrentValueAndMarker();
217 * @param {?Event} event
219 _onMouseMove: function(event)
221 var x = event.x - this._canvasContainer.totalOffsetLeft();
222 this._markerXPosition = x;
223 this._refreshCurrentValues();
226 _refreshCurrentValues: function()
228 if (this._markerXPosition === undefined)
230 for (var i = 0; i < this._counterUI.length; ++i)
231 this._counterUI[i].updateCurrentValue(this._markerXPosition);
236 this._timelineGrid.updateDividers(this._calculator);
238 this._refreshCurrentValues();
241 refreshRecords: function()
247 var ctx = this._canvas.getContext("2d");
248 ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
252 * @param {?WebInspector.TimelineModel.Record} record
253 * @param {string=} regex
254 * @param {boolean=} selectRecord
256 highlightSearchResult: function(record, regex, selectRecord)
261 * @param {?WebInspector.TimelineSelection} selection
263 setSelection: function(selection)
267 __proto__: WebInspector.SplitView.prototype
273 WebInspector.CountersGraph.Counter = function()
279 WebInspector.CountersGraph.Counter.prototype = {
281 * @param {number} time
282 * @param {number} value
284 appendSample: function(time, value)
286 if (this.values.length && this.values.peekLast() === value)
288 this.times.push(time);
289 this.values.push(value);
299 * @param {number} value
301 setLimit: function(value)
303 this._limitValue = value;
307 * @return {!{min: number, max: number}}
309 _calculateBounds: function()
313 for (var i = this._minimumIndex; i <= this._maximumIndex; i++) {
314 var value = this.values[i];
315 if (minValue === undefined || value < minValue)
317 if (maxValue === undefined || value > maxValue)
320 minValue = minValue || 0;
321 maxValue = maxValue || 1;
322 if (this._limitValue) {
323 if (maxValue > this._limitValue * 0.5)
324 maxValue = Math.max(maxValue, this._limitValue);
325 minValue = Math.min(minValue, this._limitValue);
327 return { min: minValue, max: maxValue };
331 * @param {!WebInspector.TimelineCalculator} calculator
333 _calculateVisibleIndexes: function(calculator)
335 var start = calculator.minimumBoundary();
336 var end = calculator.maximumBoundary();
338 // Maximum index of element whose time <= start.
339 this._minimumIndex = Number.constrain(this.times.upperBound(start) - 1, 0, this.times.length - 1);
341 // Minimum index of element whose time >= end.
342 this._maximumIndex = Number.constrain(this.times.lowerBound(end), 0, this.times.length - 1);
344 // Current window bounds.
345 this._minTime = start;
350 * @param {number} width
352 _calculateXValues: function(width)
354 if (!this.values.length)
357 var xFactor = width / (this._maxTime - this._minTime);
359 this.x = new Array(this.values.length);
360 for (var i = this._minimumIndex + 1; i <= this._maximumIndex; i++)
361 this.x[i] = xFactor * (this.times[i] - this._minTime);
367 * @param {!WebInspector.CountersGraph} memoryCountersPane
368 * @param {string} title
369 * @param {string} currentValueLabel
370 * @param {string} graphColor
371 * @param {!WebInspector.CountersGraph.Counter} counter
373 WebInspector.CountersGraph.CounterUI = function(memoryCountersPane, title, currentValueLabel, graphColor, counter)
375 this._memoryCountersPane = memoryCountersPane;
376 this.counter = counter;
377 var container = memoryCountersPane.sidebarElement().createChild("div", "memory-counter-sidebar-info");
378 var swatchColor = graphColor;
379 this._swatch = new WebInspector.SwatchCheckbox(WebInspector.UIString(title), swatchColor);
380 this._swatch.addEventListener(WebInspector.SwatchCheckbox.Events.Changed, this._toggleCounterGraph.bind(this));
381 container.appendChild(this._swatch.element);
382 this._range = this._swatch.element.createChild("span");
384 this._value = memoryCountersPane._currentValuesBar.createChild("span", "memory-counter-value");
385 this._value.style.color = graphColor;
386 this.graphColor = graphColor;
387 this.limitColor = WebInspector.Color.parse(graphColor).setAlpha(0.3).toString(WebInspector.Color.Format.RGBA);
388 this.graphYValues = [];
389 this._verticalPadding = 10;
391 this._currentValueLabel = currentValueLabel;
392 this._marker = memoryCountersPane._canvasContainer.createChild("div", "memory-counter-marker");
393 this._marker.style.backgroundColor = graphColor;
394 this._clearCurrentValueAndMarker();
397 WebInspector.CountersGraph.CounterUI.prototype = {
400 this._range.textContent = "";
404 * @param {number} minValue
405 * @param {number} maxValue
407 setRange: function(minValue, maxValue)
409 this._range.textContent = WebInspector.UIString("[%.0f:%.0f]", minValue, maxValue);
412 _toggleCounterGraph: function(event)
414 this._value.classList.toggle("hidden", !this._swatch.checked);
415 this._memoryCountersPane.refresh();
422 _recordIndexAt: function(x)
424 return this.counter.x.upperBound(x * window.devicePixelRatio, null, this.counter._minimumIndex + 1, this.counter._maximumIndex + 1) - 1;
430 updateCurrentValue: function(x)
432 if (!this.visible() || !this.counter.values.length)
434 var index = this._recordIndexAt(x);
435 this._value.textContent = WebInspector.UIString(this._currentValueLabel, this.counter.values[index]);
436 var y = this.graphYValues[index] / window.devicePixelRatio;
437 this._marker.style.left = x + "px";
438 this._marker.style.top = y + "px";
439 this._marker.classList.remove("hidden");
442 _clearCurrentValueAndMarker: function()
444 this._value.textContent = "";
445 this._marker.classList.add("hidden");
449 * @param {!HTMLCanvasElement} canvas
451 _drawGraph: function(canvas)
453 var ctx = canvas.getContext("2d");
454 var width = canvas.width;
455 var height = canvas.height - 2 * this._verticalPadding;
457 this.graphYValues = [];
460 var originY = this._verticalPadding;
461 var counter = this.counter;
462 var values = counter.values;
467 var bounds = counter._calculateBounds();
468 var minValue = bounds.min;
469 var maxValue = bounds.max;
470 this.setRange(minValue, maxValue);
475 var yValues = this.graphYValues;
476 var maxYRange = maxValue - minValue;
477 var yFactor = maxYRange ? height / (maxYRange) : 1;
480 ctx.lineWidth = window.devicePixelRatio;
481 if (ctx.lineWidth % 2)
482 ctx.translate(0.5, 0.5);
484 var value = values[counter._minimumIndex];
485 var currentY = Math.round(originY + height - (value - minValue) * yFactor);
486 ctx.moveTo(0, currentY);
487 for (var i = counter._minimumIndex; i <= counter._maximumIndex; i++) {
488 var x = Math.round(counter.x[i]);
489 ctx.lineTo(x, currentY);
490 var currentValue = values[i];
491 if (typeof currentValue !== "undefined")
492 value = currentValue;
493 currentY = Math.round(originY + height - (value - minValue) * yFactor);
494 ctx.lineTo(x, currentY);
495 yValues[i] = currentY;
498 ctx.lineTo(width, currentY);
499 ctx.strokeStyle = this.graphColor;
501 if (counter._limitValue) {
502 var limitLineY = Math.round(originY + height - (counter._limitValue - minValue) * yFactor);
503 ctx.moveTo(0, limitLineY);
504 ctx.lineTo(width, limitLineY);
505 ctx.strokeStyle = this.limitColor;
517 return this._swatch.checked;
524 * @extends {WebInspector.Object}
526 WebInspector.SwatchCheckbox = function(title, color)
528 this.element = document.createElement("div");
529 this._swatch = this.element.createChild("div", "swatch");
530 this.element.createChild("span", "title").textContent = title;
534 this.element.addEventListener("click", this._toggleCheckbox.bind(this), true);
537 WebInspector.SwatchCheckbox.Events = {
541 WebInspector.SwatchCheckbox.prototype = {
544 return this._checked;
551 this._swatch.style.backgroundColor = this._color;
553 this._swatch.style.backgroundColor = "";
556 _toggleCheckbox: function(event)
558 this.checked = !this.checked;
559 this.dispatchEventToListeners(WebInspector.SwatchCheckbox.Events.Changed);
562 __proto__: WebInspector.Object.prototype