1 
  2 /**
  3  * @fileoverview Simple donut chart implementation.
  4  * @version 1.0.1
  5  * @see https://google.github.io/styleguide/jsguide.html
  6  * @see https://github.com/google/closure-compiler/wiki
  7  */
  8 
  9 
 10 
 11 /**
 12  * DonutChart constructor.
 13  * @param {string|Element} container The HTML container.
 14  * @constructor
 15  * @extends {charts.PieChart} charts.PieChart
 16  * @requires formatters.NumberFormatter
 17  * @example
 18  * <b>var</b> chart = <b>new</b> charts.DonutChart('container_id');
 19  * chart.draw([['Work', 'Eat', 'Commute', 'Watch TV', 'Sleep'],
 20  *             [100, 50, 30, 10, 40], [140, 2, 110, 150, 1300]]);
 21  *
 22  * <div style="border: solid 1px #ccc; margin: 5px; padding: 5px; width: 560px">
 23  *   <div id="chart-container"
 24  *        style="width: 560px; height: 300px;"></div>
 25  * </div>
 26  * <script src="https://greylock.js.org/greylock.js"></script>
 27  * <script>
 28  *   var chart = new charts.DonutChart('chart-container');
 29  *   chart.draw([['Work', 'Eat', 'Commute', 'Watch TV', 'Sleep'],
 30  *               [100, 50, 30, 10, 40], [140, 2, 110, 150, 1300]]);
 31  * </script>
 32  */
 33 charts.DonutChart = function(container) {
 34   charts.PieChart.apply(this, arguments);
 35 
 36   /**
 37    * Saved reference to charts.PieChart.draw method.
 38    * @type {!function(!Array.<Array>, Object=)}
 39    * @private
 40    */
 41   var draw_ = this.draw;
 42 
 43   /**
 44    * Draws the chart based on <code>data</code> and <code>opt_options</code>.
 45    * @param {!Array.<Array>} data A chart data.
 46    * @param {Object=} opt_options A optional chart's configuration options.
 47    * @override
 48    * @see charts.PieChart#draw
 49    * @see charts.BaseChart#getOptions
 50    * @example
 51    * options: {
 52    *   'donut': {'color': '#fff', 'radius': 0.5}
 53    * }
 54    */
 55   this.draw = function(data, opt_options) {
 56     /** @type {number} */ var width = self_.container.offsetWidth || 200;
 57     /** @type {number} */ var height = self_.container.offsetHeight || width;
 58     draw_(data, opt_options);
 59     self_.container.style.visibility = 'hidden';
 60     data_ = data;
 61     options_ = getOptions_(opt_options);
 62     formatter_ = new formatters.NumberFormatter(
 63         /** @type {Object.<string,*>} */(options_['formatter']));
 64 
 65     if (options_['donut'] && options_['donut']['radius']) {
 66       /** @type {number} */ var x = width / 2;
 67       /** @type {number} */ var y = height / 2;
 68       /** @type {number} */
 69       var radius = Math.min(width, height) / 2 * options_['donut']['radius'];
 70       /** @type {string} */ var content = charts.IS_SVG_SUPPORTED ?
 71           getSvgContent_(x, y, radius) : getVmlContent_(x, y, radius);
 72       self_.drawContent(content, width, height);
 73 
 74       /** @type {number} */ var timer = setTimeout(function() {
 75         /** @type {Node} */ var pie = self_.container.childNodes[0];
 76         /** @type {Node} */ var donut = self_.container.childNodes[1];
 77         if (charts.IS_SVG_SUPPORTED && donut) {
 78           while (donut.firstChild) pie.appendChild(donut.firstChild);
 79           self_.container.removeChild(donut);
 80         }
 81         clearTimeout(timer);
 82         self_.container.style.visibility = 'visible';
 83       }, 0);
 84 
 85       setAreaContent_(x, y, radius);
 86     }
 87   };
 88 
 89   // Export for closure compiler.
 90   this['draw'] = this.draw;
 91 
 92   /**
 93    * Gets circle area content.
 94    * @return {string} Returns circle area content.
 95    * @protected
 96    */
 97   this.getAreaContent = function() {
 98     /** @type {string} */
 99     var content = '<div style="color:#666;font-size:22px;margin-bottom:3px;">' +
100                   formatter_.roundNumber(getTotal_()) + '</div>' +
101                   '<div style="color:#B3B0B0;font-size:11px">TOTAL</div>';
102     return content;
103   };
104 
105   // Export for closure compiler.
106   this['getAreaContent'] = this.getAreaContent;
107 
108   /**
109    * @param {number} x The X coord.
110    * @param {number} y The Y coord.
111    * @param {number} radius The donut radius.
112    * @private
113    */
114   function setAreaContent_(x, y, radius) {
115     if (!area_ || !area_.parentNode) {
116       var size = Math.sqrt(radius * radius / 2) * 2 - 1;
117 
118       area_ = self_.container.appendChild(dom.createElement('DIV'));
119       area_.style.position = 'absolute';
120       area_.style.width = size + 'px';
121       area_.style.height = size + 'px';
122       area_.style.left = x - size / 2 + 'px';
123       area_.style.top = y - size / 2 + 'px';
124       area_.style.fontFamily = options_['font']['family'];
125       area_.innerHTML = '<table style="width:100%;height:100%;' +
126                         //'text-shadow:1px 1px 2px red;letter-spacing:-1px;' +
127                         'text-align:center;vertical-align:middle"><tr><td>' +
128                         '</td></tr></table>';
129     }
130 
131     dom.getElementsByTagName(
132         area_, 'TD')[0].innerHTML = self_.getAreaContent();
133   }
134 
135   /**
136    * @return {number} Returns sum of data values.
137    * @private
138    */
139   function getTotal_() {
140     /** @type {number} */ var total = 0;
141     /** @type {!Array.<Array>} */ var rows = self_.getDataRows(data_);
142     for (/** @type {number} */ var i = 0; i < rows.length; i++) {
143       /** @type {Array} */ var row = rows[i];
144       for (/** @type {number} */ var j = 0; j < row.length; j++) {
145         total += row[j];
146       }
147     }
148     return total;
149   }
150 
151 
152   /**
153    * @param {number} x The X coord.
154    * @param {number} y The Y coord.
155    * @param {number} radius The donut radius.
156    * @return {string} Returns donut SVG markup.
157    * @private
158    */
159   function getSvgContent_(x, y, radius) {
160     /** @type {string} */ var result = formatter_.roundNumber(getTotal_());
161     return '<g>' +
162         '<circle cx="' + x + '" cy="' + y + '" r="' +
163         radius + '" fill="' + options_['donut']['color'] + '"/>' +
164 
165         // '<circle cx="' + x + '" cy="' + y + '" r="' +
166         // radius + '" fill="' + options_['donut']['color'] + '">' +
167         // '<animate attributeName="r" from="' +
168         // radius + '" to="' +
169         // (radius * 1.2) + '" begin="0.4s" dur="0.1s"/>' +
170         // '<animate attributeName="r" from="' +
171         // (radius * 1.2) + '" to="' + radius + '" begin="0.5s" dur="0.1s"/>' +
172         // '</circle>' +
173 
174         '</g>';
175   }
176 
177   /**
178    * @param {number} x The X coord.
179    * @param {number} y The Y coord.
180    * @param {number} radius The donut radius.
181    * @return {string} Returns donut VML markup.
182    * @private
183    */
184   function getVmlContent_(x, y, radius) {
185     var total = getTotal_();
186     return '<v:oval fillcolor="' + options_['donut']['color'] + '" ' +
187         'style="' +
188         'top:' + (y - radius) + 'px;' +
189         'left:' + (x - radius) + 'px;' +
190         'width:' + (radius * 2) + 'px;' +
191         'height:' + (radius * 2) + 'px;' +
192         '" stroked="false">' +
193         '</v:oval>';
194   }
195 
196   /**
197    * Gets chart's options merged with defaults chart's options.
198    * <code><pre> defaults: {
199    *   'donut': {'color': '#fff', 'radius': 0.5}
200    * }</pre></code>
201    * @param {Object=} opt_options A optional chart's configuration options.
202    * @return {!Object.<string, *>} A map of name/value pairs.
203    * @see charts.BaseChart#getOptions
204    * @private
205    */
206   function getOptions_(opt_options) {
207     opt_options = opt_options || {};
208     opt_options['donut'] = opt_options['donut'] || {};
209     opt_options['donut']['color'] = opt_options['donut']['color'] || '#fff';
210     opt_options['donut']['radius'] = opt_options['donut']['radius'] || 0.5;
211     opt_options['formatter'] = opt_options['formatter'] || {};
212     return self_.getOptions(opt_options);
213   }
214 
215   /**
216    * The reference to current class instance. Used in private methods.
217    * @type {!charts.DonutChart}
218    * @private
219    */
220   var self_ = this;
221 
222   /**
223    * @type {Array.<Array>}
224    * @private
225    */
226   var data_ = null;
227 
228   /**
229    * @dict
230    * @private
231    */
232   var options_ = null;
233 
234   /**
235    * @type {formatters.NumberFormatter}
236    * @private
237    */
238   var formatter_ = null;
239 
240   /**
241    * @type {Node}
242    * @private
243    */
244   var area_ = null;
245 };
246 
247 // Export for closure compiler.
248 charts['DonutChart'] = charts.DonutChart;
249