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  * The default domain is [1,10] and the default range is [0,1].
  5  *
  6  * @class Represents a log scale. <style
  7  * type="text/css">sub{line-height:0}</style> Most commonly, a log scale
  8  * represents a 1-dimensional log transformation from a numeric domain of input
  9  * 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) = (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  * Note that the scale is itself a function, and thus can be used as a property
 35  * directly, assuming that the data associated with a mark is a number. While
 36  * this is convenient for single-use scales, frequently it is desirable to
 37  * define scales globally:
 38  *
 39  * <pre>var y = pv.Scale.log(1, 100).range(0, 640);</pre>
 40  *
 41  * The <tt>y</tt> scale can now be equivalently referenced within a property:
 42  *
 43  * <pre>    .height(function(d) y(d))</pre>
 44  *
 45  * Alternatively, if the data are not simple numbers, the appropriate value can
 46  * be passed to the <tt>y</tt> scale (e.g., <tt>d.foo</tt>). The {@link #by}
 47  * method similarly allows the data to be mapped to a numeric value before
 48  * performing the log transformation.
 49  *
 50  * @param {number...} domain... optional domain values.
 51  * @extends pv.Scale.quantitative
 52  */
 53 pv.Scale.log = function() {
 54   var scale = pv.Scale.quantitative(1, 10),
 55       b, // logarithm base
 56       p, // cached Math.log(b)
 57       /** @ignore */ log = function(x) { return Math.log(x) / p; },
 58       /** @ignore */ pow = function(y) { return Math.pow(b, y); };
 59 
 60   /**
 61    * Returns an array of evenly-spaced, suitably-rounded values in the input
 62    * domain. These values are frequently used in conjunction with
 63    * {@link pv.Rule} to display tick marks or grid lines.
 64    *
 65    * @function
 66    * @name pv.Scale.log.prototype.ticks
 67    * @returns {number[]} an array input domain values to use as ticks.
 68    */
 69   scale.ticks = function() {
 70     // TODO support non-uniform domains
 71     var d = scale.domain(),
 72         n = d[0] < 0,
 73         i = Math.floor(n ? -log(-d[0]) : log(d[0])),
 74         j = Math.ceil(n ? -log(-d[1]) : log(d[1])),
 75         ticks = [];
 76     if (n) {
 77       ticks.push(-pow(-i));
 78       for (; i++ < j;) for (var k = b - 1; k > 0; k--) ticks.push(-pow(-i) * k);
 79     } else {
 80       for (; i < j; i++) for (var k = 1; k < b; k++) ticks.push(pow(i) * k);
 81       ticks.push(pow(i));
 82     }
 83     for (i = 0; ticks[i] < d[0]; i++); // strip small values
 84     for (j = ticks.length; ticks[j - 1] > d[1]; j--); // strip big values
 85     return ticks.slice(i, j);
 86   };
 87 
 88   /**
 89    * Formats the specified tick value using the appropriate precision, assuming
 90    * base 10.
 91    *
 92    * @function
 93    * @name pv.Scale.log.prototype.tickFormat
 94    * @param {number} t a tick value.
 95    * @returns {string} a formatted tick value.
 96    */
 97   scale.tickFormat = function(t) {
 98     return t.toPrecision(1);
 99   };
100 
101   /**
102    * "Nices" this scale, extending the bounds of the input domain to
103    * evenly-rounded values. This method uses {@link pv.logFloor} and
104    * {@link pv.logCeil}. Nicing is useful if the domain is computed dynamically
105    * from data, and may be irregular. For example, given a domain of
106    * [0.20147987687960267, 0.996679553296417], a call to <tt>nice()</tt> might
107    * extend the domain to [0.1, 1].
108    *
109    * <p>This method must be invoked each time after setting the domain (and
110    * base).
111    *
112    * @function
113    * @name pv.Scale.log.prototype.nice
114    * @returns {pv.Scale.log} <tt>this</tt>.
115    */
116   scale.nice = function() {
117     // TODO support non-uniform domains
118     var d = scale.domain();
119     return scale.domain(pv.logFloor(d[0], b), pv.logCeil(d[1], b));
120   };
121 
122   /**
123    * Sets or gets the logarithm base. Defaults to 10.
124    *
125    * @function
126    * @name pv.Scale.log.prototype.base
127    * @param {number} [v] the new base.
128    * @returns {pv.Scale.log} <tt>this</tt>, or the current base.
129    */
130   scale.base = function(v) {
131     if (arguments.length) {
132       b = Number(v);
133       p = Math.log(b);
134       scale.transform(log, pow); // update transformed domain
135       return this;
136     }
137     return b;
138   };
139 
140   scale.domain.apply(scale, arguments);
141   return scale.base(10);
142 };
143