Package com.google.speedtracer.client.visualizations.view

Source Code of com.google.speedtracer.client.visualizations.view.EventTraceBreakdown$Resources

/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.speedtracer.client.visualizations.view;

import com.google.gwt.coreext.client.JSOArray;
import com.google.gwt.coreext.client.JsIntegerDoubleMap;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style;
import com.google.gwt.graphics.client.Canvas;
import com.google.gwt.graphics.client.Color;
import com.google.gwt.graphics.client.ImageHandle;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource;
import com.google.speedtracer.client.model.UiEvent;

/**
* Class used to position and style the event graph bar UI that lives next to
* the Event Trace Tree.
*/
public class EventTraceBreakdown {

  /**
   * Css selectors.
   */
  public interface Css extends CssResource {
    int borderThickness();

    String eventGraph();

    String eventGraphGuides();

    int itemMargin();

    int listMargin();

    int masterHeight();

    String masterRender();

    int sideMargins();

    int widgetWidth();
  }

  /**
   * Overlay type that represents the {@link Element} associated the guide lines
   * for the event bar graph.
   */
  public static class EventTraceGraphGuides extends Element {
    protected EventTraceGraphGuides() {
    }
  }

  /**
   * Allows clients to control the appearance and presentation of events in the
   * breakdown graphs.
   */
  public interface Presenter {
    /**
     * Gets the color to use when rendering this event in a time breakdown.
     *
     * @param event
     * @return
     */
    Color getColor(UiEvent event);

    /**
     * Get an event's dominant color. For insignifcant events that do not have a
     * dominant color and signifcant events, implementations should return
     * <code>null</code>. A dominant color is a color that replaces an
     * insignificant event's primary color when rendering an event breakdown.
     *
     * NOTE: {@link Presenter#hasDominantType(UiEvent, UiEvent, double)} will
     * always be called on an event prior to this method, so it is recommended
     * that any computation for calculating the dominant color be done there.
     *
     * @param event
     * @return
     */
    Color getDominantTypeColor(UiEvent event);

    /**
     * Gets the theshold, in milliseconds, that determines if events are
     * considered insignificant. <code>msPerPixel</code> is provided to allow
     * implementers to base signifcance on pixel resolution.
     *
     * @param msPerPixel the number of milliseconds in each pixel used to render
     *          the event breakdown
     * @return
     */
    double getInsignificanceThreshold(double msPerPixel);

    /**
     * Indicates whether an event contains a dominant type. This method will
     * only be called on events where the duration is less than
     * {@link Presenter#getInsignificanceThreshold(double)}. This method will
     * always be called before {@link Presenter#getDominantTypeColor(UiEvent)}
     * and should generally be used to carry out the computation needed to
     * determine the dominant type.
     *
     * @see Presenter#getDominantTypeColor(UiEvent)
     *
     * @param event
     * @param rootEvent the top-level event that contains this event
     * @param msPerPixel the number of milliseconds in each pixel
     * @return
     */
    boolean hasDominantType(UiEvent event, UiEvent rootEvent, double msPerPixel);
  }

  /**
   * Capable of updating the canvas rendering for a {@link UiEvent} inside of an
   * event tree.
   */
  public class Renderer {
    private final Canvas canvas;
    private final UiEvent event;

    private Renderer(Canvas canvas, UiEvent event) {
      this.event = event;
      this.canvas = canvas;
    }

    public Element getElement() {
      return canvas.getElement();
    }

    /**
     * Renders only time spent in self leaving gaps where time was spent in
     * children.
     */
    public void renderOnlySelf() {
      final double domainToCoords = canvas.getCoordWidth()
          / event.getDuration();
      canvas.clear();
      canvas.setFillStyle(presenter.getColor(event));
      canvas.fillRect(0, 0, canvas.getCoordWidth(), canvas.getCoordHeight());

      // now cut out the immediate children.
      JSOArray<UiEvent> children = event.getChildren();
      for (int i = 0, n = children.size(); i < n; i++) {
        // The simple act of getting children takes a long time. That is crazy.
        UiEvent child = children.get(i);
        double startX = (child.getTime() - event.getTime()) * domainToCoords;
        canvas.clearRect(startX, 0, domainToCoords * child.getDuration(),
            canvas.getCoordWidth());
      }
    }

