Javascript  |  307行  |  10.14 KB

// 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
  };
});