| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| 'use strict'; |
| |
| base.requireStylesheet('tracing.tracks.counter_track'); |
| |
| base.require('tracing.tracks.heading_track'); |
| base.require('tracing.color_scheme'); |
| base.require('ui'); |
| |
| base.exportTo('tracing.tracks', function() { |
| |
| var palette = tracing.getColorPalette(); |
| |
| /** |
| * A track that displays a Counter object. |
| * @constructor |
| * @extends {HeadingTrack} |
| */ |
| |
| var CounterTrack = |
| ui.define('counter-track', tracing.tracks.HeadingTrack); |
| |
| CounterTrack.prototype = { |
| |
| __proto__: tracing.tracks.HeadingTrack.prototype, |
| |
| decorate: function(viewport) { |
| tracing.tracks.HeadingTrack.prototype.decorate.call(this, viewport); |
| this.classList.add('counter-track'); |
| this.selectedSamples_ = {}; |
| this.categoryFilter_ = new tracing.Filter(); |
| }, |
| |
| /** |
| * Called by all the addToSelection functions on the created selection |
| * hit objects. Override this function on parent classes to add |
| * context-specific information to the hit. |
| */ |
| decorateHit: function(hit) { |
| }, |
| |
| get counter() { |
| return this.counter_; |
| }, |
| |
| set counter(counter) { |
| this.counter_ = counter; |
| this.heading = counter.name + ': '; |
| }, |
| |
| get categoryFilter() { |
| return this.categoryFilter_; |
| }, |
| |
| set categoryFilter(v) { |
| this.categoryFilter_ = v; |
| }, |
| |
| /** |
| * @return {Object} A sparse, mutable map from sample index to bool. Samples |
| * indices the map that are true are drawn as selected. |
| */ |
| get selectedSamples() { |
| return this.selectedSamples_; |
| }, |
| |
| draw: function(type, viewLWorld, viewRWorld) { |
| switch (type) { |
| case tracing.tracks.DrawType.SLICE: |
| this.drawSlices_(viewLWorld, viewRWorld); |
| break; |
| } |
| }, |
| |
| drawSlices_: function(viewLWorld, viewRWorld) { |
| var ctx = this.context(); |
| var pixelRatio = window.devicePixelRatio || 1; |
| |
| var bounds = this.getBoundingClientRect(); |
| var height = bounds.height * pixelRatio; |
| |
| var counter = this.counter_; |
| |
| // Culling parametrs. |
| var vp = this.viewport; |
| var pixWidth = vp.xViewVectorToWorld(1); |
| |
| // Drop sampels that are less than skipDistancePix apart. |
| var skipDistancePix = 1; |
| var skipDistanceWorld = vp.xViewVectorToWorld(skipDistancePix); |
| |
| // Begin rendering in world space. |
| ctx.save(); |
| vp.applyTransformToCanvas(ctx); |
| |
| // Figure out where drawing should begin. |
| var numSeries = counter.numSeries; |
| var numSamples = counter.numSamples; |
| var startIndex = base.findLowIndexInSortedArray( |
| counter.timestamps, |
| function(x) { return x; }, |
| viewLWorld); |
| |
| startIndex = startIndex - 1 > 0 ? startIndex - 1 : 0; |
| // Draw indices one by one until we fall off the viewRWorld. |
| var yScale = height / counter.maxTotal; |
| for (var seriesIndex = counter.numSeries - 1; |
| seriesIndex >= 0; seriesIndex--) { |
| var colorId = counter.series[seriesIndex].color; |
| ctx.fillStyle = palette[colorId]; |
| ctx.beginPath(); |
| |
| // Set iLast and xLast such that the first sample we draw is the |
| // startIndex sample. |
| var iLast = startIndex - 1; |
| var xLast = iLast >= 0 ? |
| counter.timestamps[iLast] - skipDistanceWorld : -1; |
| var yLastView = height; |
| |
| // Iterate over samples from iLast onward until we either fall off the |
| // viewRWorld or we run out of samples. To avoid drawing too much, after |
| // drawing a sample at xLast, skip subsequent samples that are less than |
| // skipDistanceWorld from xLast. |
| var hasMoved = false; |
| |
| while (true) { |
| var i = iLast + 1; |
| if (i >= numSamples) { |
| ctx.lineTo(xLast, yLastView); |
| ctx.lineTo(xLast + 8 * pixWidth, yLastView); |
| ctx.lineTo(xLast + 8 * pixWidth, height); |
| break; |
| } |
| |
| var x = counter.timestamps[i]; |
| var y = counter.totals[i * numSeries + seriesIndex]; |
| var yView = height - (yScale * y); |
| |
| if (x > viewRWorld) { |
| ctx.lineTo(x, yLastView); |
| ctx.lineTo(x, height); |
| break; |
| } |
| |
| if (i + 1 < numSamples) { |
| var xNext = counter.timestamps[i + 1]; |
| if (xNext - xLast <= skipDistanceWorld && xNext < viewRWorld) { |
| iLast = i; |
| continue; |
| } |
| } |
| |
| if (!hasMoved) { |
| ctx.moveTo(viewLWorld, height); |
| hasMoved = true; |
| } |
| |
| if (x - xLast < skipDistanceWorld) { |
| // We know that xNext > xLast + skipDistanceWorld, so we can |
| // safely move this sample's x over that much without passing |
| // xNext. This ensure that the previous sample is visible when |
| // zoomed out very far. |
| x = xLast + skipDistanceWorld; |
| } |
| ctx.lineTo(x, yLastView); |
| ctx.lineTo(x, yView); |
| |
| iLast = i; |
| xLast = x; |
| yLastView = yView; |
| } |
| ctx.closePath(); |
| ctx.fill(); |
| } |
| |
| ctx.fillStyle = 'rgba(255, 0, 0, 1)'; |
| for (var i in this.selectedSamples_) { |
| if (!this.selectedSamples_[i]) |
| continue; |
| |
| var x = counter.timestamps[i]; |
| for (var seriesIndex = counter.numSeries - 1; |
| seriesIndex >= 0; seriesIndex--) { |
| var y = counter.totals[i * numSeries + seriesIndex]; |
| var yView = height - (yScale * y); |
| ctx.fillRect(x - pixWidth, yView - 1, 3 * pixWidth, 3); |
| } |
| } |
| ctx.restore(); |
| }, |
| |
| addIntersectingItemsInRangeToSelectionInWorldSpace: function( |
| loWX, hiWX, viewPixWidthWorld, selection) { |
| |
| function getSampleWidth(x, i) { |
| if (i === counter.timestamps.length - 1) |
| return 0; |
| return counter.timestamps[i + 1] - counter.timestamps[i]; |
| } |
| |
| var counter = this.counter_; |
| var iLo = base.findLowIndexInSortedIntervals(counter.timestamps, |
| function(x) { return x; }, |
| getSampleWidth, |
| loWX); |
| var iHi = base.findLowIndexInSortedIntervals(counter.timestamps, |
| function(x) { return x; }, |
| getSampleWidth, |
| hiWX); |
| |
| // Iterate over every sample intersecting.. |
| for (var i = iLo; i <= iHi; i++) { |
| if (i < 0) |
| continue; |
| if (i >= counter.timestamps.length) |
| continue; |
| |
| // TODO(nduca): Pick the seriesIndexHit based on the loY - hiY values. |
| var hit = selection.addCounterSample(this, this.counter, i); |
| this.decorateHit(hit); |
| } |
| }, |
| |
| addAllObjectsMatchingFilterToSelection: function(filter, selection) { |
| } |
| }; |
| |
| return { |
| CounterTrack: CounterTrack |
| }; |
| }); |