tvcm.require('tvcm.range');
tvcm.require('tvcm.ui.d3');
+tvcm.require('tvcm.ui.dom_helpers');
tvcm.require('tvcm.ui.chart_base');
tvcm.requireStylesheet('tvcm.ui.pie_chart');
var ChartBase = tvcm.ui.ChartBase;
var getColorOfKey = tvcm.ui.getColorOfKey;
+ var MIN_RADIUS = 100;
+
/**
* @constructor
*/
this.seriesKeys_ = undefined;
var chartAreaSel = d3.select(this.chartAreaElement);
- this.pieGroup_ = chartAreaSel.append('g')
- .attr('class', 'pie-group')
+ var pieGroupSel = chartAreaSel.append('g')
+ .attr('class', 'pie-group');
+ this.pieGroup_ = pieGroupSel.node();
+
+ this.pathsGroup_ = pieGroupSel.append('g')
+ .attr('class', 'paths')
+ .node();
+ this.labelsGroup_ = pieGroupSel.append('g')
+ .attr('class', 'labels')
+ .node();
+ this.linesGroup_ = pieGroupSel.append('g')
+ .attr('class', 'lines')
.node();
},
this.updateContents_();
},
+ get margin() {
+ var margin = {top: 0, right: 0, bottom: 0, left: 0};
+ if (this.chartTitle_)
+ margin.top += 40;
+ return margin;
+ },
+
+ getMinSize: function() {
+ if (!tvcm.ui.isElementAttachedToDocument(this))
+ throw new Error('Cannot measure when unattached');
+ this.updateContents_();
+
+ var labelSel = d3.select(this.labelsGroup_).selectAll('.label');
+ var maxLabelWidth = -Number.MAX_VALUE;
+ var leftTextHeightSum = 0;
+ var rightTextHeightSum = 0;
+ labelSel.each(function(l) {
+ var r = this.getBoundingClientRect();
+ maxLabelWidth = Math.max(maxLabelWidth, r.width);
+ if (this.style.textAnchor == 'end') {
+ leftTextHeightSum += r.height;
+ } else {
+ rightTextHeightSum += r.height;
+ }
+ });
+
+ var titleWidth = this.querySelector(
+ '#title').getBoundingClientRect().width;
+ var margin = this.margin;
+ var marginWidth = margin.left + margin.right;
+ var marginHeight = margin.top + margin.bottom;
+ return {
+ width: Math.max(2 * MIN_RADIUS + 2 * maxLabelWidth,
+ titleWidth * 1.1) + marginWidth,
+ height: marginHeight + Math.max(2 * MIN_RADIUS,
+ leftTextHeightSum,
+ rightTextHeightSum) * 1.25
+ };
+ },
+
+
getLegendKeys_: function() {
- if (this.data_ === undefined)
- return [];
- return this.seriesKeys_;
+ // This class creates its own legend, instead of using ChartBase.
+ return undefined;
},
updateScales_: function(width, height) {
var width = this.chartAreaSize.width;
var height = this.chartAreaSize.height;
- var radius = Math.min(width, height) / 2;
+ var radius = Math.max(MIN_RADIUS, Math.min(width, height) / 2);
- var pieGroupSel = d3.select(this.pieGroup_);
- pieGroupSel.attr('transform',
- 'translate(' + width / 2 + ',' + height / 2 + ')');
+ d3.select(this.pieGroup_).attr(
+ 'transform',
+ 'translate(' + width / 2 + ',' + height / 2 + ')');
// Bind the pie layout to its data
var pieLayout = d3.layout.pie()
.value(function(d) { return d.value; })
.sort(null);
- var piePathsSel = pieGroupSel.datum(this.data_).selectAll('path')
- .data(pieLayout);
+ var piePathsSel = d3.select(this.pathsGroup_)
+ .datum(this.data_)
+ .selectAll('path')
+ .data(pieLayout);
+ function midAngle(d) {
+ return d.startAngle + (d.endAngle - d.startAngle) / 2;
+ }
- var arcRenderer = d3.svg.arc()
+ var pathsArc = d3.svg.arc()
.innerRadius(0)
- .outerRadius(radius - 20);
+ .outerRadius(radius - 30);
+
+ var valueLabelArc = d3.svg.arc()
+ .innerRadius(radius - 100)
+ .outerRadius(radius - 30);
+
+ var lineBeginArc = d3.svg.arc()
+ .innerRadius(radius - 50)
+ .outerRadius(radius - 50);
+
+ var lineEndArc = d3.svg.arc()
+ .innerRadius(radius)
+ .outerRadius(radius);
+
+ // Paths.
piePathsSel.enter().append('path')
+ .attr('class', 'arc')
.attr('fill', function(d, i) {
var origData = this.data_[i];
- return getColorOfKey(origData.label);
+ var highlighted = (origData.label ===
+ this.currentHighlightedLegendKey);
+ return getColorOfKey(origData.label, highlighted);
+ }.bind(this))
+ .attr('d', pathsArc)
+ .on('click', function(d, i) {
+ var origData = this.data_[i];
+ if (origData.onClick)
+ origData.onClick(d, i);
+ d3.event.stopPropagation();
}.bind(this))
- .attr('d', arcRenderer);
+ .on('mouseenter', function(d, i) {
+ var origData = this.data_[i];
+ this.pushTempHighlightedLegendKey(origData.label);
+ }.bind(this))
+ .on('mouseleave', function(d, i) {
+ var origData = this.data_[i];
+ this.popTempHighlightedLegendKey(origData.label);
+ }.bind(this));
+
+ // Value labels.
+ piePathsSel.enter().append('text')
+ .attr('class', 'arc-text')
+ .attr('transform', function(d) {
+ return 'translate(' + valueLabelArc.centroid(d) + ')';
+ })
+ .attr('dy', '.35em')
+ .style('text-anchor', 'middle')
+ .text(function(d, i) {
+ var origData = this.data_[i];
+ if (origData.valueText === undefined)
+ return '';
+
+ if (d.endAngle - d.startAngle < 0.4)
+ return '';
+ return origData.valueText;
+ }.bind(this));
+
piePathsSel.exit().remove();
+
+ // Labels.
+ var labelSel = d3.select(this.labelsGroup_).selectAll('.label')
+ .data(pieLayout(this.data_));
+ labelSel.enter()
+ .append('text')
+ .attr('class', 'label')
+ .attr('dy', '.35em');
+
+ labelSel.text(function(d) {
+ return d.data.label;
+ });
+ labelSel.attr('transform', function(d) {
+ var pos = lineEndArc.centroid(d);
+ pos[0] = radius * (midAngle(d) < Math.PI ? 1 : -1);
+ return 'translate(' + pos + ')';
+ });
+ labelSel.style('text-anchor', function(d) {
+ return midAngle(d) < Math.PI ? 'start' : 'end';
+ });
+
+ // Lines.
+ var lineSel = d3.select(this.linesGroup_).selectAll('.line')
+ .data(pieLayout(this.data_));
+ lineSel.enter()
+ .append('polyline')
+ .attr('class', 'line')
+ .attr('dy', '.35em');
+ lineSel.attr('points', function(d) {
+ var pos = lineEndArc.centroid(d);
+ pos[0] = radius * 0.95 * (midAngle(d) < Math.PI ? 1 : -1);
+ return [lineBeginArc.centroid(d), lineEndArc.centroid(d), pos];
+ });
+ },
+
+ updateHighlight_: function() {
+ ChartBase.prototype.updateHighlight_.call(this);
+ // Update color of pie segments.
+ var pathsGroupSel = d3.select(this.pathsGroup_);
+ var that = this;
+ pathsGroupSel.selectAll('.arc').each(function(d, i) {
+ var origData = that.data_[i];
+ var highlighted = origData.label == that.currentHighlightedLegendKey;
+ var color = getColorOfKey(origData.label, highlighted);
+ this.style.fill = color;
+ });
}
};