| <!DOCTYPE html> |
| <!-- |
| Copyright (c) 2015 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. |
| --> |
| |
| <link rel="import" href="/base/ui.html"> |
| <link rel="import" href="/core/tracks/chart_transform.html"> |
| <link rel="import" href="/core/tracks/heading_track.html"> |
| |
| <style> |
| .chart-track { |
| height: 30px; |
| position: relative; |
| } |
| </style> |
| |
| <script> |
| 'use strict'; |
| |
| tv.exportTo('tv.c.tracks', function() { |
| |
| /** |
| * A track that displays a chart. |
| * |
| * @constructor |
| * @extends {HeadingTrack} |
| */ |
| var ChartTrack = |
| tv.b.ui.define('chart-track', tv.c.tracks.HeadingTrack); |
| |
| ChartTrack.prototype = { |
| __proto__: tv.c.tracks.HeadingTrack.prototype, |
| |
| decorate: function(viewport) { |
| tv.c.tracks.HeadingTrack.prototype.decorate.call(this, viewport); |
| this.classList.add('chart-track'); |
| this.series_ = undefined; |
| |
| // GUID -> {axis: ChartAxis, series: [ChartSeries]}. |
| this.axisGuidToAxisData_ = undefined; |
| |
| // The maximum top and bottom padding of all series. |
| this.topPadding_ = undefined; |
| this.bottomPadding_ = undefined; |
| }, |
| |
| get series() { |
| return this.series_; |
| }, |
| |
| /** |
| * Set the list of chart series to be displayed on this track. The list |
| * is assumed to be sorted in increasing z-order (i.e. the last series in |
| * the list will be drawn at the top). |
| */ |
| set series(series) { |
| this.series_ = series; |
| this.calculateAxisDataAndPadding_(); |
| this.invalidateDrawingContainer(); |
| }, |
| |
| get height() { |
| return window.getComputedStyle(this).height; |
| }, |
| |
| set height(height) { |
| this.style.height = height; |
| this.invalidateDrawingContainer(); |
| }, |
| |
| get hasVisibleContent() { |
| return !!this.series && this.series.length > 0; |
| }, |
| |
| calculateAxisDataAndPadding_: function() { |
| if (!this.series_) { |
| this.axisGuidToAxisData_ = undefined; |
| this.topPadding_ = undefined; |
| this.bottomPadding_ = undefined; |
| return; |
| } |
| |
| var axisGuidToAxisData = {}; |
| var topPadding = 0; |
| var bottomPadding = 0; |
| |
| this.series_.forEach(function(series) { |
| var axis = series.axis; |
| var axisGuid = axis.guid; |
| if (!(axisGuid in axisGuidToAxisData)) { |
| axisGuidToAxisData[axisGuid] = { |
| axis: axis, |
| series: [] |
| }; |
| } |
| axisGuidToAxisData[axisGuid].series.push(series); |
| topPadding = Math.max(topPadding, series.topPadding); |
| bottomPadding = Math.max(bottomPadding, series.bottomPadding); |
| }, this); |
| |
| this.axisGuidToAxisData_ = axisGuidToAxisData; |
| this.topPadding_ = topPadding; |
| this.bottomPadding_ = bottomPadding; |
| }, |
| |
| draw: function(type, viewLWorld, viewRWorld) { |
| switch (type) { |
| case tv.c.tracks.DrawType.GENERAL_EVENT: |
| this.drawChart_(viewLWorld, viewRWorld); |
| break; |
| } |
| }, |
| |
| drawChart_: function(viewLWorld, viewRWorld) { |
| if (!this.series_) |
| return; |
| |
| var ctx = this.context(); |
| |
| // Get track drawing parameters. |
| var displayTransform = this.viewport.currentDisplayTransform; |
| var pixelRatio = window.devicePixelRatio || 1; |
| var bounds = this.getBoundingClientRect(); |
| var highDetails = this.viewport.highDetails; |
| |
| // Pre-multiply all device-independent pixel parameters with the pixel |
| // ratio to avoid unnecessary recomputation in the performance-critical |
| // drawing code. |
| var width = bounds.width * pixelRatio; |
| var height = bounds.height * pixelRatio; |
| var topPadding = this.topPadding_ * pixelRatio; |
| var bottomPadding = this.bottomPadding_ * pixelRatio; |
| |
| // Set up clipping. |
| ctx.save(); |
| ctx.beginPath(); |
| ctx.rect(0, 0, width, height); |
| ctx.clip(); |
| |
| // Draw all series in the increasing z-order. |
| this.series_.forEach(function(series) { |
| var chartTransform = new tv.c.tracks.ChartTransform( |
| displayTransform, series.axis, width, height, topPadding, |
| bottomPadding, pixelRatio); |
| series.draw(ctx, chartTransform, highDetails); |
| }, this); |
| |
| // Stop clipping. |
| ctx.restore(); |
| }, |
| |
| addEventsToTrackMap: function(eventToTrackMap) { |
| // TODO(petrcermak): Consider adding the series to the track map instead |
| // of the track (a potential performance optimization). |
| this.series_.forEach(function(series) { |
| series.points.forEach(function(point) { |
| point.addToTrackMap(eventToTrackMap, this); |
| }, this); |
| }, this); |
| }, |
| |
| addIntersectingEventsInRangeToSelectionInWorldSpace: function( |
| loWX, hiWX, viewPixWidthWorld, selection) { |
| this.series_.forEach(function(series) { |
| series.addIntersectingEventsInRangeToSelectionInWorldSpace( |
| loWX, hiWX, viewPixWidthWorld, selection); |
| }, this); |
| }, |
| |
| addEventNearToProvidedEventToSelection: function(event, offset, selection) { |
| var foundItem = false; |
| this.series_.forEach(function(series) { |
| foundItem = foundItem || series.addEventNearToProvidedEventToSelection( |
| event, offset, selection); |
| }, this); |
| return foundItem; |
| }, |
| |
| addAllEventsMatchingFilterToSelection: function(filter, selection) { |
| // Do nothing. |
| }, |
| |
| addClosestEventToSelection: function(worldX, worldMaxDist, loY, hiY, |
| selection) { |
| this.series_.forEach(function(series) { |
| series.addClosestEventToSelection( |
| worldX, worldMaxDist, loY, hiY, selection); |
| }, this); |
| }, |
| |
| /** |
| * Automatically set the bounds of all axes on this track from the range of |
| * values of all series (in this track) associated with each of them. |
| * |
| * See the description of ChartAxis.autoSetFromRange for the optional |
| * configuration argument flags. |
| */ |
| autoSetAllAxes: function(opt_config) { |
| tv.b.iterItems(this.axisGuidToAxisData_, function(axisGuid, axisData) { |
| var axis = axisData.axis; |
| var series = axisData.series; |
| axis.autoSetFromSeries(series, opt_config); |
| }, this); |
| }, |
| |
| /** |
| * Automatically set the bounds of the provided axis from the range of |
| * values of all series (in this track) associated with it. |
| * |
| * See the description of ChartAxis.autoSetFromRange for the optional |
| * configuration argument flags. |
| */ |
| autoSetAxis: function(axis, opt_config) { |
| var series = this.axisGuidToAxisData_[axis.guid].series; |
| axis.autoSetFromSeries(series, opt_config); |
| } |
| }; |
| |
| return { |
| ChartTrack: ChartTrack |
| }; |
| }); |
| </script> |