1 /**
  2  * Source: MapKey.js
  3  * Copyright (c) 2013-2014 Oculus Info Inc.
  4  * @fileOverview Map Keys for mapping from one space (e.g. data) into another (e.g. visual)
  5  */
  6 
  7 /**
  8  * @namespace
  9  * @ignore
 10  * Ensure namespace exists
 11  */
 12 aperture = (
 13 /** @private */
 14 function(namespace) {
 15 
 16 	namespace.MapKey = namespace.Class.extend( 'aperture.MapKey',
 17 
 18 		/** @lends aperture.MapKey# */
 19 		{
 20 			/**
 21 			 * @class A MapKey object maps from a Range object, representing a variable in
 22 			 * data, to a color or numeric visual property such as a size or coordinate.
 23 			 * MapKey is abstract. Instances are constructed by calling
 24 			 * {@link aperture.Range range.mappedTo()}, and are used by {@link aperture.Mapping mappings}.
 25 			 *
 26 			 * @constructs
 27 			 * @factoryMade
 28 			 * @extends aperture.Class
 29 			 */
 30 			init : function(from, to) {
 31 				this.fromRange = from;
 32 				this.toArray = to;
 33 			},
 34 
 35 			/**
 36 			 * A label for this map key reflecting the data property
 37 			 * being mapped in readable form. This value is initialized
 38 			 * from the label in the range but may be subsequently changed here.
 39 			 *
 40 			 * @param {String} value If a parameter given, acts as a setter and sets the label.
 41 			 * @returns {String} If no parameter given, returns the MapKey's label. Otherwise sets and returns the label.
 42 			 */
 43 			label : function ( value ) {
 44 				if (arguments.length) {
 45 					// Set the value
 46 					this.label = value;
 47 				}
 48 				return this.label;
 49 			},
 50 
 51 			/**
 52 			 * Returns the Range object that this maps from.
 53 			 *
 54 			 * @returns {aperture.Range}
 55 			 */
 56 			from : function () {
 57 				return this.fromRange;
 58 			},
 59 
 60 			/**
 61 			 * Returns the set of values that this maps to.
 62 			 *
 63 			 * @returns {Array}
 64 			 */
 65 			to : function () {
 66 				return this.toArray;
 67 			}
 68 
 69 			/**
 70 			 * Returns a visual property value mapped from a data value.
 71 			 * This method is abstract and implemented by different types of map keys.
 72 			 *
 73 			 * @name map
 74 			 * @methodOf aperture.MapKey.prototype
 75 			 *
 76 			 * @param dataValue
 77 			 *      the value to be mapped using the key.
 78 			 *
 79 			 * @returns
 80 			 *      the result of the mapping.
 81 			 */
 82 
 83 			/**
 84 			 * This method is mostly relevant to scalar mappings,
 85 			 * where it can be used to set a non-linear mapping
 86 			 * function. A string can be passed indicating a standard
 87 			 * non linear-function, or a custom function may be supplied.
 88 			 * Standard types include
 89 			 * <span class="fixedFont">'linear'</span> and
 90 			 * <span class="fixedFont">'area'</span>, for area based
 91 			 * visual properties (such as a circle's radius).
 92 			 * <br><br>
 93 			 * An ordinal map key returns a type of
 94 			 * <span class="fixedFont">'ordinal'</span>.
 95 			 *
 96 			 * @name type
 97 			 * @methodOf aperture.MapKey.prototype
 98 			 *
 99 			 * @param {String|Function} [type]
100 			 *      if setting the value, the type of mapping function which
101 			 *      will map the progression of 0 to 1 input values to 0 to 1
102 			 *      output values, or a custom function.
103 			 *
104 			 * @returns {this|Function}
105 			 *		if getting the mapping function, the type or custom function, else
106 			 *		if setting the function a reference to <span class="fixedFont">this</span> is
107 			 *		returned for convenience of chaining method calls.
108 			 */
109 		}
110 	);
111 
112 	/**
113 	 * @private
114 	 * Predefined interpolators for each type
115 	 */
116 	var blenders = {
117 		'number' : function( v0, v1, weight1 ) {
118 			return v0 + weight1 * ( v1-v0 );
119 		},
120 
121 		// objects must implement a blend function
122 		'object' : function( v0, v1, weight1 ) {
123 			return v0.blend( v1, weight1 );
124 		}
125 	},
126 
127 	/**
128 	 * @private
129 	 * Default interpolation tweens
130 	 */
131 	toTypes = {
132 
133 		// useful for any visual property that is area forming, like a circle's radius,
134 		// and where the data range is absolute.
135 		'area' : function ( value ) {
136 			return Math.sqrt( value );
137 		}
138 	};
139 
140 	/**
141 	 * Implements mappings for scalar ranges.
142 	 * We privatize this from jsdoc to encourage direct
143 	 * construction from a scalar range (nothing else
144 	 * makes sense) and b/c there is nothing else to
145 	 * document here.
146 	 *
147 	 * @private
148 	 */
149 	namespace.ScalarMapKey = namespace.MapKey.extend( 'aperture.ScalarMapKey',
150 	{
151 		/**
152 		 * Constructor
153 		 * @private
154 		 */
155 		init : function( fromRange, toArray ) {
156 			namespace.MapKey.prototype.init.call( this, fromRange, toArray );
157 
158 			this.blend = blenders[typeof toArray[0]];
159 			this.toType  = 'linear';
160 		},
161 
162 		/**
163 		 * Implements the mapping function
164 		 * @private
165 		 */
166 		map : function( source ) {
167 			var mv = this.fromRange.map( source ),
168 				to = this.toArray;
169 
170 			switch ( mv ) {
171 			case 0:
172 				// start
173 				return to[0];
174 
175 			case 1:
176 				// end
177 				return to[to.length-1];
178 
179 			default:
180 				// non-linear?
181 				if ( this.tween ) {
182 					mv = this.tween( mv, source );
183 				}
184 
185 				// interpolate
186 				var i = Math.floor( mv *= to.length-1 );
187 
188 				return this.blend( to[i], to[i+1], mv - i );
189 			}
190 		},
191 
192 		/**
193 		 * A string can be passed indicating a standard
194 		 * non linear-function, or a custom function may be supplied.
195 		 * @private
196 		 * [Documented in MapKey]
197 		 */
198 		type : function ( type ) {
199 			if ( type === undefined ) {
200 				return this.toType;
201 			}
202 
203 			if ( aperture.util.isFunction(type) ) {
204 				if (type(0) != 0 || type(1) != 1) {
205 					throw Error('map key type functions must map a progression from 0 to 1');
206 				}
207 
208 				this.tween = type;
209 			} else if ( aperture.util.isString(type) ) {
210 				this.tween = toTypes[type];
211 			}
212 
213 			this.toType = type;
214 
215 			return this;
216 		}
217 	});
218 
219 	/**
220 	 * Implements mappings for ordinal ranges.
221 	 * We privatize this from jsdoc to encourage direct
222 	 * construction from an ordinal range (nothing else
223 	 * makes sense) and b/c there is nothing else to
224 	 * document here.
225 	 *
226 	 * @private
227 	 */
228 	namespace.OrdinalMapKey = namespace.MapKey.extend( 'aperture.OrdinalMapKey',
229 	{
230 		/**
231 		 * Implements the mapping function
232 		 * @private
233 		 */
234 		map : function( source ) {
235 			// Map index to index, mod to be safe (to could be smaller than range)
236 			// Missing in range array leads to -1, array[-1] is undefined as desired
237 			return this.toArray[ this.fromRange.map(source) % this.toArray.length ];
238 		},
239 
240 		/**
241 		 * For completeness.
242 		 * @private
243 		 */
244 		type : function ( type ) {
245 			if ( type === undefined ) {
246 				return 'ordinal';
247 			}
248 
249 			return this;
250 		}
251 	});
252 
253 	return namespace;
254 
255 }(aperture || {}));
256 
257