// 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('tracks.counter_track'); base.require('tracks.canvas_based_track'); base.require('color_scheme'); base.require('ui'); base.exportTo('tracing.tracks', function() { var palette = tracing.getColorPalette(); /** * A track that displays a Counter object. * @constructor * @extends {CanvasBasedTrack} */ var CounterTrack = tracing.ui.define(tracing.tracks.CanvasBasedTrack); CounterTrack.prototype = { __proto__: tracing.tracks.CanvasBasedTrack.prototype, decorate: function() { this.classList.add('counter-track'); this.addControlButtonElements_(false); 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.invalidate(); this.updateVisibility_(); }, set categoryFilter(v) { this.categoryFilter_ = v; this.updateVisibility_(); }, /** * @return {Object} A sparse, mutable map from sample index to bool. Samples * indices the map that are true are drawn as selected. Callers that mutate * the map must manually call invalidate on the track to trigger a redraw. */ get selectedSamples() { return this.selectedSamples_; }, updateVisibility_: function() { this.visible = (this.counter_ && this.categoryFilter_.matchCounter(this.counter_)); }, redraw: function() { var ctr = this.counter_; var ctx = this.ctx_; var canvasW = this.canvas_.width; var canvasH = this.canvas_.height; ctx.clearRect(0, 0, canvasW, canvasH); // Culling parametrs. var vp = this.viewport_; var pixWidth = vp.xViewVectorToWorld(1); var viewLWorld = vp.xViewToWorld(0); var viewRWorld = vp.xViewToWorld(canvasW); // Give the viewport a chance to draw onto this canvas. vp.drawUnderContent(ctx, viewLWorld, viewRWorld, canvasH); // 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 = ctr.numSeries; var numSamples = ctr.numSamples; var startIndex = tracing.findLowIndexInSortedArray(ctr.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 = canvasH / ctr.maxTotal; for (var seriesIndex = ctr.numSeries - 1; seriesIndex >= 0; seriesIndex--) { var colorId = ctr.seriesColors[seriesIndex]; 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 ? ctr.timestamps[iLast] - skipDistanceWorld : -1; var yLastView = canvasH; // 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, canvasH); break; } var x = ctr.timestamps[i]; var y = ctr.totals[i * numSeries + seriesIndex]; var yView = canvasH - (yScale * y); if (x > viewRWorld) { ctx.lineTo(x, yLastView); ctx.lineTo(x, canvasH); break; } if (i + 1 < numSamples) { var xNext = ctr.timestamps[i + 1]; if (xNext - xLast <= skipDistanceWorld && xNext < viewRWorld) { iLast = i; continue; } } if (!hasMoved) { ctx.moveTo(viewLWorld, canvasH); 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 = ctr.timestamps[i]; for (var seriesIndex = ctr.numSeries - 1; seriesIndex >= 0; seriesIndex--) { var y = ctr.totals[i * numSeries + seriesIndex]; var yView = canvasH - (yScale * y); ctx.fillRect(x - pixWidth, yView - 1, 3 * pixWidth, 3); } } ctx.restore(); // Give the viewport a chance to draw over this canvas. vp.drawOverContent(ctx, viewLWorld, viewRWorld, canvasH); }, /** * Adds items intersecting a point to a selection. * @param {number} vX X location to search at, in viewspace. * @param {number} vY Y location to search at, in viewspace. * @param {Selection} selection Selection to which to add hits. * @return {boolean} true if a slice was found, otherwise false. */ addIntersectingItemsToSelection: function(vX, vY, selection) { var clientRect = this.getBoundingClientRect(); if (vY < clientRect.top || vY >= clientRect.bottom) return false; var pixelRatio = window.devicePixelRatio || 1; var wX = this.viewport_.xViewVectorToWorld(vX * devicePixelRatio); var ctr = this.counter_; if (vX < this.counter_.timestamps[0]) return false; var i = tracing.findLowIndexInSortedArray(ctr.timestamps, function(x) { return x; }, wX); if (i < 0 || i >= ctr.timestamps.length) return false; // Sample i is going to either be exactly at wX or slightly above it, // E.g. asking for 7.5 in [7,8] gives i=1. So bump i back by 1 if needed. if (i > 0 && wX > this.counter_.timestamps[i - 1]) i--; // Some preliminaries. var canvasH = this.getBoundingClientRect().height; var yScale = canvasH / ctr.maxTotal; /* // Figure out which sample we hit var seriesIndexHit; for (var seriesIndex = 0; seriesIndex < ctr.numSeries; seriesIndex++) { var y = ctr.totals[i * ctr.numSeries + seriesIndex]; var yView = canvasH - (yScale * y) + clientRect.top; if (wY >= yView) { seriesIndexHit = seriesIndex; break; } } if (seriesIndexHit === undefined) return false; */ var hit = selection.addCounterSample(this, this.counter, i); this.decorateHit(hit); return true; }, /** * Adds items intersecting the given range to a selection. * @param {number} loVX Lower X bound of the interval to search, in * viewspace. * @param {number} hiVX Upper X bound of the interval to search, in * viewspace. * @param {number} loVY Lower Y bound of the interval to search, in * viewspace. * @param {number} hiVY Upper Y bound of the interval to search, in * viewspace. * @param {Selection} selection Selection to which to add hits. */ addIntersectingItemsInRangeToSelection: function( loVX, hiVX, loVY, hiVY, selection) { var clientRect = this.getBoundingClientRect(); var a = Math.max(loVY, clientRect.top); var b = Math.min(hiVY, clientRect.bottom); if (a > b) return; var ctr = this.counter_; var pixelRatio = window.devicePixelRatio || 1; var loWX = this.viewport_.xViewToWorld(loVX * pixelRatio); var hiWX = this.viewport_.xViewToWorld(hiVX * pixelRatio); var iLo = tracing.findLowIndexInSortedArray(ctr.timestamps, function(x) { return x; }, loWX); var iHi = tracing.findLowIndexInSortedArray(ctr.timestamps, function(x) { return x; }, hiWX); // Sample i is going to either be exactly at wX or slightly above it, // E.g. asking for 7.5 in [7,8] gives i=1. So bump i back by 1 if needed. if (iLo > 0 && loWX > ctr.timestamps[iLo - 1]) iLo--; if (iHi > 0 && hiWX > ctr.timestamps[iHi - 1]) iHi--; // Iterate over every sample intersecting.. for (var i = iLo; i <= iHi; i++) { if (i >= ctr.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 }; });