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