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