OLD | NEW |
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 | 5 |
6 /** | 6 /** |
7 * @fileoverview Renders an array of slices into the provided div, | 7 * @fileoverview Renders an array of slices into the provided div, |
8 * using a child canvas element. Uses a FastRectRenderer to draw only | 8 * using a child canvas element. Uses a FastRectRenderer to draw only |
9 * the visible slices. | 9 * the visible slices. |
10 */ | 10 */ |
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
59 var textWidthMap = { }; | 59 var textWidthMap = { }; |
60 function quickMeasureText(ctx, text) { | 60 function quickMeasureText(ctx, text) { |
61 var w = textWidthMap[text]; | 61 var w = textWidthMap[text]; |
62 if (!w) { | 62 if (!w) { |
63 w = ctx.measureText(text).width; | 63 w = ctx.measureText(text).width; |
64 textWidthMap[text] = w; | 64 textWidthMap[text] = w; |
65 } | 65 } |
66 return w; | 66 return w; |
67 } | 67 } |
68 | 68 |
69 function addTrack(thisTrack, slices) { | |
70 var track = new TimelineSliceTrack(); | |
71 | |
72 track.heading = ''; | |
73 track.slices = slices; | |
74 track.viewport = thisTrack.viewport_; | |
75 | |
76 thisTrack.tracks_.push(track); | |
77 thisTrack.appendChild(track); | |
78 } | |
79 | |
80 /** | 69 /** |
81 * Generic base class for timeline tracks | 70 * A generic track that contains other tracks as its children. |
| 71 * @constructor |
82 */ | 72 */ |
83 TimelineThreadTrack = cr.ui.define('div'); | 73 var TimelineContainerTrack = cr.ui.define('div'); |
84 TimelineThreadTrack.prototype = { | 74 TimelineContainerTrack.prototype = { |
85 __proto__: HTMLDivElement.prototype, | 75 __proto__: HTMLDivElement.prototype, |
86 | 76 |
87 decorate: function() { | 77 decorate: function() { |
88 this.className = 'timeline-thread-track'; | 78 this.tracks_ = []; |
89 }, | 79 }, |
90 | 80 |
91 set thread(thread) { | 81 detach: function() { |
92 this.thread_ = thread; | 82 for (var i = 0; i < this.tracks_.length; i++) |
93 this.updateChildTracks_(); | 83 this.tracks_[i].detach(); |
94 }, | 84 }, |
95 | 85 |
96 /** | 86 get viewport() { |
97 * @return {string} A human-readable name for the track. | 87 return this.viewport_; |
98 */ | |
99 get heading() { | |
100 if (!this.thread_) | |
101 return ''; | |
102 var tname = this.thread_.name || this.thread_.tid; | |
103 return this.thread_.parent.pid + ': ' + | |
104 tname + ':'; | |
105 }, | |
106 | |
107 set headingWidth(width) { | |
108 for (var i = 0; i < this.tracks_.length; i++) | |
109 this.tracks_[i].headingWidth = width; | |
110 }, | 88 }, |
111 | 89 |
112 set viewport(v) { | 90 set viewport(v) { |
113 this.viewport_ = v; | 91 this.viewport_ = v; |
114 for (var i = 0; i < this.tracks_.length; i++) | 92 for (var i = 0; i < this.tracks_.length; i++) |
115 this.tracks_[i].viewport = v; | 93 this.tracks_[i].viewport = v; |
116 this.invalidate(); | 94 this.updateChildTracks_(); |
117 }, | |
118 | |
119 invalidate: function() { | |
120 if (this.parentNode) | |
121 this.parentNode.invalidate(); | |
122 }, | |
123 | |
124 onResize: function() { | |
125 for (var i = 0; i < this.tracks_.length; i++) | |
126 this.tracks_[i].onResize(); | |
127 }, | 95 }, |
128 | 96 |
129 get firstCanvas() { | 97 get firstCanvas() { |
130 if (this.tracks_.length) | 98 if (this.tracks_.length) |
131 return this.tracks_[0].firstCanvas; | 99 return this.tracks_[0].firstCanvas; |
132 return undefined; | 100 return undefined; |
133 }, | 101 }, |
134 | 102 |
135 redraw: function() { | |
136 for (var i = 0; i < this.tracks_.length; i++) | |
137 this.tracks_[i].redraw(); | |
138 }, | |
139 | |
140 updateChildTracks_: function() { | |
141 this.textContent = ''; | |
142 this.tracks_ = []; | |
143 if (this.thread_) { | |
144 for (var srI = 0; srI < this.thread_.nonNestedSubRows.length; ++srI) { | |
145 addTrack(this, this.thread_.nonNestedSubRows[srI]); | |
146 } | |
147 for (var srI = 0; srI < this.thread_.subRows.length; ++srI) { | |
148 addTrack(this, this.thread_.subRows[srI]); | |
149 } | |
150 if (this.tracks_.length > 0) { | |
151 this.tracks_[0].heading = this.heading; | |
152 this.tracks_[0].tooltip = 'pid: ' + this.thread_.parent.pid + | |
153 ', tid: ' + this.thread_.tid + | |
154 (this.thread_.name ? ', name: ' + this.thread_.name : ''); | |
155 } | |
156 } | |
157 }, | |
158 | |
159 /** | 103 /** |
160 * Picks a slice, if any, at a given location. | 104 * Picks a slice, if any, at a given location. |
161 * @param {number} wX X location to search at, in worldspace. | 105 * @param {number} wX X location to search at, in worldspace. |
162 * @param {number} wY Y location to search at, in offset space. | 106 * @param {number} wY Y location to search at, in offset space. |
163 * offset space. | 107 * offset space. |
164 * @param {function():*} onHitCallback Callback to call with the slice, | 108 * @param {function():*} onHitCallback Callback to call with the slice, |
165 * if one is found. | 109 * if one is found. |
166 * @return {boolean} true if a slice was found, otherwise false. | 110 * @return {boolean} true if a slice was found, otherwise false. |
167 */ | 111 */ |
168 pick: function(wX, wY, onHitCallback) { | 112 pick: function(wX, wY, onHitCallback) { |
(...skipping 23 matching lines...) Expand all Loading... |
192 var trackClientRect = this.tracks_[i].getBoundingClientRect(); | 136 var trackClientRect = this.tracks_[i].getBoundingClientRect(); |
193 var a = Math.max(loY, trackClientRect.top); | 137 var a = Math.max(loY, trackClientRect.top); |
194 var b = Math.min(hiY, trackClientRect.bottom); | 138 var b = Math.min(hiY, trackClientRect.bottom); |
195 if (a <= b) | 139 if (a <= b) |
196 this.tracks_[i].pickRange(loWX, hiWX, loY, hiY, onHitCallback); | 140 this.tracks_[i].pickRange(loWX, hiWX, loY, hiY, onHitCallback); |
197 } | 141 } |
198 } | 142 } |
199 }; | 143 }; |
200 | 144 |
201 /** | 145 /** |
202 * Creates a new timeline track div element | 146 * Visualizes a TimelineThread using a series of of TimelineSliceTracks. |
| 147 * @constructor |
| 148 */ |
| 149 var TimelineThreadTrack = cr.ui.define(TimelineContainerTrack); |
| 150 TimelineThreadTrack.prototype = { |
| 151 __proto__: TimelineContainerTrack.prototype, |
| 152 |
| 153 decorate: function() { |
| 154 this.classList.add('timeline-thread-track'); |
| 155 }, |
| 156 |
| 157 get thread(thread) { |
| 158 return this.thread_; |
| 159 }, |
| 160 |
| 161 set thread(thread) { |
| 162 this.thread_ = thread; |
| 163 this.updateChildTracks_(); |
| 164 }, |
| 165 |
| 166 get tooltip() { |
| 167 return this.tooltip_; |
| 168 }, |
| 169 |
| 170 set tooltip(value) { |
| 171 this.tooltip_ = value; |
| 172 this.updateChildTracks_(); |
| 173 }, |
| 174 |
| 175 get heading() { |
| 176 return this.heading_; |
| 177 }, |
| 178 |
| 179 set heading(h) { |
| 180 this.heading_ = h; |
| 181 this.updateChildTracks_(); |
| 182 }, |
| 183 |
| 184 get headingWidth() { |
| 185 return this.headingWidth_; |
| 186 }, |
| 187 |
| 188 set headingWidth(width) { |
| 189 this.headingWidth_ = width; |
| 190 this.updateChildTracks_(); |
| 191 }, |
| 192 |
| 193 addTrack_: function(slices) { |
| 194 var track = new TimelineSliceTrack(); |
| 195 track.heading = ''; |
| 196 track.slices = slices; |
| 197 track.headingWidth = this.headingWidth_; |
| 198 track.viewport = this.viewport_; |
| 199 |
| 200 this.tracks_.push(track); |
| 201 this.appendChild(track); |
| 202 }, |
| 203 |
| 204 updateChildTracks_: function() { |
| 205 this.detach(); |
| 206 this.textContent = ''; |
| 207 this.tracks_ = []; |
| 208 if (this.thread_) { |
| 209 for (var srI = 0; srI < this.thread_.nonNestedSubRows.length; ++srI) { |
| 210 this.addTrack_(this.thread_.nonNestedSubRows[srI]); |
| 211 } |
| 212 for (var srI = 0; srI < this.thread_.subRows.length; ++srI) { |
| 213 this.addTrack_(this.thread_.subRows[srI]); |
| 214 } |
| 215 if (this.tracks_.length > 0) { |
| 216 this.tracks_[0].heading = this.heading_; |
| 217 this.tracks_[0].tooltip = this.tooltip_; |
| 218 } |
| 219 } |
| 220 } |
| 221 }; |
| 222 |
| 223 /** |
| 224 * A canvas-based track constructed. Provides the basic heading and |
| 225 * invalidation-managment infrastructure. Subclasses must implement drawing |
| 226 * and picking code. |
203 * @constructor | 227 * @constructor |
204 * @extends {HTMLDivElement} | 228 * @extends {HTMLDivElement} |
205 */ | 229 */ |
206 TimelineSliceTrack = cr.ui.define('div'); | 230 var CanvasBasedTrack = cr.ui.define('div'); |
207 | 231 |
208 TimelineSliceTrack.prototype = { | 232 CanvasBasedTrack.prototype = { |
209 __proto__: HTMLDivElement.prototype, | 233 __proto__: HTMLDivElement.prototype, |
210 | 234 |
211 decorate: function() { | 235 decorate: function() { |
212 this.className = 'timeline-slice-track'; | 236 this.className = 'timeline-canvas-based-track'; |
213 this.slices_ = null; | 237 this.slices_ = null; |
214 | 238 |
215 this.headingDiv_ = document.createElement('div'); | 239 this.headingDiv_ = document.createElement('div'); |
216 this.headingDiv_.className = 'timeline-slice-track-title'; | 240 this.headingDiv_.className = 'timeline-canvas-based-track-title'; |
217 this.appendChild(this.headingDiv_); | 241 this.appendChild(this.headingDiv_); |
218 | 242 |
219 this.canvasContainer_ = document.createElement('div'); | 243 this.canvasContainer_ = document.createElement('div'); |
220 this.canvasContainer_.className = 'timeline-slice-track-canvas-container'; | 244 this.canvasContainer_.className = |
| 245 'timeline-canvas-based-track-canvas-container'; |
221 this.appendChild(this.canvasContainer_); | 246 this.appendChild(this.canvasContainer_); |
222 this.canvas_ = document.createElement('canvas'); | 247 this.canvas_ = document.createElement('canvas'); |
223 this.canvas_.className = 'timeline-slice-track-canvas'; | 248 this.canvas_.className = 'timeline-canvas-based-track-canvas'; |
224 this.canvasContainer_.appendChild(this.canvas_); | 249 this.canvasContainer_.appendChild(this.canvas_); |
225 | 250 |
226 this.ctx_ = this.canvas_.getContext('2d'); | 251 this.ctx_ = this.canvas_.getContext('2d'); |
227 }, | 252 }, |
228 | 253 |
| 254 detach: function() { |
| 255 if (this.viewport_) |
| 256 this.viewport_.removeEventListener('change', |
| 257 this.viewportChangeBoundToThis_); |
| 258 }, |
| 259 |
229 set headingWidth(width) { | 260 set headingWidth(width) { |
230 this.headingDiv_.style.width = width; | 261 this.headingDiv_.style.width = width; |
231 }, | 262 }, |
232 | 263 |
233 get heading() { | 264 get heading() { |
234 return this.headingDiv_.textContent; | 265 return this.headingDiv_.textContent; |
235 }, | 266 }, |
236 | 267 |
237 set heading(text) { | 268 set heading(text) { |
238 this.headingDiv_.textContent = text; | 269 this.headingDiv_.textContent = text; |
239 }, | 270 }, |
240 | 271 |
241 set tooltip(text) { | 272 set tooltip(text) { |
242 this.headingDiv_.title = text; | 273 this.headingDiv_.title = text; |
243 }, | 274 }, |
244 | 275 |
| 276 get viewport() { |
| 277 return this.viewport_; |
| 278 }, |
| 279 |
| 280 set viewport(v) { |
| 281 this.viewport_ = v; |
| 282 if (this.viewport_) |
| 283 this.viewport_.removeEventListener('change', |
| 284 this.viewportChangeBoundToThis_); |
| 285 this.viewport_ = v; |
| 286 if (this.viewport_) { |
| 287 this.viewportChangeBoundToThis_ = this.viewportChange_.bind(this); |
| 288 this.viewport_.addEventListener('change', |
| 289 this.viewportChangeBoundToThis_); |
| 290 } |
| 291 this.invalidate(); |
| 292 }, |
| 293 |
| 294 viewportChange_: function() { |
| 295 this.invalidate(); |
| 296 }, |
| 297 |
| 298 invalidate: function() { |
| 299 if (this.rafPending_) |
| 300 return; |
| 301 webkitRequestAnimationFrame(function() { |
| 302 this.rafPending_ = false; |
| 303 if (!this.viewport_) |
| 304 return; |
| 305 |
| 306 if (this.canvas_.width != this.canvasContainer_.clientWidth) |
| 307 this.canvas_.width = this.canvasContainer_.clientWidth; |
| 308 if (this.canvas_.height != this.canvasContainer_.clientHeight) |
| 309 this.canvas_.height = this.canvasContainer_.clientHeight; |
| 310 |
| 311 this.redraw(); |
| 312 }.bind(this), this); |
| 313 this.rafPending_ = true; |
| 314 }, |
| 315 |
| 316 get firstCanvas() { |
| 317 return this.canvas_; |
| 318 } |
| 319 |
| 320 }; |
| 321 |
| 322 /** |
| 323 * A track that displays an array of TimelineSlice objects. |
| 324 * @constructor |
| 325 * @extends {CanvasBasedTrack} |
| 326 */ |
| 327 |
| 328 var TimelineSliceTrack = cr.ui.define(CanvasBasedTrack); |
| 329 |
| 330 TimelineSliceTrack.prototype = { |
| 331 |
| 332 __proto__: CanvasBasedTrack.prototype, |
| 333 |
| 334 decorate: function() { |
| 335 this.classList.add('timeline-slice-track'); |
| 336 }, |
| 337 |
| 338 get slices() { |
| 339 return this.slices_; |
| 340 }, |
| 341 |
245 set slices(slices) { | 342 set slices(slices) { |
246 this.slices_ = slices; | 343 this.slices_ = slices; |
247 this.invalidate(); | 344 this.invalidate(); |
248 }, | 345 }, |
249 | 346 |
250 set viewport(v) { | |
251 this.viewport_ = v; | |
252 this.invalidate(); | |
253 }, | |
254 | |
255 invalidate: function() { | |
256 if (this.parentNode) | |
257 this.parentNode.invalidate(); | |
258 }, | |
259 | |
260 get firstCanvas() { | |
261 return this.canvas_; | |
262 }, | |
263 | |
264 onResize: function() { | |
265 this.canvas_.width = this.canvasContainer_.clientWidth; | |
266 this.canvas_.height = this.canvasContainer_.clientHeight; | |
267 this.invalidate(); | |
268 }, | |
269 | |
270 redraw: function() { | 347 redraw: function() { |
271 if (!this.viewport_) | |
272 return; | |
273 var ctx = this.ctx_; | 348 var ctx = this.ctx_; |
274 var canvasW = this.canvas_.width; | 349 var canvasW = this.canvas_.width; |
275 var canvasH = this.canvas_.height; | 350 var canvasH = this.canvas_.height; |
276 | 351 |
277 ctx.clearRect(0, 0, canvasW, canvasH); | 352 ctx.clearRect(0, 0, canvasW, canvasH); |
278 | 353 |
279 // culling... | 354 // Culling parameters. |
280 var vp = this.viewport_; | 355 var vp = this.viewport_; |
281 var pixWidth = vp.xViewVectorToWorld(1); | 356 var pixWidth = vp.xViewVectorToWorld(1); |
282 var viewLWorld = vp.xViewToWorld(0); | 357 var viewLWorld = vp.xViewToWorld(0); |
283 var viewRWorld = vp.xViewToWorld(canvasW); | 358 var viewRWorld = vp.xViewToWorld(canvasW); |
284 | 359 |
285 // Draw grid without a transform because the scale | 360 // Draw grid without a transform because the scale |
286 // affects line width. | 361 // affects line width. |
287 if (vp.gridEnabled) { | 362 if (vp.gridEnabled) { |
288 var x = vp.gridTimebase; | 363 var x = vp.gridTimebase; |
289 ctx.beginPath(); | 364 ctx.beginPath(); |
290 while (x < viewRWorld) { | 365 while (x < viewRWorld) { |
291 if (x >= viewLWorld) { | 366 if (x >= viewLWorld) { |
292 // Do conversion to viewspace here rather than on | 367 // Do conversion to viewspace here rather than on |
293 // x to avoid precision issues. | 368 // x to avoid precision issues. |
294 var vx = vp.xWorldToView(x); | 369 var vx = vp.xWorldToView(x); |
295 ctx.moveTo(vx, 0); | 370 ctx.moveTo(vx, 0); |
296 ctx.lineTo(vx, canvasH); | 371 ctx.lineTo(vx, canvasH); |
297 } | 372 } |
298 x += vp.gridStep; | 373 x += vp.gridStep; |
299 } | 374 } |
300 ctx.strokeStyle = 'rgba(255,0,0,0.25)'; | 375 ctx.strokeStyle = 'rgba(255,0,0,0.25)'; |
301 ctx.stroke(); | 376 ctx.stroke(); |
302 } | 377 } |
303 | 378 |
304 // begin rendering in world space | 379 // Begin rendering in world space. |
305 ctx.save(); | 380 ctx.save(); |
306 vp.applyTransformToCanavs(ctx); | 381 vp.applyTransformToCanavs(ctx); |
307 | 382 |
308 // tracks | 383 // Slices. |
309 var tr = new tracing.FastRectRenderer(ctx, viewLWorld, 2 * pixWidth, | 384 var tr = new tracing.FastRectRenderer(ctx, viewLWorld, 2 * pixWidth, |
310 2 * pixWidth, viewRWorld, pallette); | 385 2 * pixWidth, viewRWorld, pallette); |
311 tr.setYandH(0, canvasH); | 386 tr.setYandH(0, canvasH); |
312 var slices = this.slices_; | 387 var slices = this.slices_; |
313 for (var i = 0; i < slices.length; ++i) { | 388 for (var i = 0; i < slices.length; ++i) { |
314 var slice = slices[i]; | 389 var slice = slices[i]; |
315 var x = slice.start; | 390 var x = slice.start; |
316 // Less than 0.001 causes short events to disappear when zoomed in. | 391 // Less than 0.001 causes short events to disappear when zoomed in. |
317 var w = Math.max(slice.duration, 0.001); | 392 var w = Math.max(slice.duration, 0.001); |
318 var colorId; | 393 var colorId; |
(...skipping 17 matching lines...) Expand all Loading... |
336 ctx.lineTo(x, 0); | 411 ctx.lineTo(x, 0); |
337 ctx.lineTo(x + (4 * pixWidth), canvasH); | 412 ctx.lineTo(x + (4 * pixWidth), canvasH); |
338 ctx.closePath(); | 413 ctx.closePath(); |
339 ctx.fill(); | 414 ctx.fill(); |
340 } | 415 } |
341 } | 416 } |
342 } | 417 } |
343 tr.flush(); | 418 tr.flush(); |
344 ctx.restore(); | 419 ctx.restore(); |
345 | 420 |
346 // labels | 421 // Labels. |
347 ctx.textAlign = 'center'; | 422 ctx.textAlign = 'center'; |
348 ctx.textBaseline = 'top'; | 423 ctx.textBaseline = 'top'; |
349 ctx.font = '10px sans-serif'; | 424 ctx.font = '10px sans-serif'; |
350 ctx.strokeStyle = 'rgb(0,0,0)'; | 425 ctx.strokeStyle = 'rgb(0,0,0)'; |
351 ctx.fillStyle = 'rgb(0,0,0)'; | 426 ctx.fillStyle = 'rgb(0,0,0)'; |
352 var quickDiscardThresshold = pixWidth * 20; // dont render until 20px wide | 427 var quickDiscardThresshold = pixWidth * 20; // dont render until 20px wide |
353 for (var i = 0; i < slices.length; ++i) { | 428 for (var i = 0; i < slices.length; ++i) { |
354 var slice = slices[i]; | 429 var slice = slices[i]; |
355 if (slice.duration > quickDiscardThresshold) { | 430 if (slice.duration > quickDiscardThresshold) { |
356 var title = slice.title; | 431 var title = slice.title; |
(...skipping 109 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
466 var index = this.indexOfSlice_(slice); | 541 var index = this.indexOfSlice_(slice); |
467 if (index == 0) | 542 if (index == 0) |
468 return undefined; | 543 return undefined; |
469 else if ((index != undefined) && (index > 0)) | 544 else if ((index != undefined) && (index > 0)) |
470 index--; | 545 index--; |
471 return index != undefined ? this.slices_[index] : undefined; | 546 return index != undefined ? this.slices_[index] : undefined; |
472 } | 547 } |
473 | 548 |
474 }; | 549 }; |
475 | 550 |
| 551 /** |
| 552 * A track that displays a TimelineCounter object. |
| 553 * @constructor |
| 554 * @extends {CanvasBasedTrack} |
| 555 */ |
| 556 |
| 557 var TimelineCounterTrack = cr.ui.define(CanvasBasedTrack); |
| 558 |
| 559 TimelineCounterTrack.prototype = { |
| 560 |
| 561 __proto__: CanvasBasedTrack.prototype, |
| 562 |
| 563 decorate: function() { |
| 564 this.classList.add('timeline-counter-track'); |
| 565 }, |
| 566 |
| 567 get counter() { |
| 568 return this.counter_; |
| 569 }, |
| 570 |
| 571 set counter(counter) { |
| 572 this.counter_ = counter; |
| 573 this.invalidate(); |
| 574 }, |
| 575 |
| 576 redraw: function() { |
| 577 var ctr = this.counter_; |
| 578 var ctx = this.ctx_; |
| 579 var canvasW = this.canvas_.width; |
| 580 var canvasH = this.canvas_.height; |
| 581 |
| 582 ctx.clearRect(0, 0, canvasW, canvasH); |
| 583 |
| 584 // Culling parametrs. |
| 585 var vp = this.viewport_; |
| 586 var pixWidth = vp.xViewVectorToWorld(1); |
| 587 var viewLWorld = vp.xViewToWorld(0); |
| 588 var viewRWorld = vp.xViewToWorld(canvasW); |
| 589 |
| 590 // Drop sampels that are less than skipDistancePix apart. |
| 591 var skipDistancePix = 16; |
| 592 var skipDistanceWorld = vp.xViewVectorToWorld(skipDistancePix); |
| 593 |
| 594 // Begin rendering in world space. |
| 595 ctx.save(); |
| 596 vp.applyTransformToCanavs(ctx); |
| 597 |
| 598 // Figure out where drawing should begin. |
| 599 var numSeries = ctr.numSeries; |
| 600 var numSamples = ctr.numSamples; |
| 601 var startIndex = tracing.findLowIndexInSortedArray(ctr.timestamps, |
| 602 function() { |
| 603 }, |
| 604 viewLWorld); |
| 605 |
| 606 // Draw indices one by one until we fall off the viewRWorld. |
| 607 var yScale = canvasH / ctr.maxTotal; |
| 608 for (var seriesIndex = ctr.numSeries - 1; |
| 609 seriesIndex >= 0; seriesIndex--) { |
| 610 var colorId = ctr.seriesColors[seriesIndex]; |
| 611 ctx.fillStyle = pallette[colorId]; |
| 612 ctx.beginPath(); |
| 613 |
| 614 // Set iLast and xLast such that the first sample we draw is the |
| 615 // startIndex sample. |
| 616 var iLast = startIndex - 1; |
| 617 var xLast = iLast >= 0 ? ctr.timestamps[iLast] - skipDistanceWorld : -1; |
| 618 var yLastView = canvasH; |
| 619 |
| 620 // Iterate over samples from iLast onward until we either fall off the |
| 621 // viewRWorld or we run out of samples. To avoid drawing too much, after |
| 622 // drawing a sample at xLast, skip subsequent samples that are less than |
| 623 // skipDistanceWorld from xLast. |
| 624 var hasMoved = false; |
| 625 while (true) { |
| 626 var i = iLast + 1; |
| 627 if (i >= numSamples) { |
| 628 ctx.lineTo(xLast, yLastView); |
| 629 ctx.lineTo(xLast + 8 * pixWidth, yLastView); |
| 630 ctx.lineTo(xLast + 8 * pixWidth, canvasH); |
| 631 break; |
| 632 } |
| 633 |
| 634 var x = ctr.timestamps[i]; |
| 635 |
| 636 var y = ctr.totals[i * numSeries + seriesIndex]; |
| 637 var yView = canvasH - (yScale * y); |
| 638 |
| 639 if (x > viewRWorld) { |
| 640 ctx.lineTo(x, yLastView); |
| 641 ctx.lineTo(x, canvasH); |
| 642 break; |
| 643 } |
| 644 |
| 645 if (x - xLast < skipDistanceWorld) { |
| 646 iLast = i; |
| 647 continue; |
| 648 } |
| 649 |
| 650 if (!hasMoved) { |
| 651 ctx.moveTo(viewLWorld, canvasH); |
| 652 hasMoved = true; |
| 653 } |
| 654 ctx.lineTo(x, yLastView); |
| 655 ctx.lineTo(x, yView); |
| 656 iLast = i; |
| 657 xLast = x; |
| 658 yLastView = yView; |
| 659 } |
| 660 ctx.closePath(); |
| 661 ctx.fill(); |
| 662 } |
| 663 ctx.restore(); |
| 664 }, |
| 665 |
| 666 /** |
| 667 * Picks a slice, if any, at a given location. |
| 668 * @param {number} wX X location to search at, in worldspace. |
| 669 * @param {number} wY Y location to search at, in offset space. |
| 670 * offset space. |
| 671 * @param {function():*} onHitCallback Callback to call with the slice, |
| 672 * if one is found. |
| 673 * @return {boolean} true if a slice was found, otherwise false. |
| 674 */ |
| 675 pick: function(wX, wY, onHitCallback) { |
| 676 }, |
| 677 |
| 678 /** |
| 679 * Finds slices intersecting the given interval. |
| 680 * @param {number} loWX Lower X bound of the interval to search, in |
| 681 * worldspace. |
| 682 * @param {number} hiWX Upper X bound of the interval to search, in |
| 683 * worldspace. |
| 684 * @param {number} loY Lower Y bound of the interval to search, in |
| 685 * offset space. |
| 686 * @param {number} hiY Upper Y bound of the interval to search, in |
| 687 * offset space. |
| 688 * @param {function():*} onHitCallback Function to call for each slice |
| 689 * intersecting the interval. |
| 690 */ |
| 691 pickRange: function(loWX, hiWX, loY, hiY, onHitCallback) { |
| 692 } |
| 693 |
| 694 }; |
| 695 |
476 return { | 696 return { |
| 697 TimelineCounterTrack: TimelineCounterTrack, |
477 TimelineSliceTrack: TimelineSliceTrack, | 698 TimelineSliceTrack: TimelineSliceTrack, |
478 TimelineThreadTrack: TimelineThreadTrack | 699 TimelineThreadTrack: TimelineThreadTrack |
479 }; | 700 }; |
480 }); | 701 }); |
OLD | NEW |