| 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 |