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
5 found in the LICENSE file.
7 <link rel="import" href="/tvcm/range.html">
8 <link rel="import" href="/tvcm/ui/d3.html">
9 <link rel="import" href="/tvcm/ui/dom_helpers.html">
10 <link rel="import" href="/tvcm/ui/chart_base.html">
11 <link rel="stylesheet" href="/tvcm/ui/pie_chart.css">
16 tvcm.exportTo('tvcm.ui', function() {
17 var ChartBase = tvcm.ui.ChartBase;
18 var getColorOfKey = tvcm.ui.getColorOfKey;
25 var PieChart = tvcm.ui.define('pie-chart', ChartBase);
27 PieChart.prototype = {
28 __proto__: ChartBase.prototype,
30 decorate: function() {
31 ChartBase.prototype.decorate.call(this);
32 this.classList.add('pie-chart');
34 this.data_ = undefined;
35 this.seriesKeys_ = undefined;
37 var chartAreaSel = d3.select(this.chartAreaElement);
38 var pieGroupSel = chartAreaSel.append('g')
39 .attr('class', 'pie-group');
40 this.pieGroup_ = pieGroupSel.node();
42 this.pathsGroup_ = pieGroupSel.append('g')
43 .attr('class', 'paths')
45 this.labelsGroup_ = pieGroupSel.append('g')
46 .attr('class', 'labels')
48 this.linesGroup_ = pieGroupSel.append('g')
49 .attr('class', 'lines')
59 * @param {Array} data Data for the chart, where each element in the array
60 * must be of the form {label: str, value: number}.
63 if (data !== undefined) {
64 // Figure out the label values in the data set. E.g. from
65 // [{label: 'a', ...}, {label: 'b', ...}]
66 // we would commpute ['a', 'y']. These become the series keys.
68 var seenSeriesKeys = {};
69 data.forEach(function(d) {
71 if (seenSeriesKeys[k])
72 throw new Error('Label ' + k + ' has been used already');
74 seenSeriesKeys[k] = true;
76 this.seriesKeys_ = seriesKeys;
78 this.seriesKeys_ = undefined;
81 this.updateContents_();
85 var margin = {top: 0, right: 0, bottom: 0, left: 0};
91 getMinSize: function() {
92 if (!tvcm.ui.isElementAttachedToDocument(this))
93 throw new Error('Cannot measure when unattached');
94 this.updateContents_();
96 var labelSel = d3.select(this.labelsGroup_).selectAll('.label');
97 var maxLabelWidth = -Number.MAX_VALUE;
98 var leftTextHeightSum = 0;
99 var rightTextHeightSum = 0;
100 labelSel.each(function(l) {
101 var r = this.getBoundingClientRect();
102 maxLabelWidth = Math.max(maxLabelWidth, r.width + 32);
103 if (this.style.textAnchor == 'end') {
104 leftTextHeightSum += r.height;
106 rightTextHeightSum += r.height;
110 var titleWidth = this.querySelector(
111 '#title').getBoundingClientRect().width;
112 var margin = this.margin;
113 var marginWidth = margin.left + margin.right;
114 var marginHeight = margin.top + margin.bottom;
116 width: Math.max(2 * MIN_RADIUS + 2 * maxLabelWidth,
117 titleWidth * 1.1) + marginWidth,
118 height: marginHeight + Math.max(2 * MIN_RADIUS,
120 rightTextHeightSum) * 1.25
125 getLegendKeys_: function() {
126 // This class creates its own legend, instead of using ChartBase.
130 updateScales_: function(width, height) {
131 if (this.data_ === undefined)
135 updateContents_: function() {
136 ChartBase.prototype.updateContents_.call(this);
140 var width = this.chartAreaSize.width;
141 var height = this.chartAreaSize.height;
142 var radius = Math.max(MIN_RADIUS, Math.min(width, height * 0.95) / 2);
144 d3.select(this.pieGroup_).attr(
146 'translate(' + width / 2 + ',' + height / 2 + ')');
148 // Bind the pie layout to its data
149 var pieLayout = d3.layout.pie()
150 .value(function(d) { return d.value; })
153 var piePathsSel = d3.select(this.pathsGroup_)
158 function midAngle(d) {
159 return d.startAngle + (d.endAngle - d.startAngle) / 2;
162 var pathsArc = d3.svg.arc()
164 .outerRadius(radius - 30);
166 var valueLabelArc = d3.svg.arc()
167 .innerRadius(radius - 100)
168 .outerRadius(radius - 30);
170 var lineBeginArc = d3.svg.arc()
171 .innerRadius(radius - 50)
172 .outerRadius(radius - 50);
174 var lineEndArc = d3.svg.arc()
176 .outerRadius(radius);
179 piePathsSel.enter().append('path')
180 .attr('class', 'arc')
181 .attr('fill', function(d, i) {
182 var origData = this.data_[i];
183 var highlighted = (origData.label ===
184 this.currentHighlightedLegendKey);
185 return getColorOfKey(origData.label, highlighted);
188 .on('click', function(d, i) {
189 var origData = this.data_[i];
190 var event = new Event('item-click');
191 event.data = origData;
193 this.dispatchEvent(event);
194 d3.event.stopPropagation();
196 .on('mouseenter', function(d, i) {
197 var origData = this.data_[i];
198 this.pushTempHighlightedLegendKey(origData.label);
200 .on('mouseleave', function(d, i) {
201 var origData = this.data_[i];
202 this.popTempHighlightedLegendKey(origData.label);
206 piePathsSel.enter().append('text')
207 .attr('class', 'arc-text')
208 .attr('transform', function(d) {
209 return 'translate(' + valueLabelArc.centroid(d) + ')';
212 .style('text-anchor', 'middle')
213 .text(function(d, i) {
214 var origData = this.data_[i];
215 if (origData.valueText === undefined)
218 if (d.endAngle - d.startAngle < 0.4)
220 return origData.valueText;
223 piePathsSel.exit().remove();
226 var labelSel = d3.select(this.labelsGroup_).selectAll('.label')
227 .data(pieLayout(this.data_));
230 .attr('class', 'label')
231 .attr('dy', '.35em');
233 labelSel.text(function(d) {
234 if (d.data.label.length > 40)
235 return d.data.label.substr(0, 40) + '...';
238 labelSel.attr('transform', function(d) {
239 var pos = lineEndArc.centroid(d);
240 pos[0] = radius * (midAngle(d) < Math.PI ? 1 : -1);
241 return 'translate(' + pos + ')';
243 labelSel.style('text-anchor', function(d) {
244 return midAngle(d) < Math.PI ? 'start' : 'end';
248 var lineSel = d3.select(this.linesGroup_).selectAll('.line')
249 .data(pieLayout(this.data_));
252 .attr('class', 'line')
253 .attr('dy', '.35em');
254 lineSel.attr('points', function(d) {
255 var pos = lineEndArc.centroid(d);
256 pos[0] = radius * 0.95 * (midAngle(d) < Math.PI ? 1 : -1);
257 return [lineBeginArc.centroid(d), lineEndArc.centroid(d), pos];
261 updateHighlight_: function() {
262 ChartBase.prototype.updateHighlight_.call(this);
263 // Update color of pie segments.
264 var pathsGroupSel = d3.select(this.pathsGroup_);
266 pathsGroupSel.selectAll('.arc').each(function(d, i) {
267 var origData = that.data_[i];
268 var highlighted = origData.label == that.currentHighlightedLegendKey;
269 var color = getColorOfKey(origData.label, highlighted);
270 this.style.fill = color;