1 
  2 /**
  3  * @fileoverview Simple bar 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  * BarChart constructor.
 13  * @param {string|Element} container The HTML container.
 14  * @constructor
 15  * @class Simple horizontal bar chart implementation.
 16  * @extends {charts.BaseChart} charts.BaseChart
 17  * @requires animation
 18  * @requires charts.Grid
 19  * @requires formatters.NumberFormatter
 20  * @example
 21  * <b>var</b> chart = <b>new</b> charts.BarChart('container_id');
 22  * chart.draw([['Year', 'Sales', 'Expenses', 'Profit'],
 23  *             [2011, 80, 30, 45], [2012, 65, 130, 90], [2013, 45, 100, 60]]);
 24  *
 25  * <div style="border: solid 1px #ccc; margin: 5px; padding: 5px; width: 560px">
 26  *   <div id="chart-container"
 27  *        style="width: 560px; height: 200px;"></div>
 28  * </div>
 29  * <script src="https://greylock.js.org/greylock.js"></script>
 30  * <script>
 31  *   var chart = new charts.BarChart('chart-container');
 32  *   chart.draw([['Year', 'Sales', 'Expenses', 'Profit'],
 33  *               [2011, 80, 30, 45], [2012, 65, 130, 90], [2013, 45, 100, 60]]);
 34  * </script>
 35  */
 36 charts.BarChart = function(container) {
 37   charts.BaseChart.apply(this, arguments);
 38 
 39   /**
 40    * Draws the chart based on <code>data</code> and <code>opt_options</code>.
 41    * @param {!Array.<Array>} data A chart data.
 42    * @param {Object=} opt_options A optional chart's configuration options.
 43    * @override
 44    * @see charts.BaseChart#getOptions
 45    * @example
 46    * options: {
 47    *   'stroke': 1,
 48    *   'font': {'size': 11},
 49    *   'flip': <b>false</b>
 50    * }
 51    */
 52   this.draw = function(data, opt_options) {
 53     data_ = data;
 54     options_ = getOptions_(opt_options);
 55     options_['direction'] = getDirection_();
 56     self_.tooltip.setOptions(options_);
 57 
 58     /** @type {string} */ var content = '';
 59     /** @type {!Array.<Array>} */ var rows = self_.getDataRows(data);
 60     /** @type {!Array.<string>} */ var columns = self_.getDataColumns(data);
 61     /** @type {!Array.<number>} */ var range = self_.getDataRange(data, 1);
 62 
 63     /** @type {number} */ var width = self_.container.offsetWidth || 200;
 64     /** @type {number} */ var height = self_.container.offsetHeight || width;
 65     /** @type {number} */
 66     var border = /** @type {number} */ (options_['stroke']);
 67 
 68     bars_ = 1; // Reset bars counter for redrawing issue.
 69     for (/** @type {number} */ var i = 0; i < rows.length; i++) {
 70       bars_ += rows[i].length;
 71     }
 72 
 73     barWidth_ = width / bars_ - border * 2;
 74     barHeight_ = height / bars_ - border * 2;
 75 
 76     bars_ = 0;
 77     for (i = 0; i < rows.length; i++) {
 78       /** @type {Array} */ var row = rows[i];
 79       /** @type {number} */ var j = 1;
 80       for (; j < row.length; j++) {
 81         /** @type {string} */ var tooltip = self_.tooltip.parse(
 82             row[0], columns[j], formatter_.formatNumber(row[j]));
 83 
 84         content += getBarContent_(getBarRect_(rows, i, j, range),
 85                                   options_['colors'][j - 1], tooltip);
 86         bars_++;
 87       }
 88 
 89       bars_++; // Add space between bar groups.
 90     }
 91 
 92     initGrid_(range);
 93     self_.drawContent(content);
 94     initEvents_();
 95   };
 96 
 97   // Export for closure compiler.
 98   this['draw'] = this.draw;
 99 
100   /**
101    * Draws content into <code>this.container</code> as <code>innerHTML</code>.
102    * @param {string} content The HTML markup content.
103    * @param {number=} opt_width Optional chart width.
104    * @param {number=} opt_height Optional chart height.
105    * @override
106    */
107   this.drawContent = function(content, opt_width, opt_height) {
108     opt_width = opt_width || self_.container.offsetWidth || 200;
109     opt_height = opt_height || self_.container.offsetHeight || opt_width;
110 
111     self_.container.style.position = 'relative';
112     self_.container.style.overflow = 'hidden';
113     self_.container.innerHTML +=
114         '<div style="position:absolute;width:' + opt_width +
115         'px;height:' + opt_height + 'px">' + content + '</div>';
116   };
117 
118   /**
119    * @param {!Array.<number>} range The data range with min and max values.
120    * @private
121    */
122   function initGrid_(range) {
123     /** @type {number} */ var minValue = range[0];
124     /** @type {number} */ var maxValue = range[1];
125     /** @type {!Array.<string>} */ var columns = [];
126     //for (/** @type {number} */ var i = 0; i < data_.length; i++) {
127     //columns.push('');
128     //columns.push(data_[i][0]);
129     //}
130     /** @type {!charts.Grid} */ var grid = new charts.Grid(self_.container);
131     options_['data'] = {'min': minValue, 'max': maxValue, 'columns': columns};
132     options_['padding'] = 0;
133     grid.draw(options_);
134   }
135 
136   /**
137    * Gets chart's direction.
138    * @return {number} Returns chart direction.
139    * @see charts.BarChart#DIRECTION
140    * @private
141    */
142   function getDirection_() {
143     /** @type {number} */ var direction = options_['direction'] ||
144                                           charts.Grid.DIRECTION.LEFT_TO_RIGHT;
145     if (options_['flip']) {
146       if (direction == charts.Grid.DIRECTION.LEFT_TO_RIGHT)
147         direction = charts.Grid.DIRECTION.RIGHT_TO_LEFT;
148       else if (direction == charts.Grid.DIRECTION.BOTTOM_TO_TOP)
149         direction = charts.Grid.DIRECTION.TOP_TO_BOTTOM;
150     }
151     return direction;
152   }
153 
154   /**
155    * @param {!Array.<Array>} rows Chart data rows.
156    * @param {number} rowIndex Current row index.
157    * @param {number} columnIndex Current column index.
158    * @param {!Array.<number>} range The data range with min and max values.
159    * @return {!Object.<string, number>} Returns rect as {x, y, width, height}.
160    * @private
161    */
162   function getBarRect_(rows, rowIndex, columnIndex, range) {
163     /** @type {!Object.<string, number>} */
164     var rect = {x: 0, y: 0, width: 0, height: 0};
165 
166     /** @type {number} */ var width = self_.container.offsetWidth || 200;
167     /** @type {number} */ var height = self_.container.offsetHeight || width;
168     /** @type {number} */
169     var border = /** @type {number} */ (options_['stroke']);
170 
171     /** @type {number} */ var minValue = range[0];
172     /** @type {number} */ var maxValue = range[1];
173 
174     /** @type {Array} */ var row = rows[rowIndex];
175     /** @type {number} */ var value = row[columnIndex];
176     /** @type {number} */ var direction = getDirection_();
177 
178     if (direction < charts.Grid.DIRECTION.BOTTOM_TO_TOP) {
179       // Horizontal charts.
180       rect.width = value / maxValue * (width - border * 4);
181       // rect.height = (height / row.length / rows.length) - border * 2;
182       rect.height = barHeight_;
183       rect.x = border;
184       rect.y = (rect.height + border * 2) * bars_ + (rect.height + border * 2);
185       //if (charts.Grid.DIRECTION.RIGHT_TO_LEFT == direction)
186       //  rect.x = width - rect.width - border;
187     } else if (direction >= charts.Grid.DIRECTION.BOTTOM_TO_TOP) {
188       // Vertical charts.
189       // rect.width = (width / row.length / rows.length) - border * 2;
190       rect.width = barWidth_;
191       rect.height = value / maxValue * (height - border * 4);
192       rect.x = (rect.width + border * 2) * bars_ + (rect.width + border * 2);
193       rect.y = border;
194       //if (charts.Grid.DIRECTION.BOTTOM_TO_TOP == direction)
195       //  rect.y = height - rect.height;
196     }
197 
198     return rect;
199   }
200 
201   /**
202    * Gets chart's options merged with defaults chart's options.
203    * @param {Object=} opt_options A optional chart's configuration options.
204    * @return {!Object.<string, *>} A map of name/value pairs.
205    * @see charts.BaseChart#getOptions
206    * @private
207    */
208   function getOptions_(opt_options) {
209     opt_options = opt_options || {};
210     opt_options['stroke'] = opt_options['stroke'] || 1;
211     opt_options['font'] = opt_options['font'] || {};
212     opt_options['font']['size'] = opt_options['font']['size'] || 11;
213     return self_.getOptions(opt_options);
214   }
215 
216   /**
217    * @param {!Object.<string, number>} rect Bar rect.
218    * @param {string} color Bar color.
219    * @param {string} tooltip Bar tooltip.
220    * @return {string} Returns bar content as HTML markup string.
221    * @private
222    */
223   function getBarContent_(rect, color, tooltip) {
224     return '<div class="bar" style="' +
225         'width:' + rect.width + 'px;' +
226         'height:' + rect.height + 'px;' +
227         'top:' + rect.y + 'px;' +
228         'left:' + rect.x + 'px;' +
229         'background:' + color + ';' +
230         'border:solid ' + options_['stroke'] + 'px #fff;' +
231         'color:#fff;' +
232         'overflow:hidden;' +
233         'position:absolute;' +
234         'opacity:' + options_['opacity'] + ';' +
235         'filter: alpha(opacity=' + (options_['opacity'] * 100) + ');' +
236         'font-family:' + options_['font']['family'] + ';' +
237         'font-size:' + options_['font']['size'] + 'px;' +
238         '" tooltip="' + tooltip + '"></div>';
239   }
240 
241   /**
242    * Initializes events handlers.
243    * @private
244    */
245   function initEvents_() {
246     /** @type {!Array|NodeList} */
247     var nodes = dom.getElementsByClassName(self_.container, 'bar');
248     for (/** @type {number} */ var i = 0; i < nodes.length; i++) {
249       setEvents_(nodes[i]);
250     }
251   }
252 
253   /**
254    * Sets events handlers.
255    * @param {!Element} node The element.
256    * @private
257    */
258   function setEvents_(node) {
259     dom.events.addEventListener(node, dom.events.TYPE.MOUSEOVER, function(e) {
260       node.style.opacity = 1;
261       node.style.filter = 'alpha(opacity=100)';
262 
263       self_.tooltip.show(e);
264       /** @type {!Object.<string, number>} */
265       var position = getTooltipPosition_(node);
266       self_.tooltip.show(e, position.x, position.y);
267     });
268 
269     dom.events.addEventListener(node, dom.events.TYPE.MOUSEOUT, function(e) {
270       node.style.opacity = options_['opacity'];
271       node.style.filter = 'alpha(opacity=' + (options_['opacity'] * 100) + ')';
272       self_.tooltip.hide(e);
273     });
274 
275     dom.events.dispatchEvent(node, dom.events.TYPE.MOUSEOUT);
276     initAnimation_(node);
277   }
278 
279   /**
280    * Initializes bar animation.
281    * @param {!Element|Node} node The element.
282    * @private
283    */
284   function initAnimation_(node) {
285     /** @type {number} */
286     var width = parseFloat(node.style.width) || node.offsetWidth;
287     /** @type {number} */
288     var height = parseFloat(node.style.height) || node.offsetHeight;
289     /** @type {number} */
290     var x = parseFloat(node.style.left) || node.offsetLeft;
291     /** @type {number} */
292     var y = parseFloat(node.style.top) || node.offsetTop;
293     /** @type {number} */ var direction = getDirection_();
294 
295     if (direction == charts.Grid.DIRECTION.LEFT_TO_RIGHT) {
296       node.style.width = '1px';
297       animation.animate(node, {'width': width});
298     } else if (direction == charts.Grid.DIRECTION.RIGHT_TO_LEFT) {
299       node.style.width = '1px';
300       node.style.right = '1px';
301       node.style.left = 'auto';
302       animation.animate(node, {'width': width});
303     } else if (direction == charts.Grid.DIRECTION.TOP_TO_BOTTOM) {
304       node.style.height = '1px';
305       animation.animate(node, {'height': height});
306     } else if (direction == charts.Grid.DIRECTION.BOTTOM_TO_TOP) {
307       node.style.height = '1px';
308       node.style.bottom = '1px';
309       node.style.top = 'auto';
310       animation.animate(node, {'height': height});
311     }
312   }
313 
314   /**
315    * @param {!Element|Node} node The element.
316    * @return {!Object.<string, number>} Returns tooltip position as {x, y}.
317    * @private
318    */
319   function getTooltipPosition_(node) {
320     /** @type {number} */ var width = node.offsetWidth;
321     /** @type {number} */ var height = node.offsetHeight;
322     /** @type {!Object.<string, number>} */
323     var result = {x: node.offsetLeft, y: node.offsetTop};
324 
325     while (node = node.offsetParent) {
326       result.x += node.offsetLeft;
327       result.y += node.offsetTop;
328     }
329 
330     /** @type {Node} */ var tooltip = self_.tooltip.getTooltipElement();
331     /** @type {number} */ var direction = getDirection_();
332     if (direction == charts.Grid.DIRECTION.LEFT_TO_RIGHT) {
333       result.x += width;
334       result.y += options_['stroke'];
335     } else if (direction == charts.Grid.DIRECTION.RIGHT_TO_LEFT) {
336       result.x -= tooltip.offsetWidth;
337       result.y += options_['stroke'];
338     } else if (direction == charts.Grid.DIRECTION.BOTTOM_TO_TOP) {
339       result.x += options_['stroke'];
340       result.y -= tooltip.offsetHeight;
341     } else if (direction == charts.Grid.DIRECTION.TOP_TO_BOTTOM) {
342       result.x += options_['stroke'];
343       result.y += height;
344     }
345     return result;
346   }
347 
348   /**
349    * The reference to current class instance. Used in private methods.
350    * @type {!charts.BarChart}
351    * @private
352    */
353   var self_ = this;
354 
355   /**
356    * @type {Array.<Array>}
357    * @private
358    */
359   var data_ = null;
360 
361   /**
362    * @dict
363    * @private
364    */
365   var options_ = null;
366 
367   /**
368    * Total bars counter.
369    * @type {number}
370    * @private
371    */
372   var bars_ = 1;
373 
374   /**
375    * Vertical charts bar width.
376    * @type {number}
377    * @private
378    */
379   var barWidth_ = 0;
380 
381   /**
382    * Horizontal charts bar height.
383    * @type {number}
384    * @private
385    */
386   var barHeight_ = 0;
387 
388   /**
389    * @type {!formatters.NumberFormatter}
390    * @private
391    */
392   var formatter_ = new formatters.NumberFormatter;
393 };
394 
395 // Export for closure compiler.
396 charts['BarChart'] = charts.BarChart;
397