OLD | NEW |
1 <!DOCTYPE html> | 1 <!DOCTYPE html> |
2 <!-- | 2 <!-- |
3 Copyright (c) 2014 The Chromium Authors. All rights reserved. | 3 Copyright (c) 2014 The Chromium Authors. All rights reserved. |
4 Use of this source code is governed by a BSD-style license that can be | 4 Use of this source code is governed by a BSD-style license that can be |
5 found in the LICENSE file. | 5 found in the LICENSE file. |
6 --> | 6 --> |
7 | 7 |
8 <link rel="import" href="/tracing/ui/base/chart_base_2d_brushable_x.html"> | 8 <link rel="import" href="/tracing/ui/base/chart_base_2d_brushable_x.html"> |
9 | 9 |
10 <script> | 10 <script> |
11 'use strict'; | 11 'use strict'; |
12 | 12 |
13 tr.exportTo('tr.ui.b', function() { | 13 tr.exportTo('tr.ui.b', function() { |
14 var ColorScheme = tr.b.ColorScheme; | 14 var ColorScheme = tr.b.ColorScheme; |
15 var ChartBase2DBrushX = tr.ui.b.ChartBase2DBrushX; | 15 var ChartBase2DBrushX = tr.ui.b.ChartBase2DBrushX; |
16 var getColorOfKey = tr.ui.b.getColorOfKey; | 16 var getColorOfKey = tr.ui.b.getColorOfKey; |
17 | 17 |
18 // @constructor | 18 // @constructor |
19 var BarChart = tr.ui.b.define('bar-chart', ChartBase2DBrushX); | 19 var BarChart = tr.ui.b.define('bar-chart', ChartBase2DBrushX); |
20 | 20 |
21 BarChart.prototype = { | 21 BarChart.prototype = { |
22 __proto__: ChartBase2DBrushX.prototype, | 22 __proto__: ChartBase2DBrushX.prototype, |
23 | 23 |
24 decorate: function() { | 24 decorate: function() { |
25 ChartBase2DBrushX.prototype.decorate.call(this); | 25 ChartBase2DBrushX.prototype.decorate.call(this); |
26 Polymer.dom(this).classList.add('bar-chart'); | 26 Polymer.dom(this).classList.add('bar-chart'); |
27 this.xCushion_ = 0; | 27 |
| 28 // BarChart allows bars to have arbitrary, non-uniform widths. Bars need |
| 29 // not all be the same width. The width of each bar is automatically |
| 30 // computed from the bar's x-coordinate and that of the next bar, which |
| 31 // can not define the width of the last bar. This is the width (in the |
| 32 // xScale's domain (as opposed to the xScale's range (which is measured in |
| 33 // pixels))) of the last bar. When there are at least 2 bars, this is |
| 34 // computed as the average width of the bars. When there is a single bar, |
| 35 // this must default to a non-zero number so that the width of the only |
| 36 // bar will not be zero. |
| 37 this.xCushion_ = 1; |
| 38 |
| 39 this.isStacked_ = false; |
| 40 }, |
| 41 |
| 42 set isStacked(stacked) { |
| 43 this.isStacked_ = true; |
| 44 this.updateContents_(); |
| 45 }, |
| 46 |
| 47 get isStacked() { |
| 48 return this.isStacked_; |
28 }, | 49 }, |
29 | 50 |
30 isDatumFieldSeries_: function(fieldName) { | 51 isDatumFieldSeries_: function(fieldName) { |
31 return fieldName != 'x'; | 52 return fieldName != 'x'; |
32 }, | 53 }, |
33 | 54 |
34 getXForDatum_: function(datum, index) { | 55 getXForDatum_: function(datum, index) { |
35 return datum.x; | 56 return datum.x; |
36 }, | 57 }, |
37 | 58 |
38 updateScales_: function() { | 59 updateScales_: function() { |
39 if (this.data_.length === 0) | 60 if (this.data_.length === 0) |
40 return; | 61 return; |
41 | 62 |
42 var xDifferences = 0; | 63 var xDifferences = 0; |
43 var currentX = undefined; | 64 var currentX = undefined; |
44 var previousX = undefined; | 65 var previousX = undefined; |
45 var yRange = new tr.b.Range(); | 66 var yRange = new tr.b.Range(); |
46 this.data_.forEach(function(datum, index) { | 67 this.data_.forEach(function(datum, index) { |
47 previousX = currentX; | 68 previousX = currentX; |
48 currentX = this.getXForDatum_(datum, index); | 69 currentX = this.getXForDatum_(datum, index); |
49 if (previousX !== undefined) { | 70 if (previousX !== undefined) { |
50 xDifferences += currentX - previousX; | 71 xDifferences += currentX - previousX; |
51 } | 72 } |
52 | 73 |
53 this.seriesKeys_.forEach(function(key) { | 74 for (var [key, series] of this.seriesByKey_) { |
54 // Allow for sparse data | 75 // Allow for sparse data |
55 if (datum[key] !== undefined) | 76 if (datum[key] !== undefined) |
56 yRange.addValue(datum[key]); | 77 yRange.addValue(datum[key]); |
57 }); | 78 } |
58 }, this); | 79 }, this); |
59 | 80 |
60 // X. | 81 // X. |
61 // Leave a cushion on the right so that the last rect doesn't | 82 // Leave a cushion on the right so that the last rect doesn't |
62 // exceed the chart boundaries. The last rect's width is set to the | 83 // exceed the chart boundaries. The last rect's width is set to the |
63 // average width of the rects, which is chart.width / data.length. | 84 // average width of the rects, which is chart.width / data.length. |
64 var width = this.chartAreaSize.width; | 85 var width = this.chartAreaSize.width; |
65 this.xScale_.range([0, width]); | 86 this.xScale_.range([0, width]); |
66 var domain = d3.extent(this.data_, this.getXForDatum_.bind(this)); | 87 var domain = d3.extent(this.data_, this.getXForDatum_.bind(this)); |
67 this.xCushion_ = xDifferences / (this.data_.length - 1); | 88 if (this.data_.length > 1) |
| 89 this.xCushion_ = xDifferences / (this.data_.length - 1); |
68 this.xScale_.domain([domain[0], domain[1] + this.xCushion_]); | 90 this.xScale_.domain([domain[0], domain[1] + this.xCushion_]); |
69 | 91 |
70 // Y. | 92 // Y. |
71 this.yScale_.range([this.chartAreaSize.height, 0]); | 93 this.yScale_.range([this.chartAreaSize.height, 0]); |
72 this.yScale_.domain(this.getYScaleDomain_(yRange.min, yRange.max)); | 94 this.yScale_.domain(this.getYScaleDomain_(yRange.min, yRange.max)); |
73 }, | 95 }, |
74 | 96 |
| 97 getYScaleDomain_: function(minValue, maxValue) { |
| 98 if (!this.isStacked) { |
| 99 return ChartBase2DBrushX.prototype.getYScaleDomain_.call( |
| 100 this, minValue, maxValue); |
| 101 } |
| 102 |
| 103 var range = new tr.b.Range(); |
| 104 range.addValue(0); |
| 105 this.data_.forEach(function(datum, index) { |
| 106 var sum = 0; |
| 107 for (var [key, series] of this.seriesByKey_) { |
| 108 if (datum[key] === undefined) |
| 109 continue; |
| 110 sum += datum[key]; |
| 111 } |
| 112 range.addValue(sum); |
| 113 }, this); |
| 114 return [range.min, range.max]; |
| 115 }, |
| 116 |
| 117 getStackedRectsForDatum_: function(datum, index) { |
| 118 var stacks = []; |
| 119 var bottom = this.yScale_.range()[0]; |
| 120 var sum = 0; |
| 121 for (var [key, series] of this.seriesByKey_) { |
| 122 if (datum[key] === undefined || !this.isSeriesEnabled(key)) |
| 123 continue; |
| 124 sum += datum[key]; |
| 125 var heightPx = bottom - this.yScale_(sum); |
| 126 bottom -= heightPx; |
| 127 stacks.push({ |
| 128 key: key, |
| 129 value: datum[key], |
| 130 color: getColorOfKey(key), |
| 131 heightPx: heightPx, |
| 132 topPx: bottom |
| 133 }); |
| 134 } |
| 135 return stacks; |
| 136 }, |
| 137 |
| 138 getRectsForDatum_: function(datum, index) { |
| 139 if (this.isStacked) |
| 140 return this.getStackedRectsForDatum_(datum, index); |
| 141 |
| 142 var stacks = []; |
| 143 for (var [key, series] of this.seriesByKey_) { |
| 144 if (datum[key] === undefined || !this.isSeriesEnabled(key)) |
| 145 continue; |
| 146 var topPx = this.yScale_(Math.max(datum[key], this.getYScaleMin_())); |
| 147 stacks.push({ |
| 148 key: key, |
| 149 value: datum[key], |
| 150 topPx: topPx, |
| 151 heightPx: this.yScale_.range()[0] - topPx, |
| 152 color: getColorOfKey(key) |
| 153 }); |
| 154 } |
| 155 stacks.sort(function(a, b) { |
| 156 return b.topPx - a.topPx; |
| 157 }); |
| 158 return stacks; |
| 159 }, |
| 160 |
75 updateDataContents_: function(dataSel) { | 161 updateDataContents_: function(dataSel) { |
76 dataSel.selectAll('*').remove(); | 162 dataSel.selectAll('*').remove(); |
77 var rectsSel = dataSel.selectAll('path').data(this.seriesKeys_); | 163 var chartAreaSel = d3.select(this.chartAreaElement); |
| 164 var rectsSel = dataSel.selectAll('path').data( |
| 165 [...this.seriesByKey_.keys()]); |
78 this.data_.forEach(function(datum, index) { | 166 this.data_.forEach(function(datum, index) { |
79 var currentX = this.getXForDatum_(datum, index); | 167 var currentX = this.getXForDatum_(datum, index); |
80 var width = undefined; | 168 var width = undefined; |
81 if (index < (this.data_.length - 1)) { | 169 if (index < (this.data_.length - 1)) { |
82 var nextX = this.getXForDatum_(this.data_[index + 1], index + 1); | 170 var nextX = this.getXForDatum_(this.data_[index + 1], index + 1); |
83 width = nextX - currentX; | 171 width = nextX - currentX; |
84 } else { | 172 } else { |
85 width = this.xCushion_; | 173 width = this.xCushion_; |
86 } | 174 } |
87 | 175 this.getRectsForDatum_(datum, index).forEach(function(rect) { |
88 var stacks = []; | 176 var leftPx = this.xScale_(currentX); |
89 this.seriesKeys_.forEach(function(key) { | 177 var rightPx = this.xScale_(currentX + width); |
90 if (datum[key] !== undefined) | 178 var widthPx = rightPx - leftPx; |
91 stacks.push({y: datum[key], color: getColorOfKey(key)}); | |
92 }); | |
93 stacks.sort(function(a, b) { | |
94 return b.y - a.y; | |
95 }); | |
96 | |
97 stacks.forEach(function(stack) { | |
98 var left = this.xScale_(currentX); | |
99 var right = this.xScale_(currentX + width); | |
100 var widthPx = right - left; | |
101 var top = this.yScale_(Math.max(stack.y, this.getYScaleMin_())); | |
102 rectsSel.enter() | 179 rectsSel.enter() |
103 .append('rect') | 180 .append('rect') |
104 .attr('fill', stack.color) | 181 .attr('fill', rect.color) |
105 .attr('x', left) | 182 .attr('x', leftPx) |
106 .attr('y', top) | 183 .attr('y', rect.topPx) |
107 .attr('width', widthPx) | 184 .attr('width', widthPx) |
108 .attr('height', this.yScale_.range()[0] - top); | 185 .attr('height', rect.heightPx) |
| 186 .on('mouseenter', function() { |
| 187 chartAreaSel.selectAll('.hover').remove(); |
| 188 chartAreaSel |
| 189 .append('rect') |
| 190 .attr('class', 'hover') |
| 191 .attr('fill', 'white') |
| 192 .attr('x', leftPx + widthPx) |
| 193 .attr('y', rect.topPx) |
| 194 .attr('width', this.margin.right) |
| 195 .attr('height', 30); |
| 196 chartAreaSel |
| 197 .append('text') |
| 198 .attr('class', 'hover') |
| 199 .attr('fill', rect.color) |
| 200 .attr('x', leftPx + widthPx + 2) |
| 201 .attr('y', rect.topPx + 10) |
| 202 .text(rect.key); |
| 203 chartAreaSel |
| 204 .append('text') |
| 205 .attr('class', 'hover') |
| 206 .attr('fill', rect.color) |
| 207 .attr('x', leftPx + widthPx + 2) |
| 208 .attr('y', rect.topPx + 25) |
| 209 .text(rect.value); |
| 210 }.bind(this)) |
| 211 .on('mouseleave', function() { |
| 212 chartAreaSel.selectAll('.hover').remove(); |
| 213 }.bind(this)); |
109 }, this); | 214 }, this); |
110 }, this); | 215 }, this); |
111 rectsSel.exit().remove(); | 216 rectsSel.exit().remove(); |
112 } | 217 } |
113 }; | 218 }; |
114 | 219 |
115 return { | 220 return { |
116 BarChart: BarChart | 221 BarChart: BarChart |
117 }; | 222 }; |
118 }); | 223 }); |
119 </script> | 224 </script> |
OLD | NEW |