Index: tracing/tracing/ui/base/bar_chart.html |
diff --git a/tracing/tracing/ui/base/bar_chart.html b/tracing/tracing/ui/base/bar_chart.html |
index fc81cd028b39e3c7ab4b244e9f9e115dc516e085..c6f06374128cfed0e440468ec12e551511ea26d9 100644 |
--- a/tracing/tracing/ui/base/bar_chart.html |
+++ b/tracing/tracing/ui/base/bar_chart.html |
@@ -24,7 +24,28 @@ tr.exportTo('tr.ui.b', function() { |
decorate: function() { |
ChartBase2DBrushX.prototype.decorate.call(this); |
Polymer.dom(this).classList.add('bar-chart'); |
- this.xCushion_ = 0; |
+ |
+ // BarChart allows bars to have arbitrary, non-uniform widths. Bars need |
+ // not all be the same width. The width of each bar is automatically |
+ // computed from the bar's x-coordinate and that of the next bar, which |
+ // can not define the width of the last bar. This is the width (in the |
+ // xScale's domain (as opposed to the xScale's range (which is measured in |
+ // pixels))) of the last bar. When there are at least 2 bars, this is |
+ // computed as the average width of the bars. When there is a single bar, |
+ // this must default to a non-zero number so that the width of the only |
+ // bar will not be zero. |
+ this.xCushion_ = 1; |
+ |
+ this.isStacked_ = false; |
+ }, |
+ |
+ set isStacked(stacked) { |
+ this.isStacked_ = true; |
+ this.updateContents_(); |
+ }, |
+ |
+ get isStacked() { |
+ return this.isStacked_; |
}, |
isDatumFieldSeries_: function(fieldName) { |
@@ -50,11 +71,11 @@ tr.exportTo('tr.ui.b', function() { |
xDifferences += currentX - previousX; |
} |
- this.seriesKeys_.forEach(function(key) { |
+ for (var [key, series] of this.seriesByKey_) { |
// Allow for sparse data |
if (datum[key] !== undefined) |
yRange.addValue(datum[key]); |
- }); |
+ } |
}, this); |
// X. |
@@ -64,7 +85,8 @@ tr.exportTo('tr.ui.b', function() { |
var width = this.chartAreaSize.width; |
this.xScale_.range([0, width]); |
var domain = d3.extent(this.data_, this.getXForDatum_.bind(this)); |
- this.xCushion_ = xDifferences / (this.data_.length - 1); |
+ if (this.data_.length > 1) |
+ this.xCushion_ = xDifferences / (this.data_.length - 1); |
this.xScale_.domain([domain[0], domain[1] + this.xCushion_]); |
// Y. |
@@ -72,9 +94,75 @@ tr.exportTo('tr.ui.b', function() { |
this.yScale_.domain(this.getYScaleDomain_(yRange.min, yRange.max)); |
}, |
+ getYScaleDomain_: function(minValue, maxValue) { |
+ if (!this.isStacked) { |
+ return ChartBase2DBrushX.prototype.getYScaleDomain_.call( |
+ this, minValue, maxValue); |
+ } |
+ |
+ var range = new tr.b.Range(); |
+ range.addValue(0); |
+ this.data_.forEach(function(datum, index) { |
+ var sum = 0; |
+ for (var [key, series] of this.seriesByKey_) { |
+ if (datum[key] === undefined) |
+ continue; |
+ sum += datum[key]; |
+ } |
+ range.addValue(sum); |
+ }, this); |
+ return [range.min, range.max]; |
+ }, |
+ |
+ getStackedRectsForDatum_: function(datum, index) { |
+ var stacks = []; |
+ var bottom = this.yScale_.range()[0]; |
+ var sum = 0; |
+ for (var [key, series] of this.seriesByKey_) { |
+ if (datum[key] === undefined || !this.isSeriesEnabled(key)) |
+ continue; |
+ sum += datum[key]; |
+ var heightPx = bottom - this.yScale_(sum); |
+ bottom -= heightPx; |
+ stacks.push({ |
+ key: key, |
+ value: datum[key], |
+ color: getColorOfKey(key), |
+ heightPx: heightPx, |
+ topPx: bottom |
+ }); |
+ } |
+ return stacks; |
+ }, |
+ |
+ getRectsForDatum_: function(datum, index) { |
+ if (this.isStacked) |
+ return this.getStackedRectsForDatum_(datum, index); |
+ |
+ var stacks = []; |
+ for (var [key, series] of this.seriesByKey_) { |
+ if (datum[key] === undefined || !this.isSeriesEnabled(key)) |
+ continue; |
+ var topPx = this.yScale_(Math.max(datum[key], this.getYScaleMin_())); |
+ stacks.push({ |
+ key: key, |
+ value: datum[key], |
+ topPx: topPx, |
+ heightPx: this.yScale_.range()[0] - topPx, |
+ color: getColorOfKey(key) |
+ }); |
+ } |
+ stacks.sort(function(a, b) { |
+ return b.topPx - a.topPx; |
+ }); |
+ return stacks; |
+ }, |
+ |
updateDataContents_: function(dataSel) { |
dataSel.selectAll('*').remove(); |
- var rectsSel = dataSel.selectAll('path').data(this.seriesKeys_); |
+ var chartAreaSel = d3.select(this.chartAreaElement); |
+ var rectsSel = dataSel.selectAll('path').data( |
+ [...this.seriesByKey_.keys()]); |
this.data_.forEach(function(datum, index) { |
var currentX = this.getXForDatum_(datum, index); |
var width = undefined; |
@@ -84,28 +172,45 @@ tr.exportTo('tr.ui.b', function() { |
} else { |
width = this.xCushion_; |
} |
- |
- var stacks = []; |
- this.seriesKeys_.forEach(function(key) { |
- if (datum[key] !== undefined) |
- stacks.push({y: datum[key], color: getColorOfKey(key)}); |
- }); |
- stacks.sort(function(a, b) { |
- return b.y - a.y; |
- }); |
- |
- stacks.forEach(function(stack) { |
- var left = this.xScale_(currentX); |
- var right = this.xScale_(currentX + width); |
- var widthPx = right - left; |
- var top = this.yScale_(Math.max(stack.y, this.getYScaleMin_())); |
+ this.getRectsForDatum_(datum, index).forEach(function(rect) { |
+ var leftPx = this.xScale_(currentX); |
+ var rightPx = this.xScale_(currentX + width); |
+ var widthPx = rightPx - leftPx; |
rectsSel.enter() |
.append('rect') |
- .attr('fill', stack.color) |
- .attr('x', left) |
- .attr('y', top) |
+ .attr('fill', rect.color) |
+ .attr('x', leftPx) |
+ .attr('y', rect.topPx) |
.attr('width', widthPx) |
- .attr('height', this.yScale_.range()[0] - top); |
+ .attr('height', rect.heightPx) |
+ .on('mouseenter', function() { |
+ chartAreaSel.selectAll('.hover').remove(); |
+ chartAreaSel |
+ .append('rect') |
+ .attr('class', 'hover') |
+ .attr('fill', 'white') |
+ .attr('x', leftPx + widthPx) |
+ .attr('y', rect.topPx) |
+ .attr('width', this.margin.right) |
+ .attr('height', 30); |
+ chartAreaSel |
+ .append('text') |
+ .attr('class', 'hover') |
+ .attr('fill', rect.color) |
+ .attr('x', leftPx + widthPx + 2) |
+ .attr('y', rect.topPx + 10) |
+ .text(rect.key); |
+ chartAreaSel |
+ .append('text') |
+ .attr('class', 'hover') |
+ .attr('fill', rect.color) |
+ .attr('x', leftPx + widthPx + 2) |
+ .attr('y', rect.topPx + 25) |
+ .text(rect.value); |
+ }.bind(this)) |
+ .on('mouseleave', function() { |
+ chartAreaSel.selectAll('.hover').remove(); |
+ }.bind(this)); |
}, this); |
}, this); |
rectsSel.exit().remove(); |