1 /**
  2  * Returns a linear scale for the specified domain. The arguments to this
  3  * constructor are optional, and equivalent to calling {@link #domain}.
  4  *
  5  * @class Represents a linear scale. <style
  6  * type="text/css">sub{line-height:0}</style> <img src="../linear.png"
  7  * width="180" height="175" align="right"> Most commonly, a linear scale
  8  * represents a 1-dimensional linear transformation from a numeric domain of
  9  * input data [<i>d<sub>0</sub></i>, <i>d<sub>1</sub></i>] to a numeric range of
 10  * pixels [<i>r<sub>0</sub></i>, <i>r<sub>1</sub></i>]. The equation for such a
 11  * scale is:
 12  *
 13  * <blockquote><i>f(x) = (x - d<sub>0</sub>) / (d<sub>1</sub> - d<sub>0</sub>) *
 14  * (r<sub>1</sub> - r<sub>0</sub>) + r<sub>0</sub></i></blockquote>
 15  *
 16  * For example, a linear scale from the domain [0, 100] to range [0, 640]:
 17  *
 18  * <blockquote><i>f(x) = (x - 0) / (100 - 0) * (640 - 0) + 0</i><br>
 19  * <i>f(x) = x / 100 * 640</i><br>
 20  * <i>f(x) = x * 6.4</i><br>
 21  * </blockquote>
 22  *
 23  * Thus, saying
 24  *
 25  * <pre>.height(function(d) d * 6.4)</pre>
 26  *
 27  * is identical to
 28  *
 29  * <pre>.height(pv.Scale.linear(0, 100).range(0, 640))</pre>
 30  *
 31  * As you can see, scales do not always make code smaller, but they should make
 32  * code more explicit and easier to maintain. In addition to readability, scales
 33  * offer several useful features:
 34  *
 35  * <p>1. The range can be expressed in colors, rather than pixels. Changing the
 36  * example above to
 37  *
 38  * <pre>.fillStyle(pv.Scale.linear(0, 100).range("red", "green"))</pre>
 39  *
 40  * will cause it to fill the marks "red" on an input value of 0, "green" on an
 41  * input value of 100, and some color in-between for intermediate values.
 42  *
 43  * <p>2. The domain and range can be subdivided for a "poly-linear"
 44  * transformation. For example, you may want a diverging color scale that is
 45  * increasingly red for negative values, and increasingly green for positive
 46  * values:
 47  *
 48  * <pre>.fillStyle(pv.Scale.linear(-1, 0, 1).range("red", "white", "green"))</pre>
 49  *
 50  * The domain can be specified as a series of <i>n</i> monotonically-increasing
 51  * values; the range must also be specified as <i>n</i> values, resulting in
 52  * <i>n - 1</i> contiguous linear scales.
 53  *
 54  * <p>3. Linear scales can be inverted for interaction. The {@link #invert}
 55  * method takes a value in the output range, and returns the corresponding value
 56  * in the input domain. This is frequently used to convert the mouse location
 57  * (see {@link pv.Mark#mouse}) to a value in the input domain. Note that
 58  * inversion is only supported for numeric ranges, and not colors.
 59  *
 60  * <p>4. A scale can be queried for reasonable "tick" values. The {@link #ticks}
 61  * method provides a convenient way to get a series of evenly-spaced rounded
 62  * values in the input domain. Frequently these are used in conjunction with
 63  * {@link pv.Rule} to display tick marks or grid lines.
 64  *
 65  * <p>5. A scale can be "niced" to extend the domain to suitable rounded
 66  * numbers. If the minimum and maximum of the domain are messy because they are
 67  * derived from data, you can use {@link #nice} to round these values down and
 68  * up to even numbers.
 69  *
 70  * @param {number...} domain... domain values.
 71  * @returns {pv.Scale.linear} a linear scale.
 72  */
 73 pv.Scale.linear = function() {
 74   var d = [0, 1], r = [0, 1], i = [pv.identity];
 75 
 76   /** @private */
 77   function scale(x) {
 78     var j = pv.search(d, x);
 79     if (j < 0) j = -j - 2;
 80     j = Math.max(0, Math.min(i.length - 1, j));
 81     return i[j]((x - d[j]) / (d[j + 1] - d[j]));
 82   }
 83 
 84   /**
 85    * Sets or gets the input domain. This method can be invoked several ways:
 86    *
 87    * <p>1. <tt>domain(min, ..., max)</tt>
 88    *
 89    * <p>Specifying the domain as a series of numbers is the most explicit and
 90    * recommended approach. Most commonly, two numbers are specified: the minimum
 91    * and maximum value. However, for a diverging scale, or other subdivided
 92    * poly-linear scales, multiple values can be specified. Values can be derived
 93    * from data using {@link pv.min} and {@link pv.max}. For example:
 94    *
 95    * <pre>.domain(0, pv.max(array))</pre>
 96    *
 97    * An alternative method for deriving minimum and maximum values from data
 98    * follows.
 99    *
100    * <p>2. <tt>domain(array, minf, maxf)</tt>
101    *
102    * <p>When both the minimum and maximum value are derived from data, the
103    * arguments to the <tt>domain</tt> method can be specified as the array of
104    * data, followed by zero, one or two accessor functions. For example, if the
105    * array of data is just an array of numbers:
106    *
107    * <pre>.domain(array)</pre>
108    *
109    * On the other hand, if the array elements are objects representing stock
110    * values per day, and the domain should consider the stock's daily low and
111    * daily high:
112    *
113    * <pre>.domain(array, function(d) d.low, function(d) d.high)</pre>
114    *
115    * The first method of setting the domain is preferred because it is more
116    * explicit; setting the domain using this second method should be used only
117    * if brevity is required.
118    *
119    * <p>3. <tt>domain()</tt>
120    *
121    * <p>Invoking the <tt>domain</tt> method with no arguments returns the
122    * current domain as an array of numbers.
123    *
124    * @function
125    * @name pv.Scale.linear.prototype.domain
126    * @param {number...} domain... domain values.
127    * @returns {pv.Scale.linear} <tt>this</tt>, or the current domain.
128    */
129   scale.domain = function(array, min, max) {
130     if (arguments.length) {
131       if (array instanceof Array) {
132         if (arguments.length < 2) min = pv.identity;
133         if (arguments.length < 3) max = min;
134         d = [pv.min(array, min), pv.max(array, max)];
135       } else {
136         d = Array.prototype.slice.call(arguments);
137       }
138       return this;
139     }
140     return d;
141   };
142 
143   /**
144    * Sets or gets the output range. This method can be invoked several ways:
145    *
146    * <p>1. <tt>range(min, ..., max)</tt>
147    *
148    * <p>The range may be specified as a series of numbers or colors. Most
149    * commonly, two numbers are specified: the minimum and maximum pixel values.
150    * For a color scale, values may be specified as {@link pv.Color}s or
151    * equivalent strings. For a diverging scale, or other subdivided poly-linear
152    * scales, multiple values can be specified. For example:
153    *
154    * <pre>.range("red", "white", "green")</pre>
155    *
156    * <p>Currently, only numbers and colors are supported as range values. The
157    * number of range values must exactly match the number of domain values, or
158    * the behavior of the scale is undefined.
159    *
160    * <p>2. <tt>range()</tt>
161    *
162    * <p>Invoking the <tt>range</tt> method with no arguments returns the current
163    * range as an array of numbers or colors.
164    *
165    * @function
166    * @name pv.Scale.linear.prototype.range
167    * @param {...} range... range values.
168    * @returns {pv.Scale.linear} <tt>this</tt>, or the current range.
169    */
170   scale.range = function() {
171     if (arguments.length) {
172       r = Array.prototype.slice.call(arguments);
173       i = [];
174       for (var j = 0; j < r.length - 1; j++) {
175         i.push(pv.Scale.interpolator(r[j], r[j + 1]));
176       }
177       return this;
178     }
179     return r;
180   };
181 
182   /**
183    * Inverts the specified value in the output range, returning the
184    * corresponding value in the input domain. This is frequently used to convert
185    * the mouse location (see {@link pv.Mark#mouse}) to a value in the input
186    * domain. Inversion is only supported for numeric ranges, and not colors.
187    *
188    * <p>Note that this method does not do any rounding or bounds checking. If
189    * the input domain is discrete (e.g., an array index), the returned value
190    * should be rounded. If the specified <tt>y</tt> value is outside the range,
191    * the returned value may be equivalently outside the input domain.
192    *
193    * @function
194    * @name pv.Scale.linear.prototype.invert
195    * @param {number} y a value in the output range (a pixel location).
196    * @returns {number} a value in the input domain.
197    */
198   scale.invert = function(y) {
199     var j = pv.search(r, y);
200     if (j < 0) j = -j - 2;
201     j = Math.max(0, Math.min(i.length - 1, j));
202     return (y - r[j]) / (r[j + 1] - r[j]) * (d[j + 1] - d[j]) + d[j];
203   };
204 
205   /**
206    * Returns an array of evenly-spaced, suitably-rounded values in the input
207    * domain. This method attempts to return between 5 and 10 tick values. These
208    * values are frequently used in conjunction with {@link pv.Rule} to display
209    * tick marks or grid lines.
210    *
211    * @function
212    * @name pv.Scale.linear.prototype.ticks
213    * @returns {number[]} an array input domain values to use as ticks.
214    */
215   scale.ticks = function() {
216     var min = d[0],
217         max = d[d.length - 1],
218         span = max - min,
219         step = pv.logCeil(span / 10, 10);
220     if (span / step < 2) step /= 5;
221     else if (span / step < 5) step /= 2;
222     var start = Math.ceil(min / step) * step,
223         end = Math.floor(max / step) * step;
224     return pv.range(start, end + step, step);
225   };
226 
227   /**
228    * "Nices" this scale, extending the bounds of the input domain to
229    * evenly-rounded values. Nicing is useful if the domain is computed
230    * dynamically from data, and may be irregular. For example, given a domain of
231    * [0.20147987687960267, 0.996679553296417], a call to <tt>nice()</tt> might
232    * extend the domain to [0.2, 1].
233    *
234    * <p>This method must be invoked each time after setting the domain.
235    *
236    * @function
237    * @name pv.Scale.linear.prototype.nice
238    * @returns {pv.Scale.linear} <tt>this</tt>.
239    */
240   scale.nice = function() {
241     var min = d[0],
242         max = d[d.length - 1],
243         step = Math.pow(10, Math.round(Math.log(max - min) / Math.log(10)) - 1);
244     d = [Math.floor(min / step) * step, Math.ceil(max / step) * step];
245     return this;
246   };
247 
248   /**
249    * Returns a view of this scale by the specified accessor function <tt>f</tt>.
250    * Given a scale <tt>y</tt>, <tt>y.by(function(d) d.foo)</tt> is equivalent to
251    * <tt>function(d) y(d.foo)</tt>.
252    *
253    * <p>This method is provided for convenience, such that scales can be
254    * succinctly defined inline. For example, given an array of data elements
255    * that have a <tt>score</tt> attribute with the domain [0, 1], the height
256    * property could be specified as:
257    *
258    * <pre>.height(pv.Scale.linear().range(0, 480).by(function(d) d.score))</pre>
259    *
260    * This is equivalent to:
261    *
262    * <pre>.height(function(d) d.score * 480)</pre>
263    *
264    * This method should be used judiciously; it is typically more clear to
265    * invoke the scale directly, passing in the value to be scaled.
266    *
267    * @function
268    * @name pv.Scale.linear.prototype.by
269    * @param {function} f an accessor function.
270    * @returns {pv.Scale.linear} a view of this scale by the specified accessor
271    * function.
272    */
273   scale.by = function(f) {
274     function by() { return scale(f.apply(this, arguments)); }
275     for (var method in scale) by[method] = scale[method];
276     return by;
277   };
278 
279   scale.domain.apply(scale, arguments);
280   return scale;
281 };
282