NOBUG: Fixed file access permissions
[moodle.git] / lib / yuilib / 3.13.0 / axis-base / axis-base.js
blobb6b63d7c2a6332a24c2e31dbf9d0d46c85f708ef
1 /*
2 YUI 3.13.0 (build 508226d)
3 Copyright 2013 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
6 */
8 YUI.add('axis-base', function (Y, NAME) {
10 /**
11  * The Charts widget provides an api for displaying data
12  * graphically.
13  *
14  * @module charts
15  * @main charts
16  */
18 /**
19  * Provides functionality for the handling of axis data in a chart.
20  *
21  * @module charts
22  * @submodule axis-base
23  */
24 var Y_Lang = Y.Lang;
26 /**
27  * The Renderer class is a base class for chart components that use the `styles`
28  * attribute.
29  *
30  * @module charts
31  * @class Renderer
32  * @constructor
33  */
34 function Renderer(){}
36 Renderer.ATTRS = {
37         /**
38          * Style properties for class
39          *
40          * @attribute styles
41          * @type Object
42          */
43         styles:
44         {
45             getter: function()
46             {
47                 this._styles = this._styles || this._getDefaultStyles();
48                 return this._styles;
49             },
51             setter: function(val)
52             {
53                 this._styles = this._setStyles(val);
54             }
55         },
57         /**
58          * The graphic in which drawings will be rendered.
59          *
60          * @attribute graphic
61          * @type Graphic
62          */
63         graphic: {}
65 Renderer.NAME = "renderer";
67 Renderer.prototype = {
68     /**
69      * Storage for `styles` attribute.
70      *
71      * @property _styles
72      * @type Object
73      * @private
74      */
75         _styles: null,
77     /**
78      * Method used by `styles` setter.
79      *
80      * @method _setStyles
81      * @param {Object} newStyles Hash of properties to update.
82      * @return Object
83      * @protected
84      */
85         _setStyles: function(newstyles)
86         {
87                 var styles = this.get("styles");
88         return this._mergeStyles(newstyles, styles);
89         },
91     /**
92      * Merges to object literals so that only specified properties are
93      * overwritten.
94      *
95      * @method _mergeStyles
96      * @param {Object} a Hash of new styles
97      * @param {Object} b Hash of original styles
98      * @return Object
99      * @protected
100      */
101     _mergeStyles: function(a, b)
102     {
103         if(!b)
104         {
105             b = {};
106         }
107         var newstyles = Y.merge(b, {});
108         Y.Object.each(a, function(value, key)
109         {
110             if(b.hasOwnProperty(key) && Y_Lang.isObject(value) && !Y_Lang.isFunction(value) && !Y_Lang.isArray(value))
111             {
112                 newstyles[key] = this._mergeStyles(value, b[key]);
113             }
114             else
115             {
116                 newstyles[key] = value;
117             }
118         }, this);
119         return newstyles;
120     },
122     /**
123      * Gets the default value for the `styles` attribute.
124      *
125      * @method _getDefaultStyles
126      * @return Object
127      * @protected
128      */
129     _getDefaultStyles: function()
130     {
131         return {padding:{
132             top:0,
133             right: 0,
134             bottom: 0,
135             left: 0
136         }};
137     }
140 Y.augment(Renderer, Y.Attribute);
141 Y.Renderer = Renderer;
144  * The axis-base submodule contains functionality for the handling of axis data in a chart.
146  * @module charts
147  * @submodule axis-base
148  */
150  * An abstract class that provides the core functionality used by the following classes:
151  * <ul>
152  *      <li>{{#crossLink "CategoryAxisBase"}}{{/crossLink}}</li>
153  *      <li>{{#crossLink "NumericAxisBase"}}{{/crossLink}}</li>
154  *      <li>{{#crossLink "StackedAxisBase"}}{{/crossLink}}</li>
155  *      <li>{{#crossLink "TimeAxisBase"}}{{/crossLink}}</li>
156  *      <li>{{#crossLink "CategoryAxis"}}{{/crossLink}}</li>
157  *      <li>{{#crossLink "NumericAxis"}}{{/crossLink}}</li>
158  *      <li>{{#crossLink "StackedAxis"}}{{/crossLink}}</li>
159  *      <li>{{#crossLink "TimeAxis"}}{{/crossLink}}</li>
160  *  </ul>
162  * @class AxisBase
163  * @constructor
164  * @extends Base
165  * @uses Renderer
166  * @param {Object} config (optional) Configuration parameters.
167  * @submodule axis-base
168  */
169 Y.AxisBase = Y.Base.create("axisBase", Y.Base, [Y.Renderer], {
170     /**
171      * @method initializer
172      * @private
173      */
174     initializer: function()
175     {
176         this.after("minimumChange", Y.bind(this._keyChangeHandler, this));
177         this.after("maximumChange", Y.bind(this._keyChangeHandler, this));
178         this.after("keysChange", this._keyChangeHandler);
179         this.after("dataProviderChange", this._dataProviderChangeHandler);
180     },
182     /**
183      * Returns the value corresponding to the origin on the axis.
184      *
185      * @method getOrigin
186      * @return Number
187      */
188     getOrigin: function() {
189         return this.get("minimum");
190     },
192     /**
193      * Handles changes to `dataProvider`.
194      *
195      * @method _dataProviderChangeHandler
196      * @param {Object} e Event object.
197      * @private
198      */
199     _dataProviderChangeHandler: function()
200     {
201         var keyCollection = this.get("keyCollection").concat(),
202             keys = this.get("keys"),
203             i;
204         if(keys)
205         {
206             for(i in keys)
207             {
208                 if(keys.hasOwnProperty(i))
209                 {
210                     delete keys[i];
211                 }
212             }
213         }
214         if(keyCollection && keyCollection.length)
215         {
216             this.set("keys", keyCollection);
217         }
218     },
220     /**
221      * Calculates the maximum and minimum values for the `Data`.
222      *
223      * @method _updateMinAndMax
224      * @private
225      */
226     _updateMinAndMax: function() {
227     },
229     /**
230      * Constant used to generate unique id.
231      *
232      * @property GUID
233      * @type String
234      * @private
235      */
236     GUID: "yuibaseaxis",
238     /**
239      * Type of data used in `Axis`.
240      *
241      * @property _type
242      * @type String
243      * @readOnly
244      * @private
245      */
246     _type: null,
248     /**
249      * Storage for `setMaximum` attribute.
250      *
251      * @property _setMaximum
252      * @type Object
253      * @private
254      */
255     _setMaximum: null,
257     /**
258      * Storage for `setMinimum` attribute.
259      *
260      * @property _setMinimum
261      * @type Object
262      * @private
263      */
264     _setMinimum: null,
266     /**
267      * Reference to data array.
268      *
269      * @property _data
270      * @type Array
271      * @private
272      */
273     _data: null,
275     /**
276      * Indicates whether the all data is up to date.
277      *
278      * @property _updateTotalDataFlag
279      * @type Boolean
280      * @private
281      */
282     _updateTotalDataFlag: true,
284     /**
285      * Storage for `dataReady` attribute.
286      *
287      * @property _dataReady
288      * @type Boolean
289      * @readOnly
290      * @private
291      */
292     _dataReady: false,
294     /**
295      * Adds an array to the key hash.
296      *
297      * @method addKey
298      * @param value Indicates what key to use in retrieving
299      * the array.
300      */
301     addKey: function (value)
302         {
303         this.set("keys", value);
304         },
306     /**
307      * Gets an array of values based on a key.
308      *
309      * @method _getKeyArray
310      * @param {String} key Value key associated with the data array.
311      * @param {Array} data Array in which the data resides.
312      * @return Array
313      * @private
314      */
315     _getKeyArray: function(key, data)
316     {
317         var i = 0,
318             obj,
319             keyArray = [],
320             len = data.length;
321         for(; i < len; ++i)
322         {
323             obj = data[i];
324             keyArray[i] = obj[key];
325         }
326         return keyArray;
327     },
329     /**
330      * Updates the total data array.
331      *
332      * @method _updateTotalData
333      * @private
334      */
335     _updateTotalData: function()
336     {
337                 var keys = this.get("keys"),
338             i;
339         this._data = [];
340         for(i in keys)
341         {
342             if(keys.hasOwnProperty(i))
343             {
344                 this._data = this._data.concat(keys[i]);
345             }
346         }
347         this._updateTotalDataFlag = false;
348     },
350     /**
351      * Removes an array from the key hash.
352      *
353      * @method removeKey
354      * @param {String} value Indicates what key to use in removing from
355      * the hash.
356      */
357     removeKey: function(value)
358     {
359         var keys = this.get("keys");
360         if(keys.hasOwnProperty(value))
361         {
362             delete keys[value];
363             this._keyChangeHandler();
364         }
365     },
367     /**
368      * Returns a value based of a key value and an index.
369      *
370      * @method getKeyValueAt
371      * @param {String} key value used to look up the correct array
372      * @param {Number} index within the array
373      * @return Number
374      */
375     getKeyValueAt: function(key, index)
376     {
377         var value = NaN,
378             keys = this.get("keys");
379         if(keys[key] && Y_Lang.isNumber(parseFloat(keys[key][index])))
380         {
381             value = keys[key][index];
382         }
383         return parseFloat(value);
384     },
386     /**
387      * Returns values based on key identifiers. When a string is passed as an argument, an array of values is returned.
388      * When an array of keys is passed as an argument, an object literal with an array of values mapped to each key is
389      * returned.
390      *
391      * @method getDataByKey
392      * @param {String|Array} value value used to identify the array
393      * @return Array|Object
394      */
395     getDataByKey: function (value)
396     {
397         var obj,
398             i,
399             len,
400             key,
401             keys = this.get("keys");
402         if(Y_Lang.isArray(value))
403         {
404             obj = {};
405             len = value.length;
406             for(i = 0; i < len; i = i + 1)
407             {
408                 key = value[i];
409                 if(keys[key])
410                 {
411                     obj[key] = this.getDataByKey(key);
412                 }
413             }
414         }
415         else if(keys[value])
416         {
417             obj = keys[value];
418         }
419         else
420         {
421             obj = null;
422         }
423         return obj;
424     },
426     /**
427      * Returns the total number of majorUnits that will appear on an axis.
428      *
429      * @method getTotalMajorUnits
430      * @return Number
431      */
432     getTotalMajorUnits: function()
433     {
434         var units,
435             majorUnit = this.get("styles").majorUnit;
436         units = majorUnit.count;
437         return units;
438     },
440     /**
441      * Gets the distance that the first and last ticks are offset from there respective
442      * edges.
443      *
444      * @method getEdgeOffset
445      * @param {Number} ct Number of ticks on the axis.
446      * @param {Number} l Length (in pixels) of the axis.
447      * @return Number
448      */
449     getEdgeOffset: function(ct, l)
450     {
451         var edgeOffset;
452         if(this.get("calculateEdgeOffset")) {
453             edgeOffset = (l/ct)/2;
454         } else {
455             edgeOffset = 0;
456         }
457         return edgeOffset;
458     },
460     /**
461      * Updates the `Axis` after a change in keys.
462      *
463      * @method _keyChangeHandler
464      * @param {Object} e Event object.
465      * @private
466      */
467     _keyChangeHandler: function()
468     {
469         this._updateMinAndMax();
470         this._updateTotalDataFlag = true;
471         this.fire("dataUpdate");
472     },
474     /**
475      * Gets the default value for the `styles` attribute. Overrides
476      * base implementation.
477      *
478      * @method _getDefaultStyles
479      * @return Object
480      * @protected
481      */
482     _getDefaultStyles: function()
483     {
484         var axisstyles = {
485             majorUnit: {
486                 determinant:"count",
487                 count:11,
488                 distance:75
489             }
490         };
491         return axisstyles;
492     },
494     /**
495      * Getter method for maximum attribute.
496      *
497      * @method _maximumGetter
498      * @return Number
499      * @private
500      */
501     _maximumGetter: function ()
502     {
503         var max = this.get("dataMaximum"),
504             min = this.get("minimum");
505         //If all values are zero, force a range so that the Axis and related series
506         //will still render.
507         if(min === 0 && max === 0)
508         {
509             max = 10;
510         }
511         if(Y_Lang.isNumber(this._setMaximum))
512         {
513             max = this._setMaximum;
514         }
515         return parseFloat(max);
516     },
518     /**
519      * Setter method for maximum attribute.
520      *
521      * @method _maximumSetter
522      * @param {Object} value
523      * @private
524      */
525     _maximumSetter: function (value)
526     {
527         this._setMaximum = parseFloat(value);
528         return value;
529     },
531     /**
532      * Getter method for minimum attribute.
533      *
534      * @method _minimumGetter
535      * @return Number
536      * @private
537      */
538     _minimumGetter: function ()
539     {
540         var min = this.get("dataMinimum");
541         if(Y_Lang.isNumber(this._setMinimum))
542         {
543             min = this._setMinimum;
544         }
545         return parseFloat(min);
546     },
548     /**
549      * Setter method for minimum attribute.
550      *
551      * @method _minimumSetter
552      * @param {Object} value
553      * @private
554      */
555     _minimumSetter: function(val)
556     {
557         this._setMinimum = parseFloat(val);
558         return val;
559     },
561     /**
562      * Indicates whether or not the maximum attribute has been explicitly set.
563      *
564      * @method _getSetMax
565      * @return Boolean
566      * @private
567      */
568     _getSetMax: function()
569     {
570         return Y_Lang.isNumber(this._setMaximum);
571     },
574     /**
575      * Returns and array of coordinates corresponding to an array of data values.
576      *
577      * @method _getCoordsFromValues
578      * @param {Number} min The minimum for the axis.
579      * @param {Number} max The maximum for the axis.
580      * @param {length} length The distance that the axis spans.
581      * @param {Array} dataValues An array of values.
582      * @param {Number} offset Value in which to offset the coordinates.
583      * @param {Boolean} reverse Indicates whether the coordinates should start from
584      * the end of an axis. Only used in the numeric implementation.
585      * @return Array
586      * @private
587      */
588     _getCoordsFromValues: function(min, max, length, dataValues, offset, reverse)
589     {
590         var i,
591             valuecoords = [],
592             len = dataValues.length;
593         for(i = 0; i < len; i = i + 1)
594         {
595             valuecoords.push(this._getCoordFromValue.apply(this, [min, max, length, dataValues[i], offset, reverse]));
596         }
597         return valuecoords;
598     },
600     /**
601      * Returns and array of data values based on the axis' range and number of values.
602      *
603      * @method _getDataValuesByCount
604      * @param {Number} count The number of values to be used.
605      * @param {Number} min The minimum value of the axis.
606      * @param {Number} max The maximum value of the axis.
607      * @return Array
608      * @private
609      */
610     _getDataValuesByCount: function(count, min, max)
611     {
612         var dataValues = [],
613             dataValue = min,
614             len = count - 1,
615             range = max - min,
616             increm = range/len,
617             i;
618         for(i = 0; i < len; i = i + 1)
619         {
620             dataValues.push(dataValue);
621             dataValue = dataValue + increm;
622         }
623         dataValues.push(max);
624         return dataValues;
625     },
627     /**
628      * Indicates whether or not the minimum attribute has been explicitly set.
629      *
630      * @method _getSetMin
631      * @return Boolean
632      * @private
633      */
634     _getSetMin: function()
635     {
636         return Y_Lang.isNumber(this._setMinimum);
637     }
638 }, {
639     ATTRS: {
640         /**
641          * Determines whether and offset is automatically calculated for the edges of the axis.
642          *
643          * @attribute calculateEdgeOffset
644          * @type Boolean
645          */
646         calculateEdgeOffset: {
647             value: false
648         },
650         labelFunction: {
651             valueFn: function() {
652                 return this.formatLabel;
653             }
654         },
656         /**
657          * Hash of array identifed by a string value.
658          *
659          * @attribute keys
660          * @type Object
661          */
662         keys: {
663             value: {},
665             setter: function(val)
666             {
667                 var keys = {},
668                     i,
669                     len,
670                     data = this.get("dataProvider");
671                 if(Y_Lang.isArray(val))
672                 {
673                     len = val.length;
674                     for(i = 0; i < len; ++i)
675                     {
676                         keys[val[i]] = this._getKeyArray(val[i], data);
677                     }
679                 }
680                 else if(Y_Lang.isString(val))
681                 {
682                     keys = this.get("keys");
683                     keys[val] = this._getKeyArray(val, data);
684                 }
685                 else
686                 {
687                     for(i in val)
688                     {
689                         if(val.hasOwnProperty(i))
690                         {
691                             keys[i] = this._getKeyArray(i, data);
692                         }
693                     }
694                 }
695                 this._updateTotalDataFlag = true;
696                 return keys;
697             }
698         },
700         /**
701          *Returns the type of axis data
702          *  <dl>
703          *      <dt>time</dt><dd>Manages time data</dd>
704          *      <dt>stacked</dt><dd>Manages stacked numeric data</dd>
705          *      <dt>numeric</dt><dd>Manages numeric data</dd>
706          *      <dt>category</dt><dd>Manages categorical data</dd>
707          *  </dl>
708          *
709          * @attribute type
710          * @type String
711          */
712         type:
713         {
714             readOnly: true,
716             getter: function ()
717             {
718                 return this._type;
719             }
720         },
722         /**
723          * Instance of `ChartDataProvider` that the class uses
724          * to build its own data.
725          *
726          * @attribute dataProvider
727          * @type Array
728          */
729         dataProvider:{
730             setter: function (value)
731             {
732                 return value;
733             }
734         },
736         /**
737          * The maximum value contained in the `data` array. Used for
738          * `maximum` when `autoMax` is true.
739          *
740          * @attribute dataMaximum
741          * @type Number
742          */
743         dataMaximum: {
744             getter: function ()
745             {
746                 if(!Y_Lang.isNumber(this._dataMaximum))
747                 {
748                     this._updateMinAndMax();
749                 }
750                 return this._dataMaximum;
751             }
752         },
754         /**
755          * The maximum value that will appear on an axis.
756          *
757          * @attribute maximum
758          * @type Number
759          */
760         maximum: {
761             lazyAdd: false,
763             getter: "_maximumGetter",
765             setter: "_maximumSetter"
766         },
768         /**
769          * The minimum value contained in the `data` array. Used for
770          * `minimum` when `autoMin` is true.
771          *
772          * @attribute dataMinimum
773          * @type Number
774          */
775         dataMinimum: {
776             getter: function ()
777             {
778                 if(!Y_Lang.isNumber(this._dataMinimum))
779                 {
780                     this._updateMinAndMax();
781                 }
782                 return this._dataMinimum;
783             }
784         },
786         /**
787          * The minimum value that will appear on an axis.
788          *
789          * @attribute minimum
790          * @type Number
791          */
792         minimum: {
793             lazyAdd: false,
795             getter: "_minimumGetter",
797             setter: "_minimumSetter"
798         },
800         /**
801          * Determines whether the maximum is calculated or explicitly
802          * set by the user.
803          *
804          * @attribute setMax
805          * @type Boolean
806          */
807         setMax: {
808             readOnly: true,
810             getter: "_getSetMax"
811         },
813         /**
814          * Determines whether the minimum is calculated or explicitly
815          * set by the user.
816          *
817          * @attribute setMin
818          * @type Boolean
819          */
820         setMin: {
821             readOnly: true,
823             getter: "_getSetMin"
824         },
826         /**
827          * Array of axis data
828          *
829          * @attribute data
830          * @type Array
831          */
832         data: {
833             getter: function ()
834             {
835                 if(!this._data || this._updateTotalDataFlag)
836                 {
837                     this._updateTotalData();
838                 }
839                 return this._data;
840             }
841         },
843         /**
844          * Array containing all the keys in the axis.
846          * @attribute keyCollection
847          * @type Array
848          */
849         keyCollection: {
850             getter: function()
851             {
852                 var keys = this.get("keys"),
853                     i,
854                     col = [];
855                 for(i in keys)
856                 {
857                     if(keys.hasOwnProperty(i))
858                     {
859                         col.push(i);
860                     }
861                 }
862                 return col;
863             },
864             readOnly: true
865         },
867         /**
868          * Object which should have by the labelFunction
869          *
870          * @attribute labelFunctionScope
871          * @type Object
872          */
873         labelFunctionScope: {}
874     }
878 }, '3.13.0', {"requires": ["classnamemanager", "datatype-number", "datatype-date", "base", "event-custom"]});