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