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