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