1 /** 2 * Returns an ordinal scale for the specified domain. The arguments to this 3 * constructor are optional, and equivalent to calling {@link #domain}. 4 * 5 * @class Represents an ordinal scale. <style 6 * type="text/css">sub{line-height:0}</style> An ordinal scale represents a 7 * pairwise mapping from <i>n</i> discrete values in the input domain to 8 * <i>n</i> discrete values in the output range. For example, an ordinal scale 9 * might map a domain of species ["setosa", "versicolor", "virginica"] to colors 10 * ["red", "green", "blue"]. Thus, saying 11 * 12 * <pre> .fillStyle(function(d) { 13 * switch (d.species) { 14 * case "setosa": return "red"; 15 * case "versicolor": return "green"; 16 * case "virginica": return "blue"; 17 * } 18 * })</pre> 19 * 20 * is equivalent to 21 * 22 * <pre> .fillStyle(pv.Scale.ordinal("setosa", "versicolor", "virginica") 23 * .range("red", "green", "blue") 24 * .by(function(d) d.species))</pre> 25 * 26 * If the mapping from species to color does not need to be specified 27 * explicitly, the domain can be omitted. In this case it will be inferred 28 * lazily from the data: 29 * 30 * <pre> .fillStyle(pv.colors("red", "green", "blue") 31 * .by(function(d) d.species))</pre> 32 * 33 * When the domain is inferred, the first time the scale is invoked, the first 34 * element from the range will be returned. Subsequent calls with unique values 35 * will return subsequent elements from the range. If the inferred domain grows 36 * larger than the range, range values will be reused. However, it is strongly 37 * recommended that the domain and the range contain the same number of 38 * elements. 39 * 40 * <p>A range can be discretized from a continuous interval (e.g., for pixel 41 * positioning) by using {@link #split}, {@link #splitFlush} or 42 * {@link #splitBanded} after the domain has been set. For example, if 43 * <tt>states</tt> is an array of the fifty U.S. state names, the state name can 44 * be encoded in the left position: 45 * 46 * <pre> .left(pv.Scale.ordinal(states) 47 * .split(0, 640) 48 * .by(function(d) d.state))</pre> 49 * 50 * <p>N.B.: ordinal scales are not invertible (at least not yet), since the 51 * domain and range and discontinuous. A workaround is to use a linear scale. 52 * 53 * @param {...} domain... optional domain values. 54 * @extends pv.Scale 55 * @see pv.colors 56 */ 57 pv.Scale.ordinal = function() { 58 var d = [], i = {}, r = [], band = 0; 59 60 /** @private */ 61 function scale(x) { 62 if (!(x in i)) i[x] = d.push(x) - 1; 63 return r[i[x] % r.length]; 64 } 65 66 /** 67 * Sets or gets the input domain. This method can be invoked several ways: 68 * 69 * <p>1. <tt>domain(values...)</tt> 70 * 71 * <p>Specifying the domain as a series of values is the most explicit and 72 * recommended approach. However, if the domain values are derived from data, 73 * you may find the second method more appropriate. 74 * 75 * <p>2. <tt>domain(array, f)</tt> 76 * 77 * <p>Rather than enumerating the domain values as explicit arguments to this 78 * method, you can specify a single argument of an array. In addition, you can 79 * specify an optional accessor function to extract the domain values from the 80 * array. 81 * 82 * <p>3. <tt>domain()</tt> 83 * 84 * <p>Invoking the <tt>domain</tt> method with no arguments returns the 85 * current domain as an array. 86 * 87 * @function 88 * @name pv.Scale.ordinal.prototype.domain 89 * @param {...} domain... domain values. 90 * @returns {pv.Scale.ordinal} <tt>this</tt>, or the current domain. 91 */ 92 scale.domain = function(array, f) { 93 if (arguments.length) { 94 array = (array instanceof Array) 95 ? ((arguments.length > 1) ? pv.map(array, f) : array) 96 : Array.prototype.slice.call(arguments); 97 98 /* Filter the specified ordinals to their unique values. */ 99 d = []; 100 var seen = {}; 101 for (var j = 0; j < array.length; j++) { 102 var o = array[j]; 103 if (!(o in seen)) { 104 seen[o] = true; 105 d.push(o); 106 } 107 } 108 109 i = pv.numerate(d); 110 return this; 111 } 112 return d; 113 }; 114 115 /** 116 * Sets or gets the output range. This method can be invoked several ways: 117 * 118 * <p>1. <tt>range(values...)</tt> 119 * 120 * <p>Specifying the range as a series of values is the most explicit and 121 * recommended approach. However, if the range values are derived from data, 122 * you may find the second method more appropriate. 123 * 124 * <p>2. <tt>range(array, f)</tt> 125 * 126 * <p>Rather than enumerating the range values as explicit arguments to this 127 * method, you can specify a single argument of an array. In addition, you can 128 * specify an optional accessor function to extract the range values from the 129 * array. 130 * 131 * <p>3. <tt>range()</tt> 132 * 133 * <p>Invoking the <tt>range</tt> method with no arguments returns the 134 * current range as an array. 135 * 136 * @function 137 * @name pv.Scale.ordinal.prototype.range 138 * @param {...} range... range values. 139 * @returns {pv.Scale.ordinal} <tt>this</tt>, or the current range. 140 */ 141 scale.range = function(array, f) { 142 if (arguments.length) { 143 r = (array instanceof Array) 144 ? ((arguments.length > 1) ? pv.map(array, f) : array) 145 : Array.prototype.slice.call(arguments); 146 if (typeof r[0] == "string") r = r.map(pv.color); 147 return this; 148 } 149 return r; 150 }; 151 152 /** 153 * Sets the range from the given continuous interval. The interval 154 * [<i>min</i>, <i>max</i>] is subdivided into <i>n</i> equispaced points, 155 * where <i>n</i> is the number of (unique) values in the domain. The first 156 * and last point are offset from the edge of the range by half the distance 157 * between points. 158 * 159 * <p>This method must be called <i>after</i> the domain is set. 160 * 161 * @function 162 * @name pv.Scale.ordinal.prototype.split 163 * @param {number} min minimum value of the output range. 164 * @param {number} max maximum value of the output range. 165 * @returns {pv.Scale.ordinal} <tt>this</tt>. 166 * @see #splitFlush 167 * @see #splitBanded 168 */ 169 scale.split = function(min, max) { 170 var step = (max - min) / this.domain().length; 171 r = pv.range(min + step / 2, max, step); 172 return this; 173 }; 174 175 /** 176 * Sets the range from the given continuous interval. The interval 177 * [<i>min</i>, <i>max</i>] is subdivided into <i>n</i> equispaced points, 178 * where <i>n</i> is the number of (unique) values in the domain. The first 179 * and last point are exactly on the edge of the range. 180 * 181 * <p>This method must be called <i>after</i> the domain is set. 182 * 183 * @function 184 * @name pv.Scale.ordinal.prototype.splitFlush 185 * @param {number} min minimum value of the output range. 186 * @param {number} max maximum value of the output range. 187 * @returns {pv.Scale.ordinal} <tt>this</tt>. 188 * @see #split 189 */ 190 scale.splitFlush = function(min, max) { 191 var n = this.domain().length, step = (max - min) / (n - 1); 192 r = (n == 1) ? [(min + max) / 2] 193 : pv.range(min, max + step / 2, step); 194 return this; 195 }; 196 197 /** 198 * Sets the range from the given continuous interval. The interval 199 * [<i>min</i>, <i>max</i>] is subdivided into <i>n</i> equispaced bands, 200 * where <i>n</i> is the number of (unique) values in the domain. The first 201 * and last band are offset from the edge of the range by the distance between 202 * bands. 203 * 204 * <p>The band width argument, <tt>band</tt>, is typically in the range [0, 1] 205 * and defaults to 1. This fraction corresponds to the amount of space in the 206 * range to allocate to the bands, as opposed to padding. A value of 0.5 means 207 * that the band width will be equal to the padding width. The computed 208 * absolute band width can be retrieved from the range as 209 * <tt>scale.range().band</tt>. 210 * 211 * <p>If the band width argument is negative, this method will allocate bands 212 * of a <i>fixed</i> width <tt>-band</tt>, rather than a relative fraction of 213 * the available space. 214 * 215 * <p>Tip: to inset the bands by a fixed amount <tt>p</tt>, specify a minimum 216 * value of <tt>min + p</tt> (or simply <tt>p</tt>, if <tt>min</tt> is 217 * 0). Then set the mark width to <tt>scale.range().band - p</tt>. 218 * 219 * <p>This method must be called <i>after</i> the domain is set. 220 * 221 * @function 222 * @name pv.Scale.ordinal.prototype.splitBanded 223 * @param {number} min minimum value of the output range. 224 * @param {number} max maximum value of the output range. 225 * @param {number} [band] the fractional band width in [0, 1]; defaults to 1. 226 * @returns {pv.Scale.ordinal} <tt>this</tt>. 227 * @see #split 228 */ 229 scale.splitBanded = function(min, max, band) { 230 if (arguments.length < 3) band = 1; 231 if (band < 0) { 232 var n = this.domain().length, 233 total = -band * n, 234 remaining = max - min - total, 235 padding = remaining / (n + 1); 236 r = pv.range(min + padding, max, padding - band); 237 r.band = -band; 238 } else { 239 var step = (max - min) / (this.domain().length + (1 - band)); 240 r = pv.range(min + step * (1 - band), max, step); 241 r.band = step * band; 242 } 243 return this; 244 }; 245 246 /** 247 * Returns a view of this scale by the specified accessor function <tt>f</tt>. 248 * Given a scale <tt>y</tt>, <tt>y.by(function(d) d.foo)</tt> is equivalent to 249 * <tt>function(d) y(d.foo)</tt>. This method should be used judiciously; it 250 * is typically more clear to invoke the scale directly, passing in the value 251 * to be scaled. 252 * 253 * @function 254 * @name pv.Scale.ordinal.prototype.by 255 * @param {function} f an accessor function. 256 * @returns {pv.Scale.ordinal} a view of this scale by the specified accessor 257 * function. 258 */ 259 scale.by = function(f) { 260 function by() { return scale(f.apply(this, arguments)); } 261 for (var method in scale) by[method] = scale[method]; 262 return by; 263 }; 264 265 scale.domain.apply(scale, arguments); 266 return scale; 267 }; 268