• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5'use strict';
6
7base.requireStylesheet('tracks.counter_track');
8
9base.require('tracks.canvas_based_track');
10base.require('color_scheme');
11base.require('ui');
12
13base.exportTo('tracing.tracks', function() {
14
15  var palette = tracing.getColorPalette();
16
17  /**
18   * A track that displays a Counter object.
19   * @constructor
20   * @extends {CanvasBasedTrack}
21   */
22
23  var CounterTrack =
24      tracing.ui.define(tracing.tracks.CanvasBasedTrack);
25
26  CounterTrack.prototype = {
27
28    __proto__: tracing.tracks.CanvasBasedTrack.prototype,
29
30    decorate: function() {
31      this.classList.add('counter-track');
32      this.addControlButtonElements_(false);
33      this.selectedSamples_ = {};
34      this.categoryFilter_ = new tracing.Filter();
35    },
36
37    /**
38     * Called by all the addToSelection functions on the created selection
39     * hit objects. Override this function on parent classes to add
40     * context-specific information to the hit.
41     */
42    decorateHit: function(hit) {
43    },
44
45    get counter() {
46      return this.counter_;
47    },
48
49    set counter(counter) {
50      this.counter_ = counter;
51      this.invalidate();
52      this.updateVisibility_();
53    },
54
55    set categoryFilter(v) {
56      this.categoryFilter_ = v;
57      this.updateVisibility_();
58    },
59
60    /**
61     * @return {Object} A sparse, mutable map from sample index to bool. Samples
62     * indices the map that are true are drawn as selected. Callers that mutate
63     * the map must manually call invalidate on the track to trigger a redraw.
64     */
65    get selectedSamples() {
66      return this.selectedSamples_;
67    },
68
69    updateVisibility_: function() {
70      this.visible = (this.counter_ &&
71                      this.categoryFilter_.matchCounter(this.counter_));
72    },
73
74    redraw: function() {
75      var ctr = this.counter_;
76      var ctx = this.ctx_;
77      var canvasW = this.canvas_.width;
78      var canvasH = this.canvas_.height;
79
80      ctx.clearRect(0, 0, canvasW, canvasH);
81
82      // Culling parametrs.
83      var vp = this.viewport_;
84      var pixWidth = vp.xViewVectorToWorld(1);
85      var viewLWorld = vp.xViewToWorld(0);
86      var viewRWorld = vp.xViewToWorld(canvasW);
87
88      // Give the viewport a chance to draw onto this canvas.
89      vp.drawUnderContent(ctx, viewLWorld, viewRWorld, canvasH);
90
91      // Drop sampels that are less than skipDistancePix apart.
92      var skipDistancePix = 1;
93      var skipDistanceWorld = vp.xViewVectorToWorld(skipDistancePix);
94
95      // Begin rendering in world space.
96      ctx.save();
97      vp.applyTransformToCanvas(ctx);
98
99      // Figure out where drawing should begin.
100      var numSeries = ctr.numSeries;
101      var numSamples = ctr.numSamples;
102      var startIndex = tracing.findLowIndexInSortedArray(ctr.timestamps,
103                                                         function(x) {
104                                                           return x;
105                                                         },
106                                                         viewLWorld);
107      startIndex = startIndex - 1 > 0 ? startIndex - 1 : 0;
108
109      // Draw indices one by one until we fall off the viewRWorld.
110      var yScale = canvasH / ctr.maxTotal;
111      for (var seriesIndex = ctr.numSeries - 1;
112           seriesIndex >= 0; seriesIndex--) {
113        var colorId = ctr.seriesColors[seriesIndex];
114        ctx.fillStyle = palette[colorId];
115        ctx.beginPath();
116
117        // Set iLast and xLast such that the first sample we draw is the
118        // startIndex sample.
119        var iLast = startIndex - 1;
120        var xLast = iLast >= 0 ? ctr.timestamps[iLast] - skipDistanceWorld : -1;
121        var yLastView = canvasH;
122
123        // Iterate over samples from iLast onward until we either fall off the
124        // viewRWorld or we run out of samples. To avoid drawing too much, after
125        // drawing a sample at xLast, skip subsequent samples that are less than
126        // skipDistanceWorld from xLast.
127        var hasMoved = false;
128        while (true) {
129          var i = iLast + 1;
130          if (i >= numSamples) {
131            ctx.lineTo(xLast, yLastView);
132            ctx.lineTo(xLast + 8 * pixWidth, yLastView);
133            ctx.lineTo(xLast + 8 * pixWidth, canvasH);
134            break;
135          }
136
137          var x = ctr.timestamps[i];
138
139          var y = ctr.totals[i * numSeries + seriesIndex];
140          var yView = canvasH - (yScale * y);
141
142          if (x > viewRWorld) {
143            ctx.lineTo(x, yLastView);
144            ctx.lineTo(x, canvasH);
145            break;
146          }
147
148          if (i + 1 < numSamples) {
149            var xNext = ctr.timestamps[i + 1];
150            if (xNext - xLast <= skipDistanceWorld && xNext < viewRWorld) {
151              iLast = i;
152              continue;
153            }
154          }
155
156          if (!hasMoved) {
157            ctx.moveTo(viewLWorld, canvasH);
158            hasMoved = true;
159          }
160          if (x - xLast < skipDistanceWorld) {
161            // We know that xNext > xLast + skipDistanceWorld, so we can
162            // safely move this sample's x over that much without passing
163            // xNext.  This ensure that the previous sample is visible when
164            // zoomed out very far.
165            x = xLast + skipDistanceWorld;
166          }
167          ctx.lineTo(x, yLastView);
168          ctx.lineTo(x, yView);
169          iLast = i;
170          xLast = x;
171          yLastView = yView;
172        }
173        ctx.closePath();
174        ctx.fill();
175      }
176      ctx.fillStyle = 'rgba(255, 0, 0, 1)';
177      for (var i in this.selectedSamples_) {
178        if (!this.selectedSamples_[i])
179          continue;
180
181        var x = ctr.timestamps[i];
182        for (var seriesIndex = ctr.numSeries - 1;
183             seriesIndex >= 0; seriesIndex--) {
184          var y = ctr.totals[i * numSeries + seriesIndex];
185          var yView = canvasH - (yScale * y);
186          ctx.fillRect(x - pixWidth, yView - 1, 3 * pixWidth, 3);
187        }
188      }
189      ctx.restore();
190
191      // Give the viewport a chance to draw over this canvas.
192      vp.drawOverContent(ctx, viewLWorld, viewRWorld, canvasH);
193    },
194
195    /**
196     * Adds items intersecting a point to a selection.
197     * @param {number} vX X location to search at, in viewspace.
198     * @param {number} vY Y location to search at, in viewspace.
199     * @param {Selection} selection Selection to which to add hits.
200     * @return {boolean} true if a slice was found, otherwise false.
201     */
202    addIntersectingItemsToSelection: function(vX, vY, selection) {
203      var clientRect = this.getBoundingClientRect();
204      if (vY < clientRect.top || vY >= clientRect.bottom)
205        return false;
206
207      var pixelRatio = window.devicePixelRatio || 1;
208      var wX = this.viewport_.xViewVectorToWorld(vX * devicePixelRatio);
209
210      var ctr = this.counter_;
211      if (vX < this.counter_.timestamps[0])
212        return false;
213      var i = tracing.findLowIndexInSortedArray(ctr.timestamps,
214                                                function(x) { return x; },
215                                                wX);
216      if (i < 0 || i >= ctr.timestamps.length)
217        return false;
218
219      // Sample i is going to either be exactly at wX or slightly above it,
220      // E.g. asking for 7.5 in [7,8] gives i=1. So bump i back by 1 if needed.
221      if (i > 0 && wX > this.counter_.timestamps[i - 1])
222        i--;
223
224      // Some preliminaries.
225      var canvasH = this.getBoundingClientRect().height;
226      var yScale = canvasH / ctr.maxTotal;
227
228      /*
229      // Figure out which sample we hit
230      var seriesIndexHit;
231      for (var seriesIndex = 0; seriesIndex < ctr.numSeries; seriesIndex++) {
232        var y = ctr.totals[i * ctr.numSeries + seriesIndex];
233        var yView = canvasH - (yScale * y) + clientRect.top;
234        if (wY >= yView) {
235          seriesIndexHit = seriesIndex;
236          break;
237        }
238      }
239      if (seriesIndexHit === undefined)
240        return false;
241      */
242      var hit = selection.addCounterSample(this, this.counter, i);
243      this.decorateHit(hit);
244      return true;
245    },
246
247    /**
248     * Adds items intersecting the given range to a selection.
249     * @param {number} loVX Lower X bound of the interval to search, in
250     *     viewspace.
251     * @param {number} hiVX Upper X bound of the interval to search, in
252     *     viewspace.
253     * @param {number} loVY Lower Y bound of the interval to search, in
254     *     viewspace.
255     * @param {number} hiVY Upper Y bound of the interval to search, in
256     *     viewspace.
257     * @param {Selection} selection Selection to which to add hits.
258     */
259    addIntersectingItemsInRangeToSelection: function(
260        loVX, hiVX, loVY, hiVY, selection) {
261
262      var clientRect = this.getBoundingClientRect();
263      var a = Math.max(loVY, clientRect.top);
264      var b = Math.min(hiVY, clientRect.bottom);
265      if (a > b)
266        return;
267
268      var ctr = this.counter_;
269
270      var pixelRatio = window.devicePixelRatio || 1;
271      var loWX = this.viewport_.xViewToWorld(loVX * pixelRatio);
272      var hiWX = this.viewport_.xViewToWorld(hiVX * pixelRatio);
273
274      var iLo = tracing.findLowIndexInSortedArray(ctr.timestamps,
275                                                  function(x) { return x; },
276                                                  loWX);
277      var iHi = tracing.findLowIndexInSortedArray(ctr.timestamps,
278                                                  function(x) { return x; },
279                                                  hiWX);
280
281      // Sample i is going to either be exactly at wX or slightly above it,
282      // E.g. asking for 7.5 in [7,8] gives i=1. So bump i back by 1 if needed.
283      if (iLo > 0 && loWX > ctr.timestamps[iLo - 1])
284        iLo--;
285      if (iHi > 0 && hiWX > ctr.timestamps[iHi - 1])
286        iHi--;
287
288      // Iterate over every sample intersecting..
289      for (var i = iLo; i <= iHi; i++) {
290        if (i >= ctr.timestamps.length)
291          continue;
292
293        // TODO(nduca): Pick the seriesIndexHit based on the loY - hiY values.
294        var hit = selection.addCounterSample(this, this.counter, i);
295        this.decorateHit(hit);
296      }
297    },
298
299    addAllObjectsMatchingFilterToSelection: function(filter, selection) {
300    }
301  };
302
303  return {
304    CounterTrack: CounterTrack
305  };
306});
307