1<!DOCTYPE html>
2<!--
3Copyright (c) 2015 The Chromium Authors. All rights reserved.
4Use of this source code is governed by a BSD-style license that can be
5found 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
23tr.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