blob: 9b677561be37ccafc68cdc9b6ce0fed3c6bf1756 [file] [log] [blame]
// 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.timeline_counter_track');
base.require('tracks.timeline_canvas_based_track');
base.require('timeline_color_scheme');
base.require('ui');
base.exportTo('tracks', function() {
var palette = tracing.getColorPalette();
/**
* A track that displays a TimelineCounter object.
* @constructor
* @extends {CanvasBasedTrack}
*/
var TimelineCounterTrack = base.ui.define(tracks.TimelineCanvasBasedTrack);
TimelineCounterTrack.prototype = {
__proto__: tracks.TimelineCanvasBasedTrack.prototype,
decorate: function() {
this.classList.add('timeline-counter-track');
this.addControlButtonElements_(false);
this.selectedSamples_ = {};
this.categoryFilter_ = new tracing.TimelineFilter();
},
/**
* 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 (x - xLast < skipDistanceWorld) {
iLast = i;
continue;
}
if (!hasMoved) {
ctx.moveTo(viewLWorld, canvasH);
hasMoved = true;
}
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 {TimelineSelection} 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 {TimelineSelection} 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 {
TimelineCounterTrack: TimelineCounterTrack
};
});