    /**
     * Renders time in self and overlays time spent in children.
     */
    public void renderSelfAndChildren() {
      canvas.clear();
      final Color dominantColor = presenter.getDominantTypeColor(event);

      // If this node has a dominant color set, then it is one of several
      // important ones... show it with a 1 pixel bar.
      if (dominantColor != null) {
        // Simple fill with this color.
        canvas.setFillStyle(dominantColor);
        canvas.fillRect(0, 0, canvas.getCoordWidth(), canvas.getCoordHeight());
      } else {
        double sx = (event.getTime() - rootEvent.getTime())
            * masterDomainToCoords;
        assert sx >= 0 : "An event starts before it's containing event.";
        double sw = event.getDuration() * masterDomainToCoords;
        // Prevent exception due to rounding errors that slightly exceed
        // MASTER_COORD_WIDTH
        sw = Math.min(sw, MASTER_COORD_WIDTH);
        // Calling drawImage with a width of exactly 0 throws an exception in
        // JavaScript. No need to even make the draw call in this situation.
        // This is a defensive guard since we have guarantees earlier on that
        // this will be non-zero. But leave it in just in case.
        if (sw > 0) {
          canvas.drawImage(getMasterImageHandle(), sx, 0, sw, COORD_HEIGHT, 0,
              0, canvas.getCoordWidth(), COORD_HEIGHT);
        }
      }
    }
  }

  /**
   * Externalized resource interface for accessing Css.
   */
  public interface Resources extends ClientBundle {
    @Source("resources/EventTraceBreakdown.css")
    Css eventTraceBreakdownCss();
  }

  private static final int COORD_HEIGHT = 10;

  private static final int MASTER_COORD_WIDTH = 1000;

  // conversion factor for converting from domain space to pixel space.
  private final double domainToPixels;

  private final double insignificanceThreshold;

  private final double masterDomainToCoords;

  private final Resources resources;

  private final UiEvent rootEvent;

  private final Presenter presenter;

  /**
   * This element is used to display the full rendering for the rootEvent and
   * all its children. It is also used as the master copy so that
   * {@link Renderer}s can sample their region to avoid doing a full redraw.
   */
  private Element masterCanvasElement;

  /**
   * Constructor.
   *
   * @param rootEvent the root event of the tree for which we are showing the
   *          breakdown bar graphs.
   * @param resources the {@link EventTraceBreakdown.Resources} for the
   *          breakdown.
   */
  public EventTraceBreakdown(UiEvent rootEvent, Presenter presenter,
      EventTraceBreakdown.Resources resources) {
    this.presenter = presenter;
    this.resources = resources;
    this.rootEvent = rootEvent;
    domainToPixels = resources.eventTraceBreakdownCss().widgetWidth()
        / rootEvent.getDuration();
    masterDomainToCoords = MASTER_COORD_WIDTH
        / ((rootEvent.getDuration() == 0) ? 1 : rootEvent.getDuration());
    insignificanceThreshold = presenter.getInsignificanceThreshold(domainToPixels);
  }

  public Element cloneRenderedCanvasElement() {
    ensureMasterIsRendered();
    int width = (int) (rootEvent.getDuration() * domainToPixels);
    final Canvas canvas = new Canvas(width, COORD_HEIGHT);
    canvas.setLineWidth(2);
    final Element element = canvas.getElement();
    element.setClassName(resources.eventTraceBreakdownCss().masterRender());
    element.getStyle().setPropertyPx("width", width);
    new Renderer(canvas, rootEvent).renderSelfAndChildren();
    return element;
  }

  /**
   * Creates a {@link EventTraceGraphGuides} and sets its and position.
   *
   * @param parentElement the {@link Element} that this new guide will attach
   *          to.
   * @param event the {@link UiEvent} associate with the node that this guide
   *          represents.
   * @param nodeDepth the depth in the event trace tree for the associate node.
   * @return returns the newly created {@link EventTraceGraphGuides}.
   */
  public EventTraceGraphGuides createBarGraphGuides(Element parentElement,
      UiEvent event, int nodeDepth) {
    Css css = resources.eventTraceBreakdownCss();
    EventTraceGraphGuides barGuides = parentElement.getOwnerDocument().createDivElement().cast();
    barGuides.setClassName(css.eventGraphGuides());
    int leftOffset = getLeftOffset(event, nodeDepth);
    // the guides are attached as a child of the <ul> element. So we have
    // to also account for the border and margin for the list that contains us.
    leftOffset -= (css.listMargin() + css.borderThickness());
    int width = getPixelWidth(event.getDuration());
    // Set positioning information.
    barGuides.getStyle().setPropertyPx("left", leftOffset);
    barGuides.getStyle().setPropertyPx("width", width);
    parentElement.appendChild(barGuides);
    return barGuides;
  }

