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('tracing.tracks.counter_track');
8
9base.require('tracing.tracks.heading_track');
10base.require('tracing.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 {HeadingTrack}
21   */
22
23  var CounterTrack =
24      ui.define('counter-track', tracing.tracks.HeadingTrack);
25
26  CounterTrack.prototype = {
27
28    __proto__: tracing.tracks.HeadingTrack.prototype,
29
30    decorate: function(viewport) {
31      tracing.tracks.HeadingTrack.prototype.decorate.call(this, viewport);
32      this.classList.add('counter-track');
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.heading = counter.name + ': ';
52    },
53
54    get categoryFilter() {
55      return this.categoryFilter_;
56    },
57
58    set categoryFilter(v) {
59      this.categoryFilter_ = v;
60    },
61
62    /**
63     * @return {Object} A sparse, mutable map from sample index to bool. Samples
64     * indices the map that are true are drawn as selected.
65     */
66    get selectedSamples() {
67      return this.selectedSamples_;
68    },
69
70    draw: function(type, viewLWorld, viewRWorld) {
71      switch (type) {
72        case tracing.tracks.DrawType.SLICE:
73          this.drawSlices_(viewLWorld, viewRWorld);
74          break;
75      }
76    },
77
78    drawSlices_: function(viewLWorld, viewRWorld) {
79      var ctx = this.context();
80      var pixelRatio = window.devicePixelRatio || 1;
81
82      var bounds = this.getBoundingClientRect();
83      var height = bounds.height * pixelRatio;
84
85      var counter = this.counter_;
86
87      // Culling parametrs.
88      var vp = this.viewport;
89      var pixWidth = vp.xViewVectorToWorld(1);
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 = counter.numSeries;
101      var numSamples = counter.numSamples;
102      var startIndex = base.findLowIndexInSortedArray(
103          counter.timestamps,
104          function(x) { return x; },
105          viewLWorld);
106
107      startIndex = startIndex - 1 > 0 ? startIndex - 1 : 0;
108      // Draw indices one by one until we fall off the viewRWorld.
109      var yScale = height / counter.maxTotal;
110      for (var seriesIndex = counter.numSeries - 1;
111           seriesIndex >= 0; seriesIndex--) {
112        var colorId = counter.series[seriesIndex].color;
113        ctx.fillStyle = palette[colorId];
114        ctx.beginPath();
115
116        // Set iLast and xLast such that the first sample we draw is the
117        // startIndex sample.
118        var iLast = startIndex - 1;
119        var xLast = iLast >= 0 ?
120            counter.timestamps[iLast] - skipDistanceWorld : -1;
121        var yLastView = height;
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
129        while (true) {
130          var i = iLast + 1;
131          if (i >= numSamples) {
132            ctx.lineTo(xLast, yLastView);
133            ctx.lineTo(xLast + 8 * pixWidth, yLastView);
134            ctx.lineTo(xLast + 8 * pixWidth, height);
135            break;
136          }
137
138          var x = counter.timestamps[i];
139          var y = counter.totals[i * numSeries + seriesIndex];
140          var yView = height - (yScale * y);
141
142          if (x > viewRWorld) {
143            ctx.lineTo(x, yLastView);
144            ctx.lineTo(x, height);
145            break;
146          }
147
148          if (i + 1 < numSamples) {
149            var xNext = counter.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, height);
158            hasMoved = true;
159          }
160
161          if (x - xLast < skipDistanceWorld) {
162            // We know that xNext > xLast + skipDistanceWorld, so we can
163            // safely move this sample's x over that much without passing
164            // xNext.  This ensure that the previous sample is visible when
165            // zoomed out very far.
166            x = xLast + skipDistanceWorld;
167          }
168          ctx.lineTo(x, yLastView);
169          ctx.lineTo(x, yView);
170
171          iLast = i;
172          xLast = x;
173          yLastView = yView;
174        }
175        ctx.closePath();
176        ctx.fill();
177      }
178
179      ctx.fillStyle = 'rgba(255, 0, 0, 1)';
180      for (var i in this.selectedSamples_) {
181        if (!this.selectedSamples_[i])
182          continue;
183
184        var x = counter.timestamps[i];
185        for (var seriesIndex = counter.numSeries - 1;
186             seriesIndex >= 0; seriesIndex--) {
187          var y = counter.totals[i * numSeries + seriesIndex];
188          var yView = height - (yScale * y);
189          ctx.fillRect(x - pixWidth, yView - 1, 3 * pixWidth, 3);
190        }
191      }
192      ctx.restore();
193    },
194
195    addIntersectingItemsInRangeToSelectionInWorldSpace: function(
196        loWX, hiWX, viewPixWidthWorld, selection) {
197
198      function getSampleWidth(x, i) {
199        if (i === counter.timestamps.length - 1)
200          return 0;
201        return counter.timestamps[i + 1] - counter.timestamps[i];
202      }
203
204      var counter = this.counter_;
205      var iLo = base.findLowIndexInSortedIntervals(counter.timestamps,
206                                                   function(x) { return x; },
207                                                   getSampleWidth,
208                                                   loWX);
209      var iHi = base.findLowIndexInSortedIntervals(counter.timestamps,
210                                                   function(x) { return x; },
211                                                   getSampleWidth,
212                                                   hiWX);
213
214      // Iterate over every sample intersecting..
215      for (var i = iLo; i <= iHi; i++) {
216        if (i < 0)
217          continue;
218        if (i >= counter.timestamps.length)
219          continue;
220
221        // TODO(nduca): Pick the seriesIndexHit based on the loY - hiY values.
222        var hit = selection.addCounterSample(this, this.counter, i);
223        this.decorateHit(hit);
224      }
225    },
226
227    addAllObjectsMatchingFilterToSelection: function(filter, selection) {
228    }
229  };
230
231  return {
232    CounterTrack: CounterTrack
233  };
234});
235