Home | History | Annotate | Download | only in tracks
      1 <!DOCTYPE html>
      2 <!--
      3 Copyright (c) 2015 The Chromium Authors. All rights reserved.
      4 Use of this source code is governed by a BSD-style license that can be
      5 found in the LICENSE file.
      6 -->
      7 
      8 <link rel="import" href="/tracing/ui/tracks/chart_transform.html">
      9 <link rel="import" href="/tracing/ui/tracks/track.html">
     10 <link rel="import" href="/tracing/ui/base/heading.html">
     11 <link rel="import" href="/tracing/ui/base/ui.html">
     12 
     13 <style>
     14 .chart-track {
     15   height: 30px;
     16   position: relative;
     17 }
     18 </style>
     19 
     20 <script>
     21 'use strict';
     22 
     23 tr.exportTo('tr.ui.tracks', function() {
     24 
     25   /**
     26    * A track that displays a chart.
     27    *
     28    * @constructor
     29    * @extends {Track}
     30    */
     31   var ChartTrack =
     32       tr.ui.b.define('chart-track', tr.ui.tracks.Track);
     33 
     34   ChartTrack.prototype = {
     35     __proto__: tr.ui.tracks.Track.prototype,
     36 
     37     decorate: function(viewport) {
     38       tr.ui.tracks.Track.prototype.decorate.call(this, viewport);
     39       this.classList.add('chart-track');
     40       this.series_ = undefined;
     41 
     42       // GUID -> {axis: ChartAxis, series: [ChartSeries]}.
     43       this.axisGuidToAxisData_ = undefined;
     44 
     45       // The maximum top and bottom padding of all series.
     46       this.topPadding_ = undefined;
     47       this.bottomPadding_ = undefined;
     48 
     49       this.heading_ = document.createElement('tr-ui-heading');
     50       this.appendChild(this.heading_);
     51     },
     52 
     53     set heading(heading) {
     54       this.heading_.heading = heading;
     55     },
     56 
     57     get heading() {
     58       return this.heading_.heading;
     59     },
     60 
     61     set tooltip(tooltip) {
     62       this.heading_.tooltip = tooltip;
     63     },
     64 
     65     get series() {
     66       return this.series_;
     67     },
     68 
     69     /**
     70      * Set the list of chart series to be displayed on this track. The list
     71      * is assumed to be sorted in increasing z-order (i.e. the last series in
     72      * the list will be drawn at the top).
     73      */
     74     set series(series) {
     75       this.series_ = series;
     76       this.calculateAxisDataAndPadding_();
     77       this.invalidateDrawingContainer();
     78     },
     79 
     80     get height() {
     81       return window.getComputedStyle(this).height;
     82     },
     83 
     84     set height(height) {
     85       this.style.height = height;
     86       this.invalidateDrawingContainer();
     87     },
     88 
     89     get hasVisibleContent() {
     90       return !!this.series && this.series.length > 0;
     91     },
     92 
     93     calculateAxisDataAndPadding_: function() {
     94       if (!this.series_) {
     95         this.axisGuidToAxisData_ = undefined;
     96         this.topPadding_ = undefined;
     97         this.bottomPadding_ = undefined;
     98         return;
     99       }
    100 
    101       var axisGuidToAxisData = {};
    102       var topPadding = 0;
    103       var bottomPadding = 0;
    104 
    105       this.series_.forEach(function(series) {
    106         var axis = series.axis;
    107         var axisGuid = axis.guid;
    108         if (!(axisGuid in axisGuidToAxisData)) {
    109           axisGuidToAxisData[axisGuid] = {
    110             axis: axis,
    111             series: []
    112           };
    113         }
    114         axisGuidToAxisData[axisGuid].series.push(series);
    115         topPadding = Math.max(topPadding, series.topPadding);
    116         bottomPadding = Math.max(bottomPadding, series.bottomPadding);
    117       }, this);
    118 
    119       this.axisGuidToAxisData_ = axisGuidToAxisData;
    120       this.topPadding_ = topPadding;
    121       this.bottomPadding_ = bottomPadding;
    122     },
    123 
    124     draw: function(type, viewLWorld, viewRWorld) {
    125       switch (type) {
    126         case tr.ui.tracks.DrawType.GENERAL_EVENT:
    127           this.drawChart_(viewLWorld, viewRWorld);
    128           break;
    129       }
    130     },
    131 
    132     drawChart_: function(viewLWorld, viewRWorld) {
    133       if (!this.series_)
    134         return;
    135 
    136       var ctx = this.context();
    137 
    138       // Get track drawing parameters.
    139       var displayTransform = this.viewport.currentDisplayTransform;
    140       var pixelRatio = window.devicePixelRatio || 1;
    141       var bounds = this.getBoundingClientRect();
    142       var highDetails = this.viewport.highDetails;
    143 
    144       // Pre-multiply all device-independent pixel parameters with the pixel
    145       // ratio to avoid unnecessary recomputation in the performance-critical
    146       // drawing code.
    147       var width = bounds.width * pixelRatio;
    148       var height = bounds.height * pixelRatio;
    149       var topPadding = this.topPadding_ * pixelRatio;
    150       var bottomPadding = this.bottomPadding_ * pixelRatio;
    151 
    152       // Set up clipping.
    153       ctx.save();
    154       ctx.beginPath();
    155       ctx.rect(0, 0, width, height);
    156       ctx.clip();
    157 
    158       // Draw all series in the increasing z-order.
    159       this.series_.forEach(function(series) {
    160         var chartTransform = new tr.ui.tracks.ChartTransform(
    161             displayTransform, series.axis, width, height, topPadding,
    162             bottomPadding, pixelRatio);
    163         series.draw(ctx, chartTransform, highDetails);
    164       }, this);
    165 
    166       // Stop clipping.
    167       ctx.restore();
    168     },
    169 
    170     addEventsToTrackMap: function(eventToTrackMap) {
    171       // TODO(petrcermak): Consider adding the series to the track map instead
    172       // of the track (a potential performance optimization).
    173       this.series_.forEach(function(series) {
    174         series.points.forEach(function(point) {
    175           point.addToTrackMap(eventToTrackMap, this);
    176         }, this);
    177       }, this);
    178     },
    179 
    180     addIntersectingEventsInRangeToSelectionInWorldSpace: function(
    181         loWX, hiWX, viewPixWidthWorld, selection) {
    182       this.series_.forEach(function(series) {
    183         series.addIntersectingEventsInRangeToSelectionInWorldSpace(
    184             loWX, hiWX, viewPixWidthWorld, selection);
    185       }, this);
    186     },
    187 
    188     addEventNearToProvidedEventToSelection: function(event, offset, selection) {
    189       var foundItem = false;
    190       this.series_.forEach(function(series) {
    191         foundItem = foundItem || series.addEventNearToProvidedEventToSelection(
    192             event, offset, selection);
    193       }, this);
    194       return foundItem;
    195     },
    196 
    197     addAllEventsMatchingFilterToSelection: function(filter, selection) {
    198       // Do nothing.
    199     },
    200 
    201     addClosestEventToSelection: function(worldX, worldMaxDist, loY, hiY,
    202                                          selection) {
    203       this.series_.forEach(function(series) {
    204         series.addClosestEventToSelection(
    205             worldX, worldMaxDist, loY, hiY, selection);
    206       }, this);
    207     },
    208 
    209     /**
    210      * Automatically set the bounds of all axes on this track from the range of
    211      * values of all series (in this track) associated with each of them.
    212      *
    213      * See the description of ChartAxis.autoSetFromRange for the optional
    214      * configuration argument flags.
    215      */
    216     autoSetAllAxes: function(opt_config) {
    217       tr.b.iterItems(this.axisGuidToAxisData_, function(axisGuid, axisData) {
    218         var axis = axisData.axis;
    219         var series = axisData.series;
    220         axis.autoSetFromSeries(series, opt_config);
    221       }, this);
    222     },
    223 
    224     /**
    225      * Automatically set the bounds of the provided axis from the range of
    226      * values of all series (in this track) associated with it.
    227      *
    228      * See the description of ChartAxis.autoSetFromRange for the optional
    229      * configuration argument flags.
    230      */
    231     autoSetAxis: function(axis, opt_config) {
    232       var series = this.axisGuidToAxisData_[axis.guid].series;
    233       axis.autoSetFromSeries(series, opt_config);
    234     }
    235   };
    236 
    237   return {
    238     ChartTrack: ChartTrack
    239   };
    240 });
    241 </script>
    242