  public Renderer createRenderer(UiEvent event, int depth) {
    ensureMasterIsRendered();
    int width = (int) (event.getDuration() * domainToPixels);
    // We may have truncated something that, on aggregate may matter.
    // If this node has a dominant color set, then it contains a child that is
    // one of the important ones... show it with a 1 pixel bar.
    if (width == 0
        && presenter.hasDominantType(event, rootEvent, domainToPixels)) {
      width = 1;
    }

    Css css = resources.eventTraceBreakdownCss();

    final Canvas canvas = new Canvas(width, COORD_HEIGHT);
    canvas.setLineWidth(2);
    final Element element = canvas.getElement();
    final Style style = element.getStyle();
    element.setClassName(css.eventGraph());
    style.setPropertyPx("left", getLeftOffset(event, depth));
    style.setPropertyPx("width", width);

    return new Renderer(canvas, event);
  }

  public Element getRenderedCanvasElement() {
    ensureMasterIsRendered();
    return masterCanvasElement;
  }

  private void ensureMasterIsRendered() {
    if (masterCanvasElement != null) {
      return;
    }

    // See comment in renderNode.
    final JsIntegerDoubleMap accumlatedErrorByType = JsIntegerDoubleMap.create();
    final Canvas canvas = new Canvas(MASTER_COORD_WIDTH, COORD_HEIGHT);
    traverseAndRender(canvas, null, rootEvent, accumlatedErrorByType);
    masterCanvasElement = canvas.getElement();
  }

  /**
   * Returns the offset in pixels from the left of the EventTraceBreakdown
   * widget that we want to place an element. The offset is determined by how
   * deep a node is in the tree, and the start time of the node in the tree.
   *
   * We use negative positioning absolute positioning. So we also have to
   * account for margins and border thickness to get the things lined up.
   */
  private int getLeftOffset(UiEvent event, int nodeDepth) {
    Css css = resources.eventTraceBreakdownCss();
    // How far should the bar be offset from the left?
    double domainOffset = event.getTime() - rootEvent.getTime();
    int offsetPixels = (css.widgetWidth() + css.sideMargins())
        - (int) (domainOffset * domainToPixels);

    // include margins and borders
    offsetPixels += (nodeDepth * (css.listMargin() + css.itemMargin() + css.borderThickness()));

    return -offsetPixels;
  }

  private ImageHandle getMasterImageHandle() {
    return masterCanvasElement.cast();
  }

  private int getPixelWidth(double duration) {
    return (int) Math.max(1, domainToPixels * duration);
  }

  private void renderNode(Canvas canvas, UiEvent parent, UiEvent node,
      JsIntegerDoubleMap accumulatedErrorByType) {
    double startX = (node.getTime() - rootEvent.getTime())
        * masterDomainToCoords;
    double width = node.getDuration() * masterDomainToCoords;
    final int nodeType = node.getType();
    // Insignificance is tricky. If we have lots of insignificant things, they
    // can add up to a significant thing.
    if (node.getDuration() < insignificanceThreshold) {
      // When the sub-pixel strokes are composited, we get a misleading color
      // blend. We use a unique aliasing scheme here where we suppress short
      // duration events but keep up with the total time we've suppressed for
      // each suppressed type. If the total suppressed time for a type ends up
      // being significant, we will synthesize a single aggregate event to
      // correct our accounting.
      double correctedTime = node.getSelfTime();
      if (accumulatedErrorByType.hasKey(nodeType)) {
        correctedTime += accumulatedErrorByType.get(nodeType);
      }

      if (correctedTime < insignificanceThreshold) {
        accumulatedErrorByType.put(nodeType, correctedTime);
        return;
      }

      // We want to draw a discrete bar.
      width = insignificanceThreshold * masterDomainToCoords;
      // Reset the type specific aggregation.
      accumulatedErrorByType.put(nodeType, 0);
    }

    canvas.setFillStyle(presenter.getColor(node));
    canvas.fillRect(startX, 0, width, COORD_HEIGHT);
  }

  /**
   * Should render back to front using a simple pre-order traversal.
   *
   * @param node the current node in the traversal
   */
  private void traverseAndRender(Canvas canvas, UiEvent prev, UiEvent node,
      JsIntegerDoubleMap accumulatedErrorByType) {
    renderNode(canvas, prev, node, accumulatedErrorByType);
    JSOArray<UiEvent> children = node.getChildren();
    for (int i = 0, n = children.size(); i < n; i++) {
      traverseAndRender(canvas, node, children.get(i), accumulatedErrorByType);
    }
  }
}
TOP

Related Classes of com.google.speedtracer.client.visualizations.view.EventTraceBreakdown$Resources

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.