1 /**
  2  * Returns an ordinal scale for the specified domain. The arguments to this
  3  * constructor are optional, and equivalent to calling {@link #domain}.
  4  *
  5  * @class Represents an ordinal scale. <style
  6  * type="text/css">sub{line-height:0}</style> An ordinal scale represents a
  7  * pairwise mapping from <i>n</i> discrete values in the input domain to
  8  * <i>n</i> discrete values in the output range. For example, an ordinal scale
  9  * might map a domain of species ["setosa", "versicolor", "virginica"] to colors
 10  * ["red", "green", "blue"]. Thus, saying
 11  *
 12  * <pre>    .fillStyle(function(d) {
 13  *         switch (d.species) {
 14  *           case "setosa": return "red";
 15  *           case "versicolor": return "green";
 16  *           case "virginica": return "blue";
 17  *         }
 18  *       })</pre>
 19  *
 20  * is equivalent to
 21  *
 22  * <pre>    .fillStyle(pv.Scale.ordinal("setosa", "versicolor", "virginica")
 23  *         .range("red", "green", "blue")
 24  *         .by(function(d) d.species))</pre>
 25  *
 26  * If the mapping from species to color does not need to be specified
 27  * explicitly, the domain can be omitted. In this case it will be inferred
 28  * lazily from the data:
 29  *
 30  * <pre>    .fillStyle(pv.colors("red", "green", "blue")
 31  *         .by(function(d) d.species))</pre>
 32  *
 33  * When the domain is inferred, the first time the scale is invoked, the first
 34  * element from the range will be returned. Subsequent calls with unique values
 35  * will return subsequent elements from the range. If the inferred domain grows
 36  * larger than the range, range values will be reused. However, it is strongly
 37  * recommended that the domain and the range contain the same number of
 38  * elements.
 39  *
 40  * <p>A range can be discretized from a continuous interval (e.g., for pixel
 41  * positioning) by using {@link #split}, {@link #splitFlush} or
 42  * {@link #splitBanded} after the domain has been set. For example, if
 43  * <tt>states</tt> is an array of the fifty U.S. state names, the state name can
 44  * be encoded in the left position:
 45  *
 46  * <pre>    .left(pv.Scale.ordinal(states)
 47  *         .split(0, 640)
 48  *         .by(function(d) d.state))</pre>
 49  *
 50  * <p>N.B.: ordinal scales are not invertible (at least not yet), since the
 51  * domain and range and discontinuous. A workaround is to use a linear scale.
 52  *
 53  * @param {...} domain... optional domain values.
 54  * @extends pv.Scale
 55  * @see pv.colors
 56  */
 57 pv.Scale.ordinal = function() {
 58   var d = [], i = {}, r = [], band = 0;
 59 
 60   /** @private */
 61   function scale(x) {
 62     if (!(x in i)) i[x] = d.push(x) - 1;
 63     return r[i[x] % r.length];
 64   }
 65 
 66   /**
 67    * Sets or gets the input domain. This method can be invoked several ways:
 68    *
 69    * <p>1. <tt>domain(values...)</tt>
 70    *
 71    * <p>Specifying the domain as a series of values is the most explicit and
 72    * recommended approach. However, if the domain values are derived from data,
 73    * you may find the second method more appropriate.
 74    *
 75    * <p>2. <tt>domain(array, f)</tt>
 76    *
 77    * <p>Rather than enumerating the domain values as explicit arguments to this
 78    * method, you can specify a single argument of an array. In addition, you can
 79    * specify an optional accessor function to extract the domain values from the
 80    * array.
 81    *
 82    * <p>3. <tt>domain()</tt>
 83    *
 84    * <p>Invoking the <tt>domain</tt> method with no arguments returns the
 85    * current domain as an array.
 86    *
 87    * @function
 88    * @name pv.Scale.ordinal.prototype.domain
 89    * @param {...} domain... domain values.
 90    * @returns {pv.Scale.ordinal} <tt>this</tt>, or the current domain.
 91    */
 92   scale.domain = function(array, f) {
 93     if (arguments.length) {
 94       array = (array instanceof Array)
 95           ? ((arguments.length > 1) ? pv.map(array, f) : array)
 96           : Array.prototype.slice.call(arguments);
 97 
 98       /* Filter the specified ordinals to their unique values. */
 99       d = [];
100       var seen = {};
101       for (var j = 0; j < array.length; j++) {
102         var o = array[j];
103         if (!(o in seen)) {
104           seen[o] = true;
105           d.push(o);
106         }
107       }
108 
109       i = pv.numerate(d);
110       return this;
111     }
112     return d;
113   };
114 
115   /**
116    * Sets or gets the output range. This method can be invoked several ways:
117    *
118    * <p>1. <tt>range(values...)</tt>
119    *
120    * <p>Specifying the range as a series of values is the most explicit and
121    * recommended approach. However, if the range values are derived from data,
122    * you may find the second method more appropriate.
123    *
124    * <p>2. <tt>range(array, f)</tt>
125    *
126    * <p>Rather than enumerating the range values as explicit arguments to this
127    * method, you can specify a single argument of an array. In addition, you can
128    * specify an optional accessor function to extract the range values from the
129    * array.
130    *
131    * <p>3. <tt>range()</tt>
132    *
133    * <p>Invoking the <tt>range</tt> method with no arguments returns the
134    * current range as an array.
135    *
136    * @function
137    * @name pv.Scale.ordinal.prototype.range
138    * @param {...} range... range values.
139    * @returns {pv.Scale.ordinal} <tt>this</tt>, or the current range.
140    */
141   scale.range = function(array, f) {
142     if (arguments.length) {
143       r = (array instanceof Array)
144           ? ((arguments.length > 1) ? pv.map(array, f) : array)
145           : Array.prototype.slice.call(arguments);
146       if (typeof r[0] == "string") r = r.map(pv.color);
147       return this;
148     }
149     return r;
150   };
151 
152   /**
153    * Sets the range from the given continuous interval. The interval
154    * [<i>min</i>, <i>max</i>] is subdivided into <i>n</i> equispaced points,
155    * where <i>n</i> is the number of (unique) values in the domain. The first
156    * and last point are offset from the edge of the range by half the distance
157    * between points.
158    *
159    * <p>This method must be called <i>after</i> the domain is set.
160    *
161    * @function
162    * @name pv.Scale.ordinal.prototype.split
163    * @param {number} min minimum value of the output range.
164    * @param {number} max maximum value of the output range.
165    * @returns {pv.Scale.ordinal} <tt>this</tt>.
166    * @see #splitFlush
167    * @see #splitBanded
168    */
169   scale.split = function(min, max) {
170     var step = (max - min) / this.domain().length;
171     r = pv.range(min + step / 2, max, step);
172     return this;
173   };
174 
175   /**
176    * Sets the range from the given continuous interval. The interval
177    * [<i>min</i>, <i>max</i>] is subdivided into <i>n</i> equispaced points,
178    * where <i>n</i> is the number of (unique) values in the domain. The first
179    * and last point are exactly on the edge of the range.
180    *
181    * <p>This method must be called <i>after</i> the domain is set.
182    *
183    * @function
184    * @name pv.Scale.ordinal.prototype.splitFlush
185    * @param {number} min minimum value of the output range.
186    * @param {number} max maximum value of the output range.
187    * @returns {pv.Scale.ordinal} <tt>this</tt>.
188    * @see #split
189    */
190   scale.splitFlush = function(min, max) {
191     var n = this.domain().length, step = (max - min) / (n - 1);
192     r = (n == 1) ? [(min + max) / 2]
193         : pv.range(min, max + step / 2, step);
194     return this;
195   };
196 
197   /**
198    * Sets the range from the given continuous interval. The interval
199    * [<i>min</i>, <i>max</i>] is subdivided into <i>n</i> equispaced bands,
200    * where <i>n</i> is the number of (unique) values in the domain. The first
201    * and last band are offset from the edge of the range by the distance between
202    * bands.
203    *
204    * <p>The band width argument, <tt>band</tt>, is typically in the range [0, 1]
205    * and defaults to 1. This fraction corresponds to the amount of space in the
206    * range to allocate to the bands, as opposed to padding. A value of 0.5 means
207    * that the band width will be equal to the padding width. The computed
208    * absolute band width can be retrieved from the range as
209    * <tt>scale.range().band</tt>.
210    *
211    * <p>If the band width argument is negative, this method will allocate bands
212    * of a <i>fixed</i> width <tt>-band</tt>, rather than a relative fraction of
213    * the available space.
214    *
215    * <p>Tip: to inset the bands by a fixed amount <tt>p</tt>, specify a minimum
216    * value of <tt>min + p</tt> (or simply <tt>p</tt>, if <tt>min</tt> is
217    * 0). Then set the mark width to <tt>scale.range().band - p</tt>.
218    *
219    * <p>This method must be called <i>after</i> the domain is set.
220    *
221    * @function
222    * @name pv.Scale.ordinal.prototype.splitBanded
223    * @param {number} min minimum value of the output range.
224    * @param {number} max maximum value of the output range.
225    * @param {number} [band] the fractional band width in [0, 1]; defaults to 1.
226    * @returns {pv.Scale.ordinal} <tt>this</tt>.
227    * @see #split
228    */
229   scale.splitBanded = function(min, max, band) {
230     if (arguments.length < 3) band = 1;
231     if (band < 0) {
232       var n = this.domain().length,
233           total = -band * n,
234           remaining = max - min - total,
235           padding = remaining / (n + 1);
236       r = pv.range(min + padding, max, padding - band);
237       r.band = -band;
238     } else {
239       var step = (max - min) / (this.domain().length + (1 - band));
240       r = pv.range(min + step * (1 - band), max, step);
241       r.band = step * band;
242     }
243     return this;
244   };
245 
246   /**
247    * Returns a view of this scale by the specified accessor function <tt>f</tt>.
248    * Given a scale <tt>y</tt>, <tt>y.by(function(d) d.foo)</tt> is equivalent to
249    * <tt>function(d) y(d.foo)</tt>. This method should be used judiciously; it
250    * is typically more clear to invoke the scale directly, passing in the value
251    * to be scaled.
252    *
253    * @function
254    * @name pv.Scale.ordinal.prototype.by
255    * @param {function} f an accessor function.
256    * @returns {pv.Scale.ordinal} a view of this scale by the specified accessor
257    * function.
258    */
259   scale.by = function(f) {
260     function by() { return scale(f.apply(this, arguments)); }
261     for (var method in scale) by[method] = scale[method];
262     return by;
263   };
264 
265   scale.domain.apply(scale, arguments);
266   return scale;
267 };
268