OLD | NEW |
1 /* | 1 /* |
2 * Copyright (C) 2012 Google Inc. All rights reserved. | 2 * Copyright (C) 2012 Google Inc. All rights reserved. |
3 * | 3 * |
4 * Redistribution and use in source and binary forms, with or without | 4 * Redistribution and use in source and binary forms, with or without |
5 * modification, are permitted provided that the following conditions are | 5 * modification, are permitted provided that the following conditions are |
6 * met: | 6 * met: |
7 * | 7 * |
8 * * Redistributions of source code must retain the above copyright | 8 * * Redistributions of source code must retain the above copyright |
9 * notice, this list of conditions and the following disclaimer. | 9 * notice, this list of conditions and the following disclaimer. |
10 * * Redistributions in binary form must reproduce the above | 10 * * Redistributions in binary form must reproduce the above |
11 * copyright notice, this list of conditions and the following disclaimer | 11 * copyright notice, this list of conditions and the following disclaimer |
12 * in the documentation and/or other materials provided with the | 12 * in the documentation and/or other materials provided with the |
13 * distribution. | 13 * distribution. |
14 * * Neither the name of Google Inc. nor the names of its | 14 * * Neither the name of Google Inc. nor the names of its |
15 * contributors may be used to endorse or promote products derived from | 15 * contributors may be used to endorse or promote products derived from |
16 * this software without specific prior written permission. | 16 * this software without specific prior written permission. |
17 * | 17 * |
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 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. | 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
29 */ | 29 */ |
| 30 /** |
| 31 * @implements {WebInspector.TimelineModeView} |
| 32 * @unrestricted |
| 33 */ |
| 34 WebInspector.CountersGraph = class extends WebInspector.VBox { |
| 35 /** |
| 36 * @param {!WebInspector.TimelineModeViewDelegate} delegate |
| 37 * @param {!WebInspector.TimelineModel} model |
| 38 * @param {!Array<!WebInspector.TimelineModel.Filter>} filters |
| 39 */ |
| 40 constructor(delegate, model, filters) { |
| 41 super(); |
30 | 42 |
31 /** | 43 this.element.id = 'memory-graphs-container'; |
32 * @constructor | |
33 * @extends {WebInspector.VBox} | |
34 * @implements {WebInspector.TimelineModeView} | |
35 * @param {!WebInspector.TimelineModeViewDelegate} delegate | |
36 * @param {!WebInspector.TimelineModel} model | |
37 * @param {!Array<!WebInspector.TimelineModel.Filter>} filters | |
38 */ | |
39 WebInspector.CountersGraph = function(delegate, model, filters) | |
40 { | |
41 WebInspector.VBox.call(this); | |
42 | |
43 this.element.id = "memory-graphs-container"; | |
44 | 44 |
45 this._delegate = delegate; | 45 this._delegate = delegate; |
46 this._model = model; | 46 this._model = model; |
47 this._filters = filters; | 47 this._filters = filters; |
48 this._calculator = new WebInspector.CounterGraphCalculator(this._model); | 48 this._calculator = new WebInspector.CounterGraphCalculator(this._model); |
49 | 49 |
50 // Create selectors | 50 // Create selectors |
51 this._infoWidget = new WebInspector.HBox(); | 51 this._infoWidget = new WebInspector.HBox(); |
52 this._infoWidget.element.classList.add("memory-counter-selector-swatches", "
timeline-toolbar-resizer"); | 52 this._infoWidget.element.classList.add('memory-counter-selector-swatches', '
timeline-toolbar-resizer'); |
53 this._infoWidget.show(this.element); | 53 this._infoWidget.show(this.element); |
54 | 54 |
55 this._graphsContainer = new WebInspector.VBox(); | 55 this._graphsContainer = new WebInspector.VBox(); |
56 this._graphsContainer.show(this.element); | 56 this._graphsContainer.show(this.element); |
57 var canvasWidget = new WebInspector.VBoxWithResizeCallback(this._resize.bind
(this)); | 57 var canvasWidget = new WebInspector.VBoxWithResizeCallback(this._resize.bind
(this)); |
58 canvasWidget.show(this._graphsContainer.element); | 58 canvasWidget.show(this._graphsContainer.element); |
59 this._createCurrentValuesBar(); | 59 this._createCurrentValuesBar(); |
60 this._canvasContainer = canvasWidget.element; | 60 this._canvasContainer = canvasWidget.element; |
61 this._canvasContainer.id = "memory-graphs-canvas-container"; | 61 this._canvasContainer.id = 'memory-graphs-canvas-container'; |
62 this._canvas = this._canvasContainer.createChild("canvas"); | 62 this._canvas = this._canvasContainer.createChild('canvas'); |
63 this._canvas.id = "memory-counters-graph"; | 63 this._canvas.id = 'memory-counters-graph'; |
64 | 64 |
65 this._canvasContainer.addEventListener("mouseover", this._onMouseMove.bind(t
his), true); | 65 this._canvasContainer.addEventListener('mouseover', this._onMouseMove.bind(t
his), true); |
66 this._canvasContainer.addEventListener("mousemove", this._onMouseMove.bind(t
his), true); | 66 this._canvasContainer.addEventListener('mousemove', this._onMouseMove.bind(t
his), true); |
67 this._canvasContainer.addEventListener("mouseleave", this._onMouseLeave.bind
(this), true); | 67 this._canvasContainer.addEventListener('mouseleave', this._onMouseLeave.bind
(this), true); |
68 this._canvasContainer.addEventListener("click", this._onClick.bind(this), tr
ue); | 68 this._canvasContainer.addEventListener('click', this._onClick.bind(this), tr
ue); |
69 // We create extra timeline grid here to reuse its event dividers. | 69 // We create extra timeline grid here to reuse its event dividers. |
70 this._timelineGrid = new WebInspector.TimelineGrid(); | 70 this._timelineGrid = new WebInspector.TimelineGrid(); |
71 this._canvasContainer.appendChild(this._timelineGrid.dividersElement); | 71 this._canvasContainer.appendChild(this._timelineGrid.dividersElement); |
72 | 72 |
73 this._counters = []; | 73 this._counters = []; |
74 this._counterUI = []; | 74 this._counterUI = []; |
| 75 } |
| 76 |
| 77 _createCurrentValuesBar() { |
| 78 this._currentValuesBar = this._graphsContainer.element.createChild('div'); |
| 79 this._currentValuesBar.id = 'counter-values-bar'; |
| 80 } |
| 81 |
| 82 /** |
| 83 * @param {string} uiName |
| 84 * @param {string} uiValueTemplate |
| 85 * @param {string} color |
| 86 * @param {function(number):string=} formatter |
| 87 * @return {!WebInspector.CountersGraph.Counter} |
| 88 */ |
| 89 createCounter(uiName, uiValueTemplate, color, formatter) { |
| 90 var counter = new WebInspector.CountersGraph.Counter(); |
| 91 this._counters.push(counter); |
| 92 this._counterUI.push( |
| 93 new WebInspector.CountersGraph.CounterUI(this, uiName, uiValueTemplate,
color, counter, formatter)); |
| 94 return counter; |
| 95 } |
| 96 |
| 97 /** |
| 98 * @override |
| 99 * @return {!WebInspector.Widget} |
| 100 */ |
| 101 view() { |
| 102 return this; |
| 103 } |
| 104 |
| 105 /** |
| 106 * @override |
| 107 */ |
| 108 dispose() { |
| 109 } |
| 110 |
| 111 /** |
| 112 * @override |
| 113 */ |
| 114 reset() { |
| 115 for (var i = 0; i < this._counters.length; ++i) { |
| 116 this._counters[i].reset(); |
| 117 this._counterUI[i].reset(); |
| 118 } |
| 119 this.refresh(); |
| 120 } |
| 121 |
| 122 /** |
| 123 * @override |
| 124 * @return {?Element} |
| 125 */ |
| 126 resizerElement() { |
| 127 return this._infoWidget.element; |
| 128 } |
| 129 |
| 130 _resize() { |
| 131 var parentElement = this._canvas.parentElement; |
| 132 this._canvas.width = parentElement.clientWidth * window.devicePixelRatio; |
| 133 this._canvas.height = parentElement.clientHeight * window.devicePixelRatio; |
| 134 var timelinePaddingLeft = 15; |
| 135 this._calculator.setDisplayWindow(this._canvas.width, timelinePaddingLeft); |
| 136 this.refresh(); |
| 137 } |
| 138 |
| 139 /** |
| 140 * @override |
| 141 * @param {number} startTime |
| 142 * @param {number} endTime |
| 143 */ |
| 144 setWindowTimes(startTime, endTime) { |
| 145 this._calculator.setWindow(startTime, endTime); |
| 146 this.scheduleRefresh(); |
| 147 } |
| 148 |
| 149 scheduleRefresh() { |
| 150 WebInspector.invokeOnceAfterBatchUpdate(this, this.refresh); |
| 151 } |
| 152 |
| 153 draw() { |
| 154 for (var i = 0; i < this._counters.length; ++i) { |
| 155 this._counters[i]._calculateVisibleIndexes(this._calculator); |
| 156 this._counters[i]._calculateXValues(this._canvas.width); |
| 157 } |
| 158 this._clear(); |
| 159 |
| 160 for (var i = 0; i < this._counterUI.length; i++) |
| 161 this._counterUI[i]._drawGraph(this._canvas); |
| 162 } |
| 163 |
| 164 /** |
| 165 * @param {!Event} event |
| 166 */ |
| 167 _onClick(event) { |
| 168 var x = event.x - this._canvasContainer.totalOffsetLeft(); |
| 169 var minDistance = Infinity; |
| 170 var bestTime; |
| 171 for (var i = 0; i < this._counterUI.length; ++i) { |
| 172 var counterUI = this._counterUI[i]; |
| 173 if (!counterUI.counter.times.length) |
| 174 continue; |
| 175 var index = counterUI._recordIndexAt(x); |
| 176 var distance = Math.abs(x * window.devicePixelRatio - counterUI.counter.x[
index]); |
| 177 if (distance < minDistance) { |
| 178 minDistance = distance; |
| 179 bestTime = counterUI.counter.times[index]; |
| 180 } |
| 181 } |
| 182 if (bestTime !== undefined) |
| 183 this._delegate.selectEntryAtTime(bestTime); |
| 184 } |
| 185 |
| 186 /** |
| 187 * @param {!Event} event |
| 188 */ |
| 189 _onMouseLeave(event) { |
| 190 delete this._markerXPosition; |
| 191 this._clearCurrentValueAndMarker(); |
| 192 } |
| 193 |
| 194 _clearCurrentValueAndMarker() { |
| 195 for (var i = 0; i < this._counterUI.length; i++) |
| 196 this._counterUI[i]._clearCurrentValueAndMarker(); |
| 197 } |
| 198 |
| 199 /** |
| 200 * @param {!Event} event |
| 201 */ |
| 202 _onMouseMove(event) { |
| 203 var x = event.x - this._canvasContainer.totalOffsetLeft(); |
| 204 this._markerXPosition = x; |
| 205 this._refreshCurrentValues(); |
| 206 } |
| 207 |
| 208 _refreshCurrentValues() { |
| 209 if (this._markerXPosition === undefined) |
| 210 return; |
| 211 for (var i = 0; i < this._counterUI.length; ++i) |
| 212 this._counterUI[i].updateCurrentValue(this._markerXPosition); |
| 213 } |
| 214 |
| 215 refresh() { |
| 216 this._timelineGrid.updateDividers(this._calculator); |
| 217 this.draw(); |
| 218 this._refreshCurrentValues(); |
| 219 } |
| 220 |
| 221 /** |
| 222 * @override |
| 223 */ |
| 224 refreshRecords() { |
| 225 } |
| 226 |
| 227 _clear() { |
| 228 var ctx = this._canvas.getContext('2d'); |
| 229 ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); |
| 230 } |
| 231 |
| 232 /** |
| 233 * @override |
| 234 * @param {?WebInspector.TracingModel.Event} event |
| 235 * @param {string=} regex |
| 236 * @param {boolean=} select |
| 237 */ |
| 238 highlightSearchResult(event, regex, select) { |
| 239 } |
| 240 |
| 241 /** |
| 242 * @override |
| 243 * @param {?WebInspector.TracingModel.Event} event |
| 244 */ |
| 245 highlightEvent(event) { |
| 246 } |
| 247 |
| 248 /** |
| 249 * @override |
| 250 * @param {?WebInspector.TimelineSelection} selection |
| 251 */ |
| 252 setSelection(selection) { |
| 253 } |
75 }; | 254 }; |
76 | 255 |
77 WebInspector.CountersGraph.prototype = { | |
78 _createCurrentValuesBar: function() | |
79 { | |
80 this._currentValuesBar = this._graphsContainer.element.createChild("div"
); | |
81 this._currentValuesBar.id = "counter-values-bar"; | |
82 }, | |
83 | |
84 /** | |
85 * @param {string} uiName | |
86 * @param {string} uiValueTemplate | |
87 * @param {string} color | |
88 * @param {function(number):string=} formatter | |
89 * @return {!WebInspector.CountersGraph.Counter} | |
90 */ | |
91 createCounter: function(uiName, uiValueTemplate, color, formatter) | |
92 { | |
93 var counter = new WebInspector.CountersGraph.Counter(); | |
94 this._counters.push(counter); | |
95 this._counterUI.push(new WebInspector.CountersGraph.CounterUI(this, uiNa
me, uiValueTemplate, color, counter, formatter)); | |
96 return counter; | |
97 }, | |
98 | |
99 /** | |
100 * @override | |
101 * @return {!WebInspector.Widget} | |
102 */ | |
103 view: function() | |
104 { | |
105 return this; | |
106 }, | |
107 | |
108 /** | |
109 * @override | |
110 */ | |
111 dispose: function() | |
112 { | |
113 }, | |
114 | |
115 /** | |
116 * @override | |
117 */ | |
118 reset: function() | |
119 { | |
120 for (var i = 0; i < this._counters.length; ++i) { | |
121 this._counters[i].reset(); | |
122 this._counterUI[i].reset(); | |
123 } | |
124 this.refresh(); | |
125 }, | |
126 | |
127 /** | |
128 * @override | |
129 * @return {?Element} | |
130 */ | |
131 resizerElement: function() | |
132 { | |
133 return this._infoWidget.element; | |
134 }, | |
135 | |
136 _resize: function() | |
137 { | |
138 var parentElement = this._canvas.parentElement; | |
139 this._canvas.width = parentElement.clientWidth * window.devicePixelRati
o; | |
140 this._canvas.height = parentElement.clientHeight * window.devicePixelRat
io; | |
141 var timelinePaddingLeft = 15; | |
142 this._calculator.setDisplayWindow(this._canvas.width, timelinePaddingLef
t); | |
143 this.refresh(); | |
144 }, | |
145 | |
146 /** | |
147 * @override | |
148 * @param {number} startTime | |
149 * @param {number} endTime | |
150 */ | |
151 setWindowTimes: function(startTime, endTime) | |
152 { | |
153 this._calculator.setWindow(startTime, endTime); | |
154 this.scheduleRefresh(); | |
155 }, | |
156 | |
157 scheduleRefresh: function() | |
158 { | |
159 WebInspector.invokeOnceAfterBatchUpdate(this, this.refresh); | |
160 }, | |
161 | |
162 draw: function() | |
163 { | |
164 for (var i = 0; i < this._counters.length; ++i) { | |
165 this._counters[i]._calculateVisibleIndexes(this._calculator); | |
166 this._counters[i]._calculateXValues(this._canvas.width); | |
167 } | |
168 this._clear(); | |
169 | |
170 for (var i = 0; i < this._counterUI.length; i++) | |
171 this._counterUI[i]._drawGraph(this._canvas); | |
172 }, | |
173 | |
174 /** | |
175 * @param {!Event} event | |
176 */ | |
177 _onClick: function(event) | |
178 { | |
179 var x = event.x - this._canvasContainer.totalOffsetLeft(); | |
180 var minDistance = Infinity; | |
181 var bestTime; | |
182 for (var i = 0; i < this._counterUI.length; ++i) { | |
183 var counterUI = this._counterUI[i]; | |
184 if (!counterUI.counter.times.length) | |
185 continue; | |
186 var index = counterUI._recordIndexAt(x); | |
187 var distance = Math.abs(x * window.devicePixelRatio - counterUI.coun
ter.x[index]); | |
188 if (distance < minDistance) { | |
189 minDistance = distance; | |
190 bestTime = counterUI.counter.times[index]; | |
191 } | |
192 } | |
193 if (bestTime !== undefined) | |
194 this._delegate.selectEntryAtTime(bestTime); | |
195 }, | |
196 | |
197 /** | |
198 * @param {!Event} event | |
199 */ | |
200 _onMouseLeave: function(event) | |
201 { | |
202 delete this._markerXPosition; | |
203 this._clearCurrentValueAndMarker(); | |
204 }, | |
205 | |
206 _clearCurrentValueAndMarker: function() | |
207 { | |
208 for (var i = 0; i < this._counterUI.length; i++) | |
209 this._counterUI[i]._clearCurrentValueAndMarker(); | |
210 }, | |
211 | |
212 /** | |
213 * @param {!Event} event | |
214 */ | |
215 _onMouseMove: function(event) | |
216 { | |
217 var x = event.x - this._canvasContainer.totalOffsetLeft(); | |
218 this._markerXPosition = x; | |
219 this._refreshCurrentValues(); | |
220 }, | |
221 | |
222 _refreshCurrentValues: function() | |
223 { | |
224 if (this._markerXPosition === undefined) | |
225 return; | |
226 for (var i = 0; i < this._counterUI.length; ++i) | |
227 this._counterUI[i].updateCurrentValue(this._markerXPosition); | |
228 }, | |
229 | |
230 refresh: function() | |
231 { | |
232 this._timelineGrid.updateDividers(this._calculator); | |
233 this.draw(); | |
234 this._refreshCurrentValues(); | |
235 }, | |
236 | |
237 /** | |
238 * @override | |
239 */ | |
240 refreshRecords: function() { }, | |
241 | |
242 _clear: function() | |
243 { | |
244 var ctx = this._canvas.getContext("2d"); | |
245 ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); | |
246 }, | |
247 | |
248 /** | |
249 * @override | |
250 * @param {?WebInspector.TracingModel.Event} event | |
251 * @param {string=} regex | |
252 * @param {boolean=} select | |
253 */ | |
254 highlightSearchResult: function(event, regex, select) | |
255 { | |
256 }, | |
257 | |
258 /** | |
259 * @override | |
260 * @param {?WebInspector.TracingModel.Event} event | |
261 */ | |
262 highlightEvent: function(event) | |
263 { | |
264 }, | |
265 | |
266 /** | |
267 * @override | |
268 * @param {?WebInspector.TimelineSelection} selection | |
269 */ | |
270 setSelection: function(selection) | |
271 { | |
272 }, | |
273 | |
274 __proto__: WebInspector.VBox.prototype | |
275 }; | |
276 | |
277 /** | 256 /** |
278 * @constructor | 257 * @unrestricted |
279 */ | 258 */ |
280 WebInspector.CountersGraph.Counter = function() | 259 WebInspector.CountersGraph.Counter = class { |
281 { | 260 constructor() { |
282 this.times = []; | 261 this.times = []; |
283 this.values = []; | 262 this.values = []; |
| 263 } |
| 264 |
| 265 /** |
| 266 * @param {number} time |
| 267 * @param {number} value |
| 268 */ |
| 269 appendSample(time, value) { |
| 270 if (this.values.length && this.values.peekLast() === value) |
| 271 return; |
| 272 this.times.push(time); |
| 273 this.values.push(value); |
| 274 } |
| 275 |
| 276 reset() { |
| 277 this.times = []; |
| 278 this.values = []; |
| 279 } |
| 280 |
| 281 /** |
| 282 * @param {number} value |
| 283 */ |
| 284 setLimit(value) { |
| 285 this._limitValue = value; |
| 286 } |
| 287 |
| 288 /** |
| 289 * @return {!{min: number, max: number}} |
| 290 */ |
| 291 _calculateBounds() { |
| 292 var maxValue; |
| 293 var minValue; |
| 294 for (var i = this._minimumIndex; i <= this._maximumIndex; i++) { |
| 295 var value = this.values[i]; |
| 296 if (minValue === undefined || value < minValue) |
| 297 minValue = value; |
| 298 if (maxValue === undefined || value > maxValue) |
| 299 maxValue = value; |
| 300 } |
| 301 minValue = minValue || 0; |
| 302 maxValue = maxValue || 1; |
| 303 if (this._limitValue) { |
| 304 if (maxValue > this._limitValue * 0.5) |
| 305 maxValue = Math.max(maxValue, this._limitValue); |
| 306 minValue = Math.min(minValue, this._limitValue); |
| 307 } |
| 308 return {min: minValue, max: maxValue}; |
| 309 } |
| 310 |
| 311 /** |
| 312 * @param {!WebInspector.CounterGraphCalculator} calculator |
| 313 */ |
| 314 _calculateVisibleIndexes(calculator) { |
| 315 var start = calculator.minimumBoundary(); |
| 316 var end = calculator.maximumBoundary(); |
| 317 |
| 318 // Maximum index of element whose time <= start. |
| 319 this._minimumIndex = Number.constrain(this.times.upperBound(start) - 1, 0, t
his.times.length - 1); |
| 320 |
| 321 // Minimum index of element whose time >= end. |
| 322 this._maximumIndex = Number.constrain(this.times.lowerBound(end), 0, this.ti
mes.length - 1); |
| 323 |
| 324 // Current window bounds. |
| 325 this._minTime = start; |
| 326 this._maxTime = end; |
| 327 } |
| 328 |
| 329 /** |
| 330 * @param {number} width |
| 331 */ |
| 332 _calculateXValues(width) { |
| 333 if (!this.values.length) |
| 334 return; |
| 335 |
| 336 var xFactor = width / (this._maxTime - this._minTime); |
| 337 |
| 338 this.x = new Array(this.values.length); |
| 339 for (var i = this._minimumIndex + 1; i <= this._maximumIndex; i++) |
| 340 this.x[i] = xFactor * (this.times[i] - this._minTime); |
| 341 } |
284 }; | 342 }; |
285 | 343 |
286 WebInspector.CountersGraph.Counter.prototype = { | |
287 /** | |
288 * @param {number} time | |
289 * @param {number} value | |
290 */ | |
291 appendSample: function(time, value) | |
292 { | |
293 if (this.values.length && this.values.peekLast() === value) | |
294 return; | |
295 this.times.push(time); | |
296 this.values.push(value); | |
297 }, | |
298 | |
299 reset: function() | |
300 { | |
301 this.times = []; | |
302 this.values = []; | |
303 }, | |
304 | |
305 /** | |
306 * @param {number} value | |
307 */ | |
308 setLimit: function(value) | |
309 { | |
310 this._limitValue = value; | |
311 }, | |
312 | |
313 /** | |
314 * @return {!{min: number, max: number}} | |
315 */ | |
316 _calculateBounds: function() | |
317 { | |
318 var maxValue; | |
319 var minValue; | |
320 for (var i = this._minimumIndex; i <= this._maximumIndex; i++) { | |
321 var value = this.values[i]; | |
322 if (minValue === undefined || value < minValue) | |
323 minValue = value; | |
324 if (maxValue === undefined || value > maxValue) | |
325 maxValue = value; | |
326 } | |
327 minValue = minValue || 0; | |
328 maxValue = maxValue || 1; | |
329 if (this._limitValue) { | |
330 if (maxValue > this._limitValue * 0.5) | |
331 maxValue = Math.max(maxValue, this._limitValue); | |
332 minValue = Math.min(minValue, this._limitValue); | |
333 } | |
334 return { min: minValue, max: maxValue }; | |
335 }, | |
336 | |
337 /** | |
338 * @param {!WebInspector.CounterGraphCalculator} calculator | |
339 */ | |
340 _calculateVisibleIndexes: function(calculator) | |
341 { | |
342 var start = calculator.minimumBoundary(); | |
343 var end = calculator.maximumBoundary(); | |
344 | |
345 // Maximum index of element whose time <= start. | |
346 this._minimumIndex = Number.constrain(this.times.upperBound(start) - 1,
0, this.times.length - 1); | |
347 | |
348 // Minimum index of element whose time >= end. | |
349 this._maximumIndex = Number.constrain(this.times.lowerBound(end), 0, thi
s.times.length - 1); | |
350 | |
351 // Current window bounds. | |
352 this._minTime = start; | |
353 this._maxTime = end; | |
354 }, | |
355 | |
356 /** | |
357 * @param {number} width | |
358 */ | |
359 _calculateXValues: function(width) | |
360 { | |
361 if (!this.values.length) | |
362 return; | |
363 | |
364 var xFactor = width / (this._maxTime - this._minTime); | |
365 | |
366 this.x = new Array(this.values.length); | |
367 for (var i = this._minimumIndex + 1; i <= this._maximumIndex; i++) | |
368 this.x[i] = xFactor * (this.times[i] - this._minTime); | |
369 } | |
370 }; | |
371 | |
372 /** | 344 /** |
373 * @constructor | 345 * @unrestricted |
374 * @param {!WebInspector.CountersGraph} memoryCountersPane | |
375 * @param {string} title | |
376 * @param {string} currentValueLabel | |
377 * @param {string} graphColor | |
378 * @param {!WebInspector.CountersGraph.Counter} counter | |
379 * @param {(function(number): string)|undefined} formatter | |
380 */ | 346 */ |
381 WebInspector.CountersGraph.CounterUI = function(memoryCountersPane, title, curre
ntValueLabel, graphColor, counter, formatter) | 347 WebInspector.CountersGraph.CounterUI = class { |
382 { | 348 /** |
| 349 * @param {!WebInspector.CountersGraph} memoryCountersPane |
| 350 * @param {string} title |
| 351 * @param {string} currentValueLabel |
| 352 * @param {string} graphColor |
| 353 * @param {!WebInspector.CountersGraph.Counter} counter |
| 354 * @param {(function(number): string)|undefined} formatter |
| 355 */ |
| 356 constructor(memoryCountersPane, title, currentValueLabel, graphColor, counter,
formatter) { |
383 this._memoryCountersPane = memoryCountersPane; | 357 this._memoryCountersPane = memoryCountersPane; |
384 this.counter = counter; | 358 this.counter = counter; |
385 this._formatter = formatter || Number.withThousandsSeparator; | 359 this._formatter = formatter || Number.withThousandsSeparator; |
386 var container = memoryCountersPane._infoWidget.element.createChild("div", "m
emory-counter-selector-info"); | 360 var container = memoryCountersPane._infoWidget.element.createChild('div', 'm
emory-counter-selector-info'); |
387 | 361 |
388 this._setting = WebInspector.settings.createSetting("timelineCountersGraph-"
+ title, true); | 362 this._setting = WebInspector.settings.createSetting('timelineCountersGraph-'
+ title, true); |
389 this._filter = new WebInspector.ToolbarCheckbox(title, title, this._setting)
; | 363 this._filter = new WebInspector.ToolbarCheckbox(title, title, this._setting)
; |
390 this._filter.inputElement.classList.add("-theme-preserve"); | 364 this._filter.inputElement.classList.add('-theme-preserve'); |
391 var color = WebInspector.Color.parse(graphColor).setAlpha(0.5).asString(WebI
nspector.Color.Format.RGBA); | 365 var color = WebInspector.Color.parse(graphColor).setAlpha(0.5).asString(WebI
nspector.Color.Format.RGBA); |
392 if (color) { | 366 if (color) { |
393 this._filter.element.backgroundColor = color; | 367 this._filter.element.backgroundColor = color; |
394 this._filter.element.borderColor = "transparent"; | 368 this._filter.element.borderColor = 'transparent'; |
395 } | 369 } |
396 this._filter.inputElement.addEventListener("click", this._toggleCounterGraph
.bind(this)); | 370 this._filter.inputElement.addEventListener('click', this._toggleCounterGraph
.bind(this)); |
397 container.appendChild(this._filter.element); | 371 container.appendChild(this._filter.element); |
398 this._range = this._filter.element.createChild("span", "range"); | 372 this._range = this._filter.element.createChild('span', 'range'); |
399 | 373 |
400 this._value = memoryCountersPane._currentValuesBar.createChild("span", "memo
ry-counter-value"); | 374 this._value = memoryCountersPane._currentValuesBar.createChild('span', 'memo
ry-counter-value'); |
401 this._value.style.color = graphColor; | 375 this._value.style.color = graphColor; |
402 this.graphColor = graphColor; | 376 this.graphColor = graphColor; |
403 this.limitColor = WebInspector.Color.parse(graphColor).setAlpha(0.3).asStrin
g(WebInspector.Color.Format.RGBA); | 377 this.limitColor = WebInspector.Color.parse(graphColor).setAlpha(0.3).asStrin
g(WebInspector.Color.Format.RGBA); |
404 this.graphYValues = []; | 378 this.graphYValues = []; |
405 this._verticalPadding = 10; | 379 this._verticalPadding = 10; |
406 | 380 |
407 this._currentValueLabel = currentValueLabel; | 381 this._currentValueLabel = currentValueLabel; |
408 this._marker = memoryCountersPane._canvasContainer.createChild("div", "memor
y-counter-marker"); | 382 this._marker = memoryCountersPane._canvasContainer.createChild('div', 'memor
y-counter-marker'); |
409 this._marker.style.backgroundColor = graphColor; | 383 this._marker.style.backgroundColor = graphColor; |
410 this._clearCurrentValueAndMarker(); | 384 this._clearCurrentValueAndMarker(); |
| 385 } |
| 386 |
| 387 reset() { |
| 388 this._range.textContent = ''; |
| 389 } |
| 390 |
| 391 /** |
| 392 * @param {number} minValue |
| 393 * @param {number} maxValue |
| 394 */ |
| 395 setRange(minValue, maxValue) { |
| 396 var min = this._formatter(minValue); |
| 397 var max = this._formatter(maxValue); |
| 398 this._range.textContent = WebInspector.UIString('[%s\u2009\u2013\u2009%s]',
min, max); |
| 399 } |
| 400 |
| 401 /** |
| 402 * @param {!WebInspector.Event} event |
| 403 */ |
| 404 _toggleCounterGraph(event) { |
| 405 this._value.classList.toggle('hidden', !this._filter.checked()); |
| 406 this._memoryCountersPane.refresh(); |
| 407 } |
| 408 |
| 409 /** |
| 410 * @param {number} x |
| 411 * @return {number} |
| 412 */ |
| 413 _recordIndexAt(x) { |
| 414 return this.counter.x.upperBound( |
| 415 x * window.devicePixelRatio, null, this.counter._minimumIndex + 1
, this.counter._maximumIndex + 1) - |
| 416 1; |
| 417 } |
| 418 |
| 419 /** |
| 420 * @param {number} x |
| 421 */ |
| 422 updateCurrentValue(x) { |
| 423 if (!this.visible() || !this.counter.values.length || !this.counter.x) |
| 424 return; |
| 425 var index = this._recordIndexAt(x); |
| 426 var value = Number.withThousandsSeparator(this.counter.values[index]); |
| 427 this._value.textContent = WebInspector.UIString(this._currentValueLabel, val
ue); |
| 428 var y = this.graphYValues[index] / window.devicePixelRatio; |
| 429 this._marker.style.left = x + 'px'; |
| 430 this._marker.style.top = y + 'px'; |
| 431 this._marker.classList.remove('hidden'); |
| 432 } |
| 433 |
| 434 _clearCurrentValueAndMarker() { |
| 435 this._value.textContent = ''; |
| 436 this._marker.classList.add('hidden'); |
| 437 } |
| 438 |
| 439 /** |
| 440 * @param {!HTMLCanvasElement} canvas |
| 441 */ |
| 442 _drawGraph(canvas) { |
| 443 var ctx = canvas.getContext('2d'); |
| 444 var width = canvas.width; |
| 445 var height = canvas.height - 2 * this._verticalPadding; |
| 446 if (height <= 0) { |
| 447 this.graphYValues = []; |
| 448 return; |
| 449 } |
| 450 var originY = this._verticalPadding; |
| 451 var counter = this.counter; |
| 452 var values = counter.values; |
| 453 |
| 454 if (!values.length) |
| 455 return; |
| 456 |
| 457 var bounds = counter._calculateBounds(); |
| 458 var minValue = bounds.min; |
| 459 var maxValue = bounds.max; |
| 460 this.setRange(minValue, maxValue); |
| 461 |
| 462 if (!this.visible()) |
| 463 return; |
| 464 |
| 465 var yValues = this.graphYValues; |
| 466 var maxYRange = maxValue - minValue; |
| 467 var yFactor = maxYRange ? height / (maxYRange) : 1; |
| 468 |
| 469 ctx.save(); |
| 470 ctx.lineWidth = window.devicePixelRatio; |
| 471 if (ctx.lineWidth % 2) |
| 472 ctx.translate(0.5, 0.5); |
| 473 ctx.beginPath(); |
| 474 var value = values[counter._minimumIndex]; |
| 475 var currentY = Math.round(originY + height - (value - minValue) * yFactor); |
| 476 ctx.moveTo(0, currentY); |
| 477 for (var i = counter._minimumIndex; i <= counter._maximumIndex; i++) { |
| 478 var x = Math.round(counter.x[i]); |
| 479 ctx.lineTo(x, currentY); |
| 480 var currentValue = values[i]; |
| 481 if (typeof currentValue !== 'undefined') |
| 482 value = currentValue; |
| 483 currentY = Math.round(originY + height - (value - minValue) * yFactor); |
| 484 ctx.lineTo(x, currentY); |
| 485 yValues[i] = currentY; |
| 486 } |
| 487 yValues.length = i; |
| 488 ctx.lineTo(width, currentY); |
| 489 ctx.strokeStyle = this.graphColor; |
| 490 ctx.stroke(); |
| 491 if (counter._limitValue) { |
| 492 var limitLineY = Math.round(originY + height - (counter._limitValue - minV
alue) * yFactor); |
| 493 ctx.moveTo(0, limitLineY); |
| 494 ctx.lineTo(width, limitLineY); |
| 495 ctx.strokeStyle = this.limitColor; |
| 496 ctx.stroke(); |
| 497 } |
| 498 ctx.closePath(); |
| 499 ctx.restore(); |
| 500 } |
| 501 |
| 502 /** |
| 503 * @return {boolean} |
| 504 */ |
| 505 visible() { |
| 506 return this._filter.checked(); |
| 507 } |
411 }; | 508 }; |
412 | 509 |
413 WebInspector.CountersGraph.CounterUI.prototype = { | 510 /** |
414 reset: function() | 511 * @implements {WebInspector.TimelineGrid.Calculator} |
415 { | 512 * @unrestricted |
416 this._range.textContent = ""; | 513 */ |
417 }, | 514 WebInspector.CounterGraphCalculator = class { |
418 | 515 /** |
419 /** | 516 * @param {!WebInspector.TimelineModel} model |
420 * @param {number} minValue | 517 */ |
421 * @param {number} maxValue | 518 constructor(model) { |
422 */ | 519 this._model = model; |
423 setRange: function(minValue, maxValue) | 520 } |
424 { | 521 |
425 var min = this._formatter(minValue); | 522 /** |
426 var max = this._formatter(maxValue); | 523 * @override |
427 this._range.textContent = WebInspector.UIString("[%s\u2009\u2013\u2009%s
]", min, max); | 524 * @return {number} |
428 }, | 525 */ |
429 | 526 paddingLeft() { |
430 /** | 527 return this._paddingLeft; |
431 * @param {!WebInspector.Event} event | 528 } |
432 */ | 529 |
433 _toggleCounterGraph: function(event) | 530 /** |
434 { | 531 * @override |
435 this._value.classList.toggle("hidden", !this._filter.checked()); | 532 * @param {number} time |
436 this._memoryCountersPane.refresh(); | 533 * @return {number} |
437 }, | 534 */ |
438 | 535 computePosition(time) { |
439 /** | 536 return (time - this._minimumBoundary) / this.boundarySpan() * this._workingA
rea + this._paddingLeft; |
440 * @param {number} x | 537 } |
441 * @return {number} | 538 |
442 */ | 539 setWindow(minimumBoundary, maximumBoundary) { |
443 _recordIndexAt: function(x) | 540 this._minimumBoundary = minimumBoundary; |
444 { | 541 this._maximumBoundary = maximumBoundary; |
445 return this.counter.x.upperBound(x * window.devicePixelRatio, null, this
.counter._minimumIndex + 1, this.counter._maximumIndex + 1) - 1; | 542 } |
446 }, | 543 |
447 | 544 /** |
448 /** | 545 * @param {number} clientWidth |
449 * @param {number} x | 546 * @param {number=} paddingLeft |
450 */ | 547 */ |
451 updateCurrentValue: function(x) | 548 setDisplayWindow(clientWidth, paddingLeft) { |
452 { | 549 this._paddingLeft = paddingLeft || 0; |
453 if (!this.visible() || !this.counter.values.length || !this.counter.x) | 550 this._workingArea = clientWidth - WebInspector.CounterGraphCalculator._minWi
dth - this._paddingLeft; |
454 return; | 551 } |
455 var index = this._recordIndexAt(x); | 552 |
456 var value = Number.withThousandsSeparator(this.counter.values[index]); | 553 /** |
457 this._value.textContent = WebInspector.UIString(this._currentValueLabel,
value); | 554 * @override |
458 var y = this.graphYValues[index] / window.devicePixelRatio; | 555 * @param {number} value |
459 this._marker.style.left = x + "px"; | 556 * @param {number=} precision |
460 this._marker.style.top = y + "px"; | 557 * @return {string} |
461 this._marker.classList.remove("hidden"); | 558 */ |
462 }, | 559 formatValue(value, precision) { |
463 | 560 return Number.preciseMillisToString(value - this.zeroTime(), precision); |
464 _clearCurrentValueAndMarker: function() | 561 } |
465 { | 562 |
466 this._value.textContent = ""; | 563 /** |
467 this._marker.classList.add("hidden"); | 564 * @override |
468 }, | 565 * @return {number} |
469 | 566 */ |
470 /** | 567 maximumBoundary() { |
471 * @param {!HTMLCanvasElement} canvas | 568 return this._maximumBoundary; |
472 */ | 569 } |
473 _drawGraph: function(canvas) | 570 |
474 { | 571 /** |
475 var ctx = canvas.getContext("2d"); | 572 * @override |
476 var width = canvas.width; | 573 * @return {number} |
477 var height = canvas.height - 2 * this._verticalPadding; | 574 */ |
478 if (height <= 0) { | 575 minimumBoundary() { |
479 this.graphYValues = []; | 576 return this._minimumBoundary; |
480 return; | 577 } |
481 } | 578 |
482 var originY = this._verticalPadding; | 579 /** |
483 var counter = this.counter; | 580 * @override |
484 var values = counter.values; | 581 * @return {number} |
485 | 582 */ |
486 if (!values.length) | 583 zeroTime() { |
487 return; | 584 return this._model.minimumRecordTime(); |
488 | 585 } |
489 var bounds = counter._calculateBounds(); | 586 |
490 var minValue = bounds.min; | 587 /** |
491 var maxValue = bounds.max; | 588 * @override |
492 this.setRange(minValue, maxValue); | 589 * @return {number} |
493 | 590 */ |
494 if (!this.visible()) | 591 boundarySpan() { |
495 return; | 592 return this._maximumBoundary - this._minimumBoundary; |
496 | 593 } |
497 var yValues = this.graphYValues; | |
498 var maxYRange = maxValue - minValue; | |
499 var yFactor = maxYRange ? height / (maxYRange) : 1; | |
500 | |
501 ctx.save(); | |
502 ctx.lineWidth = window.devicePixelRatio; | |
503 if (ctx.lineWidth % 2) | |
504 ctx.translate(0.5, 0.5); | |
505 ctx.beginPath(); | |
506 var value = values[counter._minimumIndex]; | |
507 var currentY = Math.round(originY + height - (value - minValue) * yFacto
r); | |
508 ctx.moveTo(0, currentY); | |
509 for (var i = counter._minimumIndex; i <= counter._maximumIndex; i++) { | |
510 var x = Math.round(counter.x[i]); | |
511 ctx.lineTo(x, currentY); | |
512 var currentValue = values[i]; | |
513 if (typeof currentValue !== "undefined") | |
514 value = currentValue; | |
515 currentY = Math.round(originY + height - (value - minValue) * yFacto
r); | |
516 ctx.lineTo(x, currentY); | |
517 yValues[i] = currentY; | |
518 } | |
519 yValues.length = i; | |
520 ctx.lineTo(width, currentY); | |
521 ctx.strokeStyle = this.graphColor; | |
522 ctx.stroke(); | |
523 if (counter._limitValue) { | |
524 var limitLineY = Math.round(originY + height - (counter._limitValue
- minValue) * yFactor); | |
525 ctx.moveTo(0, limitLineY); | |
526 ctx.lineTo(width, limitLineY); | |
527 ctx.strokeStyle = this.limitColor; | |
528 ctx.stroke(); | |
529 } | |
530 ctx.closePath(); | |
531 ctx.restore(); | |
532 }, | |
533 | |
534 /** | |
535 * @return {boolean} | |
536 */ | |
537 visible: function() | |
538 { | |
539 return this._filter.checked(); | |
540 } | |
541 }; | 594 }; |
542 | 595 |
543 /** | |
544 * @constructor | |
545 * @param {!WebInspector.TimelineModel} model | |
546 * @implements {WebInspector.TimelineGrid.Calculator} | |
547 */ | |
548 WebInspector.CounterGraphCalculator = function(model) | |
549 { | |
550 this._model = model; | |
551 }; | |
552 | |
553 WebInspector.CounterGraphCalculator._minWidth = 5; | 596 WebInspector.CounterGraphCalculator._minWidth = 5; |
554 | |
555 WebInspector.CounterGraphCalculator.prototype = { | |
556 /** | |
557 * @override | |
558 * @return {number} | |
559 */ | |
560 paddingLeft: function() | |
561 { | |
562 return this._paddingLeft; | |
563 }, | |
564 | |
565 /** | |
566 * @override | |
567 * @param {number} time | |
568 * @return {number} | |
569 */ | |
570 computePosition: function(time) | |
571 { | |
572 return (time - this._minimumBoundary) / this.boundarySpan() * this._work
ingArea + this._paddingLeft; | |
573 }, | |
574 | |
575 setWindow: function(minimumBoundary, maximumBoundary) | |
576 { | |
577 this._minimumBoundary = minimumBoundary; | |
578 this._maximumBoundary = maximumBoundary; | |
579 }, | |
580 | |
581 /** | |
582 * @param {number} clientWidth | |
583 * @param {number=} paddingLeft | |
584 */ | |
585 setDisplayWindow: function(clientWidth, paddingLeft) | |
586 { | |
587 this._paddingLeft = paddingLeft || 0; | |
588 this._workingArea = clientWidth - WebInspector.CounterGraphCalculator._m
inWidth - this._paddingLeft; | |
589 }, | |
590 | |
591 /** | |
592 * @override | |
593 * @param {number} value | |
594 * @param {number=} precision | |
595 * @return {string} | |
596 */ | |
597 formatValue: function(value, precision) | |
598 { | |
599 return Number.preciseMillisToString(value - this.zeroTime(), precision); | |
600 }, | |
601 | |
602 /** | |
603 * @override | |
604 * @return {number} | |
605 */ | |
606 maximumBoundary: function() | |
607 { | |
608 return this._maximumBoundary; | |
609 }, | |
610 | |
611 /** | |
612 * @override | |
613 * @return {number} | |
614 */ | |
615 minimumBoundary: function() | |
616 { | |
617 return this._minimumBoundary; | |
618 }, | |
619 | |
620 /** | |
621 * @override | |
622 * @return {number} | |
623 */ | |
624 zeroTime: function() | |
625 { | |
626 return this._model.minimumRecordTime(); | |
627 }, | |
628 | |
629 /** | |
630 * @override | |
631 * @return {number} | |
632 */ | |
633 boundarySpan: function() | |
634 { | |
635 return this._maximumBoundary - this._minimumBoundary; | |
636 } | |
637 }; | |
OLD | NEW |