1 
  2 /**
  3  * @fileoverview A base class of all visualization charts.
  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  * A base class of all visualization charts.
 13  * @param {string|Element} container The HTML container.
 14  * @constructor
 15  * @class A base class of all visualization charts.
 16  * @requires charts.Tooltip
 17  * @requires util.Object
 18  */
 19 charts.BaseChart = function(container) {
 20 
 21   /**
 22    * Defaults chart options.
 23    * @dict
 24    * @see charts.BaseChart#getOptions
 25    * @example <code>{
 26    *   'font': {'family': 'Arial', 'size': 13},
 27    *   'opacity': 0.8,
 28    *   'colors': [list of colors]
 29    * }</code>
 30    */
 31   this.defaults = {
 32     'font': {'family': 'Arial', 'size': 13},
 33     'opacity': 0.7,
 34     'colors': charts.BaseChart.DEFAULT_COLORS
 35   };
 36 
 37   /**
 38    * The reference to HTML chart container.
 39    * @type {Element}
 40    */
 41   this.container = typeof container == 'string' ?
 42       dom.getElementById(container) : container;
 43 
 44   /**
 45    * Instance of <code>charts.Tooltip</code>.
 46    * @type {!charts.Tooltip}
 47    * @see charts.Tooltip
 48    * @protected
 49    */
 50   this.tooltip = new charts.Tooltip;
 51 
 52   /**
 53    * Draws the chart based on <code>data</code> and <code>opt_options</code>.
 54    * Abstract method, should be overwritten in nested classes.
 55    * All overwritten methods should be exported for closure compiler.
 56    * @param {!Array.<Array>} data A chart data.
 57    * @param {Object=} opt_options A optional chart's configuration options.
 58    */
 59   this.draw = function(data, opt_options) {};
 60 
 61   /**
 62    * Gets chart's options merged with defaults chart's options.
 63    * @param {Object.<string, *>=} opt_options Options map.
 64    * @return {!Object.<string, *>} A map of name/value pairs.
 65    * @see charts.BaseChart#defaults
 66    */
 67   this.getOptions = function(opt_options) {
 68     return util.Object.extend(self_.defaults, opt_options || {});
 69   };
 70 
 71   /**
 72    * Extracts columns from <code>data</code>.
 73    * @param {Array.<Array>} data The chart data.
 74    * @return {!Array.<string>} Returns data columns.
 75    */
 76   this.getDataColumns = function(data) {
 77     // Return copy of first data row.
 78     return /** @type {!Array.<string>} */ (data[0].slice());
 79   };
 80 
 81   /**
 82    * Extracts rows from <code>data</code>.
 83    * @param {Array.<Array>} data The chart data.
 84    * @return {!Array.<Array>} Returns cloned <code>data</code> rows.
 85    */
 86   this.getDataRows = function(data) {
 87     // Return copy of data rows except first row.
 88     return data.slice(1);
 89   };
 90 
 91   /**
 92    * Gets data range with min and max values.
 93    * @param {Array.<Array>} data The chart data.
 94    * @param {number=} opt_column Optional columns index starting from.
 95    * @return {!Array.<number>} Returns range as <code>[min, max]</code> array.
 96    */
 97   this.getDataRange = function(data, opt_column) {
 98     if (!data.range_) {
 99       opt_column = opt_column || 0;
100       /** @type {!Array.<Array>} */ var rows = self_.getDataRows(data);
101       /** @type {number} */ var maxValue = 0;
102       /** @type {?number} */ var minValue = null;
103       for (/** @type {number} */ var i = 0; i < rows.length;) {
104         /** @type {Array.<number>} */ var row = rows[i++];
105         for (/** @type {number} */ var j = opt_column; j < row.length; j++) {
106           maxValue = Math.max(maxValue, row[j]);
107           if (minValue == null) minValue = maxValue;
108           minValue = Math.min(minValue, row[j]);
109         }
110       }
111       data.range_ = [minValue, maxValue];
112     }
113     return data.range_;
114   };
115 
116   /**
117    * Gets max value.
118    * @param {Array.<Array>} data The chart data.
119    * @return {number} Returns max value.
120    */
121   this.getMaxValue = function(data) {
122     return self_.getDataRange(data)[1];
123   };
124 
125   /**
126    * Gets min value.
127    * @param {Array.<Array>} data The chart data.
128    * @return {number} Returns min value.
129    */
130   this.getMinValue = function(data) {
131     return self_.getDataRange(data)[0];
132   };
133 
134   /**
135    * Draws content into <code>this.container</code> as <code>innerHTML</code>.
136    * @param {string} content SVG or VML markup content.
137    * @param {number=} opt_width Optional chart width.
138    * @param {number=} opt_height Optional chart height.
139    */
140   this.drawContent = function(content, opt_width, opt_height) {
141     opt_width = opt_width || self_.container.offsetWidth || 200;
142     opt_height = opt_height || self_.container.offsetHeight || opt_width;
143 
144     self_.container.style.position = 'relative';
145     self_.container.style.overflow = 'hidden';
146 
147     self_.container.innerHTML += (charts.IS_SVG_SUPPORTED ?
148         wrapSvgContent_ : wrapVmlContent_)(content, opt_width, opt_height);
149   };
150 
151   /**
152    * Wraps SVG markup content into <code><svg></code> container.
153    * @param {string} content SVG markup content.
154    * @param {number} width The chart width.
155    * @param {number} height The chart height.
156    * @return {string} Returns wrapped SVG markup.
157    * @private
158    */
159   function wrapSvgContent_(content, width, height) {
160     // style="shape-rendering:geometricPrecision;
161     //        text-rendering:geometricPrecision;
162     //        image-rendering:optimizeQuality;
163     //        fill-rule:evenodd;
164     //        clip-rule:evenodd"
165     return '<svg width="' + width + '" height="' + height + '" version="1.0" ' +
166            'xmlns="http://www.w3.org/2000/svg" ' +
167            'xmlns:xlink="http://www.w3.org/1999/xlink" ' +
168            'style="position:absolute">' +
169            content + '</svg>';
170   }
171 
172   /**
173    * Wraps VML markup content into <code><vml:group></code> container.
174    * @param {string} content VML markup content.
175    * @param {number} width The chart width.
176    * @param {number} height The chart height.
177    * @return {string} Returns wrapped VML markup.
178    * @private
179    */
180   function wrapVmlContent_(content, width, height) {
181     return '<v:group coordorigin="0 0" ' +
182            'coordsize="' + width + ' ' + height + '" ' +
183            'style="position:absolute;left:0;top:0;' +
184            'width:' + width + 'px;height:' + height + 'px">' +
185            content + '</v:group>';
186   }
187 
188   /**
189    * Initializes default behaviors.
190    * @private
191    */
192   function init_() {
193 
194     if (!charts.IS_SVG_SUPPORTED) {
195       try {
196         dom.document['namespaces']['add'](
197             'v', 'urn:schemas-microsoft-com:vml', '#default#VML');
198       } catch (e) {
199         //window.console && console.log(['vml:namespaces.add', e.message || e]);
200       }
201 
202       try {
203         /** @type {Object} */ var sheet = dom.document['createStyleSheet']();
204         sheet['addRule']('v\\:group',
205                          'behavior:url(#default#VML);antialias:true;' +
206                          'display:inline-block');
207       } catch (e) {
208         //window.console && console.log(['vml:sheet.addRule', e.message || e]);
209       }
210     }
211   }
212 
213   /**
214    * The reference to current class instance. Used in private methods.
215    * @type {!charts.BaseChart}
216    * @private
217    */
218   var self_ = this;
219 
220   init_();
221 };
222 
223 
224 /**
225  * List of default colors.
226  * @type {!Array.<string>}
227  * @static
228  */
229 charts.BaseChart.DEFAULT_COLORS = [
230   '#3366BB', '#DD3311', '#FF9911', '#119911', '#991199',
231   '#0099CC', '#DD4488', '#66AA33', '#BB2222', '#336699',
232   '#9955AA', '#11AA99', '#AABB11', '#6633DD', '#EE7700',
233   '#880000', '#661166', '#339966', '#5577AA', '#3333AA',
234   '#BB7722', '#11DD22', '#BB1188', '#FF3399', '#995533',
235   '#AACC11', '#227788', '#668811', '#BBAA11', '#005522',
236   '#773311', '#4EDB05', '#377D18', '#AD9E88', '#4C49E3',
237   '#86D0DD', '#5613EA', '#29F847', '#828295', '#B7F439',
238   '#224256', '#490A7F', '#622A17', '#E188A1', '#F65A2D',
239   '#87F586', '#DF56E4', '#F3E815', '#C528D6', '#32BDC0',
240   '#91F51C', '#A75590', '#556F7C', '#520036', '#64B6AA',
241   '#825B4A', '#63B75A', '#DC8BF7', '#811D02', '#FB1D45',
242   '#76856A', '#F240BB', '#D53C9B', '#67AEC8', '#01786A',
243   '#A08A65', '#715E3F', '#92B88A', '#3A5D6D', '#B8D9D9',
244   '#622C1B', '#1F566F', '#F221EE', '#04808D', '#7A787A',
245   '#F48A8A', '#A60FFA', '#5FDF71', '#F4D11E', '#EDA971',
246   '#3F0AF2', '#E84203', '#2344FA', '#98CFB5', '#E0F068',
247   '#83D7E3', '#1500EA', '#99C760', '#21E636', '#241BB8',
248   '#C44FF5', '#3499BF', '#CFC58F', '#91A739', '#7222B0'];
249