| 1 | // fba9dc272a443cf9fdb984676a7732a6a082f4c0 |
| 2 | /** |
| 3 | * @class The built-in Array class. |
| 4 | * @name Array |
| 5 | */ |
| 6 | |
| 7 | /** |
| 8 | * Creates a new array with the results of calling a provided function on every |
| 9 | * element in this array. Implemented in Javascript 1.6. |
| 10 | * |
| 11 | * @function |
| 12 | * @name Array.prototype.map |
| 13 | * @see <a |
| 14 | * href="https://developer.mozilla.org/En/Core_JavaScript_1.5_Reference/Objects/Array/Map">map</a> |
| 15 | * documentation. |
| 16 | * @param {function} f function that produces an element of the new Array from |
| 17 | * an element of the current one. |
| 18 | * @param [o] object to use as <tt>this</tt> when executing <tt>f</tt>. |
| 19 | */ |
| 20 | if (!Array.prototype.map) Array.prototype.map = function(f, o) { |
| 21 | var n = this.length; |
| 22 | var result = new Array(n); |
| 23 | for (var i = 0; i < n; i++) { |
| 24 | if (i in this) { |
| 25 | result[i] = f.call(o, this[i], i, this); |
| 26 | } |
| 27 | } |
| 28 | return result; |
| 29 | }; |
| 30 | |
| 31 | /** |
| 32 | * Creates a new array with all elements that pass the test implemented by the |
| 33 | * provided function. Implemented in Javascript 1.6. |
| 34 | * |
| 35 | * @function |
| 36 | * @name Array.prototype.filter |
| 37 | * @see <a |
| 38 | * href="https://developer.mozilla.org/En/Core_JavaScript_1.5_Reference/Objects/Array/filter">filter</a> |
| 39 | * documentation. |
| 40 | * @param {function} f function to test each element of the array. |
| 41 | * @param [o] object to use as <tt>this</tt> when executing <tt>f</tt>. |
| 42 | */ |
| 43 | if (!Array.prototype.filter) Array.prototype.filter = function(f, o) { |
| 44 | var n = this.length; |
| 45 | var result = new Array(); |
| 46 | for (var i = 0; i < n; i++) { |
| 47 | if (i in this) { |
| 48 | var v = this[i]; |
| 49 | if (f.call(o, v, i, this)) result.push(v); |
| 50 | } |
| 51 | } |
| 52 | return result; |
| 53 | }; |
| 54 | |
| 55 | /** |
| 56 | * Executes a provided function once per array element. Implemented in |
| 57 | * Javascript 1.6. |
| 58 | * |
| 59 | * @function |
| 60 | * @name Array.prototype.forEach |
| 61 | * @see <a |
| 62 | * href="https://developer.mozilla.org/En/Core_JavaScript_1.5_Reference/Objects/Array/ForEach">forEach</a> |
| 63 | * documentation. |
| 64 | * @param {function} f function to execute for each element. |
| 65 | * @param [o] object to use as <tt>this</tt> when executing <tt>f</tt>. |
| 66 | */ |
| 67 | if (!Array.prototype.forEach) Array.prototype.forEach = function(f, o) { |
| 68 | var n = this.length >>> 0; |
| 69 | for (var i = 0; i < n; i++) { |
| 70 | if (i in this) f.call(o, this[i], i, this); |
| 71 | } |
| 72 | }; |
| 73 | |
| 74 | /** |
| 75 | * Apply a function against an accumulator and each value of the array (from |
| 76 | * left-to-right) as to reduce it to a single value. Implemented in Javascript |
| 77 | * 1.8. |
| 78 | * |
| 79 | * @function |
| 80 | * @name Array.prototype.reduce |
| 81 | * @see <a |
| 82 | * href="https://developer.mozilla.org/En/Core_JavaScript_1.5_Reference/Objects/Array/Reduce">reduce</a> |
| 83 | * documentation. |
| 84 | * @param {function} f function to execute on each value in the array. |
| 85 | * @param [v] object to use as the first argument to the first call of |
| 86 | * <tt>t</tt>. |
| 87 | */ |
| 88 | if (!Array.prototype.reduce) Array.prototype.reduce = function(f, v) { |
| 89 | var len = this.length; |
| 90 | if (!len && (arguments.length == 1)) { |
| 91 | throw new Error("reduce: empty array, no initial value"); |
| 92 | } |
| 93 | |
| 94 | var i = 0; |
| 95 | if (arguments.length < 2) { |
| 96 | while (true) { |
| 97 | if (i in this) { |
| 98 | v = this[i++]; |
| 99 | break; |
| 100 | } |
| 101 | if (++i >= len) { |
| 102 | throw new Error("reduce: no values, no initial value"); |
| 103 | } |
| 104 | } |
| 105 | } |
| 106 | |
| 107 | for (; i < len; i++) { |
| 108 | if (i in this) { |
| 109 | v = f(v, this[i], i, this); |
| 110 | } |
| 111 | } |
| 112 | return v; |
| 113 | }; |
| 114 | /** |
| 115 | * The top-level Protovis namespace. All public methods and fields should be |
| 116 | * registered on this object. Note that core Protovis source is surrounded by an |
| 117 | * anonymous function, so any other declared globals will not be visible outside |
| 118 | * of core methods. This also allows multiple versions of Protovis to coexist, |
| 119 | * since each version will see their own <tt>pv</tt> namespace. |
| 120 | * |
| 121 | * @namespace The top-level Protovis namespace, <tt>pv</tt>. |
| 122 | */ |
| 123 | var pv = {}; |
| 124 | |
| 125 | /** |
| 126 | * Protovis major and minor version numbers. |
| 127 | * |
| 128 | * @namespace Protovis major and minor version numbers. |
| 129 | */ |
| 130 | pv.version = { |
| 131 | /** |
| 132 | * The major version number. |
| 133 | * |
| 134 | * @type number |
| 135 | * @constant |
| 136 | */ |
| 137 | major: 3, |
| 138 | |
| 139 | /** |
| 140 | * The minor version number. |
| 141 | * |
| 142 | * @type number |
| 143 | * @constant |
| 144 | */ |
| 145 | minor: 2 |
| 146 | }; |
| 147 | |
| 148 | /** |
| 149 | * Returns the passed-in argument, <tt>x</tt>; the identity function. This method |
| 150 | * is provided for convenience since it is used as the default behavior for a |
| 151 | * number of property functions. |
| 152 | * |
| 153 | * @param x a value. |
| 154 | * @returns the value <tt>x</tt>. |
| 155 | */ |
| 156 | pv.identity = function(x) { return x; }; |
| 157 | |
| 158 | /** |
| 159 | * Returns <tt>this.index</tt>. This method is provided for convenience for use |
| 160 | * with scales. For example, to color bars by their index, say: |
| 161 | * |
| 162 | * <pre>.fillStyle(pv.Colors.category10().by(pv.index))</pre> |
| 163 | * |
| 164 | * This method is equivalent to <tt>function() this.index</tt>, but more |
| 165 | * succinct. Note that the <tt>index</tt> property is also supported for |
| 166 | * accessor functions with {@link pv.max}, {@link pv.min} and other array |
| 167 | * utility methods. |
| 168 | * |
| 169 | * @see pv.Scale |
| 170 | * @see pv.Mark#index |
| 171 | */ |
| 172 | pv.index = function() { return this.index; }; |
| 173 | |
| 174 | /** |
| 175 | * Returns <tt>this.childIndex</tt>. This method is provided for convenience for |
| 176 | * use with scales. For example, to color bars by their child index, say: |
| 177 | * |
| 178 | * <pre>.fillStyle(pv.Colors.category10().by(pv.child))</pre> |
| 179 | * |
| 180 | * This method is equivalent to <tt>function() this.childIndex</tt>, but more |
| 181 | * succinct. |
| 182 | * |
| 183 | * @see pv.Scale |
| 184 | * @see pv.Mark#childIndex |
| 185 | */ |
| 186 | pv.child = function() { return this.childIndex; }; |
| 187 | |
| 188 | /** |
| 189 | * Returns <tt>this.parent.index</tt>. This method is provided for convenience |
| 190 | * for use with scales. This method is provided for convenience for use with |
| 191 | * scales. For example, to color bars by their parent index, say: |
| 192 | * |
| 193 | * <pre>.fillStyle(pv.Colors.category10().by(pv.parent))</pre> |
| 194 | * |
| 195 | * Tthis method is equivalent to <tt>function() this.parent.index</tt>, but more |
| 196 | * succinct. |
| 197 | * |
| 198 | * @see pv.Scale |
| 199 | * @see pv.Mark#index |
| 200 | */ |
| 201 | pv.parent = function() { return this.parent.index; }; |
| 202 | |
| 203 | /** |
| 204 | * Stores the current event. This field is only set within event handlers. |
| 205 | * |
| 206 | * @type Event |
| 207 | * @name pv.event |
| 208 | */ |
| 209 | /** |
| 210 | * @private Returns a prototype object suitable for extending the given class |
| 211 | * <tt>f</tt>. Rather than constructing a new instance of <tt>f</tt> to serve as |
| 212 | * the prototype (which unnecessarily runs the constructor on the created |
| 213 | * prototype object, potentially polluting it), an anonymous function is |
| 214 | * generated internally that shares the same prototype: |
| 215 | * |
| 216 | * <pre>function g() {} |
| 217 | * g.prototype = f.prototype; |
| 218 | * return new g();</pre> |
| 219 | * |
| 220 | * For more details, see Douglas Crockford's essay on prototypal inheritance. |
| 221 | * |
| 222 | * @param {function} f a constructor. |
| 223 | * @returns a suitable prototype object. |
| 224 | * @see Douglas Crockford's essay on <a |
| 225 | * href="http://javascript.crockford.com/prototypal.html">prototypal |
| 226 | * inheritance</a>. |
| 227 | */ |
| 228 | pv.extend = function(f) { |
| 229 | function g() {} |
| 230 | g.prototype = f.prototype || f; |
| 231 | return new g(); |
| 232 | }; |
| 233 | |
| 234 | try { |
| 235 | eval("pv.parse = function(x) x;"); // native support |
| 236 | } catch (e) { |
| 237 | |
| 238 | /** |
| 239 | * @private Parses a Protovis specification, which may use JavaScript 1.8 |
| 240 | * function expresses, replacing those function expressions with proper |
| 241 | * functions such that the code can be run by a JavaScript 1.6 interpreter. This |
| 242 | * hack only supports function expressions (using clumsy regular expressions, no |
| 243 | * less), and not other JavaScript 1.8 features such as let expressions. |
| 244 | * |
| 245 | * @param {string} s a Protovis specification (i.e., a string of JavaScript 1.8 |
| 246 | * source code). |
| 247 | * @returns {string} a conformant JavaScript 1.6 source code. |
| 248 | */ |
| 249 | pv.parse = function(js) { // hacky regex support |
| 250 | var re = new RegExp("function\\s*(\\b\\w+)?\\s*\\([^)]*\\)\\s*", "mg"), m, d, i = 0, s = ""; |
| 251 | while (m = re.exec(js)) { |
| 252 | var j = m.index + m[0].length; |
| 253 | if (js.charAt(j) != '{') { |
| 254 | s += js.substring(i, j) + "{return "; |
| 255 | i = j; |
| 256 | for (var p = 0; p >= 0 && j < js.length; j++) { |
| 257 | var c = js.charAt(j); |
| 258 | switch (c) { |
| 259 | case '"': case '\'': { |
| 260 | while (++j < js.length && (d = js.charAt(j)) != c) { |
| 261 | if (d == '\\') j++; |
| 262 | } |
| 263 | break; |
| 264 | } |
| 265 | case '[': case '(': p++; break; |
| 266 | case ']': case ')': p--; break; |
| 267 | case ';': |
| 268 | case ',': if (p == 0) p--; break; |
| 269 | } |
| 270 | } |
| 271 | s += pv.parse(js.substring(i, --j)) + ";}"; |
| 272 | i = j; |
| 273 | } |
| 274 | re.lastIndex = j; |
| 275 | } |
| 276 | s += js.substring(i); |
| 277 | return s; |
| 278 | }; |
| 279 | } |
| 280 | |
| 281 | /** |
| 282 | * @private Computes the value of the specified CSS property <tt>p</tt> on the |
| 283 | * specified element <tt>e</tt>. |
| 284 | * |
| 285 | * @param {string} p the name of the CSS property. |
| 286 | * @param e the element on which to compute the CSS property. |
| 287 | */ |
| 288 | pv.css = function(e, p) { |
| 289 | return window.getComputedStyle |
| 290 | ? window.getComputedStyle(e, null).getPropertyValue(p) |
| 291 | : e.currentStyle[p]; |
| 292 | }; |
| 293 | |
| 294 | /** |
| 295 | * @private Reports the specified error to the JavaScript console. Mozilla only |
| 296 | * allows logging to the console for privileged code; if the console is |
| 297 | * unavailable, the alert dialog box is used instead. |
| 298 | * |
| 299 | * @param e the exception that triggered the error. |
| 300 | */ |
| 301 | pv.error = function(e) { |
| 302 | (typeof console == "undefined") ? alert(e) : console.error(e); |
| 303 | }; |
| 304 | |
| 305 | /** |
| 306 | * @private Registers the specified listener for events of the specified type on |
| 307 | * the specified target. For standards-compliant browsers, this method uses |
| 308 | * <tt>addEventListener</tt>; for Internet Explorer, <tt>attachEvent</tt>. |
| 309 | * |
| 310 | * @param target a DOM element. |
| 311 | * @param {string} type the type of event, such as "click". |
| 312 | * @param {function} the event handler callback. |
| 313 | */ |
| 314 | pv.listen = function(target, type, listener) { |
| 315 | listener = pv.listener(listener); |
| 316 | return target.addEventListener |
| 317 | ? target.addEventListener(type, listener, false) |
| 318 | : target.attachEvent("on" + type, listener); |
| 319 | }; |
| 320 | |
| 321 | /** |
| 322 | * @private Returns a wrapper for the specified listener function such that the |
| 323 | * {@link pv.event} is set for the duration of the listener's invocation. The |
| 324 | * wrapper is cached on the returned function, such that duplicate registrations |
| 325 | * of the wrapped event handler are ignored. |
| 326 | * |
| 327 | * @param {function} f an event handler. |
| 328 | * @returns {function} the wrapped event handler. |
| 329 | */ |
| 330 | pv.listener = function(f) { |
| 331 | return f.$listener || (f.$listener = function(e) { |
| 332 | try { |
| 333 | pv.event = e; |
| 334 | return f.call(this, e); |
| 335 | } finally { |
| 336 | delete pv.event; |
| 337 | } |
| 338 | }); |
| 339 | }; |
| 340 | |
| 341 | /** |
| 342 | * @private Returns true iff <i>a</i> is an ancestor of <i>e</i>. This is useful |
| 343 | * for ignoring mouseout and mouseover events that are contained within the |
| 344 | * target element. |
| 345 | */ |
| 346 | pv.ancestor = function(a, e) { |
| 347 | while (e) { |
| 348 | if (e == a) return true; |
| 349 | e = e.parentNode; |
| 350 | } |
| 351 | return false; |
| 352 | }; |
| 353 | |
| 354 | /** @private Returns a locally-unique positive id. */ |
| 355 | pv.id = function() { |
| 356 | var id = 1; return function() { return id++; }; |
| 357 | }(); |
| 358 | |
| 359 | /** @private Returns a function wrapping the specified constant. */ |
| 360 | pv.functor = function(v) { |
| 361 | return typeof v == "function" ? v : function() { return v; }; |
| 362 | }; |
| 363 | /* |
| 364 | * Parses the Protovis specifications on load, allowing the use of JavaScript |
| 365 | * 1.8 function expressions on browsers that only support JavaScript 1.6. |
| 366 | * |
| 367 | * @see pv.parse |
| 368 | */ |
| 369 | pv.listen(window, "load", function() { |
| 370 | /* |
| 371 | * Note: in Firefox any variables declared here are visible to the eval'd |
| 372 | * script below. Even worse, any global variables declared by the script |
| 373 | * could overwrite local variables here (such as the index, `i`)! To protect |
| 374 | * against this, all variables are explicitly scoped on a pv.$ object. |
| 375 | */ |
| 376 | pv.$ = {i:0, x:document.getElementsByTagName("script")}; |
| 377 | for (; pv.$.i < pv.$.x.length; pv.$.i++) { |
| 378 | pv.$.s = pv.$.x[pv.$.i]; |
| 379 | if (pv.$.s.type == "text/javascript+protovis") { |
| 380 | try { |
| 381 | window.eval(pv.parse(pv.$.s.text)); |
| 382 | } catch (e) { |
| 383 | pv.error(e); |
| 384 | } |
| 385 | } |
| 386 | } |
| 387 | delete pv.$; |
| 388 | }); |
| 389 | /** |
| 390 | * Abstract; see an implementing class. |
| 391 | * |
| 392 | * @class Represents an abstract text formatter and parser. A <i>format</i> is a |
| 393 | * function that converts an object of a given type, such as a <tt>Date</tt>, to |
| 394 | * a human-readable string representation. The format may also have a |
| 395 | * {@link #parse} method for converting a string representation back to the |
| 396 | * given object type. |
| 397 | * |
| 398 | * <p>Because formats are themselves functions, they can be used directly as |
| 399 | * mark properties. For example, if the data associated with a label are dates, |
| 400 | * a date format can be used as label text: |
| 401 | * |
| 402 | * <pre> .text(pv.Format.date("%m/%d/%y"))</pre> |
| 403 | * |
| 404 | * And as with scales, if the format is used in multiple places, it can be |
| 405 | * convenient to declare it as a global variable and then reference it from the |
| 406 | * appropriate property functions. For example, if the data has a <tt>date</tt> |
| 407 | * attribute, and <tt>format</tt> references a given date format: |
| 408 | * |
| 409 | * <pre> .text(function(d) format(d.date))</pre> |
| 410 | * |
| 411 | * Similarly, to parse a string into a date: |
| 412 | * |
| 413 | * <pre>var date = format.parse("4/30/2010");</pre> |
| 414 | * |
| 415 | * Not all format implementations support parsing. See the implementing class |
| 416 | * for details. |
| 417 | * |
| 418 | * @see pv.Format.date |
| 419 | * @see pv.Format.number |
| 420 | * @see pv.Format.time |
| 421 | */ |
| 422 | pv.Format = {}; |
| 423 | |
| 424 | /** |
| 425 | * Formats the specified object, returning the string representation. |
| 426 | * |
| 427 | * @function |
| 428 | * @name pv.Format.prototype.format |
| 429 | * @param {object} x the object to format. |
| 430 | * @returns {string} the formatted string. |
| 431 | */ |
| 432 | |
| 433 | /** |
| 434 | * Parses the specified string, returning the object representation. |
| 435 | * |
| 436 | * @function |
| 437 | * @name pv.Format.prototype.parse |
| 438 | * @param {string} x the string to parse. |
| 439 | * @returns {object} the parsed object. |
| 440 | */ |
| 441 | |
| 442 | /** |
| 443 | * @private Given a string that may be used as part of a regular expression, |
| 444 | * this methods returns an appropriately quoted version of the specified string, |
| 445 | * with any special characters escaped. |
| 446 | * |
| 447 | * @param {string} s a string to quote. |
| 448 | * @returns {string} the quoted string. |
| 449 | */ |
| 450 | pv.Format.re = function(s) { |
| 451 | return s.replace(/[\\\^\$\*\+\?\[\]\(\)\.\{\}]/g, "\\$&"); |
| 452 | }; |
| 453 | |
| 454 | /** |
| 455 | * @private Optionally pads the specified string <i>s</i> so that it is at least |
| 456 | * <i>n</i> characters long, using the padding character <i>c</i>. |
| 457 | * |
| 458 | * @param {string} c the padding character. |
| 459 | * @param {number} n the minimum string length. |
| 460 | * @param {string} s the string to pad. |
| 461 | * @returns {string} the padded string. |
| 462 | */ |
| 463 | pv.Format.pad = function(c, n, s) { |
| 464 | var m = n - String(s).length; |
| 465 | return (m < 1) ? s : new Array(m + 1).join(c) + s; |
| 466 | }; |
| 467 | /** |
| 468 | * Constructs a new date format with the specified string pattern. |
| 469 | * |
| 470 | * @class The format string is in the same format expected by the |
| 471 | * <tt>strftime</tt> function in C. The following conversion specifications are |
| 472 | * supported:<ul> |
| 473 | * |
| 474 | * <li>%a - abbreviated weekday name.</li> |
| 475 | * <li>%A - full weekday name.</li> |
| 476 | * <li>%b - abbreviated month names.</li> |
| 477 | * <li>%B - full month names.</li> |
| 478 | * <li>%c - locale's appropriate date and time.</li> |
| 479 | * <li>%C - century number.</li> |
| 480 | * <li>%d - day of month [01,31] (zero padded).</li> |
| 481 | * <li>%D - same as %m/%d/%y.</li> |
| 482 | * <li>%e - day of month [ 1,31] (space padded).</li> |
| 483 | * <li>%h - same as %b.</li> |
| 484 | * <li>%H - hour (24-hour clock) [00,23] (zero padded).</li> |
| 485 | * <li>%I - hour (12-hour clock) [01,12] (zero padded).</li> |
| 486 | * <li>%m - month number [01,12] (zero padded).</li> |
| 487 | * <li>%M - minute [0,59] (zero padded).</li> |
| 488 | * <li>%n - newline character.</li> |
| 489 | * <li>%p - locale's equivalent of a.m. or p.m.</li> |
| 490 | * <li>%r - same as %I:%M:%S %p.</li> |
| 491 | * <li>%R - same as %H:%M.</li> |
| 492 | * <li>%S - second [00,61] (zero padded).</li> |
| 493 | * <li>%t - tab character.</li> |
| 494 | * <li>%T - same as %H:%M:%S.</li> |
| 495 | * <li>%x - same as %m/%d/%y.</li> |
| 496 | * <li>%X - same as %I:%M:%S %p.</li> |
| 497 | * <li>%y - year with century [00,99] (zero padded).</li> |
| 498 | * <li>%Y - year including century.</li> |
| 499 | * <li>%% - %.</li> |
| 500 | * |
| 501 | * </ul>The following conversion specifications are currently <i>unsupported</i> |
| 502 | * for formatting:<ul> |
| 503 | * |
| 504 | * <li>%j - day number [1,366].</li> |
| 505 | * <li>%u - weekday number [1,7].</li> |
| 506 | * <li>%U - week number [00,53].</li> |
| 507 | * <li>%V - week number [01,53].</li> |
| 508 | * <li>%w - weekday number [0,6].</li> |
| 509 | * <li>%W - week number [00,53].</li> |
| 510 | * <li>%Z - timezone name or abbreviation.</li> |
| 511 | * |
| 512 | * </ul>In addition, the following conversion specifications are currently |
| 513 | * <i>unsupported</i> for parsing:<ul> |
| 514 | * |
| 515 | * <li>%a - day of week, either abbreviated or full name.</li> |
| 516 | * <li>%A - same as %a.</li> |
| 517 | * <li>%c - locale's appropriate date and time.</li> |
| 518 | * <li>%C - century number.</li> |
| 519 | * <li>%D - same as %m/%d/%y.</li> |
| 520 | * <li>%I - hour (12-hour clock) [1,12].</li> |
| 521 | * <li>%n - any white space.</li> |
| 522 | * <li>%p - locale's equivalent of a.m. or p.m.</li> |
| 523 | * <li>%r - same as %I:%M:%S %p.</li> |
| 524 | * <li>%R - same as %H:%M.</li> |
| 525 | * <li>%t - same as %n.</li> |
| 526 | * <li>%T - same as %H:%M:%S.</li> |
| 527 | * <li>%x - locale's equivalent to %m/%d/%y.</li> |
| 528 | * <li>%X - locale's equivalent to %I:%M:%S %p.</li> |
| 529 | * |
| 530 | * </ul> |
| 531 | * |
| 532 | * @see <a |
| 533 | * href="http://www.opengroup.org/onlinepubs/007908799/xsh/strftime.html">strftime</a> |
| 534 | * documentation. |
| 535 | * @see <a |
| 536 | * href="http://www.opengroup.org/onlinepubs/007908799/xsh/strptime.html">strptime</a> |
| 537 | * documentation. |
| 538 | * @extends pv.Format |
| 539 | * @param {string} pattern the format pattern. |
| 540 | */ |
| 541 | pv.Format.date = function(pattern) { |
| 542 | var pad = pv.Format.pad; |
| 543 | |
| 544 | /** @private */ |
| 545 | function format(d) { |
| 546 | return pattern.replace(/%[a-zA-Z0-9]/g, function(s) { |
| 547 | switch (s) { |
| 548 | case '%a': return [ |
| 549 | "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" |
| 550 | ][d.getDay()]; |
| 551 | case '%A': return [ |
| 552 | "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", |
| 553 | "Saturday" |
| 554 | ][d.getDay()]; |
| 555 | case '%h': |
| 556 | case '%b': return [ |
| 557 | "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", |
| 558 | "Oct", "Nov", "Dec" |
| 559 | ][d.getMonth()]; |
| 560 | case '%B': return [ |
| 561 | "January", "February", "March", "April", "May", "June", "July", |
| 562 | "August", "September", "October", "November", "December" |
| 563 | ][d.getMonth()]; |
| 564 | case '%c': return d.toLocaleString(); |
| 565 | case '%C': return pad("0", 2, Math.floor(d.getFullYear() / 100) % 100); |
| 566 | case '%d': return pad("0", 2, d.getDate()); |
| 567 | case '%x': |
| 568 | case '%D': return pad("0", 2, d.getMonth() + 1) |
| 569 | + "/" + pad("0", 2, d.getDate()) |
| 570 | + "/" + pad("0", 2, d.getFullYear() % 100); |
| 571 | case '%e': return pad(" ", 2, d.getDate()); |
| 572 | case '%H': return pad("0", 2, d.getHours()); |
| 573 | case '%I': { |
| 574 | var h = d.getHours() % 12; |
| 575 | return h ? pad("0", 2, h) : 12; |
| 576 | } |
| 577 | // TODO %j: day of year as a decimal number [001,366] |
| 578 | case '%m': return pad("0", 2, d.getMonth() + 1); |
| 579 | case '%M': return pad("0", 2, d.getMinutes()); |
| 580 | case '%n': return "\n"; |
| 581 | case '%p': return d.getHours() < 12 ? "AM" : "PM"; |
| 582 | case '%T': |
| 583 | case '%X': |
| 584 | case '%r': { |
| 585 | var h = d.getHours() % 12; |
| 586 | return (h ? pad("0", 2, h) : 12) |
| 587 | + ":" + pad("0", 2, d.getMinutes()) |
| 588 | + ":" + pad("0", 2, d.getSeconds()) |
| 589 | + " " + (d.getHours() < 12 ? "AM" : "PM"); |
| 590 | } |
| 591 | case '%R': return pad("0", 2, d.getHours()) + ":" + pad("0", 2, d.getMinutes()); |
| 592 | case '%S': return pad("0", 2, d.getSeconds()); |
| 593 | case '%Q': return pad("0", 3, d.getMilliseconds()); |
| 594 | case '%t': return "\t"; |
| 595 | case '%u': { |
| 596 | var w = d.getDay(); |
| 597 | return w ? w : 1; |
| 598 | } |
| 599 | // TODO %U: week number (sunday first day) [00,53] |
| 600 | // TODO %V: week number (monday first day) [01,53] ... with weirdness |
| 601 | case '%w': return d.getDay(); |
| 602 | // TODO %W: week number (monday first day) [00,53] ... with weirdness |
| 603 | case '%y': return pad("0", 2, d.getFullYear() % 100); |
| 604 | case '%Y': return d.getFullYear(); |
| 605 | // TODO %Z: timezone name or abbreviation |
| 606 | case '%%': return "%"; |
| 607 | } |
| 608 | return s; |
| 609 | }); |
| 610 | } |
| 611 | |
| 612 | /** |
| 613 | * Converts a date to a string using the associated formatting pattern. |
| 614 | * |
| 615 | * @function |
| 616 | * @name pv.Format.date.prototype.format |
| 617 | * @param {Date} date a date to format. |
| 618 | * @returns {string} the formatted date as a string. |
| 619 | */ |
| 620 | format.format = format; |
| 621 | |
| 622 | /** |
| 623 | * Parses a date from a string using the associated formatting pattern. |
| 624 | * |
| 625 | * @function |
| 626 | * @name pv.Format.date.prototype.parse |
| 627 | * @param {string} s the string to parse as a date. |
| 628 | * @returns {Date} the parsed date. |
| 629 | */ |
| 630 | format.parse = function(s) { |
| 631 | var year = 1970, month = 0, date = 1, hour = 0, minute = 0, second = 0; |
| 632 | var fields = [function() {}]; |
| 633 | |
| 634 | /* Register callbacks for each field in the format pattern. */ |
| 635 | var re = pv.Format.re(pattern).replace(/%[a-zA-Z0-9]/g, function(s) { |
| 636 | switch (s) { |
| 637 | // TODO %a: day of week, either abbreviated or full name |
| 638 | // TODO %A: same as %a |
| 639 | case '%b': { |
| 640 | fields.push(function(x) { month = { |
| 641 | Jan: 0, Feb: 1, Mar: 2, Apr: 3, May: 4, Jun: 5, Jul: 6, Aug: 7, |
| 642 | Sep: 8, Oct: 9, Nov: 10, Dec: 11 |
| 643 | }[x]; }); |
| 644 | return "([A-Za-z]+)"; |
| 645 | } |
| 646 | case '%h': |
| 647 | case '%B': { |
| 648 | fields.push(function(x) { month = { |
| 649 | January: 0, February: 1, March: 2, April: 3, May: 4, June: 5, |
| 650 | July: 6, August: 7, September: 8, October: 9, November: 10, |
| 651 | December: 11 |
| 652 | }[x]; }); |
| 653 | return "([A-Za-z]+)"; |
| 654 | } |
| 655 | // TODO %c: locale's appropriate date and time |
| 656 | // TODO %C: century number[0,99] |
| 657 | case '%e': |
| 658 | case '%d': { |
| 659 | fields.push(function(x) { date = x; }); |
| 660 | return "([0-9]+)"; |
| 661 | } |
| 662 | // TODO %D: same as %m/%d/%y |
| 663 | case '%I': |
| 664 | case '%H': { |
| 665 | fields.push(function(x) { hour = x; }); |
| 666 | return "([0-9]+)"; |
| 667 | } |
| 668 | // TODO %j: day number [1,366] |
| 669 | case '%m': { |
| 670 | fields.push(function(x) { month = x - 1; }); |
| 671 | return "([0-9]+)"; |
| 672 | } |
| 673 | case '%M': { |
| 674 | fields.push(function(x) { minute = x; }); |
| 675 | return "([0-9]+)"; |
| 676 | } |
| 677 | // TODO %n: any white space |
| 678 | // TODO %p: locale's equivalent of a.m. or p.m. |
| 679 | case '%p': { // TODO this is a hack |
| 680 | fields.push(function(x) { |
| 681 | if (hour == 12) { |
| 682 | if (x == "am") hour = 0; |
| 683 | } else if (x == "pm") { |
| 684 | hour = Number(hour) + 12; |
| 685 | } |
| 686 | }); |
| 687 | return "(am|pm)"; |
| 688 | } |
| 689 | // TODO %r: %I:%M:%S %p |
| 690 | // TODO %R: %H:%M |
| 691 | case '%S': { |
| 692 | fields.push(function(x) { second = x; }); |
| 693 | return "([0-9]+)"; |
| 694 | } |
| 695 | // TODO %t: any white space |
| 696 | // TODO %T: %H:%M:%S |
| 697 | // TODO %U: week number [00,53] |
| 698 | // TODO %w: weekday [0,6] |
| 699 | // TODO %W: week number [00, 53] |
| 700 | // TODO %x: locale date (%m/%d/%y) |
| 701 | // TODO %X: locale time (%I:%M:%S %p) |
| 702 | case '%y': { |
| 703 | fields.push(function(x) { |
| 704 | x = Number(x); |
| 705 | year = x + (((0 <= x) && (x < 69)) ? 2000 |
| 706 | : (((x >= 69) && (x < 100) ? 1900 : 0))); |
| 707 | }); |
| 708 | return "([0-9]+)"; |
| 709 | } |
| 710 | case '%Y': { |
| 711 | fields.push(function(x) { year = x; }); |
| 712 | return "([0-9]+)"; |
| 713 | } |
| 714 | case '%%': { |
| 715 | fields.push(function() {}); |
| 716 | return "%"; |
| 717 | } |
| 718 | } |
| 719 | return s; |
| 720 | }); |
| 721 | |
| 722 | var match = s.match(re); |
| 723 | if (match) match.forEach(function(m, i) { fields[i](m); }); |
| 724 | return new Date(year, month, date, hour, minute, second); |
| 725 | }; |
| 726 | |
| 727 | return format; |
| 728 | }; |
| 729 | /** |
| 730 | * Returns a time format of the given type, either "short" or "long". |
| 731 | * |
| 732 | * @class Represents a time format, converting between a <tt>number</tt> |
| 733 | * representing a duration in milliseconds, and a <tt>string</tt>. Two types of |
| 734 | * time formats are supported: "short" and "long". The <i>short</i> format type |
| 735 | * returns a string such as "3.3 days" or "12.1 minutes", while the <i>long</i> |
| 736 | * format returns "13:04:12" or similar. |
| 737 | * |
| 738 | * @extends pv.Format |
| 739 | * @param {string} type the type; "short" or "long". |
| 740 | */ |
| 741 | pv.Format.time = function(type) { |
| 742 | var pad = pv.Format.pad; |
| 743 | |
| 744 | /* |
| 745 | * MILLISECONDS = 1 |
| 746 | * SECONDS = 1e3 |
| 747 | * MINUTES = 6e4 |
| 748 | * HOURS = 36e5 |
| 749 | * DAYS = 864e5 |
| 750 | * WEEKS = 6048e5 |
| 751 | * MONTHS = 2592e6 |
| 752 | * YEARS = 31536e6 |
| 753 | */ |
| 754 | |
| 755 | /** @private */ |
| 756 | function format(t) { |
| 757 | t = Number(t); // force conversion from Date |
| 758 | switch (type) { |
| 759 | case "short": { |
| 760 | if (t >= 31536e6) { |
| 761 | return (t / 31536e6).toFixed(1) + " years"; |
| 762 | } else if (t >= 6048e5) { |
| 763 | return (t / 6048e5).toFixed(1) + " weeks"; |
| 764 | } else if (t >= 864e5) { |
| 765 | return (t / 864e5).toFixed(1) + " days"; |
| 766 | } else if (t >= 36e5) { |
| 767 | return (t / 36e5).toFixed(1) + " hours"; |
| 768 | } else if (t >= 6e4) { |
| 769 | return (t / 6e4).toFixed(1) + " minutes"; |
| 770 | } |
| 771 | return (t / 1e3).toFixed(1) + " seconds"; |
| 772 | } |
| 773 | case "long": { |
| 774 | var a = [], |
| 775 | s = ((t % 6e4) / 1e3) >> 0, |
| 776 | m = ((t % 36e5) / 6e4) >> 0; |
| 777 | a.push(pad("0", 2, s)); |
| 778 | if (t >= 36e5) { |
| 779 | var h = ((t % 864e5) / 36e5) >> 0; |
| 780 | a.push(pad("0", 2, m)); |
| 781 | if (t >= 864e5) { |
| 782 | a.push(pad("0", 2, h)); |
| 783 | a.push(Math.floor(t / 864e5).toFixed()); |
| 784 | } else { |
| 785 | a.push(h.toFixed()); |
| 786 | } |
| 787 | } else { |
| 788 | a.push(m.toFixed()); |
| 789 | } |
| 790 | return a.reverse().join(":"); |
| 791 | } |
| 792 | } |
| 793 | } |
| 794 | |
| 795 | /** |
| 796 | * Formats the specified time, returning the string representation. |
| 797 | * |
| 798 | * @function |
| 799 | * @name pv.Format.time.prototype.format |
| 800 | * @param {number} t the duration in milliseconds. May also be a <tt>Date</tt>. |
| 801 | * @returns {string} the formatted string. |
| 802 | */ |
| 803 | format.format = format; |
| 804 | |
| 805 | /** |
| 806 | * Parses the specified string, returning the time in milliseconds. |
| 807 | * |
| 808 | * @function |
| 809 | * @name pv.Format.time.prototype.parse |
| 810 | * @param {string} s a formatted string. |
| 811 | * @returns {number} the parsed duration in milliseconds. |
| 812 | */ |
| 813 | format.parse = function(s) { |
| 814 | switch (type) { |
| 815 | case "short": { |
| 816 | var re = /([0-9,.]+)\s*([a-z]+)/g, a, t = 0; |
| 817 | while (a = re.exec(s)) { |
| 818 | var f = parseFloat(a[0].replace(",", "")), u = 0; |
| 819 | switch (a[2].toLowerCase()) { |
| 820 | case "year": case "years": u = 31536e6; break; |
| 821 | case "week": case "weeks": u = 6048e5; break; |
| 822 | case "day": case "days": u = 864e5; break; |
| 823 | case "hour": case "hours": u = 36e5; break; |
| 824 | case "minute": case "minutes": u = 6e4; break; |
| 825 | case "second": case "seconds": u = 1e3; break; |
| 826 | } |
| 827 | t += f * u; |
| 828 | } |
| 829 | return t; |
| 830 | } |
| 831 | case "long": { |
| 832 | var a = s.replace(",", "").split(":").reverse(), t = 0; |
| 833 | if (a.length) t += parseFloat(a[0]) * 1e3; |
| 834 | if (a.length > 1) t += parseFloat(a[1]) * 6e4; |
| 835 | if (a.length > 2) t += parseFloat(a[2]) * 36e5; |
| 836 | if (a.length > 3) t += parseFloat(a[3]) * 864e5; |
| 837 | return t; |
| 838 | } |
| 839 | } |
| 840 | } |
| 841 | |
| 842 | return format; |
| 843 | }; |
| 844 | /** |
| 845 | * Returns a default number format. |
| 846 | * |
| 847 | * @class Represents a number format, converting between a <tt>number</tt> and a |
| 848 | * <tt>string</tt>. This class allows numbers to be formatted with variable |
| 849 | * precision (both for the integral and fractional part of the number), optional |
| 850 | * thousands grouping, and optional padding. The thousands (",") and decimal |
| 851 | * (".") separator can be customized. |
| 852 | * |
| 853 | * @returns {pv.Format.number} a number format. |
| 854 | */ |
| 855 | pv.Format.number = function() { |
| 856 | var mini = 0, // default minimum integer digits |
| 857 | maxi = Infinity, // default maximum integer digits |
| 858 | mins = 0, // mini, including group separators |
| 859 | minf = 0, // default minimum fraction digits |
| 860 | maxf = 0, // default maximum fraction digits |
| 861 | maxk = 1, // 10^maxf |
| 862 | padi = "0", // default integer pad |
| 863 | padf = "0", // default fraction pad |
| 864 | padg = true, // whether group separator affects integer padding |
| 865 | decimal = ".", // default decimal separator |
| 866 | group = ","; // default group separator |
| 867 | |
| 868 | /** @private */ |
| 869 | function format(x) { |
| 870 | /* Round the fractional part, and split on decimal separator. */ |
| 871 | if (Infinity > maxf) x = Math.round(x * maxk) / maxk; |
| 872 | var s = String(Math.abs(x)).split("."); |
| 873 | |
| 874 | /* Pad, truncate and group the integral part. */ |
| 875 | var i = s[0], n = (x < 0) ? "-" : ""; |
| 876 | if (i.length > maxi) i = i.substring(i.length - maxi); |
| 877 | if (padg && (i.length < mini)) i = n + new Array(mini - i.length + 1).join(padi) + i; |
| 878 | if (i.length > 3) i = i.replace(/\B(?=(?:\d{3})+(?!\d))/g, group); |
| 879 | if (!padg && (i.length < mins)) i = new Array(mins - i.length + 1).join(padi) + n + i; |
| 880 | s[0] = i; |
| 881 | |
| 882 | /* Pad the fractional part. */ |
| 883 | var f = s[1] || ""; |
| 884 | if (f.length < minf) s[1] = f + new Array(minf - f.length + 1).join(padf); |
| 885 | |
| 886 | return s.join(decimal); |
| 887 | } |
| 888 | |
| 889 | /** |
| 890 | * @function |
| 891 | * @name pv.Format.number.prototype.format |
| 892 | * @param {number} x |
| 893 | * @returns {string} |
| 894 | */ |
| 895 | format.format = format; |
| 896 | |
| 897 | /** |
| 898 | * Parses the specified string as a number. Before parsing, leading and |
| 899 | * trailing padding is removed. Group separators are also removed, and the |
| 900 | * decimal separator is replaced with the standard point ("."). The integer |
| 901 | * part is truncated per the maximum integer digits, and the fraction part is |
| 902 | * rounded per the maximum fraction digits. |
| 903 | * |
| 904 | * @function |
| 905 | * @name pv.Format.number.prototype.parse |
| 906 | * @param {string} x the string to parse. |
| 907 | * @returns {number} the parsed number. |
| 908 | */ |
| 909 | format.parse = function(x) { |
| 910 | var re = pv.Format.re; |
| 911 | |
| 912 | /* Remove leading and trailing padding. Split on the decimal separator. */ |
| 913 | var s = String(x) |
| 914 | .replace(new RegExp("^(" + re(padi) + ")*"), "") |
| 915 | .replace(new RegExp("(" + re(padf) + ")*$"), "") |
| 916 | .split(decimal); |
| 917 | |
| 918 | /* Remove grouping and truncate the integral part. */ |
| 919 | var i = s[0].replace(new RegExp(re(group), "g"), ""); |
| 920 | if (i.length > maxi) i = i.substring(i.length - maxi); |
| 921 | |
| 922 | /* Round the fractional part. */ |
| 923 | var f = s[1] ? Number("0." + s[1]) : 0; |
| 924 | if (Infinity > maxf) f = Math.round(f * maxk) / maxk; |
| 925 | |
| 926 | return Math.round(i) + f; |
| 927 | }; |
| 928 | |
| 929 | /** |
| 930 | * Sets or gets the minimum and maximum number of integer digits. This |
| 931 | * controls the number of decimal digits to display before the decimal |
| 932 | * separator for the integral part of the number. If the number of digits is |
| 933 | * smaller than the minimum, the digits are padded; if the number of digits is |
| 934 | * larger, the digits are truncated, showing only the lower-order digits. The |
| 935 | * default range is [0, Infinity]. |
| 936 | * |
| 937 | * <p>If only one argument is specified to this method, this value is used as |
| 938 | * both the minimum and maximum number. If no arguments are specified, a |
| 939 | * two-element array is returned containing the minimum and the maximum. |
| 940 | * |
| 941 | * @function |
| 942 | * @name pv.Format.number.prototype.integerDigits |
| 943 | * @param {number} [min] the minimum integer digits. |
| 944 | * @param {number} [max] the maximum integer digits. |
| 945 | * @returns {pv.Format.number} <tt>this</tt>, or the current integer digits. |
| 946 | */ |
| 947 | format.integerDigits = function(min, max) { |
| 948 | if (arguments.length) { |
| 949 | mini = Number(min); |
| 950 | maxi = (arguments.length > 1) ? Number(max) : mini; |
| 951 | mins = mini + Math.floor(mini / 3) * group.length; |
| 952 | return this; |
| 953 | } |
| 954 | return [mini, maxi]; |
| 955 | }; |
| 956 | |
| 957 | /** |
| 958 | * Sets or gets the minimum and maximum number of fraction digits. The |
| 959 | * controls the number of decimal digits to display after the decimal |
| 960 | * separator for the fractional part of the number. If the number of digits is |
| 961 | * smaller than the minimum, the digits are padded; if the number of digits is |
| 962 | * larger, the fractional part is rounded, showing only the higher-order |
| 963 | * digits. The default range is [0, 0]. |
| 964 | * |
| 965 | * <p>If only one argument is specified to this method, this value is used as |
| 966 | * both the minimum and maximum number. If no arguments are specified, a |
| 967 | * two-element array is returned containing the minimum and the maximum. |
| 968 | * |
| 969 | * @function |
| 970 | * @name pv.Format.number.prototype.fractionDigits |
| 971 | * @param {number} [min] the minimum fraction digits. |
| 972 | * @param {number} [max] the maximum fraction digits. |
| 973 | * @returns {pv.Format.number} <tt>this</tt>, or the current fraction digits. |
| 974 | */ |
| 975 | format.fractionDigits = function(min, max) { |
| 976 | if (arguments.length) { |
| 977 | minf = Number(min); |
| 978 | maxf = (arguments.length > 1) ? Number(max) : minf; |
| 979 | maxk = Math.pow(10, maxf); |
| 980 | return this; |
| 981 | } |
| 982 | return [minf, maxf]; |
| 983 | }; |
| 984 | |
| 985 | /** |
| 986 | * Sets or gets the character used to pad the integer part. The integer pad is |
| 987 | * used when the number of integer digits is smaller than the minimum. The |
| 988 | * default pad character is "0" (zero). |
| 989 | * |
| 990 | * @param {string} [x] the new pad character. |
| 991 | * @returns {pv.Format.number} <tt>this</tt> or the current pad character. |
| 992 | */ |
| 993 | format.integerPad = function(x) { |
| 994 | if (arguments.length) { |
| 995 | padi = String(x); |
| 996 | padg = /\d/.test(padi); |
| 997 | return this; |
| 998 | } |
| 999 | return padi; |
| 1000 | }; |
| 1001 | |
| 1002 | /** |
| 1003 | * Sets or gets the character used to pad the fration part. The fraction pad |
| 1004 | * is used when the number of fraction digits is smaller than the minimum. The |
| 1005 | * default pad character is "0" (zero). |
| 1006 | * |
| 1007 | * @param {string} [x] the new pad character. |
| 1008 | * @returns {pv.Format.number} <tt>this</tt> or the current pad character. |
| 1009 | */ |
| 1010 | format.fractionPad = function(x) { |
| 1011 | if (arguments.length) { |
| 1012 | padf = String(x); |
| 1013 | return this; |
| 1014 | } |
| 1015 | return padf; |
| 1016 | }; |
| 1017 | |
| 1018 | /** |
| 1019 | * Sets or gets the character used as the decimal point, separating the |
| 1020 | * integer and fraction parts of the number. The default decimal point is ".". |
| 1021 | * |
| 1022 | * @param {string} [x] the new decimal separator. |
| 1023 | * @returns {pv.Format.number} <tt>this</tt> or the current decimal separator. |
| 1024 | */ |
| 1025 | format.decimal = function(x) { |
| 1026 | if (arguments.length) { |
| 1027 | decimal = String(x); |
| 1028 | return this; |
| 1029 | } |
| 1030 | return decimal; |
| 1031 | }; |
| 1032 | |
| 1033 | /** |
| 1034 | * Sets or gets the character used as the group separator, grouping integer |
| 1035 | * digits by thousands. The default decimal point is ",". Grouping can be |
| 1036 | * disabled by using "" for the separator. |
| 1037 | * |
| 1038 | * @param {string} [x] the new group separator. |
| 1039 | * @returns {pv.Format.number} <tt>this</tt> or the current group separator. |
| 1040 | */ |
| 1041 | format.group = function(x) { |
| 1042 | if (arguments.length) { |
| 1043 | group = x ? String(x) : ""; |
| 1044 | mins = mini + Math.floor(mini / 3) * group.length; |
| 1045 | return this; |
| 1046 | } |
| 1047 | return group; |
| 1048 | }; |
| 1049 | |
| 1050 | return format; |
| 1051 | }; |
| 1052 | /** |
| 1053 | * @private A private variant of Array.prototype.map that supports the index |
| 1054 | * property. |
| 1055 | */ |
| 1056 | pv.map = function(array, f) { |
| 1057 | var o = {}; |
| 1058 | return f |
| 1059 | ? array.map(function(d, i) { o.index = i; return f.call(o, d); }) |
| 1060 | : array.slice(); |
| 1061 | }; |
| 1062 | |
| 1063 | /** |
| 1064 | * Concatenates the specified array with itself <i>n</i> times. For example, |
| 1065 | * <tt>pv.repeat([1, 2])</tt> returns [1, 2, 1, 2]. |
| 1066 | * |
| 1067 | * @param {array} a an array. |
| 1068 | * @param {number} [n] the number of times to repeat; defaults to two. |
| 1069 | * @returns {array} an array that repeats the specified array. |
| 1070 | */ |
| 1071 | pv.repeat = function(array, n) { |
| 1072 | if (arguments.length == 1) n = 2; |
| 1073 | return pv.blend(pv.range(n).map(function() { return array; })); |
| 1074 | }; |
| 1075 | |
| 1076 | /** |
| 1077 | * Given two arrays <tt>a</tt> and <tt>b</tt>, <style |
| 1078 | * type="text/css">sub{line-height:0}</style> returns an array of all possible |
| 1079 | * pairs of elements [a<sub>i</sub>, b<sub>j</sub>]. The outer loop is on array |
| 1080 | * <i>a</i>, while the inner loop is on <i>b</i>, such that the order of |
| 1081 | * returned elements is [a<sub>0</sub>, b<sub>0</sub>], [a<sub>0</sub>, |
| 1082 | * b<sub>1</sub>], ... [a<sub>0</sub>, b<sub>m</sub>], [a<sub>1</sub>, |
| 1083 | * b<sub>0</sub>], [a<sub>1</sub>, b<sub>1</sub>], ... [a<sub>1</sub>, |
| 1084 | * b<sub>m</sub>], ... [a<sub>n</sub>, b<sub>m</sub>]. If either array is empty, |
| 1085 | * an empty array is returned. |
| 1086 | * |
| 1087 | * @param {array} a an array. |
| 1088 | * @param {array} b an array. |
| 1089 | * @returns {array} an array of pairs of elements in <tt>a</tt> and <tt>b</tt>. |
| 1090 | */ |
| 1091 | pv.cross = function(a, b) { |
| 1092 | var array = []; |
| 1093 | for (var i = 0, n = a.length, m = b.length; i < n; i++) { |
| 1094 | for (var j = 0, x = a[i]; j < m; j++) { |
| 1095 | array.push([x, b[j]]); |
| 1096 | } |
| 1097 | } |
| 1098 | return array; |
| 1099 | }; |
| 1100 | |
| 1101 | /** |
| 1102 | * Given the specified array of arrays, concatenates the arrays into a single |
| 1103 | * array. If the individual arrays are explicitly known, an alternative to blend |
| 1104 | * is to use JavaScript's <tt>concat</tt> method directly. These two equivalent |
| 1105 | * expressions:<ul> |
| 1106 | * |
| 1107 | * <li><tt>pv.blend([[1, 2, 3], ["a", "b", "c"]])</tt> |
| 1108 | * <li><tt>[1, 2, 3].concat(["a", "b", "c"])</tt> |
| 1109 | * |
| 1110 | * </ul>return [1, 2, 3, "a", "b", "c"]. |
| 1111 | * |
| 1112 | * @param {array[]} arrays an array of arrays. |
| 1113 | * @returns {array} an array containing all the elements of each array in |
| 1114 | * <tt>arrays</tt>. |
| 1115 | */ |
| 1116 | pv.blend = function(arrays) { |
| 1117 | return Array.prototype.concat.apply([], arrays); |
| 1118 | }; |
| 1119 | |
| 1120 | /** |
| 1121 | * Given the specified array of arrays, <style |
| 1122 | * type="text/css">sub{line-height:0}</style> transposes each element |
| 1123 | * array<sub>ij</sub> with array<sub>ji</sub>. If the array has dimensions |
| 1124 | * <i>n</i>×<i>m</i>, it will have dimensions <i>m</i>×<i>n</i> |
| 1125 | * after this method returns. This method transposes the elements of the array |
| 1126 | * in place, mutating the array, and returning a reference to the array. |
| 1127 | * |
| 1128 | * @param {array[]} arrays an array of arrays. |
| 1129 | * @returns {array[]} the passed-in array, after transposing the elements. |
| 1130 | */ |
| 1131 | pv.transpose = function(arrays) { |
| 1132 | var n = arrays.length, m = pv.max(arrays, function(d) { return d.length; }); |
| 1133 | |
| 1134 | if (m > n) { |
| 1135 | arrays.length = m; |
| 1136 | for (var i = n; i < m; i++) { |
| 1137 | arrays[i] = new Array(n); |
| 1138 | } |
| 1139 | for (var i = 0; i < n; i++) { |
| 1140 | for (var j = i + 1; j < m; j++) { |
| 1141 | var t = arrays[i][j]; |
| 1142 | arrays[i][j] = arrays[j][i]; |
| 1143 | arrays[j][i] = t; |
| 1144 | } |
| 1145 | } |
| 1146 | } else { |
| 1147 | for (var i = 0; i < m; i++) { |
| 1148 | arrays[i].length = n; |
| 1149 | } |
| 1150 | for (var i = 0; i < n; i++) { |
| 1151 | for (var j = 0; j < i; j++) { |
| 1152 | var t = arrays[i][j]; |
| 1153 | arrays[i][j] = arrays[j][i]; |
| 1154 | arrays[j][i] = t; |
| 1155 | } |
| 1156 | } |
| 1157 | } |
| 1158 | |
| 1159 | arrays.length = m; |
| 1160 | for (var i = 0; i < m; i++) { |
| 1161 | arrays[i].length = n; |
| 1162 | } |
| 1163 | |
| 1164 | return arrays; |
| 1165 | }; |
| 1166 | |
| 1167 | /** |
| 1168 | * Returns a normalized copy of the specified array, such that the sum of the |
| 1169 | * returned elements sum to one. If the specified array is not an array of |
| 1170 | * numbers, an optional accessor function <tt>f</tt> can be specified to map the |
| 1171 | * elements to numbers. For example, if <tt>array</tt> is an array of objects, |
| 1172 | * and each object has a numeric property "foo", the expression |
| 1173 | * |
| 1174 | * <pre>pv.normalize(array, function(d) d.foo)</pre> |
| 1175 | * |
| 1176 | * returns a normalized array on the "foo" property. If an accessor function is |
| 1177 | * not specified, the identity function is used. Accessor functions can refer to |
| 1178 | * <tt>this.index</tt>. |
| 1179 | * |
| 1180 | * @param {array} array an array of objects, or numbers. |
| 1181 | * @param {function} [f] an optional accessor function. |
| 1182 | * @returns {number[]} an array of numbers that sums to one. |
| 1183 | */ |
| 1184 | pv.normalize = function(array, f) { |
| 1185 | var norm = pv.map(array, f), sum = pv.sum(norm); |
| 1186 | for (var i = 0; i < norm.length; i++) norm[i] /= sum; |
| 1187 | return norm; |
| 1188 | }; |
| 1189 | |
| 1190 | /** |
| 1191 | * Returns a permutation of the specified array, using the specified array of |
| 1192 | * indexes. The returned array contains the corresponding element in |
| 1193 | * <tt>array</tt> for each index in <tt>indexes</tt>, in order. For example, |
| 1194 | * |
| 1195 | * <pre>pv.permute(["a", "b", "c"], [1, 2, 0])</pre> |
| 1196 | * |
| 1197 | * returns <tt>["b", "c", "a"]</tt>. It is acceptable for the array of indexes |
| 1198 | * to be a different length from the array of elements, and for indexes to be |
| 1199 | * duplicated or omitted. The optional accessor function <tt>f</tt> can be used |
| 1200 | * to perform a simultaneous mapping of the array elements. Accessor functions |
| 1201 | * can refer to <tt>this.index</tt>. |
| 1202 | * |
| 1203 | * @param {array} array an array. |
| 1204 | * @param {number[]} indexes an array of indexes into <tt>array</tt>. |
| 1205 | * @param {function} [f] an optional accessor function. |
| 1206 | * @returns {array} an array of elements from <tt>array</tt>; a permutation. |
| 1207 | */ |
| 1208 | pv.permute = function(array, indexes, f) { |
| 1209 | if (!f) f = pv.identity; |
| 1210 | var p = new Array(indexes.length), o = {}; |
| 1211 | indexes.forEach(function(j, i) { o.index = j; p[i] = f.call(o, array[j]); }); |
| 1212 | return p; |
| 1213 | }; |
| 1214 | |
| 1215 | /** |
| 1216 | * Returns a map from key to index for the specified <tt>keys</tt> array. For |
| 1217 | * example, |
| 1218 | * |
| 1219 | * <pre>pv.numerate(["a", "b", "c"])</pre> |
| 1220 | * |
| 1221 | * returns <tt>{a: 0, b: 1, c: 2}</tt>. Note that since JavaScript maps only |
| 1222 | * support string keys, <tt>keys</tt> must contain strings, or other values that |
| 1223 | * naturally map to distinct string values. Alternatively, an optional accessor |
| 1224 | * function <tt>f</tt> can be specified to compute the string key for the given |
| 1225 | * element. Accessor functions can refer to <tt>this.index</tt>. |
| 1226 | * |
| 1227 | * @param {array} keys an array, usually of string keys. |
| 1228 | * @param {function} [f] an optional key function. |
| 1229 | * @returns a map from key to index. |
| 1230 | */ |
| 1231 | pv.numerate = function(keys, f) { |
| 1232 | if (!f) f = pv.identity; |
| 1233 | var map = {}, o = {}; |
| 1234 | keys.forEach(function(x, i) { o.index = i; map[f.call(o, x)] = i; }); |
| 1235 | return map; |
| 1236 | }; |
| 1237 | |
| 1238 | /** |
| 1239 | * Returns the unique elements in the specified array, in the order they appear. |
| 1240 | * Note that since JavaScript maps only support string keys, <tt>array</tt> must |
| 1241 | * contain strings, or other values that naturally map to distinct string |
| 1242 | * values. Alternatively, an optional accessor function <tt>f</tt> can be |
| 1243 | * specified to compute the string key for the given element. Accessor functions |
| 1244 | * can refer to <tt>this.index</tt>. |
| 1245 | * |
| 1246 | * @param {array} array an array, usually of string keys. |
| 1247 | * @param {function} [f] an optional key function. |
| 1248 | * @returns {array} the unique values. |
| 1249 | */ |
| 1250 | pv.uniq = function(array, f) { |
| 1251 | if (!f) f = pv.identity; |
| 1252 | var map = {}, keys = [], o = {}, y; |
| 1253 | array.forEach(function(x, i) { |
| 1254 | o.index = i; |
| 1255 | y = f.call(o, x); |
| 1256 | if (!(y in map)) map[y] = keys.push(y); |
| 1257 | }); |
| 1258 | return keys; |
| 1259 | }; |
| 1260 | |
| 1261 | /** |
| 1262 | * The comparator function for natural order. This can be used in conjunction with |
| 1263 | * the built-in array <tt>sort</tt> method to sort elements by their natural |
| 1264 | * order, ascending. Note that if no comparator function is specified to the |
| 1265 | * built-in <tt>sort</tt> method, the default order is lexicographic, <i>not</i> |
| 1266 | * natural! |
| 1267 | * |
| 1268 | * @see <a |
| 1269 | * href="http://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/sort">Array.sort</a>. |
| 1270 | * @param a an element to compare. |
| 1271 | * @param b an element to compare. |
| 1272 | * @returns {number} negative if a < b; positive if a > b; otherwise 0. |
| 1273 | */ |
| 1274 | pv.naturalOrder = function(a, b) { |
| 1275 | return (a < b) ? -1 : ((a > b) ? 1 : 0); |
| 1276 | }; |
| 1277 | |
| 1278 | /** |
| 1279 | * The comparator function for reverse natural order. This can be used in |
| 1280 | * conjunction with the built-in array <tt>sort</tt> method to sort elements by |
| 1281 | * their natural order, descending. Note that if no comparator function is |
| 1282 | * specified to the built-in <tt>sort</tt> method, the default order is |
| 1283 | * lexicographic, <i>not</i> natural! |
| 1284 | * |
| 1285 | * @see #naturalOrder |
| 1286 | * @param a an element to compare. |
| 1287 | * @param b an element to compare. |
| 1288 | * @returns {number} negative if a < b; positive if a > b; otherwise 0. |
| 1289 | */ |
| 1290 | pv.reverseOrder = function(b, a) { |
| 1291 | return (a < b) ? -1 : ((a > b) ? 1 : 0); |
| 1292 | }; |
| 1293 | |
| 1294 | /** |
| 1295 | * Searches the specified array of numbers for the specified value using the |
| 1296 | * binary search algorithm. The array must be sorted (as by the <tt>sort</tt> |
| 1297 | * method) prior to making this call. If it is not sorted, the results are |
| 1298 | * undefined. If the array contains multiple elements with the specified value, |
| 1299 | * there is no guarantee which one will be found. |
| 1300 | * |
| 1301 | * <p>The <i>insertion point</i> is defined as the point at which the value |
| 1302 | * would be inserted into the array: the index of the first element greater than |
| 1303 | * the value, or <tt>array.length</tt>, if all elements in the array are less |
| 1304 | * than the specified value. Note that this guarantees that the return value |
| 1305 | * will be nonnegative if and only if the value is found. |
| 1306 | * |
| 1307 | * @param {number[]} array the array to be searched. |
| 1308 | * @param {number} value the value to be searched for. |
| 1309 | * @returns the index of the search value, if it is contained in the array; |
| 1310 | * otherwise, (-(<i>insertion point</i>) - 1). |
| 1311 | * @param {function} [f] an optional key function. |
| 1312 | */ |
| 1313 | pv.search = function(array, value, f) { |
| 1314 | if (!f) f = pv.identity; |
| 1315 | var low = 0, high = array.length - 1; |
| 1316 | while (low <= high) { |
| 1317 | var mid = (low + high) >> 1, midValue = f(array[mid]); |
| 1318 | if (midValue < value) low = mid + 1; |
| 1319 | else if (midValue > value) high = mid - 1; |
| 1320 | else return mid; |
| 1321 | } |
| 1322 | return -low - 1; |
| 1323 | }; |
| 1324 | |
| 1325 | pv.search.index = function(array, value, f) { |
| 1326 | var i = pv.search(array, value, f); |
| 1327 | return (i < 0) ? (-i - 1) : i; |
| 1328 | }; |
| 1329 | /** |
| 1330 | * Returns an array of numbers, starting at <tt>start</tt>, incrementing by |
| 1331 | * <tt>step</tt>, until <tt>stop</tt> is reached. The stop value is |
| 1332 | * exclusive. If only a single argument is specified, this value is interpeted |
| 1333 | * as the <i>stop</i> value, with the <i>start</i> value as zero. If only two |
| 1334 | * arguments are specified, the step value is implied to be one. |
| 1335 | * |
| 1336 | * <p>The method is modeled after the built-in <tt>range</tt> method from |
| 1337 | * Python. See the Python documentation for more details. |
| 1338 | * |
| 1339 | * @see <a href="http://docs.python.org/library/functions.html#range">Python range</a> |
| 1340 | * @param {number} [start] the start value. |
| 1341 | * @param {number} stop the stop value. |
| 1342 | * @param {number} [step] the step value. |
| 1343 | * @returns {number[]} an array of numbers. |
| 1344 | */ |
| 1345 | pv.range = function(start, stop, step) { |
| 1346 | if (arguments.length == 1) { |
| 1347 | stop = start; |
| 1348 | start = 0; |
| 1349 | } |
| 1350 | if (step == undefined) step = 1; |
| 1351 | if ((stop - start) / step == Infinity) throw new Error("range must be finite"); |
| 1352 | var array = [], i = 0, j; |
| 1353 | if (step < 0) { |
| 1354 | while ((j = start + step * i++) > stop) { |
| 1355 | array.push(j); |
| 1356 | } |
| 1357 | } else { |
| 1358 | while ((j = start + step * i++) < stop) { |
| 1359 | array.push(j); |
| 1360 | } |
| 1361 | } |
| 1362 | return array; |
| 1363 | }; |
| 1364 | |
| 1365 | /** |
| 1366 | * Returns a random number in the range [<tt>start</tt>, <tt>stop</tt>) that is |
| 1367 | * a multiple of <tt>step</tt>. More specifically, the returned number is of the |
| 1368 | * form <tt>start</tt> + <i>n</i> * <tt>step</tt>, where <i>n</i> is a |
| 1369 | * nonnegative integer. If <tt>step</tt> is not specified, it defaults to 1, |
| 1370 | * returning a random integer if <tt>start</tt> is also an integer. |
| 1371 | * |
| 1372 | * @param {number} [start] the start value value. |
| 1373 | * @param {number} stop the stop value. |
| 1374 | * @param {number} [step] the step value. |
| 1375 | * @returns {number} a random number between <i>start</i> and <i>stop</i>. |
| 1376 | */ |
| 1377 | pv.random = function(start, stop, step) { |
| 1378 | if (arguments.length == 1) { |
| 1379 | stop = start; |
| 1380 | start = 0; |
| 1381 | } |
| 1382 | if (step == undefined) step = 1; |
| 1383 | return step |
| 1384 | ? (Math.floor(Math.random() * (stop - start) / step) * step + start) |
| 1385 | : (Math.random() * (stop - start) + start); |
| 1386 | }; |
| 1387 | |
| 1388 | /** |
| 1389 | * Returns the sum of the specified array. If the specified array is not an |
| 1390 | * array of numbers, an optional accessor function <tt>f</tt> can be specified |
| 1391 | * to map the elements to numbers. See {@link #normalize} for an example. |
| 1392 | * Accessor functions can refer to <tt>this.index</tt>. |
| 1393 | * |
| 1394 | * @param {array} array an array of objects, or numbers. |
| 1395 | * @param {function} [f] an optional accessor function. |
| 1396 | * @returns {number} the sum of the specified array. |
| 1397 | */ |
| 1398 | pv.sum = function(array, f) { |
| 1399 | var o = {}; |
| 1400 | return array.reduce(f |
| 1401 | ? function(p, d, i) { o.index = i; return p + f.call(o, d); } |
| 1402 | : function(p, d) { return p + d; }, 0); |
| 1403 | }; |
| 1404 | |
| 1405 | /** |
| 1406 | * Returns the maximum value of the specified array. If the specified array is |
| 1407 | * not an array of numbers, an optional accessor function <tt>f</tt> can be |
| 1408 | * specified to map the elements to numbers. See {@link #normalize} for an |
| 1409 | * example. Accessor functions can refer to <tt>this.index</tt>. |
| 1410 | * |
| 1411 | * @param {array} array an array of objects, or numbers. |
| 1412 | * @param {function} [f] an optional accessor function. |
| 1413 | * @returns {number} the maximum value of the specified array. |
| 1414 | */ |
| 1415 | pv.max = function(array, f) { |
| 1416 | if (f == pv.index) return array.length - 1; |
| 1417 | return Math.max.apply(null, f ? pv.map(array, f) : array); |
| 1418 | }; |
| 1419 | |
| 1420 | /** |
| 1421 | * Returns the index of the maximum value of the specified array. If the |
| 1422 | * specified array is not an array of numbers, an optional accessor function |
| 1423 | * <tt>f</tt> can be specified to map the elements to numbers. See |
| 1424 | * {@link #normalize} for an example. Accessor functions can refer to |
| 1425 | * <tt>this.index</tt>. |
| 1426 | * |
| 1427 | * @param {array} array an array of objects, or numbers. |
| 1428 | * @param {function} [f] an optional accessor function. |
| 1429 | * @returns {number} the index of the maximum value of the specified array. |
| 1430 | */ |
| 1431 | pv.max.index = function(array, f) { |
| 1432 | if (!array.length) return -1; |
| 1433 | if (f == pv.index) return array.length - 1; |
| 1434 | if (!f) f = pv.identity; |
| 1435 | var maxi = 0, maxx = -Infinity, o = {}; |
| 1436 | for (var i = 0; i < array.length; i++) { |
| 1437 | o.index = i; |
| 1438 | var x = f.call(o, array[i]); |
| 1439 | if (x > maxx) { |
| 1440 | maxx = x; |
| 1441 | maxi = i; |
| 1442 | } |
| 1443 | } |
| 1444 | return maxi; |
| 1445 | } |
| 1446 | |
| 1447 | /** |
| 1448 | * Returns the minimum value of the specified array of numbers. If the specified |
| 1449 | * array is not an array of numbers, an optional accessor function <tt>f</tt> |
| 1450 | * can be specified to map the elements to numbers. See {@link #normalize} for |
| 1451 | * an example. Accessor functions can refer to <tt>this.index</tt>. |
| 1452 | * |
| 1453 | * @param {array} array an array of objects, or numbers. |
| 1454 | * @param {function} [f] an optional accessor function. |
| 1455 | * @returns {number} the minimum value of the specified array. |
| 1456 | */ |
| 1457 | pv.min = function(array, f) { |
| 1458 | if (f == pv.index) return 0; |
| 1459 | return Math.min.apply(null, f ? pv.map(array, f) : array); |
| 1460 | }; |
| 1461 | |
| 1462 | /** |
| 1463 | * Returns the index of the minimum value of the specified array. If the |
| 1464 | * specified array is not an array of numbers, an optional accessor function |
| 1465 | * <tt>f</tt> can be specified to map the elements to numbers. See |
| 1466 | * {@link #normalize} for an example. Accessor functions can refer to |
| 1467 | * <tt>this.index</tt>. |
| 1468 | * |
| 1469 | * @param {array} array an array of objects, or numbers. |
| 1470 | * @param {function} [f] an optional accessor function. |
| 1471 | * @returns {number} the index of the minimum value of the specified array. |
| 1472 | */ |
| 1473 | pv.min.index = function(array, f) { |
| 1474 | if (!array.length) return -1; |
| 1475 | if (f == pv.index) return 0; |
| 1476 | if (!f) f = pv.identity; |
| 1477 | var mini = 0, minx = Infinity, o = {}; |
| 1478 | for (var i = 0; i < array.length; i++) { |
| 1479 | o.index = i; |
| 1480 | var x = f.call(o, array[i]); |
| 1481 | if (x < minx) { |
| 1482 | minx = x; |
| 1483 | mini = i; |
| 1484 | } |
| 1485 | } |
| 1486 | return mini; |
| 1487 | } |
| 1488 | |
| 1489 | /** |
| 1490 | * Returns the arithmetic mean, or average, of the specified array. If the |
| 1491 | * specified array is not an array of numbers, an optional accessor function |
| 1492 | * <tt>f</tt> can be specified to map the elements to numbers. See |
| 1493 | * {@link #normalize} for an example. Accessor functions can refer to |
| 1494 | * <tt>this.index</tt>. |
| 1495 | * |
| 1496 | * @param {array} array an array of objects, or numbers. |
| 1497 | * @param {function} [f] an optional accessor function. |
| 1498 | * @returns {number} the mean of the specified array. |
| 1499 | */ |
| 1500 | pv.mean = function(array, f) { |
| 1501 | return pv.sum(array, f) / array.length; |
| 1502 | }; |
| 1503 | |
| 1504 | /** |
| 1505 | * Returns the median of the specified array. If the specified array is not an |
| 1506 | * array of numbers, an optional accessor function <tt>f</tt> can be specified |
| 1507 | * to map the elements to numbers. See {@link #normalize} for an example. |
| 1508 | * Accessor functions can refer to <tt>this.index</tt>. |
| 1509 | * |
| 1510 | * @param {array} array an array of objects, or numbers. |
| 1511 | * @param {function} [f] an optional accessor function. |
| 1512 | * @returns {number} the median of the specified array. |
| 1513 | */ |
| 1514 | pv.median = function(array, f) { |
| 1515 | if (f == pv.index) return (array.length - 1) / 2; |
| 1516 | array = pv.map(array, f).sort(pv.naturalOrder); |
| 1517 | if (array.length % 2) return array[Math.floor(array.length / 2)]; |
| 1518 | var i = array.length / 2; |
| 1519 | return (array[i - 1] + array[i]) / 2; |
| 1520 | }; |
| 1521 | |
| 1522 | /** |
| 1523 | * Returns the unweighted variance of the specified array. If the specified |
| 1524 | * array is not an array of numbers, an optional accessor function <tt>f</tt> |
| 1525 | * can be specified to map the elements to numbers. See {@link #normalize} for |
| 1526 | * an example. Accessor functions can refer to <tt>this.index</tt>. |
| 1527 | * |
| 1528 | * @param {array} array an array of objects, or numbers. |
| 1529 | * @param {function} [f] an optional accessor function. |
| 1530 | * @returns {number} the variance of the specified array. |
| 1531 | */ |
| 1532 | pv.variance = function(array, f) { |
| 1533 | if (array.length < 1) return NaN; |
| 1534 | if (array.length == 1) return 0; |
| 1535 | var mean = pv.mean(array, f), sum = 0, o = {}; |
| 1536 | if (!f) f = pv.identity; |
| 1537 | for (var i = 0; i < array.length; i++) { |
| 1538 | o.index = i; |
| 1539 | var d = f.call(o, array[i]) - mean; |
| 1540 | sum += d * d; |
| 1541 | } |
| 1542 | return sum; |
| 1543 | }; |
| 1544 | |
| 1545 | /** |
| 1546 | * Returns an unbiased estimation of the standard deviation of a population, |
| 1547 | * given the specified random sample. If the specified array is not an array of |
| 1548 | * numbers, an optional accessor function <tt>f</tt> can be specified to map the |
| 1549 | * elements to numbers. See {@link #normalize} for an example. Accessor |
| 1550 | * functions can refer to <tt>this.index</tt>. |
| 1551 | * |
| 1552 | * @param {array} array an array of objects, or numbers. |
| 1553 | * @param {function} [f] an optional accessor function. |
| 1554 | * @returns {number} the standard deviation of the specified array. |
| 1555 | */ |
| 1556 | pv.deviation = function(array, f) { |
| 1557 | return Math.sqrt(pv.variance(array, f) / (array.length - 1)); |
| 1558 | }; |
| 1559 | |
| 1560 | /** |
| 1561 | * Returns the logarithm with a given base value. |
| 1562 | * |
| 1563 | * @param {number} x the number for which to compute the logarithm. |
| 1564 | * @param {number} b the base of the logarithm. |
| 1565 | * @returns {number} the logarithm value. |
| 1566 | */ |
| 1567 | pv.log = function(x, b) { |
| 1568 | return Math.log(x) / Math.log(b); |
| 1569 | }; |
| 1570 | |
| 1571 | /** |
| 1572 | * Computes a zero-symmetric logarithm. Computes the logarithm of the absolute |
| 1573 | * value of the input, and determines the sign of the output according to the |
| 1574 | * sign of the input value. |
| 1575 | * |
| 1576 | * @param {number} x the number for which to compute the logarithm. |
| 1577 | * @param {number} b the base of the logarithm. |
| 1578 | * @returns {number} the symmetric log value. |
| 1579 | */ |
| 1580 | pv.logSymmetric = function(x, b) { |
| 1581 | return (x == 0) ? 0 : ((x < 0) ? -pv.log(-x, b) : pv.log(x, b)); |
| 1582 | }; |
| 1583 | |
| 1584 | /** |
| 1585 | * Computes a zero-symmetric logarithm, with adjustment to values between zero |
| 1586 | * and the logarithm base. This adjustment introduces distortion for values less |
| 1587 | * than the base number, but enables simultaneous plotting of log-transformed |
| 1588 | * data involving both positive and negative numbers. |
| 1589 | * |
| 1590 | * @param {number} x the number for which to compute the logarithm. |
| 1591 | * @param {number} b the base of the logarithm. |
| 1592 | * @returns {number} the adjusted, symmetric log value. |
| 1593 | */ |
| 1594 | pv.logAdjusted = function(x, b) { |
| 1595 | if (!isFinite(x)) return x; |
| 1596 | var negative = x < 0; |
| 1597 | if (x < b) x += (b - x) / b; |
| 1598 | return negative ? -pv.log(x, b) : pv.log(x, b); |
| 1599 | }; |
| 1600 | |
| 1601 | /** |
| 1602 | * Rounds an input value down according to its logarithm. The method takes the |
| 1603 | * floor of the logarithm of the value and then uses the resulting value as an |
| 1604 | * exponent for the base value. |
| 1605 | * |
| 1606 | * @param {number} x the number for which to compute the logarithm floor. |
| 1607 | * @param {number} b the base of the logarithm. |
| 1608 | * @returns {number} the rounded-by-logarithm value. |
| 1609 | */ |
| 1610 | pv.logFloor = function(x, b) { |
| 1611 | return (x > 0) |
| 1612 | ? Math.pow(b, Math.floor(pv.log(x, b))) |
| 1613 | : -Math.pow(b, -Math.floor(-pv.log(-x, b))); |
| 1614 | }; |
| 1615 | |
| 1616 | /** |
| 1617 | * Rounds an input value up according to its logarithm. The method takes the |
| 1618 | * ceiling of the logarithm of the value and then uses the resulting value as an |
| 1619 | * exponent for the base value. |
| 1620 | * |
| 1621 | * @param {number} x the number for which to compute the logarithm ceiling. |
| 1622 | * @param {number} b the base of the logarithm. |
| 1623 | * @returns {number} the rounded-by-logarithm value. |
| 1624 | */ |
| 1625 | pv.logCeil = function(x, b) { |
| 1626 | return (x > 0) |
| 1627 | ? Math.pow(b, Math.ceil(pv.log(x, b))) |
| 1628 | : -Math.pow(b, -Math.ceil(-pv.log(-x, b))); |
| 1629 | }; |
| 1630 | |
| 1631 | (function() { |
| 1632 | var radians = Math.PI / 180, |
| 1633 | degrees = 180 / Math.PI; |
| 1634 | |
| 1635 | /** Returns the number of radians corresponding to the specified degrees. */ |
| 1636 | pv.radians = function(degrees) { return radians * degrees; }; |
| 1637 | |
| 1638 | /** Returns the number of degrees corresponding to the specified radians. */ |
| 1639 | pv.degrees = function(radians) { return degrees * radians; }; |
| 1640 | })(); |
| 1641 | /** |
| 1642 | * Returns all of the property names (keys) of the specified object (a map). The |
| 1643 | * order of the returned array is not defined. |
| 1644 | * |
| 1645 | * @param map an object. |
| 1646 | * @returns {string[]} an array of strings corresponding to the keys. |
| 1647 | * @see #entries |
| 1648 | */ |
| 1649 | pv.keys = function(map) { |
| 1650 | var array = []; |
| 1651 | for (var key in map) { |
| 1652 | array.push(key); |
| 1653 | } |
| 1654 | return array; |
| 1655 | }; |
| 1656 | |
| 1657 | /** |
| 1658 | * Returns all of the entries (key-value pairs) of the specified object (a |
| 1659 | * map). The order of the returned array is not defined. Each key-value pair is |
| 1660 | * represented as an object with <tt>key</tt> and <tt>value</tt> attributes, |
| 1661 | * e.g., <tt>{key: "foo", value: 42}</tt>. |
| 1662 | * |
| 1663 | * @param map an object. |
| 1664 | * @returns {array} an array of key-value pairs corresponding to the keys. |
| 1665 | */ |
| 1666 | pv.entries = function(map) { |
| 1667 | var array = []; |
| 1668 | for (var key in map) { |
| 1669 | array.push({ key: key, value: map[key] }); |
| 1670 | } |
| 1671 | return array; |
| 1672 | }; |
| 1673 | |
| 1674 | /** |
| 1675 | * Returns all of the values (attribute values) of the specified object (a |
| 1676 | * map). The order of the returned array is not defined. |
| 1677 | * |
| 1678 | * @param map an object. |
| 1679 | * @returns {array} an array of objects corresponding to the values. |
| 1680 | * @see #entries |
| 1681 | */ |
| 1682 | pv.values = function(map) { |
| 1683 | var array = []; |
| 1684 | for (var key in map) { |
| 1685 | array.push(map[key]); |
| 1686 | } |
| 1687 | return array; |
| 1688 | }; |
| 1689 | |
| 1690 | /** |
| 1691 | * Returns a map constructed from the specified <tt>keys</tt>, using the |
| 1692 | * function <tt>f</tt> to compute the value for each key. The single argument to |
| 1693 | * the value function is the key. The callback is invoked only for indexes of |
| 1694 | * the array which have assigned values; it is not invoked for indexes which |
| 1695 | * have been deleted or which have never been assigned values. |
| 1696 | * |
| 1697 | * <p>For example, this expression creates a map from strings to string length: |
| 1698 | * |
| 1699 | * <pre>pv.dict(["one", "three", "seventeen"], function(s) s.length)</pre> |
| 1700 | * |
| 1701 | * The returned value is <tt>{one: 3, three: 5, seventeen: 9}</tt>. Accessor |
| 1702 | * functions can refer to <tt>this.index</tt>. |
| 1703 | * |
| 1704 | * @param {array} keys an array. |
| 1705 | * @param {function} f a value function. |
| 1706 | * @returns a map from keys to values. |
| 1707 | */ |
| 1708 | pv.dict = function(keys, f) { |
| 1709 | var m = {}, o = {}; |
| 1710 | for (var i = 0; i < keys.length; i++) { |
| 1711 | if (i in keys) { |
| 1712 | var k = keys[i]; |
| 1713 | o.index = i; |
| 1714 | m[k] = f.call(o, k); |
| 1715 | } |
| 1716 | } |
| 1717 | return m; |
| 1718 | }; |
| 1719 | /** |
| 1720 | * Returns a {@link pv.Dom} operator for the given map. This is a convenience |
| 1721 | * factory method, equivalent to <tt>new pv.Dom(map)</tt>. To apply the operator |
| 1722 | * and retrieve the root node, call {@link pv.Dom#root}; to retrieve all nodes |
| 1723 | * flattened, use {@link pv.Dom#nodes}. |
| 1724 | * |
| 1725 | * @see pv.Dom |
| 1726 | * @param map a map from which to construct a DOM. |
| 1727 | * @returns {pv.Dom} a DOM operator for the specified map. |
| 1728 | */ |
| 1729 | pv.dom = function(map) { |
| 1730 | return new pv.Dom(map); |
| 1731 | }; |
| 1732 | |
| 1733 | /** |
| 1734 | * Constructs a DOM operator for the specified map. This constructor should not |
| 1735 | * be invoked directly; use {@link pv.dom} instead. |
| 1736 | * |
| 1737 | * @class Represets a DOM operator for the specified map. This allows easy |
| 1738 | * transformation of a hierarchical JavaScript object (such as a JSON map) to a |
| 1739 | * W3C Document Object Model hierarchy. For more information on which attributes |
| 1740 | * and methods from the specification are supported, see {@link pv.Dom.Node}. |
| 1741 | * |
| 1742 | * <p>Leaves in the map are determined using an associated <i>leaf</i> function; |
| 1743 | * see {@link #leaf}. By default, leaves are any value whose type is not |
| 1744 | * "object", such as numbers or strings. |
| 1745 | * |
| 1746 | * @param map a map from which to construct a DOM. |
| 1747 | */ |
| 1748 | pv.Dom = function(map) { |
| 1749 | this.$map = map; |
| 1750 | }; |
| 1751 | |
| 1752 | /** @private The default leaf function. */ |
| 1753 | pv.Dom.prototype.$leaf = function(n) { |
| 1754 | return typeof n != "object"; |
| 1755 | }; |
| 1756 | |
| 1757 | /** |
| 1758 | * Sets or gets the leaf function for this DOM operator. The leaf function |
| 1759 | * identifies which values in the map are leaves, and which are internal nodes. |
| 1760 | * By default, objects are considered internal nodes, and primitives (such as |
| 1761 | * numbers and strings) are considered leaves. |
| 1762 | * |
| 1763 | * @param {function} f the new leaf function. |
| 1764 | * @returns the current leaf function, or <tt>this</tt>. |
| 1765 | */ |
| 1766 | pv.Dom.prototype.leaf = function(f) { |
| 1767 | if (arguments.length) { |
| 1768 | this.$leaf = f; |
| 1769 | return this; |
| 1770 | } |
| 1771 | return this.$leaf; |
| 1772 | }; |
| 1773 | |
| 1774 | /** |
| 1775 | * Applies the DOM operator, returning the root node. |
| 1776 | * |
| 1777 | * @returns {pv.Dom.Node} the root node. |
| 1778 | * @param {string} [nodeName] optional node name for the root. |
| 1779 | */ |
| 1780 | pv.Dom.prototype.root = function(nodeName) { |
| 1781 | var leaf = this.$leaf, root = recurse(this.$map); |
| 1782 | |
| 1783 | /** @private */ |
| 1784 | function recurse(map) { |
| 1785 | var n = new pv.Dom.Node(); |
| 1786 | for (var k in map) { |
| 1787 | var v = map[k]; |
| 1788 | n.appendChild(leaf(v) ? new pv.Dom.Node(v) : recurse(v)).nodeName = k; |
| 1789 | } |
| 1790 | return n; |
| 1791 | } |
| 1792 | |
| 1793 | root.nodeName = nodeName; |
| 1794 | return root; |
| 1795 | }; |
| 1796 | |
| 1797 | /** |
| 1798 | * Applies the DOM operator, returning the array of all nodes in preorder |
| 1799 | * traversal. |
| 1800 | * |
| 1801 | * @returns {array} the array of nodes in preorder traversal. |
| 1802 | */ |
| 1803 | pv.Dom.prototype.nodes = function() { |
| 1804 | return this.root().nodes(); |
| 1805 | }; |
| 1806 | |
| 1807 | /** |
| 1808 | * Constructs a DOM node for the specified value. Instances of this class are |
| 1809 | * not typically created directly; instead they are generated from a JavaScript |
| 1810 | * map using the {@link pv.Dom} operator. |
| 1811 | * |
| 1812 | * @class Represents a <tt>Node</tt> in the W3C Document Object Model. |
| 1813 | */ |
| 1814 | pv.Dom.Node = function(value) { |
| 1815 | this.nodeValue = value; |
| 1816 | this.childNodes = []; |
| 1817 | }; |
| 1818 | |
| 1819 | /** |
| 1820 | * The node name. When generated from a map, the node name corresponds to the |
| 1821 | * key at the given level in the map. Note that the root node has no associated |
| 1822 | * key, and thus has an undefined node name (and no <tt>parentNode</tt>). |
| 1823 | * |
| 1824 | * @type string |
| 1825 | * @field pv.Dom.Node.prototype.nodeName |
| 1826 | */ |
| 1827 | |
| 1828 | /** |
| 1829 | * The node value. When generated from a map, node value corresponds to the leaf |
| 1830 | * value for leaf nodes, and is undefined for internal nodes. |
| 1831 | * |
| 1832 | * @field pv.Dom.Node.prototype.nodeValue |
| 1833 | */ |
| 1834 | |
| 1835 | /** |
| 1836 | * The array of child nodes. This array is empty for leaf nodes. An easy way to |
| 1837 | * check if child nodes exist is to query <tt>firstChild</tt>. |
| 1838 | * |
| 1839 | * @type array |
| 1840 | * @field pv.Dom.Node.prototype.childNodes |
| 1841 | */ |
| 1842 | |
| 1843 | /** |
| 1844 | * The parent node, which is null for root nodes. |
| 1845 | * |
| 1846 | * @type pv.Dom.Node |
| 1847 | */ |
| 1848 | pv.Dom.Node.prototype.parentNode = null; |
| 1849 | |
| 1850 | /** |
| 1851 | * The first child, which is null for leaf nodes. |
| 1852 | * |
| 1853 | * @type pv.Dom.Node |
| 1854 | */ |
| 1855 | pv.Dom.Node.prototype.firstChild = null; |
| 1856 | |
| 1857 | /** |
| 1858 | * The last child, which is null for leaf nodes. |
| 1859 | * |
| 1860 | * @type pv.Dom.Node |
| 1861 | */ |
| 1862 | pv.Dom.Node.prototype.lastChild = null; |
| 1863 | |
| 1864 | /** |
| 1865 | * The previous sibling node, which is null for the first child. |
| 1866 | * |
| 1867 | * @type pv.Dom.Node |
| 1868 | */ |
| 1869 | pv.Dom.Node.prototype.previousSibling = null; |
| 1870 | |
| 1871 | /** |
| 1872 | * The next sibling node, which is null for the last child. |
| 1873 | * |
| 1874 | * @type pv.Dom.Node |
| 1875 | */ |
| 1876 | pv.Dom.Node.prototype.nextSibling = null; |
| 1877 | |
| 1878 | /** |
| 1879 | * Removes the specified child node from this node. |
| 1880 | * |
| 1881 | * @throws Error if the specified child is not a child of this node. |
| 1882 | * @returns {pv.Dom.Node} the removed child. |
| 1883 | */ |
| 1884 | pv.Dom.Node.prototype.removeChild = function(n) { |
| 1885 | var i = this.childNodes.indexOf(n); |
| 1886 | if (i == -1) throw new Error("child not found"); |
| 1887 | this.childNodes.splice(i, 1); |
| 1888 | if (n.previousSibling) n.previousSibling.nextSibling = n.nextSibling; |
| 1889 | else this.firstChild = n.nextSibling; |
| 1890 | if (n.nextSibling) n.nextSibling.previousSibling = n.previousSibling; |
| 1891 | else this.lastChild = n.previousSibling; |
| 1892 | delete n.nextSibling; |
| 1893 | delete n.previousSibling; |
| 1894 | delete n.parentNode; |
| 1895 | return n; |
| 1896 | }; |
| 1897 | |
| 1898 | /** |
| 1899 | * Appends the specified child node to this node. If the specified child is |
| 1900 | * already part of the DOM, the child is first removed before being added to |
| 1901 | * this node. |
| 1902 | * |
| 1903 | * @returns {pv.Dom.Node} the appended child. |
| 1904 | */ |
| 1905 | pv.Dom.Node.prototype.appendChild = function(n) { |
| 1906 | if (n.parentNode) n.parentNode.removeChild(n); |
| 1907 | n.parentNode = this; |
| 1908 | n.previousSibling = this.lastChild; |
| 1909 | if (this.lastChild) this.lastChild.nextSibling = n; |
| 1910 | else this.firstChild = n; |
| 1911 | this.lastChild = n; |
| 1912 | this.childNodes.push(n); |
| 1913 | return n; |
| 1914 | }; |
| 1915 | |
| 1916 | /** |
| 1917 | * Inserts the specified child <i>n</i> before the given reference child |
| 1918 | * <i>r</i> of this node. If <i>r</i> is null, this method is equivalent to |
| 1919 | * {@link #appendChild}. If <i>n</i> is already part of the DOM, it is first |
| 1920 | * removed before being inserted. |
| 1921 | * |
| 1922 | * @throws Error if <i>r</i> is non-null and not a child of this node. |
| 1923 | * @returns {pv.Dom.Node} the inserted child. |
| 1924 | */ |
| 1925 | pv.Dom.Node.prototype.insertBefore = function(n, r) { |
| 1926 | if (!r) return this.appendChild(n); |
| 1927 | var i = this.childNodes.indexOf(r); |
| 1928 | if (i == -1) throw new Error("child not found"); |
| 1929 | if (n.parentNode) n.parentNode.removeChild(n); |
| 1930 | n.parentNode = this; |
| 1931 | n.nextSibling = r; |
| 1932 | n.previousSibling = r.previousSibling; |
| 1933 | if (r.previousSibling) { |
| 1934 | r.previousSibling.nextSibling = n; |
| 1935 | } else { |
| 1936 | if (r == this.lastChild) this.lastChild = n; |
| 1937 | this.firstChild = n; |
| 1938 | } |
| 1939 | this.childNodes.splice(i, 0, n); |
| 1940 | return n; |
| 1941 | }; |
| 1942 | |
| 1943 | /** |
| 1944 | * Replaces the specified child <i>r</i> of this node with the node <i>n</i>. If |
| 1945 | * <i>n</i> is already part of the DOM, it is first removed before being added. |
| 1946 | * |
| 1947 | * @throws Error if <i>r</i> is not a child of this node. |
| 1948 | */ |
| 1949 | pv.Dom.Node.prototype.replaceChild = function(n, r) { |
| 1950 | var i = this.childNodes.indexOf(r); |
| 1951 | if (i == -1) throw new Error("child not found"); |
| 1952 | if (n.parentNode) n.parentNode.removeChild(n); |
| 1953 | n.parentNode = this; |
| 1954 | n.nextSibling = r.nextSibling; |
| 1955 | n.previousSibling = r.previousSibling; |
| 1956 | if (r.previousSibling) r.previousSibling.nextSibling = n; |
| 1957 | else this.firstChild = n; |
| 1958 | if (r.nextSibling) r.nextSibling.previousSibling = n; |
| 1959 | else this.lastChild = n; |
| 1960 | this.childNodes[i] = n; |
| 1961 | return r; |
| 1962 | }; |
| 1963 | |
| 1964 | /** |
| 1965 | * Visits each node in the tree in preorder traversal, applying the specified |
| 1966 | * function <i>f</i>. The arguments to the function are:<ol> |
| 1967 | * |
| 1968 | * <li>The current node. |
| 1969 | * <li>The current depth, starting at 0 for the root node.</ol> |
| 1970 | * |
| 1971 | * @param {function} f a function to apply to each node. |
| 1972 | */ |
| 1973 | pv.Dom.Node.prototype.visitBefore = function(f) { |
| 1974 | function visit(n, i) { |
| 1975 | f(n, i); |
| 1976 | for (var c = n.firstChild; c; c = c.nextSibling) { |
| 1977 | visit(c, i + 1); |
| 1978 | } |
| 1979 | } |
| 1980 | visit(this, 0); |
| 1981 | }; |
| 1982 | |
| 1983 | /** |
| 1984 | * Visits each node in the tree in postorder traversal, applying the specified |
| 1985 | * function <i>f</i>. The arguments to the function are:<ol> |
| 1986 | * |
| 1987 | * <li>The current node. |
| 1988 | * <li>The current depth, starting at 0 for the root node.</ol> |
| 1989 | * |
| 1990 | * @param {function} f a function to apply to each node. |
| 1991 | */ |
| 1992 | pv.Dom.Node.prototype.visitAfter = function(f) { |
| 1993 | function visit(n, i) { |
| 1994 | for (var c = n.firstChild; c; c = c.nextSibling) { |
| 1995 | visit(c, i + 1); |
| 1996 | } |
| 1997 | f(n, i); |
| 1998 | } |
| 1999 | visit(this, 0); |
| 2000 | }; |
| 2001 | |
| 2002 | /** |
| 2003 | * Sorts child nodes of this node, and all descendent nodes recursively, using |
| 2004 | * the specified comparator function <tt>f</tt>. The comparator function is |
| 2005 | * passed two nodes to compare. |
| 2006 | * |
| 2007 | * <p>Note: during the sort operation, the comparator function should not rely |
| 2008 | * on the tree being well-formed; the values of <tt>previousSibling</tt> and |
| 2009 | * <tt>nextSibling</tt> for the nodes being compared are not defined during the |
| 2010 | * sort operation. |
| 2011 | * |
| 2012 | * @param {function} f a comparator function. |
| 2013 | * @returns this. |
| 2014 | */ |
| 2015 | pv.Dom.Node.prototype.sort = function(f) { |
| 2016 | if (this.firstChild) { |
| 2017 | this.childNodes.sort(f); |
| 2018 | var p = this.firstChild = this.childNodes[0], c; |
| 2019 | delete p.previousSibling; |
| 2020 | for (var i = 1; i < this.childNodes.length; i++) { |
| 2021 | p.sort(f); |
| 2022 | c = this.childNodes[i]; |
| 2023 | c.previousSibling = p; |
| 2024 | p = p.nextSibling = c; |
| 2025 | } |
| 2026 | this.lastChild = p; |
| 2027 | delete p.nextSibling; |
| 2028 | p.sort(f); |
| 2029 | } |
| 2030 | return this; |
| 2031 | }; |
| 2032 | |
| 2033 | /** |
| 2034 | * Reverses all sibling nodes. |
| 2035 | * |
| 2036 | * @returns this. |
| 2037 | */ |
| 2038 | pv.Dom.Node.prototype.reverse = function() { |
| 2039 | var childNodes = []; |
| 2040 | this.visitAfter(function(n) { |
| 2041 | while (n.lastChild) childNodes.push(n.removeChild(n.lastChild)); |
| 2042 | for (var c; c = childNodes.pop();) n.insertBefore(c, n.firstChild); |
| 2043 | }); |
| 2044 | return this; |
| 2045 | }; |
| 2046 | |
| 2047 | /** Returns all descendants of this node in preorder traversal. */ |
| 2048 | pv.Dom.Node.prototype.nodes = function() { |
| 2049 | var array = []; |
| 2050 | |
| 2051 | /** @private */ |
| 2052 | function flatten(node) { |
| 2053 | array.push(node); |
| 2054 | node.childNodes.forEach(flatten); |
| 2055 | } |
| 2056 | |
| 2057 | flatten(this, array); |
| 2058 | return array; |
| 2059 | }; |
| 2060 | |
| 2061 | /** |
| 2062 | * Toggles the child nodes of this node. If this node is not yet toggled, this |
| 2063 | * method removes all child nodes and appends them to a new <tt>toggled</tt> |
| 2064 | * array attribute on this node. Otherwise, if this node is toggled, this method |
| 2065 | * re-adds all toggled child nodes and deletes the <tt>toggled</tt> attribute. |
| 2066 | * |
| 2067 | * <p>This method has no effect if the node has no child nodes. |
| 2068 | * |
| 2069 | * @param {boolean} [recursive] whether the toggle should apply to descendants. |
| 2070 | */ |
| 2071 | pv.Dom.Node.prototype.toggle = function(recursive) { |
| 2072 | if (recursive) return this.toggled |
| 2073 | ? this.visitBefore(function(n) { if (n.toggled) n.toggle(); }) |
| 2074 | : this.visitAfter(function(n) { if (!n.toggled) n.toggle(); }); |
| 2075 | var n = this; |
| 2076 | if (n.toggled) { |
| 2077 | for (var c; c = n.toggled.pop();) n.appendChild(c); |
| 2078 | delete n.toggled; |
| 2079 | } else if (n.lastChild) { |
| 2080 | n.toggled = []; |
| 2081 | while (n.lastChild) n.toggled.push(n.removeChild(n.lastChild)); |
| 2082 | } |
| 2083 | }; |
| 2084 | |
| 2085 | /** |
| 2086 | * Given a flat array of values, returns a simple DOM with each value wrapped by |
| 2087 | * a node that is a child of the root node. |
| 2088 | * |
| 2089 | * @param {array} values. |
| 2090 | * @returns {array} nodes. |
| 2091 | */ |
| 2092 | pv.nodes = function(values) { |
| 2093 | var root = new pv.Dom.Node(); |
| 2094 | for (var i = 0; i < values.length; i++) { |
| 2095 | root.appendChild(new pv.Dom.Node(values[i])); |
| 2096 | } |
| 2097 | return root.nodes(); |
| 2098 | }; |
| 2099 | /** |
| 2100 | * Returns a {@link pv.Tree} operator for the specified array. This is a |
| 2101 | * convenience factory method, equivalent to <tt>new pv.Tree(array)</tt>. |
| 2102 | * |
| 2103 | * @see pv.Tree |
| 2104 | * @param {array} array an array from which to construct a tree. |
| 2105 | * @returns {pv.Tree} a tree operator for the specified array. |
| 2106 | */ |
| 2107 | pv.tree = function(array) { |
| 2108 | return new pv.Tree(array); |
| 2109 | }; |
| 2110 | |
| 2111 | /** |
| 2112 | * Constructs a tree operator for the specified array. This constructor should |
| 2113 | * not be invoked directly; use {@link pv.tree} instead. |
| 2114 | * |
| 2115 | * @class Represents a tree operator for the specified array. The tree operator |
| 2116 | * allows a hierarchical map to be constructed from an array; it is similar to |
| 2117 | * the {@link pv.Nest} operator, except the hierarchy is derived dynamically |
| 2118 | * from the array elements. |
| 2119 | * |
| 2120 | * <p>For example, given an array of size information for ActionScript classes: |
| 2121 | * |
| 2122 | * <pre>{ name: "flare.flex.FlareVis", size: 4116 }, |
| 2123 | * { name: "flare.physics.DragForce", size: 1082 }, |
| 2124 | * { name: "flare.physics.GravityForce", size: 1336 }, ...</pre> |
| 2125 | * |
| 2126 | * To facilitate visualization, it may be useful to nest the elements by their |
| 2127 | * package hierarchy: |
| 2128 | * |
| 2129 | * <pre>var tree = pv.tree(classes) |
| 2130 | * .keys(function(d) d.name.split(".")) |
| 2131 | * .map();</pre> |
| 2132 | * |
| 2133 | * The resulting tree is: |
| 2134 | * |
| 2135 | * <pre>{ flare: { |
| 2136 | * flex: { |
| 2137 | * FlareVis: { |
| 2138 | * name: "flare.flex.FlareVis", |
| 2139 | * size: 4116 } }, |
| 2140 | * physics: { |
| 2141 | * DragForce: { |
| 2142 | * name: "flare.physics.DragForce", |
| 2143 | * size: 1082 }, |
| 2144 | * GravityForce: { |
| 2145 | * name: "flare.physics.GravityForce", |
| 2146 | * size: 1336 } }, |
| 2147 | * ... } }</pre> |
| 2148 | * |
| 2149 | * By specifying a value function, |
| 2150 | * |
| 2151 | * <pre>var tree = pv.tree(classes) |
| 2152 | * .keys(function(d) d.name.split(".")) |
| 2153 | * .value(function(d) d.size) |
| 2154 | * .map();</pre> |
| 2155 | * |
| 2156 | * we can further eliminate redundant data: |
| 2157 | * |
| 2158 | * <pre>{ flare: { |
| 2159 | * flex: { |
| 2160 | * FlareVis: 4116 }, |
| 2161 | * physics: { |
| 2162 | * DragForce: 1082, |
| 2163 | * GravityForce: 1336 }, |
| 2164 | * ... } }</pre> |
| 2165 | * |
| 2166 | * For visualizations with large data sets, performance improvements may be seen |
| 2167 | * by storing the data in a tree format, and then flattening it into an array at |
| 2168 | * runtime with {@link pv.Flatten}. |
| 2169 | * |
| 2170 | * @param {array} array an array from which to construct a tree. |
| 2171 | */ |
| 2172 | pv.Tree = function(array) { |
| 2173 | this.array = array; |
| 2174 | }; |
| 2175 | |
| 2176 | /** |
| 2177 | * Assigns a <i>keys</i> function to this operator; required. The keys function |
| 2178 | * returns an array of <tt>string</tt>s for each element in the associated |
| 2179 | * array; these keys determine how the elements are nested in the tree. The |
| 2180 | * returned keys should be unique for each element in the array; otherwise, the |
| 2181 | * behavior of this operator is undefined. |
| 2182 | * |
| 2183 | * @param {function} k the keys function. |
| 2184 | * @returns {pv.Tree} this. |
| 2185 | */ |
| 2186 | pv.Tree.prototype.keys = function(k) { |
| 2187 | this.k = k; |
| 2188 | return this; |
| 2189 | }; |
| 2190 | |
| 2191 | /** |
| 2192 | * Assigns a <i>value</i> function to this operator; optional. The value |
| 2193 | * function specifies an optional transformation of the element in the array |
| 2194 | * before it is inserted into the map. If no value function is specified, it is |
| 2195 | * equivalent to using the identity function. |
| 2196 | * |
| 2197 | * @param {function} k the value function. |
| 2198 | * @returns {pv.Tree} this. |
| 2199 | */ |
| 2200 | pv.Tree.prototype.value = function(v) { |
| 2201 | this.v = v; |
| 2202 | return this; |
| 2203 | }; |
| 2204 | |
| 2205 | /** |
| 2206 | * Returns a hierarchical map of values. The hierarchy is determined by the keys |
| 2207 | * function; the values in the map are determined by the value function. |
| 2208 | * |
| 2209 | * @returns a hierarchical map of values. |
| 2210 | */ |
| 2211 | pv.Tree.prototype.map = function() { |
| 2212 | var map = {}, o = {}; |
| 2213 | for (var i = 0; i < this.array.length; i++) { |
| 2214 | o.index = i; |
| 2215 | var value = this.array[i], keys = this.k.call(o, value), node = map; |
| 2216 | for (var j = 0; j < keys.length - 1; j++) { |
| 2217 | node = node[keys[j]] || (node[keys[j]] = {}); |
| 2218 | } |
| 2219 | node[keys[j]] = this.v ? this.v.call(o, value) : value; |
| 2220 | } |
| 2221 | return map; |
| 2222 | }; |
| 2223 | /** |
| 2224 | * Returns a {@link pv.Nest} operator for the specified array. This is a |
| 2225 | * convenience factory method, equivalent to <tt>new pv.Nest(array)</tt>. |
| 2226 | * |
| 2227 | * @see pv.Nest |
| 2228 | * @param {array} array an array of elements to nest. |
| 2229 | * @returns {pv.Nest} a nest operator for the specified array. |
| 2230 | */ |
| 2231 | pv.nest = function(array) { |
| 2232 | return new pv.Nest(array); |
| 2233 | }; |
| 2234 | |
| 2235 | /** |
| 2236 | * Constructs a nest operator for the specified array. This constructor should |
| 2237 | * not be invoked directly; use {@link pv.nest} instead. |
| 2238 | * |
| 2239 | * @class Represents a {@link Nest} operator for the specified array. Nesting |
| 2240 | * allows elements in an array to be grouped into a hierarchical tree |
| 2241 | * structure. The levels in the tree are specified by <i>key</i> functions. The |
| 2242 | * leaf nodes of the tree can be sorted by value, while the internal nodes can |
| 2243 | * be sorted by key. Finally, the tree can be returned either has a |
| 2244 | * multidimensional array via {@link #entries}, or as a hierarchical map via |
| 2245 | * {@link #map}. The {@link #rollup} routine similarly returns a map, collapsing |
| 2246 | * the elements in each leaf node using a summary function. |
| 2247 | * |
| 2248 | * <p>For example, consider the following tabular data structure of Barley |
| 2249 | * yields, from various sites in Minnesota during 1931-2: |
| 2250 | * |
| 2251 | * <pre>{ yield: 27.00, variety: "Manchuria", year: 1931, site: "University Farm" }, |
| 2252 | * { yield: 48.87, variety: "Manchuria", year: 1931, site: "Waseca" }, |
| 2253 | * { yield: 27.43, variety: "Manchuria", year: 1931, site: "Morris" }, ...</pre> |
| 2254 | * |
| 2255 | * To facilitate visualization, it may be useful to nest the elements first by |
| 2256 | * year, and then by variety, as follows: |
| 2257 | * |
| 2258 | * <pre>var nest = pv.nest(yields) |
| 2259 | * .key(function(d) d.year) |
| 2260 | * .key(function(d) d.variety) |
| 2261 | * .entries();</pre> |
| 2262 | * |
| 2263 | * This returns a nested array. Each element of the outer array is a key-values |
| 2264 | * pair, listing the values for each distinct key: |
| 2265 | * |
| 2266 | * <pre>{ key: 1931, values: [ |
| 2267 | * { key: "Manchuria", values: [ |
| 2268 | * { yield: 27.00, variety: "Manchuria", year: 1931, site: "University Farm" }, |
| 2269 | * { yield: 48.87, variety: "Manchuria", year: 1931, site: "Waseca" }, |
| 2270 | * { yield: 27.43, variety: "Manchuria", year: 1931, site: "Morris" }, |
| 2271 | * ... |
| 2272 | * ] }, |
| 2273 | * { key: "Glabron", values: [ |
| 2274 | * { yield: 43.07, variety: "Glabron", year: 1931, site: "University Farm" }, |
| 2275 | * { yield: 55.20, variety: "Glabron", year: 1931, site: "Waseca" }, |
| 2276 | * ... |
| 2277 | * ] }, |
| 2278 | * ] }, |
| 2279 | * { key: 1932, values: ... }</pre> |
| 2280 | * |
| 2281 | * Further details, including sorting and rollup, is provided below on the |
| 2282 | * corresponding methods. |
| 2283 | * |
| 2284 | * @param {array} array an array of elements to nest. |
| 2285 | */ |
| 2286 | pv.Nest = function(array) { |
| 2287 | this.array = array; |
| 2288 | this.keys = []; |
| 2289 | }; |
| 2290 | |
| 2291 | /** |
| 2292 | * Nests using the specified key function. Multiple keys may be added to the |
| 2293 | * nest; the array elements will be nested in the order keys are specified. |
| 2294 | * |
| 2295 | * @param {function} key a key function; must return a string or suitable map |
| 2296 | * key. |
| 2297 | * @returns {pv.Nest} this. |
| 2298 | */ |
| 2299 | pv.Nest.prototype.key = function(key) { |
| 2300 | this.keys.push(key); |
| 2301 | return this; |
| 2302 | }; |
| 2303 | |
| 2304 | /** |
| 2305 | * Sorts the previously-added keys. The natural sort order is used by default |
| 2306 | * (see {@link pv.naturalOrder}); if an alternative order is desired, |
| 2307 | * <tt>order</tt> should be a comparator function. If this method is not called |
| 2308 | * (i.e., keys are <i>unsorted</i>), keys will appear in the order they appear |
| 2309 | * in the underlying elements array. For example, |
| 2310 | * |
| 2311 | * <pre>pv.nest(yields) |
| 2312 | * .key(function(d) d.year) |
| 2313 | * .key(function(d) d.variety) |
| 2314 | * .sortKeys() |
| 2315 | * .entries()</pre> |
| 2316 | * |
| 2317 | * groups yield data by year, then variety, and sorts the variety groups |
| 2318 | * lexicographically (since the variety attribute is a string). |
| 2319 | * |
| 2320 | * <p>Key sort order is only used in conjunction with {@link #entries}, which |
| 2321 | * returns an array of key-values pairs. If the nest is used to construct a |
| 2322 | * {@link #map} instead, keys are unsorted. |
| 2323 | * |
| 2324 | * @param {function} [order] an optional comparator function. |
| 2325 | * @returns {pv.Nest} this. |
| 2326 | */ |
| 2327 | pv.Nest.prototype.sortKeys = function(order) { |
| 2328 | this.keys[this.keys.length - 1].order = order || pv.naturalOrder; |
| 2329 | return this; |
| 2330 | }; |
| 2331 | |
| 2332 | /** |
| 2333 | * Sorts the leaf values. The natural sort order is used by default (see |
| 2334 | * {@link pv.naturalOrder}); if an alternative order is desired, <tt>order</tt> |
| 2335 | * should be a comparator function. If this method is not called (i.e., values |
| 2336 | * are <i>unsorted</i>), values will appear in the order they appear in the |
| 2337 | * underlying elements array. For example, |
| 2338 | * |
| 2339 | * <pre>pv.nest(yields) |
| 2340 | * .key(function(d) d.year) |
| 2341 | * .key(function(d) d.variety) |
| 2342 | * .sortValues(function(a, b) a.yield - b.yield) |
| 2343 | * .entries()</pre> |
| 2344 | * |
| 2345 | * groups yield data by year, then variety, and sorts the values for each |
| 2346 | * variety group by yield. |
| 2347 | * |
| 2348 | * <p>Value sort order, unlike keys, applies to both {@link #entries} and |
| 2349 | * {@link #map}. It has no effect on {@link #rollup}. |
| 2350 | * |
| 2351 | * @param {function} [order] an optional comparator function. |
| 2352 | * @returns {pv.Nest} this. |
| 2353 | */ |
| 2354 | pv.Nest.prototype.sortValues = function(order) { |
| 2355 | this.order = order || pv.naturalOrder; |
| 2356 | return this; |
| 2357 | }; |
| 2358 | |
| 2359 | /** |
| 2360 | * Returns a hierarchical map of values. Each key adds one level to the |
| 2361 | * hierarchy. With only a single key, the returned map will have a key for each |
| 2362 | * distinct value of the key function; the correspond value with be an array of |
| 2363 | * elements with that key value. If a second key is added, this will be a nested |
| 2364 | * map. For example: |
| 2365 | * |
| 2366 | * <pre>pv.nest(yields) |
| 2367 | * .key(function(d) d.variety) |
| 2368 | * .key(function(d) d.site) |
| 2369 | * .map()</pre> |
| 2370 | * |
| 2371 | * returns a map <tt>m</tt> such that <tt>m[variety][site]</tt> is an array, a subset of |
| 2372 | * <tt>yields</tt>, with each element having the given variety and site. |
| 2373 | * |
| 2374 | * @returns a hierarchical map of values. |
| 2375 | */ |
| 2376 | pv.Nest.prototype.map = function() { |
| 2377 | var map = {}, values = []; |
| 2378 | |
| 2379 | /* Build the map. */ |
| 2380 | for (var i, j = 0; j < this.array.length; j++) { |
| 2381 | var x = this.array[j]; |
| 2382 | var m = map; |
| 2383 | for (i = 0; i < this.keys.length - 1; i++) { |
| 2384 | var k = this.keys[i](x); |
| 2385 | if (!m[k]) m[k] = {}; |
| 2386 | m = m[k]; |
| 2387 | } |
| 2388 | k = this.keys[i](x); |
| 2389 | if (!m[k]) { |
| 2390 | var a = []; |
| 2391 | values.push(a); |
| 2392 | m[k] = a; |
| 2393 | } |
| 2394 | m[k].push(x); |
| 2395 | } |
| 2396 | |
| 2397 | /* Sort each leaf array. */ |
| 2398 | if (this.order) { |
| 2399 | for (var i = 0; i < values.length; i++) { |
| 2400 | values[i].sort(this.order); |
| 2401 | } |
| 2402 | } |
| 2403 | |
| 2404 | return map; |
| 2405 | }; |
| 2406 | |
| 2407 | /** |
| 2408 | * Returns a hierarchical nested array. This method is similar to |
| 2409 | * {@link pv.entries}, but works recursively on the entire hierarchy. Rather |
| 2410 | * than returning a map like {@link #map}, this method returns a nested |
| 2411 | * array. Each element of the array has a <tt>key</tt> and <tt>values</tt> |
| 2412 | * field. For leaf nodes, the <tt>values</tt> array will be a subset of the |
| 2413 | * underlying elements array; for non-leaf nodes, the <tt>values</tt> array will |
| 2414 | * contain more key-values pairs. |
| 2415 | * |
| 2416 | * <p>For an example usage, see the {@link Nest} constructor. |
| 2417 | * |
| 2418 | * @returns a hierarchical nested array. |
| 2419 | */ |
| 2420 | pv.Nest.prototype.entries = function() { |
| 2421 | |
| 2422 | /** Recursively extracts the entries for the given map. */ |
| 2423 | function entries(map) { |
| 2424 | var array = []; |
| 2425 | for (var k in map) { |
| 2426 | var v = map[k]; |
| 2427 | array.push({ key: k, values: (v instanceof Array) ? v : entries(v) }); |
| 2428 | }; |
| 2429 | return array; |
| 2430 | } |
| 2431 | |
| 2432 | /** Recursively sorts the values for the given key-values array. */ |
| 2433 | function sort(array, i) { |
| 2434 | var o = this.keys[i].order; |
| 2435 | if (o) array.sort(function(a, b) { return o(a.key, b.key); }); |
| 2436 | if (++i < this.keys.length) { |
| 2437 | for (var j = 0; j < array.length; j++) { |
| 2438 | sort.call(this, array[j].values, i); |
| 2439 | } |
| 2440 | } |
| 2441 | return array; |
| 2442 | } |
| 2443 | |
| 2444 | return sort.call(this, entries(this.map()), 0); |
| 2445 | }; |
| 2446 | |
| 2447 | /** |
| 2448 | * Returns a rollup map. The behavior of this method is the same as |
| 2449 | * {@link #map}, except that the leaf values are replaced with the return value |
| 2450 | * of the specified rollup function <tt>f</tt>. For example, |
| 2451 | * |
| 2452 | * <pre>pv.nest(yields) |
| 2453 | * .key(function(d) d.site) |
| 2454 | * .rollup(function(v) pv.median(v, function(d) d.yield))</pre> |
| 2455 | * |
| 2456 | * first groups yield data by site, and then returns a map from site to median |
| 2457 | * yield for the given site. |
| 2458 | * |
| 2459 | * @see #map |
| 2460 | * @param {function} f a rollup function. |
| 2461 | * @returns a hierarchical map, with the leaf values computed by <tt>f</tt>. |
| 2462 | */ |
| 2463 | pv.Nest.prototype.rollup = function(f) { |
| 2464 | |
| 2465 | /** Recursively descends to the leaf nodes (arrays) and does rollup. */ |
| 2466 | function rollup(map) { |
| 2467 | for (var key in map) { |
| 2468 | var value = map[key]; |
| 2469 | if (value instanceof Array) { |
| 2470 | map[key] = f(value); |
| 2471 | } else { |
| 2472 | rollup(value); |
| 2473 | } |
| 2474 | } |
| 2475 | return map; |
| 2476 | } |
| 2477 | |
| 2478 | return rollup(this.map()); |
| 2479 | }; |
| 2480 | /** |
| 2481 | * Returns a {@link pv.Flatten} operator for the specified map. This is a |
| 2482 | * convenience factory method, equivalent to <tt>new pv.Flatten(map)</tt>. |
| 2483 | * |
| 2484 | * @see pv.Flatten |
| 2485 | * @param map a map to flatten. |
| 2486 | * @returns {pv.Flatten} a flatten operator for the specified map. |
| 2487 | */ |
| 2488 | pv.flatten = function(map) { |
| 2489 | return new pv.Flatten(map); |
| 2490 | }; |
| 2491 | |
| 2492 | /** |
| 2493 | * Constructs a flatten operator for the specified map. This constructor should |
| 2494 | * not be invoked directly; use {@link pv.flatten} instead. |
| 2495 | * |
| 2496 | * @class Represents a flatten operator for the specified array. Flattening |
| 2497 | * allows hierarchical maps to be flattened into an array. The levels in the |
| 2498 | * input tree are specified by <i>key</i> functions. |
| 2499 | * |
| 2500 | * <p>For example, consider the following hierarchical data structure of Barley |
| 2501 | * yields, from various sites in Minnesota during 1931-2: |
| 2502 | * |
| 2503 | * <pre>{ 1931: { |
| 2504 | * Manchuria: { |
| 2505 | * "University Farm": 27.00, |
| 2506 | * "Waseca": 48.87, |
| 2507 | * "Morris": 27.43, |
| 2508 | * ... }, |
| 2509 | * Glabron: { |
| 2510 | * "University Farm": 43.07, |
| 2511 | * "Waseca": 55.20, |
| 2512 | * ... } }, |
| 2513 | * 1932: { |
| 2514 | * ... } }</pre> |
| 2515 | * |
| 2516 | * To facilitate visualization, it may be useful to flatten the tree into a |
| 2517 | * tabular array: |
| 2518 | * |
| 2519 | * <pre>var array = pv.flatten(yields) |
| 2520 | * .key("year") |
| 2521 | * .key("variety") |
| 2522 | * .key("site") |
| 2523 | * .key("yield") |
| 2524 | * .array();</pre> |
| 2525 | * |
| 2526 | * This returns an array of object elements. Each element in the array has |
| 2527 | * attributes corresponding to this flatten operator's keys: |
| 2528 | * |
| 2529 | * <pre>{ site: "University Farm", variety: "Manchuria", year: 1931, yield: 27 }, |
| 2530 | * { site: "Waseca", variety: "Manchuria", year: 1931, yield: 48.87 }, |
| 2531 | * { site: "Morris", variety: "Manchuria", year: 1931, yield: 27.43 }, |
| 2532 | * { site: "University Farm", variety: "Glabron", year: 1931, yield: 43.07 }, |
| 2533 | * { site: "Waseca", variety: "Glabron", year: 1931, yield: 55.2 }, ...</pre> |
| 2534 | * |
| 2535 | * <p>The flatten operator is roughly the inverse of the {@link pv.Nest} and |
| 2536 | * {@link pv.Tree} operators. |
| 2537 | * |
| 2538 | * @param map a map to flatten. |
| 2539 | */ |
| 2540 | pv.Flatten = function(map) { |
| 2541 | this.map = map; |
| 2542 | this.keys = []; |
| 2543 | }; |
| 2544 | |
| 2545 | /** |
| 2546 | * Flattens using the specified key function. Multiple keys may be added to the |
| 2547 | * flatten; the tiers of the underlying tree must correspond to the specified |
| 2548 | * keys, in order. The order of the returned array is undefined; however, you |
| 2549 | * can easily sort it. |
| 2550 | * |
| 2551 | * @param {string} key the key name. |
| 2552 | * @param {function} [f] an optional value map function. |
| 2553 | * @returns {pv.Nest} this. |
| 2554 | */ |
| 2555 | pv.Flatten.prototype.key = function(key, f) { |
| 2556 | this.keys.push({name: key, value: f}); |
| 2557 | delete this.$leaf; |
| 2558 | return this; |
| 2559 | }; |
| 2560 | |
| 2561 | /** |
| 2562 | * Flattens using the specified leaf function. This is an alternative to |
| 2563 | * specifying an explicit set of keys; the tiers of the underlying tree will be |
| 2564 | * determined dynamically by recursing on the values, and the resulting keys |
| 2565 | * will be stored in the entries <tt>keys</tt> attribute. The leaf function must |
| 2566 | * return true for leaves, and false for internal nodes. |
| 2567 | * |
| 2568 | * @param {function} f a leaf function. |
| 2569 | * @returns {pv.Nest} this. |
| 2570 | */ |
| 2571 | pv.Flatten.prototype.leaf = function(f) { |
| 2572 | this.keys.length = 0; |
| 2573 | this.$leaf = f; |
| 2574 | return this; |
| 2575 | }; |
| 2576 | |
| 2577 | /** |
| 2578 | * Returns the flattened array. Each entry in the array is an object; each |
| 2579 | * object has attributes corresponding to this flatten operator's keys. |
| 2580 | * |
| 2581 | * @returns an array of elements from the flattened map. |
| 2582 | */ |
| 2583 | pv.Flatten.prototype.array = function() { |
| 2584 | var entries = [], stack = [], keys = this.keys, leaf = this.$leaf; |
| 2585 | |
| 2586 | /* Recursively visit using the leaf function. */ |
| 2587 | if (leaf) { |
| 2588 | function recurse(value, i) { |
| 2589 | if (leaf(value)) { |
| 2590 | entries.push({keys: stack.slice(), value: value}); |
| 2591 | } else { |
| 2592 | for (var key in value) { |
| 2593 | stack.push(key); |
| 2594 | recurse(value[key], i + 1); |
| 2595 | stack.pop(); |
| 2596 | } |
| 2597 | } |
| 2598 | } |
| 2599 | recurse(this.map, 0); |
| 2600 | return entries; |
| 2601 | } |
| 2602 | |
| 2603 | /* Recursively visits the specified value. */ |
| 2604 | function visit(value, i) { |
| 2605 | if (i < keys.length - 1) { |
| 2606 | for (var key in value) { |
| 2607 | stack.push(key); |
| 2608 | visit(value[key], i + 1); |
| 2609 | stack.pop(); |
| 2610 | } |
| 2611 | } else { |
| 2612 | entries.push(stack.concat(value)); |
| 2613 | } |
| 2614 | } |
| 2615 | |
| 2616 | visit(this.map, 0); |
| 2617 | return entries.map(function(stack) { |
| 2618 | var m = {}; |
| 2619 | for (var i = 0; i < keys.length; i++) { |
| 2620 | var k = keys[i], v = stack[i]; |
| 2621 | m[k.name] = k.value ? k.value.call(null, v) : v; |
| 2622 | } |
| 2623 | return m; |
| 2624 | }); |
| 2625 | }; |
| 2626 | /** |
| 2627 | * Returns a {@link pv.Vector} for the specified <i>x</i> and <i>y</i> |
| 2628 | * coordinate. This is a convenience factory method, equivalent to <tt>new |
| 2629 | * pv.Vector(x, y)</tt>. |
| 2630 | * |
| 2631 | * @see pv.Vector |
| 2632 | * @param {number} x the <i>x</i> coordinate. |
| 2633 | * @param {number} y the <i>y</i> coordinate. |
| 2634 | * @returns {pv.Vector} a vector for the specified coordinates. |
| 2635 | */ |
| 2636 | pv.vector = function(x, y) { |
| 2637 | return new pv.Vector(x, y); |
| 2638 | }; |
| 2639 | |
| 2640 | /** |
| 2641 | * Constructs a {@link pv.Vector} for the specified <i>x</i> and <i>y</i> |
| 2642 | * coordinate. This constructor should not be invoked directly; use |
| 2643 | * {@link pv.vector} instead. |
| 2644 | * |
| 2645 | * @class Represents a two-dimensional vector; a 2-tuple <i>⟨x, |
| 2646 | * y⟩</i>. The intent of this class is to simplify vector math. Note that |
| 2647 | * in performance-sensitive cases it may be more efficient to represent 2D |
| 2648 | * vectors as simple objects with <tt>x</tt> and <tt>y</tt> attributes, rather |
| 2649 | * than using instances of this class. |
| 2650 | * |
| 2651 | * @param {number} x the <i>x</i> coordinate. |
| 2652 | * @param {number} y the <i>y</i> coordinate. |
| 2653 | */ |
| 2654 | pv.Vector = function(x, y) { |
| 2655 | this.x = x; |
| 2656 | this.y = y; |
| 2657 | }; |
| 2658 | |
| 2659 | /** |
| 2660 | * Returns a vector perpendicular to this vector: <i>⟨-y, x⟩</i>. |
| 2661 | * |
| 2662 | * @returns {pv.Vector} a perpendicular vector. |
| 2663 | */ |
| 2664 | pv.Vector.prototype.perp = function() { |
| 2665 | return new pv.Vector(-this.y, this.x); |
| 2666 | }; |
| 2667 | |
| 2668 | /** |
| 2669 | * Returns a normalized copy of this vector: a vector with the same direction, |
| 2670 | * but unit length. If this vector has zero length this method returns a copy of |
| 2671 | * this vector. |
| 2672 | * |
| 2673 | * @returns {pv.Vector} a unit vector. |
| 2674 | */ |
| 2675 | pv.Vector.prototype.norm = function() { |
| 2676 | var l = this.length(); |
| 2677 | return this.times(l ? (1 / l) : 1); |
| 2678 | }; |
| 2679 | |
| 2680 | /** |
| 2681 | * Returns the magnitude of this vector, defined as <i>sqrt(x * x + y * y)</i>. |
| 2682 | * |
| 2683 | * @returns {number} a length. |
| 2684 | */ |
| 2685 | pv.Vector.prototype.length = function() { |
| 2686 | return Math.sqrt(this.x * this.x + this.y * this.y); |
| 2687 | }; |
| 2688 | |
| 2689 | /** |
| 2690 | * Returns a scaled copy of this vector: <i>⟨x * k, y * k⟩</i>. |
| 2691 | * To perform the equivalent divide operation, use <i>1 / k</i>. |
| 2692 | * |
| 2693 | * @param {number} k the scale factor. |
| 2694 | * @returns {pv.Vector} a scaled vector. |
| 2695 | */ |
| 2696 | pv.Vector.prototype.times = function(k) { |
| 2697 | return new pv.Vector(this.x * k, this.y * k); |
| 2698 | }; |
| 2699 | |
| 2700 | /** |
| 2701 | * Returns this vector plus the vector <i>v</i>: <i>⟨x + v.x, y + |
| 2702 | * v.y⟩</i>. If only one argument is specified, it is interpreted as the |
| 2703 | * vector <i>v</i>. |
| 2704 | * |
| 2705 | * @param {number} x the <i>x</i> coordinate to add. |
| 2706 | * @param {number} y the <i>y</i> coordinate to add. |
| 2707 | * @returns {pv.Vector} a new vector. |
| 2708 | */ |
| 2709 | pv.Vector.prototype.plus = function(x, y) { |
| 2710 | return (arguments.length == 1) |
| 2711 | ? new pv.Vector(this.x + x.x, this.y + x.y) |
| 2712 | : new pv.Vector(this.x + x, this.y + y); |
| 2713 | }; |
| 2714 | |
| 2715 | /** |
| 2716 | * Returns this vector minus the vector <i>v</i>: <i>⟨x - v.x, y - |
| 2717 | * v.y⟩</i>. If only one argument is specified, it is interpreted as the |
| 2718 | * vector <i>v</i>. |
| 2719 | * |
| 2720 | * @param {number} x the <i>x</i> coordinate to subtract. |
| 2721 | * @param {number} y the <i>y</i> coordinate to subtract. |
| 2722 | * @returns {pv.Vector} a new vector. |
| 2723 | */ |
| 2724 | pv.Vector.prototype.minus = function(x, y) { |
| 2725 | return (arguments.length == 1) |
| 2726 | ? new pv.Vector(this.x - x.x, this.y - x.y) |
| 2727 | : new pv.Vector(this.x - x, this.y - y); |
| 2728 | }; |
| 2729 | |
| 2730 | /** |
| 2731 | * Returns the dot product of this vector and the vector <i>v</i>: <i>x * v.x + |
| 2732 | * y * v.y</i>. If only one argument is specified, it is interpreted as the |
| 2733 | * vector <i>v</i>. |
| 2734 | * |
| 2735 | * @param {number} x the <i>x</i> coordinate to dot. |
| 2736 | * @param {number} y the <i>y</i> coordinate to dot. |
| 2737 | * @returns {number} a dot product. |
| 2738 | */ |
| 2739 | pv.Vector.prototype.dot = function(x, y) { |
| 2740 | return (arguments.length == 1) |
| 2741 | ? this.x * x.x + this.y * x.y |
| 2742 | : this.x * x + this.y * y; |
| 2743 | }; |
| 2744 | /** |
| 2745 | * Returns a new identity transform. |
| 2746 | * |
| 2747 | * @class Represents a transformation matrix. The transformation matrix is |
| 2748 | * limited to expressing translate and uniform scale transforms only; shearing, |
| 2749 | * rotation, general affine, and other transforms are not supported. |
| 2750 | * |
| 2751 | * <p>The methods on this class treat the transform as immutable, returning a |
| 2752 | * copy of the transformation matrix with the specified transform applied. Note, |
| 2753 | * alternatively, that the matrix fields can be get and set directly. |
| 2754 | */ |
| 2755 | pv.Transform = function() {}; |
| 2756 | pv.Transform.prototype = {k: 1, x: 0, y: 0}; |
| 2757 | |
| 2758 | /** |
| 2759 | * The scale magnitude; defaults to 1. |
| 2760 | * |
| 2761 | * @type number |
| 2762 | * @name pv.Transform.prototype.k |
| 2763 | */ |
| 2764 | |
| 2765 | /** |
| 2766 | * The x-offset; defaults to 0. |
| 2767 | * |
| 2768 | * @type number |
| 2769 | * @name pv.Transform.prototype.x |
| 2770 | */ |
| 2771 | |
| 2772 | /** |
| 2773 | * The y-offset; defaults to 0. |
| 2774 | * |
| 2775 | * @type number |
| 2776 | * @name pv.Transform.prototype.y |
| 2777 | */ |
| 2778 | |
| 2779 | /** |
| 2780 | * @private The identity transform. |
| 2781 | * |
| 2782 | * @type pv.Transform |
| 2783 | */ |
| 2784 | pv.Transform.identity = new pv.Transform(); |
| 2785 | |
| 2786 | // k 0 x 1 0 a k 0 ka+x |
| 2787 | // 0 k y * 0 1 b = 0 k kb+y |
| 2788 | // 0 0 1 0 0 1 0 0 1 |
| 2789 | |
| 2790 | /** |
| 2791 | * Returns a translated copy of this transformation matrix. |
| 2792 | * |
| 2793 | * @param {number} x the x-offset. |
| 2794 | * @param {number} y the y-offset. |
| 2795 | * @returns {pv.Transform} the translated transformation matrix. |
| 2796 | */ |
| 2797 | pv.Transform.prototype.translate = function(x, y) { |
| 2798 | var v = new pv.Transform(); |
| 2799 | v.k = this.k; |
| 2800 | v.x = this.k * x + this.x; |
| 2801 | v.y = this.k * y + this.y; |
| 2802 | return v; |
| 2803 | }; |
| 2804 | |
| 2805 | // k 0 x d 0 0 kd 0 x |
| 2806 | // 0 k y * 0 d 0 = 0 kd y |
| 2807 | // 0 0 1 0 0 1 0 0 1 |
| 2808 | |
| 2809 | /** |
| 2810 | * Returns a scaled copy of this transformation matrix. |
| 2811 | * |
| 2812 | * @param {number} k |
| 2813 | * @returns {pv.Transform} the scaled transformation matrix. |
| 2814 | */ |
| 2815 | pv.Transform.prototype.scale = function(k) { |
| 2816 | var v = new pv.Transform(); |
| 2817 | v.k = this.k * k; |
| 2818 | v.x = this.x; |
| 2819 | v.y = this.y; |
| 2820 | return v; |
| 2821 | }; |
| 2822 | |
| 2823 | /** |
| 2824 | * Returns the inverse of this transformation matrix. |
| 2825 | * |
| 2826 | * @returns {pv.Transform} the inverted transformation matrix. |
| 2827 | */ |
| 2828 | pv.Transform.prototype.invert = function() { |
| 2829 | var v = new pv.Transform(), k = 1 / this.k; |
| 2830 | v.k = k; |
| 2831 | v.x = -this.x * k; |
| 2832 | v.y = -this.y * k; |
| 2833 | return v; |
| 2834 | }; |
| 2835 | |
| 2836 | // k 0 x d 0 a kd 0 ka+x |
| 2837 | // 0 k y * 0 d b = 0 kd kb+y |
| 2838 | // 0 0 1 0 0 1 0 0 1 |
| 2839 | |
| 2840 | /** |
| 2841 | * Returns this matrix post-multiplied by the specified matrix <i>m</i>. |
| 2842 | * |
| 2843 | * @param {pv.Transform} m |
| 2844 | * @returns {pv.Transform} the post-multiplied transformation matrix. |
| 2845 | */ |
| 2846 | pv.Transform.prototype.times = function(m) { |
| 2847 | var v = new pv.Transform(); |
| 2848 | v.k = this.k * m.k; |
| 2849 | v.x = this.k * m.x + this.x; |
| 2850 | v.y = this.k * m.y + this.y; |
| 2851 | return v; |
| 2852 | }; |
| 2853 | /** |
| 2854 | * Abstract; see the various scale implementations. |
| 2855 | * |
| 2856 | * @class Represents a scale; a function that performs a transformation from |
| 2857 | * data domain to visual range. For quantitative and quantile scales, the domain |
| 2858 | * is expressed as numbers; for ordinal scales, the domain is expressed as |
| 2859 | * strings (or equivalently objects with unique string representations). The |
| 2860 | * "visual range" may correspond to pixel space, colors, font sizes, and the |
| 2861 | * like. |
| 2862 | * |
| 2863 | * <p>Note that scales are functions, and thus can be used as properties |
| 2864 | * directly, assuming that the data associated with a mark is a number. While |
| 2865 | * this is convenient for single-use scales, frequently it is desirable to |
| 2866 | * define scales globally: |
| 2867 | * |
| 2868 | * <pre>var y = pv.Scale.linear(0, 100).range(0, 640);</pre> |
| 2869 | * |
| 2870 | * The <tt>y</tt> scale can now be equivalently referenced within a property: |
| 2871 | * |
| 2872 | * <pre> .height(function(d) y(d))</pre> |
| 2873 | * |
| 2874 | * Alternatively, if the data are not simple numbers, the appropriate value can |
| 2875 | * be passed to the <tt>y</tt> scale (e.g., <tt>d.foo</tt>). The {@link #by} |
| 2876 | * method similarly allows the data to be mapped to a numeric value before |
| 2877 | * performing the linear transformation. |
| 2878 | * |
| 2879 | * @see pv.Scale.quantitative |
| 2880 | * @see pv.Scale.quantile |
| 2881 | * @see pv.Scale.ordinal |
| 2882 | * @extends function |
| 2883 | */ |
| 2884 | pv.Scale = function() {}; |
| 2885 | |
| 2886 | /** |
| 2887 | * @private Returns a function that interpolators from the start value to the |
| 2888 | * end value, given a parameter <i>t</i> in [0, 1]. |
| 2889 | * |
| 2890 | * @param start the start value. |
| 2891 | * @param end the end value. |
| 2892 | */ |
| 2893 | pv.Scale.interpolator = function(start, end) { |
| 2894 | if (typeof start == "number") { |
| 2895 | return function(t) { |
| 2896 | return t * (end - start) + start; |
| 2897 | }; |
| 2898 | } |
| 2899 | |
| 2900 | /* For now, assume color. */ |
| 2901 | start = pv.color(start).rgb(); |
| 2902 | end = pv.color(end).rgb(); |
| 2903 | return function(t) { |
| 2904 | var a = start.a * (1 - t) + end.a * t; |
| 2905 | if (a < 1e-5) a = 0; // avoid scientific notation |
| 2906 | return (start.a == 0) ? pv.rgb(end.r, end.g, end.b, a) |
| 2907 | : ((end.a == 0) ? pv.rgb(start.r, start.g, start.b, a) |
| 2908 | : pv.rgb( |
| 2909 | Math.round(start.r * (1 - t) + end.r * t), |
| 2910 | Math.round(start.g * (1 - t) + end.g * t), |
| 2911 | Math.round(start.b * (1 - t) + end.b * t), a)); |
| 2912 | }; |
| 2913 | }; |
| 2914 | |
| 2915 | /** |
| 2916 | * Returns a view of this scale by the specified accessor function <tt>f</tt>. |
| 2917 | * Given a scale <tt>y</tt>, <tt>y.by(function(d) d.foo)</tt> is equivalent to |
| 2918 | * <tt>function(d) y(d.foo)</tt>. |
| 2919 | * |
| 2920 | * <p>This method is provided for convenience, such that scales can be |
| 2921 | * succinctly defined inline. For example, given an array of data elements that |
| 2922 | * have a <tt>score</tt> attribute with the domain [0, 1], the height property |
| 2923 | * could be specified as: |
| 2924 | * |
| 2925 | * <pre> .height(pv.Scale.linear().range(0, 480).by(function(d) d.score))</pre> |
| 2926 | * |
| 2927 | * This is equivalent to: |
| 2928 | * |
| 2929 | * <pre> .height(function(d) d.score * 480)</pre> |
| 2930 | * |
| 2931 | * This method should be used judiciously; it is typically more clear to invoke |
| 2932 | * the scale directly, passing in the value to be scaled. |
| 2933 | * |
| 2934 | * @function |
| 2935 | * @name pv.Scale.prototype.by |
| 2936 | * @param {function} f an accessor function. |
| 2937 | * @returns {pv.Scale} a view of this scale by the specified accessor function. |
| 2938 | */ |
| 2939 | /** |
| 2940 | * Returns a default quantitative, linear, scale for the specified domain. The |
| 2941 | * arguments to this constructor are optional, and equivalent to calling |
| 2942 | * {@link #domain}. The default domain and range are [0,1]. |
| 2943 | * |
| 2944 | * <p>This constructor is typically not used directly; see one of the |
| 2945 | * quantitative scale implementations instead. |
| 2946 | * |
| 2947 | * @class Represents an abstract quantitative scale; a function that performs a |
| 2948 | * numeric transformation. This class is typically not used directly; see one of |
| 2949 | * the quantitative scale implementations (linear, log, root, etc.) |
| 2950 | * instead. <style type="text/css">sub{line-height:0}</style> A quantitative |
| 2951 | * scale represents a 1-dimensional transformation from a numeric domain of |
| 2952 | * input data [<i>d<sub>0</sub></i>, <i>d<sub>1</sub></i>] to a numeric range of |
| 2953 | * pixels [<i>r<sub>0</sub></i>, <i>r<sub>1</sub></i>]. In addition to |
| 2954 | * readability, scales offer several useful features: |
| 2955 | * |
| 2956 | * <p>1. The range can be expressed in colors, rather than pixels. For example: |
| 2957 | * |
| 2958 | * <pre> .fillStyle(pv.Scale.linear(0, 100).range("red", "green"))</pre> |
| 2959 | * |
| 2960 | * will fill the marks "red" on an input value of 0, "green" on an input value |
| 2961 | * of 100, and some color in-between for intermediate values. |
| 2962 | * |
| 2963 | * <p>2. The domain and range can be subdivided for a non-uniform |
| 2964 | * transformation. For example, you may want a diverging color scale that is |
| 2965 | * increasingly red for negative values, and increasingly green for positive |
| 2966 | * values: |
| 2967 | * |
| 2968 | * <pre> .fillStyle(pv.Scale.linear(-1, 0, 1).range("red", "white", "green"))</pre> |
| 2969 | * |
| 2970 | * The domain can be specified as a series of <i>n</i> monotonically-increasing |
| 2971 | * values; the range must also be specified as <i>n</i> values, resulting in |
| 2972 | * <i>n - 1</i> contiguous linear scales. |
| 2973 | * |
| 2974 | * <p>3. Quantitative scales can be inverted for interaction. The |
| 2975 | * {@link #invert} method takes a value in the output range, and returns the |
| 2976 | * corresponding value in the input domain. This is frequently used to convert |
| 2977 | * the mouse location (see {@link pv.Mark#mouse}) to a value in the input |
| 2978 | * domain. Note that inversion is only supported for numeric ranges, and not |
| 2979 | * colors. |
| 2980 | * |
| 2981 | * <p>4. A scale can be queried for reasonable "tick" values. The {@link #ticks} |
| 2982 | * method provides a convenient way to get a series of evenly-spaced rounded |
| 2983 | * values in the input domain. Frequently these are used in conjunction with |
| 2984 | * {@link pv.Rule} to display tick marks or grid lines. |
| 2985 | * |
| 2986 | * <p>5. A scale can be "niced" to extend the domain to suitable rounded |
| 2987 | * numbers. If the minimum and maximum of the domain are messy because they are |
| 2988 | * derived from data, you can use {@link #nice} to round these values down and |
| 2989 | * up to even numbers. |
| 2990 | * |
| 2991 | * @param {number...} domain... optional domain values. |
| 2992 | * @see pv.Scale.linear |
| 2993 | * @see pv.Scale.log |
| 2994 | * @see pv.Scale.root |
| 2995 | * @extends pv.Scale |
| 2996 | */ |
| 2997 | pv.Scale.quantitative = function() { |
| 2998 | var d = [0, 1], // default domain |
| 2999 | l = [0, 1], // default transformed domain |
| 3000 | r = [0, 1], // default range |
| 3001 | i = [pv.identity], // default interpolators |
| 3002 | type = Number, // default type |
| 3003 | n = false, // whether the domain is negative |
| 3004 | f = pv.identity, // default forward transform |
| 3005 | g = pv.identity, // default inverse transform |
| 3006 | tickFormat = String; // default tick formatting function |
| 3007 | |
| 3008 | /** @private */ |
| 3009 | function newDate(x) { |
| 3010 | return new Date(x); |
| 3011 | } |
| 3012 | |
| 3013 | /** @private */ |
| 3014 | function scale(x) { |
| 3015 | var j = pv.search(d, x); |
| 3016 | if (j < 0) j = -j - 2; |
| 3017 | j = Math.max(0, Math.min(i.length - 1, j)); |
| 3018 | return i[j]((f(x) - l[j]) / (l[j + 1] - l[j])); |
| 3019 | } |
| 3020 | |
| 3021 | /** @private */ |
| 3022 | scale.transform = function(forward, inverse) { |
| 3023 | /** @ignore */ f = function(x) { return n ? -forward(-x) : forward(x); }; |
| 3024 | /** @ignore */ g = function(y) { return n ? -inverse(-y) : inverse(y); }; |
| 3025 | l = d.map(f); |
| 3026 | return this; |
| 3027 | }; |
| 3028 | |
| 3029 | /** |
| 3030 | * Sets or gets the input domain. This method can be invoked several ways: |
| 3031 | * |
| 3032 | * <p>1. <tt>domain(min, ..., max)</tt> |
| 3033 | * |
| 3034 | * <p>Specifying the domain as a series of numbers is the most explicit and |
| 3035 | * recommended approach. Most commonly, two numbers are specified: the minimum |
| 3036 | * and maximum value. However, for a diverging scale, or other subdivided |
| 3037 | * non-uniform scales, multiple values can be specified. Values can be derived |
| 3038 | * from data using {@link pv.min} and {@link pv.max}. For example: |
| 3039 | * |
| 3040 | * <pre> .domain(0, pv.max(array))</pre> |
| 3041 | * |
| 3042 | * An alternative method for deriving minimum and maximum values from data |
| 3043 | * follows. |
| 3044 | * |
| 3045 | * <p>2. <tt>domain(array, minf, maxf)</tt> |
| 3046 | * |
| 3047 | * <p>When both the minimum and maximum value are derived from data, the |
| 3048 | * arguments to the <tt>domain</tt> method can be specified as the array of |
| 3049 | * data, followed by zero, one or two accessor functions. For example, if the |
| 3050 | * array of data is just an array of numbers: |
| 3051 | * |
| 3052 | * <pre> .domain(array)</pre> |
| 3053 | * |
| 3054 | * On the other hand, if the array elements are objects representing stock |
| 3055 | * values per day, and the domain should consider the stock's daily low and |
| 3056 | * daily high: |
| 3057 | * |
| 3058 | * <pre> .domain(array, function(d) d.low, function(d) d.high)</pre> |
| 3059 | * |
| 3060 | * The first method of setting the domain is preferred because it is more |
| 3061 | * explicit; setting the domain using this second method should be used only |
| 3062 | * if brevity is required. |
| 3063 | * |
| 3064 | * <p>3. <tt>domain()</tt> |
| 3065 | * |
| 3066 | * <p>Invoking the <tt>domain</tt> method with no arguments returns the |
| 3067 | * current domain as an array of numbers. |
| 3068 | * |
| 3069 | * @function |
| 3070 | * @name pv.Scale.quantitative.prototype.domain |
| 3071 | * @param {number...} domain... domain values. |
| 3072 | * @returns {pv.Scale.quantitative} <tt>this</tt>, or the current domain. |
| 3073 | */ |
| 3074 | scale.domain = function(array, min, max) { |
| 3075 | if (arguments.length) { |
| 3076 | var o; // the object we use to infer the domain type |
| 3077 | if (array instanceof Array) { |
| 3078 | if (arguments.length < 2) min = pv.identity; |
| 3079 | if (arguments.length < 3) max = min; |
| 3080 | o = array.length && min(array[0]); |
| 3081 | d = array.length ? [pv.min(array, min), pv.max(array, max)] : []; |
| 3082 | } else { |
| 3083 | o = array; |
| 3084 | d = Array.prototype.slice.call(arguments).map(Number); |
| 3085 | } |
| 3086 | if (!d.length) d = [-Infinity, Infinity]; |
| 3087 | else if (d.length == 1) d = [d[0], d[0]]; |
| 3088 | n = (d[0] || d[d.length - 1]) < 0; |
| 3089 | l = d.map(f); |
| 3090 | type = (o instanceof Date) ? newDate : Number; |
| 3091 | return this; |
| 3092 | } |
| 3093 | return d.map(type); |
| 3094 | }; |
| 3095 | |
| 3096 | /** |
| 3097 | * Sets or gets the output range. This method can be invoked several ways: |
| 3098 | * |
| 3099 | * <p>1. <tt>range(min, ..., max)</tt> |
| 3100 | * |
| 3101 | * <p>The range may be specified as a series of numbers or colors. Most |
| 3102 | * commonly, two numbers are specified: the minimum and maximum pixel values. |
| 3103 | * For a color scale, values may be specified as {@link pv.Color}s or |
| 3104 | * equivalent strings. For a diverging scale, or other subdivided non-uniform |
| 3105 | * scales, multiple values can be specified. For example: |
| 3106 | * |
| 3107 | * <pre> .range("red", "white", "green")</pre> |
| 3108 | * |
| 3109 | * <p>Currently, only numbers and colors are supported as range values. The |
| 3110 | * number of range values must exactly match the number of domain values, or |
| 3111 | * the behavior of the scale is undefined. |
| 3112 | * |
| 3113 | * <p>2. <tt>range()</tt> |
| 3114 | * |
| 3115 | * <p>Invoking the <tt>range</tt> method with no arguments returns the current |
| 3116 | * range as an array of numbers or colors. |
| 3117 | * |
| 3118 | * @function |
| 3119 | * @name pv.Scale.quantitative.prototype.range |
| 3120 | * @param {...} range... range values. |
| 3121 | * @returns {pv.Scale.quantitative} <tt>this</tt>, or the current range. |
| 3122 | */ |
| 3123 | scale.range = function() { |
| 3124 | if (arguments.length) { |
| 3125 | r = Array.prototype.slice.call(arguments); |
| 3126 | if (!r.length) r = [-Infinity, Infinity]; |
| 3127 | else if (r.length == 1) r = [r[0], r[0]]; |
| 3128 | i = []; |
| 3129 | for (var j = 0; j < r.length - 1; j++) { |
| 3130 | i.push(pv.Scale.interpolator(r[j], r[j + 1])); |
| 3131 | } |
| 3132 | return this; |
| 3133 | } |
| 3134 | return r; |
| 3135 | }; |
| 3136 | |
| 3137 | /** |
| 3138 | * Inverts the specified value in the output range, returning the |
| 3139 | * corresponding value in the input domain. This is frequently used to convert |
| 3140 | * the mouse location (see {@link pv.Mark#mouse}) to a value in the input |
| 3141 | * domain. Inversion is only supported for numeric ranges, and not colors. |
| 3142 | * |
| 3143 | * <p>Note that this method does not do any rounding or bounds checking. If |
| 3144 | * the input domain is discrete (e.g., an array index), the returned value |
| 3145 | * should be rounded. If the specified <tt>y</tt> value is outside the range, |
| 3146 | * the returned value may be equivalently outside the input domain. |
| 3147 | * |
| 3148 | * @function |
| 3149 | * @name pv.Scale.quantitative.prototype.invert |
| 3150 | * @param {number} y a value in the output range (a pixel location). |
| 3151 | * @returns {number} a value in the input domain. |
| 3152 | */ |
| 3153 | scale.invert = function(y) { |
| 3154 | var j = pv.search(r, y); |
| 3155 | if (j < 0) j = -j - 2; |
| 3156 | j = Math.max(0, Math.min(i.length - 1, j)); |
| 3157 | return type(g(l[j] + (y - r[j]) / (r[j + 1] - r[j]) * (l[j + 1] - l[j]))); |
| 3158 | }; |
| 3159 | |
| 3160 | /** |
| 3161 | * Returns an array of evenly-spaced, suitably-rounded values in the input |
| 3162 | * domain. This method attempts to return between 5 and 10 tick values. These |
| 3163 | * values are frequently used in conjunction with {@link pv.Rule} to display |
| 3164 | * tick marks or grid lines. |
| 3165 | * |
| 3166 | * @function |
| 3167 | * @name pv.Scale.quantitative.prototype.ticks |
| 3168 | * @param {number} [m] optional number of desired ticks. |
| 3169 | * @returns {number[]} an array input domain values to use as ticks. |
| 3170 | */ |
| 3171 | scale.ticks = function(m) { |
| 3172 | var start = d[0], |
| 3173 | end = d[d.length - 1], |
| 3174 | reverse = end < start, |
| 3175 | min = reverse ? end : start, |
| 3176 | max = reverse ? start : end, |
| 3177 | span = max - min; |
| 3178 | |
| 3179 | /* Special case: empty, invalid or infinite span. */ |
| 3180 | if (!span || !isFinite(span)) { |
| 3181 | if (type == newDate) tickFormat = pv.Format.date("%x"); |
| 3182 | return [type(min)]; |
| 3183 | } |
| 3184 | |
| 3185 | /* Special case: dates. */ |
| 3186 | if (type == newDate) { |
| 3187 | /* Floor the date d given the precision p. */ |
| 3188 | function floor(d, p) { |
| 3189 | switch (p) { |
| 3190 | case 31536e6: d.setMonth(0); |
| 3191 | case 2592e6: d.setDate(1); |
| 3192 | case 6048e5: if (p == 6048e5) d.setDate(d.getDate() - d.getDay()); |
| 3193 | case 864e5: d.setHours(0); |
| 3194 | case 36e5: d.setMinutes(0); |
| 3195 | case 6e4: d.setSeconds(0); |
| 3196 | case 1e3: d.setMilliseconds(0); |
| 3197 | } |
| 3198 | } |
| 3199 | |
| 3200 | var precision, format, increment, step = 1; |
| 3201 | if (span >= 3 * 31536e6) { |
| 3202 | precision = 31536e6; |
| 3203 | format = "%Y"; |
| 3204 | /** @ignore */ increment = function(d) { d.setFullYear(d.getFullYear() + step); }; |
| 3205 | } else if (span >= 3 * 2592e6) { |
| 3206 | precision = 2592e6; |
| 3207 | format = "%m/%Y"; |
| 3208 | /** @ignore */ increment = function(d) { d.setMonth(d.getMonth() + step); }; |
| 3209 | } else if (span >= 3 * 6048e5) { |
| 3210 | precision = 6048e5; |
| 3211 | format = "%m/%d"; |
| 3212 | /** @ignore */ increment = function(d) { d.setDate(d.getDate() + 7 * step); }; |
| 3213 | } else if (span >= 3 * 864e5) { |
| 3214 | precision = 864e5; |
| 3215 | format = "%m/%d"; |
| 3216 | /** @ignore */ increment = function(d) { d.setDate(d.getDate() + step); }; |
| 3217 | } else if (span >= 3 * 36e5) { |
| 3218 | precision = 36e5; |
| 3219 | format = "%I:%M %p"; |
| 3220 | /** @ignore */ increment = function(d) { d.setHours(d.getHours() + step); }; |
| 3221 | } else if (span >= 3 * 6e4) { |
| 3222 | precision = 6e4; |
| 3223 | format = "%I:%M %p"; |
| 3224 | /** @ignore */ increment = function(d) { d.setMinutes(d.getMinutes() + step); }; |
| 3225 | } else if (span >= 3 * 1e3) { |
| 3226 | precision = 1e3; |
| 3227 | format = "%I:%M:%S"; |
| 3228 | /** @ignore */ increment = function(d) { d.setSeconds(d.getSeconds() + step); }; |
| 3229 | } else { |
| 3230 | precision = 1; |
| 3231 | format = "%S.%Qs"; |
| 3232 | /** @ignore */ increment = function(d) { d.setTime(d.getTime() + step); }; |
| 3233 | } |
| 3234 | tickFormat = pv.Format.date(format); |
| 3235 | |
| 3236 | var date = new Date(min), dates = []; |
| 3237 | floor(date, precision); |
| 3238 | |
| 3239 | /* If we'd generate too many ticks, skip some!. */ |
| 3240 | var n = span / precision; |
| 3241 | if (n > 10) { |
| 3242 | switch (precision) { |
| 3243 | case 36e5: { |
| 3244 | step = (n > 20) ? 6 : 3; |
| 3245 | date.setHours(Math.floor(date.getHours() / step) * step); |
| 3246 | break; |
| 3247 | } |
| 3248 | case 2592e6: { |
| 3249 | step = 3; // seasons |
| 3250 | date.setMonth(Math.floor(date.getMonth() / step) * step); |
| 3251 | break; |
| 3252 | } |
| 3253 | case 6e4: { |
| 3254 | step = (n > 30) ? 15 : ((n > 15) ? 10 : 5); |
| 3255 | date.setMinutes(Math.floor(date.getMinutes() / step) * step); |
| 3256 | break; |
| 3257 | } |
| 3258 | case 1e3: { |
| 3259 | step = (n > 90) ? 15 : ((n > 60) ? 10 : 5); |
| 3260 | date.setSeconds(Math.floor(date.getSeconds() / step) * step); |
| 3261 | break; |
| 3262 | } |
| 3263 | case 1: { |
| 3264 | step = (n > 1000) ? 250 : ((n > 200) ? 100 : ((n > 100) ? 50 : ((n > 50) ? 25 : 5))); |
| 3265 | date.setMilliseconds(Math.floor(date.getMilliseconds() / step) * step); |
| 3266 | break; |
| 3267 | } |
| 3268 | default: { |
| 3269 | step = pv.logCeil(n / 15, 10); |
| 3270 | if (n / step < 2) step /= 5; |
| 3271 | else if (n / step < 5) step /= 2; |
| 3272 | date.setFullYear(Math.floor(date.getFullYear() / step) * step); |
| 3273 | break; |
| 3274 | } |
| 3275 | } |
| 3276 | } |
| 3277 | |
| 3278 | while (true) { |
| 3279 | increment(date); |
| 3280 | if (date > max) break; |
| 3281 | dates.push(new Date(date)); |
| 3282 | } |
| 3283 | return reverse ? dates.reverse() : dates; |
| 3284 | } |
| 3285 | |
| 3286 | /* Normal case: numbers. */ |
| 3287 | if (!arguments.length) m = 10; |
| 3288 | var step = pv.logFloor(span / m, 10), |
| 3289 | err = m / (span / step); |
| 3290 | if (err <= .15) step *= 10; |
| 3291 | else if (err <= .35) step *= 5; |
| 3292 | else if (err <= .75) step *= 2; |
| 3293 | var start = Math.ceil(min / step) * step, |
| 3294 | end = Math.floor(max / step) * step; |
| 3295 | tickFormat = pv.Format.number() |
| 3296 | .fractionDigits(Math.max(0, -Math.floor(pv.log(step, 10) + .01))); |
| 3297 | var ticks = pv.range(start, end + step, step); |
| 3298 | return reverse ? ticks.reverse() : ticks; |
| 3299 | }; |
| 3300 | |
| 3301 | /** |
| 3302 | * Formats the specified tick value using the appropriate precision, based on |
| 3303 | * the step interval between tick marks. If {@link #ticks} has not been called, |
| 3304 | * the argument is converted to a string, but no formatting is applied. |
| 3305 | * |
| 3306 | * @function |
| 3307 | * @name pv.Scale.quantitative.prototype.tickFormat |
| 3308 | * @param {number} t a tick value. |
| 3309 | * @returns {string} a formatted tick value. |
| 3310 | */ |
| 3311 | scale.tickFormat = function (t) { return tickFormat(t); }; |
| 3312 | |
| 3313 | /** |
| 3314 | * "Nices" this scale, extending the bounds of the input domain to |
| 3315 | * evenly-rounded values. Nicing is useful if the domain is computed |
| 3316 | * dynamically from data, and may be irregular. For example, given a domain of |
| 3317 | * [0.20147987687960267, 0.996679553296417], a call to <tt>nice()</tt> might |
| 3318 | * extend the domain to [0.2, 1]. |
| 3319 | * |
| 3320 | * <p>This method must be invoked each time after setting the domain. |
| 3321 | * |
| 3322 | * @function |
| 3323 | * @name pv.Scale.quantitative.prototype.nice |
| 3324 | * @returns {pv.Scale.quantitative} <tt>this</tt>. |
| 3325 | */ |
| 3326 | scale.nice = function() { |
| 3327 | if (d.length != 2) return this; // TODO support non-uniform domains |
| 3328 | var start = d[0], |
| 3329 | end = d[d.length - 1], |
| 3330 | reverse = end < start, |
| 3331 | min = reverse ? end : start, |
| 3332 | max = reverse ? start : end, |
| 3333 | span = max - min; |
| 3334 | |
| 3335 | /* Special case: empty, invalid or infinite span. */ |
| 3336 | if (!span || !isFinite(span)) return this; |
| 3337 | |
| 3338 | var step = Math.pow(10, Math.round(Math.log(span) / Math.log(10)) - 1); |
| 3339 | d = [Math.floor(min / step) * step, Math.ceil(max / step) * step]; |
| 3340 | if (reverse) d.reverse(); |
| 3341 | l = d.map(f); |
| 3342 | return this; |
| 3343 | }; |
| 3344 | |
| 3345 | /** |
| 3346 | * Returns a view of this scale by the specified accessor function <tt>f</tt>. |
| 3347 | * Given a scale <tt>y</tt>, <tt>y.by(function(d) d.foo)</tt> is equivalent to |
| 3348 | * <tt>function(d) y(d.foo)</tt>. |
| 3349 | * |
| 3350 | * <p>This method is provided for convenience, such that scales can be |
| 3351 | * succinctly defined inline. For example, given an array of data elements |
| 3352 | * that have a <tt>score</tt> attribute with the domain [0, 1], the height |
| 3353 | * property could be specified as: |
| 3354 | * |
| 3355 | * <pre> .height(pv.Scale.linear().range(0, 480).by(function(d) d.score))</pre> |
| 3356 | * |
| 3357 | * This is equivalent to: |
| 3358 | * |
| 3359 | * <pre> .height(function(d) d.score * 480)</pre> |
| 3360 | * |
| 3361 | * This method should be used judiciously; it is typically more clear to |
| 3362 | * invoke the scale directly, passing in the value to be scaled. |
| 3363 | * |
| 3364 | * @function |
| 3365 | * @name pv.Scale.quantitative.prototype.by |
| 3366 | * @param {function} f an accessor function. |
| 3367 | * @returns {pv.Scale.quantitative} a view of this scale by the specified |
| 3368 | * accessor function. |
| 3369 | */ |
| 3370 | scale.by = function(f) { |
| 3371 | function by() { return scale(f.apply(this, arguments)); } |
| 3372 | for (var method in scale) by[method] = scale[method]; |
| 3373 | return by; |
| 3374 | }; |
| 3375 | |
| 3376 | scale.domain.apply(scale, arguments); |
| 3377 | return scale; |
| 3378 | }; |
| 3379 | /** |
| 3380 | * Returns a linear scale for the specified domain. The arguments to this |
| 3381 | * constructor are optional, and equivalent to calling {@link #domain}. |
| 3382 | * The default domain and range are [0,1]. |
| 3383 | * |
| 3384 | * @class Represents a linear scale; a function that performs a linear |
| 3385 | * transformation. <style type="text/css">sub{line-height:0}</style> Most |
| 3386 | * commonly, a linear scale represents a 1-dimensional linear transformation |
| 3387 | * from a numeric domain of input data [<i>d<sub>0</sub></i>, |
| 3388 | * <i>d<sub>1</sub></i>] to a numeric range of pixels [<i>r<sub>0</sub></i>, |
| 3389 | * <i>r<sub>1</sub></i>]. The equation for such a scale is: |
| 3390 | * |
| 3391 | * <blockquote><i>f(x) = (x - d<sub>0</sub>) / (d<sub>1</sub> - d<sub>0</sub>) * |
| 3392 | * (r<sub>1</sub> - r<sub>0</sub>) + r<sub>0</sub></i></blockquote> |
| 3393 | * |
| 3394 | * For example, a linear scale from the domain [0, 100] to range [0, 640]: |
| 3395 | * |
| 3396 | * <blockquote><i>f(x) = (x - 0) / (100 - 0) * (640 - 0) + 0</i><br> |
| 3397 | * <i>f(x) = x / 100 * 640</i><br> |
| 3398 | * <i>f(x) = x * 6.4</i><br> |
| 3399 | * </blockquote> |
| 3400 | * |
| 3401 | * Thus, saying |
| 3402 | * |
| 3403 | * <pre> .height(function(d) d * 6.4)</pre> |
| 3404 | * |
| 3405 | * is identical to |
| 3406 | * |
| 3407 | * <pre> .height(pv.Scale.linear(0, 100).range(0, 640))</pre> |
| 3408 | * |
| 3409 | * Note that the scale is itself a function, and thus can be used as a property |
| 3410 | * directly, assuming that the data associated with a mark is a number. While |
| 3411 | * this is convenient for single-use scales, frequently it is desirable to |
| 3412 | * define scales globally: |
| 3413 | * |
| 3414 | * <pre>var y = pv.Scale.linear(0, 100).range(0, 640);</pre> |
| 3415 | * |
| 3416 | * The <tt>y</tt> scale can now be equivalently referenced within a property: |
| 3417 | * |
| 3418 | * <pre> .height(function(d) y(d))</pre> |
| 3419 | * |
| 3420 | * Alternatively, if the data are not simple numbers, the appropriate value can |
| 3421 | * be passed to the <tt>y</tt> scale (e.g., <tt>d.foo</tt>). The {@link #by} |
| 3422 | * method similarly allows the data to be mapped to a numeric value before |
| 3423 | * performing the linear transformation. |
| 3424 | * |
| 3425 | * @param {number...} domain... optional domain values. |
| 3426 | * @extends pv.Scale.quantitative |
| 3427 | */ |
| 3428 | pv.Scale.linear = function() { |
| 3429 | var scale = pv.Scale.quantitative(); |
| 3430 | scale.domain.apply(scale, arguments); |
| 3431 | return scale; |
| 3432 | }; |
| 3433 | /** |
| 3434 | * Returns a log scale for the specified domain. The arguments to this |
| 3435 | * constructor are optional, and equivalent to calling {@link #domain}. |
| 3436 | * The default domain is [1,10] and the default range is [0,1]. |
| 3437 | * |
| 3438 | * @class Represents a log scale. <style |
| 3439 | * type="text/css">sub{line-height:0}</style> Most commonly, a log scale |
| 3440 | * represents a 1-dimensional log transformation from a numeric domain of input |
| 3441 | * data [<i>d<sub>0</sub></i>, <i>d<sub>1</sub></i>] to a numeric range of |
| 3442 | * pixels [<i>r<sub>0</sub></i>, <i>r<sub>1</sub></i>]. The equation for such a |
| 3443 | * scale is: |
| 3444 | * |
| 3445 | * <blockquote><i>f(x) = (log(x) - log(d<sub>0</sub>)) / (log(d<sub>1</sub>) - |
| 3446 | * log(d<sub>0</sub>)) * (r<sub>1</sub> - r<sub>0</sub>) + |
| 3447 | * r<sub>0</sub></i></blockquote> |
| 3448 | * |
| 3449 | * where <i>log(x)</i> represents the zero-symmetric logarthim of <i>x</i> using |
| 3450 | * the scale's associated base (default: 10, see {@link pv.logSymmetric}). For |
| 3451 | * example, a log scale from the domain [1, 100] to range [0, 640]: |
| 3452 | * |
| 3453 | * <blockquote><i>f(x) = (log(x) - log(1)) / (log(100) - log(1)) * (640 - 0) + 0</i><br> |
| 3454 | * <i>f(x) = log(x) / 2 * 640</i><br> |
| 3455 | * <i>f(x) = log(x) * 320</i><br> |
| 3456 | * </blockquote> |
| 3457 | * |
| 3458 | * Thus, saying |
| 3459 | * |
| 3460 | * <pre> .height(function(d) Math.log(d) * 138.974)</pre> |
| 3461 | * |
| 3462 | * is equivalent to |
| 3463 | * |
| 3464 | * <pre> .height(pv.Scale.log(1, 100).range(0, 640))</pre> |
| 3465 | * |
| 3466 | * Note that the scale is itself a function, and thus can be used as a property |
| 3467 | * directly, assuming that the data associated with a mark is a number. While |
| 3468 | * this is convenient for single-use scales, frequently it is desirable to |
| 3469 | * define scales globally: |
| 3470 | * |
| 3471 | * <pre>var y = pv.Scale.log(1, 100).range(0, 640);</pre> |
| 3472 | * |
| 3473 | * The <tt>y</tt> scale can now be equivalently referenced within a property: |
| 3474 | * |
| 3475 | * <pre> .height(function(d) y(d))</pre> |
| 3476 | * |
| 3477 | * Alternatively, if the data are not simple numbers, the appropriate value can |
| 3478 | * be passed to the <tt>y</tt> scale (e.g., <tt>d.foo</tt>). The {@link #by} |
| 3479 | * method similarly allows the data to be mapped to a numeric value before |
| 3480 | * performing the log transformation. |
| 3481 | * |
| 3482 | * @param {number...} domain... optional domain values. |
| 3483 | * @extends pv.Scale.quantitative |
| 3484 | */ |
| 3485 | pv.Scale.log = function() { |
| 3486 | var scale = pv.Scale.quantitative(1, 10), |
| 3487 | b, // logarithm base |
| 3488 | p, // cached Math.log(b) |
| 3489 | /** @ignore */ log = function(x) { return Math.log(x) / p; }, |
| 3490 | /** @ignore */ pow = function(y) { return Math.pow(b, y); }; |
| 3491 | |
| 3492 | /** |
| 3493 | * Returns an array of evenly-spaced, suitably-rounded values in the input |
| 3494 | * domain. These values are frequently used in conjunction with |
| 3495 | * {@link pv.Rule} to display tick marks or grid lines. |
| 3496 | * |
| 3497 | * @function |
| 3498 | * @name pv.Scale.log.prototype.ticks |
| 3499 | * @returns {number[]} an array input domain values to use as ticks. |
| 3500 | */ |
| 3501 | scale.ticks = function() { |
| 3502 | // TODO support non-uniform domains |
| 3503 | var d = scale.domain(), |
| 3504 | n = d[0] < 0, |
| 3505 | i = Math.floor(n ? -log(-d[0]) : log(d[0])), |
| 3506 | j = Math.ceil(n ? -log(-d[1]) : log(d[1])), |
| 3507 | ticks = []; |
| 3508 | if (n) { |
| 3509 | ticks.push(-pow(-i)); |
| 3510 | for (; i++ < j;) for (var k = b - 1; k > 0; k--) ticks.push(-pow(-i) * k); |
| 3511 | } else { |
| 3512 | for (; i < j; i++) for (var k = 1; k < b; k++) ticks.push(pow(i) * k); |
| 3513 | ticks.push(pow(i)); |
| 3514 | } |
| 3515 | for (i = 0; ticks[i] < d[0]; i++); // strip small values |
| 3516 | for (j = ticks.length; ticks[j - 1] > d[1]; j--); // strip big values |
| 3517 | return ticks.slice(i, j); |
| 3518 | }; |
| 3519 | |
| 3520 | /** |
| 3521 | * Formats the specified tick value using the appropriate precision, assuming |
| 3522 | * base 10. |
| 3523 | * |
| 3524 | * @function |
| 3525 | * @name pv.Scale.log.prototype.tickFormat |
| 3526 | * @param {number} t a tick value. |
| 3527 | * @returns {string} a formatted tick value. |
| 3528 | */ |
| 3529 | scale.tickFormat = function(t) { |
| 3530 | return t.toPrecision(1); |
| 3531 | }; |
| 3532 | |
| 3533 | /** |
| 3534 | * "Nices" this scale, extending the bounds of the input domain to |
| 3535 | * evenly-rounded values. This method uses {@link pv.logFloor} and |
| 3536 | * {@link pv.logCeil}. Nicing is useful if the domain is computed dynamically |
| 3537 | * from data, and may be irregular. For example, given a domain of |
| 3538 | * [0.20147987687960267, 0.996679553296417], a call to <tt>nice()</tt> might |
| 3539 | * extend the domain to [0.1, 1]. |
| 3540 | * |
| 3541 | * <p>This method must be invoked each time after setting the domain (and |
| 3542 | * base). |
| 3543 | * |
| 3544 | * @function |
| 3545 | * @name pv.Scale.log.prototype.nice |
| 3546 | * @returns {pv.Scale.log} <tt>this</tt>. |
| 3547 | */ |
| 3548 | scale.nice = function() { |
| 3549 | // TODO support non-uniform domains |
| 3550 | var d = scale.domain(); |
| 3551 | return scale.domain(pv.logFloor(d[0], b), pv.logCeil(d[1], b)); |
| 3552 | }; |
| 3553 | |
| 3554 | /** |
| 3555 | * Sets or gets the logarithm base. Defaults to 10. |
| 3556 | * |
| 3557 | * @function |
| 3558 | * @name pv.Scale.log.prototype.base |
| 3559 | * @param {number} [v] the new base. |
| 3560 | * @returns {pv.Scale.log} <tt>this</tt>, or the current base. |
| 3561 | */ |
| 3562 | scale.base = function(v) { |
| 3563 | if (arguments.length) { |
| 3564 | b = Number(v); |
| 3565 | p = Math.log(b); |
| 3566 | scale.transform(log, pow); // update transformed domain |
| 3567 | return this; |
| 3568 | } |
| 3569 | return b; |
| 3570 | }; |
| 3571 | |
| 3572 | scale.domain.apply(scale, arguments); |
| 3573 | return scale.base(10); |
| 3574 | }; |
| 3575 | /** |
| 3576 | * Returns a root scale for the specified domain. The arguments to this |
| 3577 | * constructor are optional, and equivalent to calling {@link #domain}. |
| 3578 | * The default domain and range are [0,1]. |
| 3579 | * |
| 3580 | * @class Represents a root scale; a function that performs a power |
| 3581 | * transformation. <style type="text/css">sub{line-height:0}</style> Most |
| 3582 | * commonly, a root scale represents a 1-dimensional root transformation from a |
| 3583 | * numeric domain of input data [<i>d<sub>0</sub></i>, <i>d<sub>1</sub></i>] to |
| 3584 | * a numeric range of pixels [<i>r<sub>0</sub></i>, <i>r<sub>1</sub></i>]. |
| 3585 | * |
| 3586 | * <p>Note that the scale is itself a function, and thus can be used as a |
| 3587 | * property directly, assuming that the data associated with a mark is a |
| 3588 | * number. While this is convenient for single-use scales, frequently it is |
| 3589 | * desirable to define scales globally: |
| 3590 | * |
| 3591 | * <pre>var y = pv.Scale.root(0, 100).range(0, 640);</pre> |
| 3592 | * |
| 3593 | * The <tt>y</tt> scale can now be equivalently referenced within a property: |
| 3594 | * |
| 3595 | * <pre> .height(function(d) y(d))</pre> |
| 3596 | * |
| 3597 | * Alternatively, if the data are not simple numbers, the appropriate value can |
| 3598 | * be passed to the <tt>y</tt> scale (e.g., <tt>d.foo</tt>). The {@link #by} |
| 3599 | * method similarly allows the data to be mapped to a numeric value before |
| 3600 | * performing the root transformation. |
| 3601 | * |
| 3602 | * @param {number...} domain... optional domain values. |
| 3603 | * @extends pv.Scale.quantitative |
| 3604 | */ |
| 3605 | pv.Scale.root = function() { |
| 3606 | var scale = pv.Scale.quantitative(); |
| 3607 | |
| 3608 | /** |
| 3609 | * Sets or gets the exponent; defaults to 2. |
| 3610 | * |
| 3611 | * @function |
| 3612 | * @name pv.Scale.root.prototype.power |
| 3613 | * @param {number} [v] the new exponent. |
| 3614 | * @returns {pv.Scale.root} <tt>this</tt>, or the current base. |
| 3615 | */ |
| 3616 | scale.power = function(v) { |
| 3617 | if (arguments.length) { |
| 3618 | var b = Number(v), p = 1 / b; |
| 3619 | scale.transform( |
| 3620 | function(x) { return Math.pow(x, p); }, |
| 3621 | function(y) { return Math.pow(y, b); }); |
| 3622 | return this; |
| 3623 | } |
| 3624 | return b; |
| 3625 | }; |
| 3626 | |
| 3627 | scale.domain.apply(scale, arguments); |
| 3628 | return scale.power(2); |
| 3629 | }; |
| 3630 | /** |
| 3631 | * Returns an ordinal scale for the specified domain. The arguments to this |
| 3632 | * constructor are optional, and equivalent to calling {@link #domain}. |
| 3633 | * |
| 3634 | * @class Represents an ordinal scale. <style |
| 3635 | * type="text/css">sub{line-height:0}</style> An ordinal scale represents a |
| 3636 | * pairwise mapping from <i>n</i> discrete values in the input domain to |
| 3637 | * <i>n</i> discrete values in the output range. For example, an ordinal scale |
| 3638 | * might map a domain of species ["setosa", "versicolor", "virginica"] to colors |
| 3639 | * ["red", "green", "blue"]. Thus, saying |
| 3640 | * |
| 3641 | * <pre> .fillStyle(function(d) { |
| 3642 | * switch (d.species) { |
| 3643 | * case "setosa": return "red"; |
| 3644 | * case "versicolor": return "green"; |
| 3645 | * case "virginica": return "blue"; |
| 3646 | * } |
| 3647 | * })</pre> |
| 3648 | * |
| 3649 | * is equivalent to |
| 3650 | * |
| 3651 | * <pre> .fillStyle(pv.Scale.ordinal("setosa", "versicolor", "virginica") |
| 3652 | * .range("red", "green", "blue") |
| 3653 | * .by(function(d) d.species))</pre> |
| 3654 | * |
| 3655 | * If the mapping from species to color does not need to be specified |
| 3656 | * explicitly, the domain can be omitted. In this case it will be inferred |
| 3657 | * lazily from the data: |
| 3658 | * |
| 3659 | * <pre> .fillStyle(pv.colors("red", "green", "blue") |
| 3660 | * .by(function(d) d.species))</pre> |
| 3661 | * |
| 3662 | * When the domain is inferred, the first time the scale is invoked, the first |
| 3663 | * element from the range will be returned. Subsequent calls with unique values |
| 3664 | * will return subsequent elements from the range. If the inferred domain grows |
| 3665 | * larger than the range, range values will be reused. However, it is strongly |
| 3666 | * recommended that the domain and the range contain the same number of |
| 3667 | * elements. |
| 3668 | * |
| 3669 | * <p>A range can be discretized from a continuous interval (e.g., for pixel |
| 3670 | * positioning) by using {@link #split}, {@link #splitFlush} or |
| 3671 | * {@link #splitBanded} after the domain has been set. For example, if |
| 3672 | * <tt>states</tt> is an array of the fifty U.S. state names, the state name can |
| 3673 | * be encoded in the left position: |
| 3674 | * |
| 3675 | * <pre> .left(pv.Scale.ordinal(states) |
| 3676 | * .split(0, 640) |
| 3677 | * .by(function(d) d.state))</pre> |
| 3678 | * |
| 3679 | * <p>N.B.: ordinal scales are not invertible (at least not yet), since the |
| 3680 | * domain and range and discontinuous. A workaround is to use a linear scale. |
| 3681 | * |
| 3682 | * @param {...} domain... optional domain values. |
| 3683 | * @extends pv.Scale |
| 3684 | * @see pv.colors |
| 3685 | */ |
| 3686 | pv.Scale.ordinal = function() { |
| 3687 | var d = [], i = {}, r = [], band = 0; |
| 3688 | |
| 3689 | /** @private */ |
| 3690 | function scale(x) { |
| 3691 | if (!(x in i)) i[x] = d.push(x) - 1; |
| 3692 | return r[i[x] % r.length]; |
| 3693 | } |
| 3694 | |
| 3695 | /** |
| 3696 | * Sets or gets the input domain. This method can be invoked several ways: |
| 3697 | * |
| 3698 | * <p>1. <tt>domain(values...)</tt> |
| 3699 | * |
| 3700 | * <p>Specifying the domain as a series of values is the most explicit and |
| 3701 | * recommended approach. However, if the domain values are derived from data, |
| 3702 | * you may find the second method more appropriate. |
| 3703 | * |
| 3704 | * <p>2. <tt>domain(array, f)</tt> |
| 3705 | * |
| 3706 | * <p>Rather than enumerating the domain values as explicit arguments to this |
| 3707 | * method, you can specify a single argument of an array. In addition, you can |
| 3708 | * specify an optional accessor function to extract the domain values from the |
| 3709 | * array. |
| 3710 | * |
| 3711 | * <p>3. <tt>domain()</tt> |
| 3712 | * |
| 3713 | * <p>Invoking the <tt>domain</tt> method with no arguments returns the |
| 3714 | * current domain as an array. |
| 3715 | * |
| 3716 | * @function |
| 3717 | * @name pv.Scale.ordinal.prototype.domain |
| 3718 | * @param {...} domain... domain values. |
| 3719 | * @returns {pv.Scale.ordinal} <tt>this</tt>, or the current domain. |
| 3720 | */ |
| 3721 | scale.domain = function(array, f) { |
| 3722 | if (arguments.length) { |
| 3723 | array = (array instanceof Array) |
| 3724 | ? ((arguments.length > 1) ? pv.map(array, f) : array) |
| 3725 | : Array.prototype.slice.call(arguments); |
| 3726 | |
| 3727 | /* Filter the specified ordinals to their unique values. */ |
| 3728 | d = []; |
| 3729 | var seen = {}; |
| 3730 | for (var j = 0; j < array.length; j++) { |
| 3731 | var o = array[j]; |
| 3732 | if (!(o in seen)) { |
| 3733 | seen[o] = true; |
| 3734 | d.push(o); |
| 3735 | } |
| 3736 | } |
| 3737 | |
| 3738 | i = pv.numerate(d); |
| 3739 | return this; |
| 3740 | } |
| 3741 | return d; |
| 3742 | }; |
| 3743 | |
| 3744 | /** |
| 3745 | * Sets or gets the output range. This method can be invoked several ways: |
| 3746 | * |
| 3747 | * <p>1. <tt>range(values...)</tt> |
| 3748 | * |
| 3749 | * <p>Specifying the range as a series of values is the most explicit and |
| 3750 | * recommended approach. However, if the range values are derived from data, |
| 3751 | * you may find the second method more appropriate. |
| 3752 | * |
| 3753 | * <p>2. <tt>range(array, f)</tt> |
| 3754 | * |
| 3755 | * <p>Rather than enumerating the range values as explicit arguments to this |
| 3756 | * method, you can specify a single argument of an array. In addition, you can |
| 3757 | * specify an optional accessor function to extract the range values from the |
| 3758 | * array. |
| 3759 | * |
| 3760 | * <p>3. <tt>range()</tt> |
| 3761 | * |
| 3762 | * <p>Invoking the <tt>range</tt> method with no arguments returns the |
| 3763 | * current range as an array. |
| 3764 | * |
| 3765 | * @function |
| 3766 | * @name pv.Scale.ordinal.prototype.range |
| 3767 | * @param {...} range... range values. |
| 3768 | * @returns {pv.Scale.ordinal} <tt>this</tt>, or the current range. |
| 3769 | */ |
| 3770 | scale.range = function(array, f) { |
| 3771 | if (arguments.length) { |
| 3772 | r = (array instanceof Array) |
| 3773 | ? ((arguments.length > 1) ? pv.map(array, f) : array) |
| 3774 | : Array.prototype.slice.call(arguments); |
| 3775 | if (typeof r[0] == "string") r = r.map(pv.color); |
| 3776 | return this; |
| 3777 | } |
| 3778 | return r; |
| 3779 | }; |
| 3780 | |
| 3781 | /** |
| 3782 | * Sets the range from the given continuous interval. The interval |
| 3783 | * [<i>min</i>, <i>max</i>] is subdivided into <i>n</i> equispaced points, |
| 3784 | * where <i>n</i> is the number of (unique) values in the domain. The first |
| 3785 | * and last point are offset from the edge of the range by half the distance |
| 3786 | * between points. |
| 3787 | * |
| 3788 | * <p>This method must be called <i>after</i> the domain is set. |
| 3789 | * |
| 3790 | * @function |
| 3791 | * @name pv.Scale.ordinal.prototype.split |
| 3792 | * @param {number} min minimum value of the output range. |
| 3793 | * @param {number} max maximum value of the output range. |
| 3794 | * @returns {pv.Scale.ordinal} <tt>this</tt>. |
| 3795 | * @see #splitFlush |
| 3796 | * @see #splitBanded |
| 3797 | */ |
| 3798 | scale.split = function(min, max) { |
| 3799 | var step = (max - min) / this.domain().length; |
| 3800 | r = pv.range(min + step / 2, max, step); |
| 3801 | return this; |
| 3802 | }; |
| 3803 | |
| 3804 | /** |
| 3805 | * Sets the range from the given continuous interval. The interval |
| 3806 | * [<i>min</i>, <i>max</i>] is subdivided into <i>n</i> equispaced points, |
| 3807 | * where <i>n</i> is the number of (unique) values in the domain. The first |
| 3808 | * and last point are exactly on the edge of the range. |
| 3809 | * |
| 3810 | * <p>This method must be called <i>after</i> the domain is set. |
| 3811 | * |
| 3812 | * @function |
| 3813 | * @name pv.Scale.ordinal.prototype.splitFlush |
| 3814 | * @param {number} min minimum value of the output range. |
| 3815 | * @param {number} max maximum value of the output range. |
| 3816 | * @returns {pv.Scale.ordinal} <tt>this</tt>. |
| 3817 | * @see #split |
| 3818 | */ |
| 3819 | scale.splitFlush = function(min, max) { |
| 3820 | var n = this.domain().length, step = (max - min) / (n - 1); |
| 3821 | r = (n == 1) ? [(min + max) / 2] |
| 3822 | : pv.range(min, max + step / 2, step); |
| 3823 | return this; |
| 3824 | }; |
| 3825 | |
| 3826 | /** |
| 3827 | * Sets the range from the given continuous interval. The interval |
| 3828 | * [<i>min</i>, <i>max</i>] is subdivided into <i>n</i> equispaced bands, |
| 3829 | * where <i>n</i> is the number of (unique) values in the domain. The first |
| 3830 | * and last band are offset from the edge of the range by the distance between |
| 3831 | * bands. |
| 3832 | * |
| 3833 | * <p>The band width argument, <tt>band</tt>, is typically in the range [0, 1] |
| 3834 | * and defaults to 1. This fraction corresponds to the amount of space in the |
| 3835 | * range to allocate to the bands, as opposed to padding. A value of 0.5 means |
| 3836 | * that the band width will be equal to the padding width. The computed |
| 3837 | * absolute band width can be retrieved from the range as |
| 3838 | * <tt>scale.range().band</tt>. |
| 3839 | * |
| 3840 | * <p>If the band width argument is negative, this method will allocate bands |
| 3841 | * of a <i>fixed</i> width <tt>-band</tt>, rather than a relative fraction of |
| 3842 | * the available space. |
| 3843 | * |
| 3844 | * <p>Tip: to inset the bands by a fixed amount <tt>p</tt>, specify a minimum |
| 3845 | * value of <tt>min + p</tt> (or simply <tt>p</tt>, if <tt>min</tt> is |
| 3846 | * 0). Then set the mark width to <tt>scale.range().band - p</tt>. |
| 3847 | * |
| 3848 | * <p>This method must be called <i>after</i> the domain is set. |
| 3849 | * |
| 3850 | * @function |
| 3851 | * @name pv.Scale.ordinal.prototype.splitBanded |
| 3852 | * @param {number} min minimum value of the output range. |
| 3853 | * @param {number} max maximum value of the output range. |
| 3854 | * @param {number} [band] the fractional band width in [0, 1]; defaults to 1. |
| 3855 | * @returns {pv.Scale.ordinal} <tt>this</tt>. |
| 3856 | * @see #split |
| 3857 | */ |
| 3858 | scale.splitBanded = function(min, max, band) { |
| 3859 | if (arguments.length < 3) band = 1; |
| 3860 | if (band < 0) { |
| 3861 | var n = this.domain().length, |
| 3862 | total = -band * n, |
| 3863 | remaining = max - min - total, |
| 3864 | padding = remaining / (n + 1); |
| 3865 | r = pv.range(min + padding, max, padding - band); |
| 3866 | r.band = -band; |
| 3867 | } else { |
| 3868 | var step = (max - min) / (this.domain().length + (1 - band)); |
| 3869 | r = pv.range(min + step * (1 - band), max, step); |
| 3870 | r.band = step * band; |
| 3871 | } |
| 3872 | return this; |
| 3873 | }; |
| 3874 | |
| 3875 | /** |
| 3876 | * Returns a view of this scale by the specified accessor function <tt>f</tt>. |
| 3877 | * Given a scale <tt>y</tt>, <tt>y.by(function(d) d.foo)</tt> is equivalent to |
| 3878 | * <tt>function(d) y(d.foo)</tt>. This method should be used judiciously; it |
| 3879 | * is typically more clear to invoke the scale directly, passing in the value |
| 3880 | * to be scaled. |
| 3881 | * |
| 3882 | * @function |
| 3883 | * @name pv.Scale.ordinal.prototype.by |
| 3884 | * @param {function} f an accessor function. |
| 3885 | * @returns {pv.Scale.ordinal} a view of this scale by the specified accessor |
| 3886 | * function. |
| 3887 | */ |
| 3888 | scale.by = function(f) { |
| 3889 | function by() { return scale(f.apply(this, arguments)); } |
| 3890 | for (var method in scale) by[method] = scale[method]; |
| 3891 | return by; |
| 3892 | }; |
| 3893 | |
| 3894 | scale.domain.apply(scale, arguments); |
| 3895 | return scale; |
| 3896 | }; |
| 3897 | /** |
| 3898 | * Constructs a default quantile scale. The arguments to this constructor are |
| 3899 | * optional, and equivalent to calling {@link #domain}. The default domain is |
| 3900 | * the empty set, and the default range is [0,1]. |
| 3901 | * |
| 3902 | * @class Represents a quantile scale; a function that maps from a value within |
| 3903 | * a sortable domain to a quantized numeric range. Typically, the domain is a |
| 3904 | * set of numbers, but any sortable value (such as strings) can be used as the |
| 3905 | * domain of a quantile scale. The range defaults to [0,1], with 0 corresponding |
| 3906 | * to the smallest value in the domain, 1 the largest, .5 the median, etc. |
| 3907 | * |
| 3908 | * <p>By default, the number of quantiles in the range corresponds to the number |
| 3909 | * of values in the domain. The {@link #quantiles} method can be used to specify |
| 3910 | * an explicit number of quantiles; for example, <tt>quantiles(4)</tt> produces |
| 3911 | * a standard quartile scale. A quartile scale's range is a set of four discrete |
| 3912 | * values, such as [0, 1/3, 2/3, 1]. Calling the {@link #range} method will |
| 3913 | * scale these discrete values accordingly, similar to {@link |
| 3914 | * pv.Scale.ordinal#splitFlush}. |
| 3915 | * |
| 3916 | * <p>For example, given the strings ["c", "a", "b"], a default quantile scale: |
| 3917 | * |
| 3918 | * <pre>pv.Scale.quantile("c", "a", "b")</pre> |
| 3919 | * |
| 3920 | * will return 0 for "a", .5 for "b", and 1 for "c". |
| 3921 | * |
| 3922 | * @extends pv.Scale |
| 3923 | */ |
| 3924 | pv.Scale.quantile = function() { |
| 3925 | var n = -1, // number of quantiles |
| 3926 | j = -1, // max quantile index |
| 3927 | q = [], // quantile boundaries |
| 3928 | d = [], // domain |
| 3929 | y = pv.Scale.linear(); // range |
| 3930 | |
| 3931 | /** @private */ |
| 3932 | function scale(x) { |
| 3933 | return y(Math.max(0, Math.min(j, pv.search.index(q, x) - 1)) / j); |
| 3934 | } |
| 3935 | |
| 3936 | /** |
| 3937 | * Sets or gets the quantile boundaries. By default, each element in the |
| 3938 | * domain is in its own quantile. If the argument to this method is a number, |
| 3939 | * it specifies the number of equal-sized quantiles by which to divide the |
| 3940 | * domain. |
| 3941 | * |
| 3942 | * <p>If no arguments are specified, this method returns the quantile |
| 3943 | * boundaries; the first element is always the minimum value of the domain, |
| 3944 | * and the last element is the maximum value of the domain. Thus, the length |
| 3945 | * of the returned array is always one greater than the number of quantiles. |
| 3946 | * |
| 3947 | * @function |
| 3948 | * @name pv.Scale.quantile.prototype.quantiles |
| 3949 | * @param {number} x the number of quantiles. |
| 3950 | */ |
| 3951 | scale.quantiles = function(x) { |
| 3952 | if (arguments.length) { |
| 3953 | n = Number(x); |
| 3954 | if (n < 0) { |
| 3955 | q = [d[0]].concat(d); |
| 3956 | j = d.length - 1; |
| 3957 | } else { |
| 3958 | q = []; |
| 3959 | q[0] = d[0]; |
| 3960 | for (var i = 1; i <= n; i++) { |
| 3961 | q[i] = d[~~(i * (d.length - 1) / n)]; |
| 3962 | } |
| 3963 | j = n - 1; |
| 3964 | } |
| 3965 | return this; |
| 3966 | } |
| 3967 | return q; |
| 3968 | }; |
| 3969 | |
| 3970 | /** |
| 3971 | * Sets or gets the input domain. This method can be invoked several ways: |
| 3972 | * |
| 3973 | * <p>1. <tt>domain(values...)</tt> |
| 3974 | * |
| 3975 | * <p>Specifying the domain as a series of values is the most explicit and |
| 3976 | * recommended approach. However, if the domain values are derived from data, |
| 3977 | * you may find the second method more appropriate. |
| 3978 | * |
| 3979 | * <p>2. <tt>domain(array, f)</tt> |
| 3980 | * |
| 3981 | * <p>Rather than enumerating the domain values as explicit arguments to this |
| 3982 | * method, you can specify a single argument of an array. In addition, you can |
| 3983 | * specify an optional accessor function to extract the domain values from the |
| 3984 | * array. |
| 3985 | * |
| 3986 | * <p>3. <tt>domain()</tt> |
| 3987 | * |
| 3988 | * <p>Invoking the <tt>domain</tt> method with no arguments returns the |
| 3989 | * current domain as an array. |
| 3990 | * |
| 3991 | * @function |
| 3992 | * @name pv.Scale.quantile.prototype.domain |
| 3993 | * @param {...} domain... domain values. |
| 3994 | * @returns {pv.Scale.quantile} <tt>this</tt>, or the current domain. |
| 3995 | */ |
| 3996 | scale.domain = function(array, f) { |
| 3997 | if (arguments.length) { |
| 3998 | d = (array instanceof Array) |
| 3999 | ? pv.map(array, f) |
| 4000 | : Array.prototype.slice.call(arguments); |
| 4001 | d.sort(pv.naturalOrder); |
| 4002 | scale.quantiles(n); // recompute quantiles |
| 4003 | return this; |
| 4004 | } |
| 4005 | return d; |
| 4006 | }; |
| 4007 | |
| 4008 | /** |
| 4009 | * Sets or gets the output range. This method can be invoked several ways: |
| 4010 | * |
| 4011 | * <p>1. <tt>range(min, ..., max)</tt> |
| 4012 | * |
| 4013 | * <p>The range may be specified as a series of numbers or colors. Most |
| 4014 | * commonly, two numbers are specified: the minimum and maximum pixel values. |
| 4015 | * For a color scale, values may be specified as {@link pv.Color}s or |
| 4016 | * equivalent strings. For a diverging scale, or other subdivided non-uniform |
| 4017 | * scales, multiple values can be specified. For example: |
| 4018 | * |
| 4019 | * <pre> .range("red", "white", "green")</pre> |
| 4020 | * |
| 4021 | * <p>Currently, only numbers and colors are supported as range values. The |
| 4022 | * number of range values must exactly match the number of domain values, or |
| 4023 | * the behavior of the scale is undefined. |
| 4024 | * |
| 4025 | * <p>2. <tt>range()</tt> |
| 4026 | * |
| 4027 | * <p>Invoking the <tt>range</tt> method with no arguments returns the current |
| 4028 | * range as an array of numbers or colors. |
| 4029 | * |
| 4030 | * @function |
| 4031 | * @name pv.Scale.quantile.prototype.range |
| 4032 | * @param {...} range... range values. |
| 4033 | * @returns {pv.Scale.quantile} <tt>this</tt>, or the current range. |
| 4034 | */ |
| 4035 | scale.range = function() { |
| 4036 | if (arguments.length) { |
| 4037 | y.range.apply(y, arguments); |
| 4038 | return this; |
| 4039 | } |
| 4040 | return y.range(); |
| 4041 | }; |
| 4042 | |
| 4043 | /** |
| 4044 | * Returns a view of this scale by the specified accessor function <tt>f</tt>. |
| 4045 | * Given a scale <tt>y</tt>, <tt>y.by(function(d) d.foo)</tt> is equivalent to |
| 4046 | * <tt>function(d) y(d.foo)</tt>. |
| 4047 | * |
| 4048 | * <p>This method is provided for convenience, such that scales can be |
| 4049 | * succinctly defined inline. For example, given an array of data elements |
| 4050 | * that have a <tt>score</tt> attribute with the domain [0, 1], the height |
| 4051 | * property could be specified as: |
| 4052 | * |
| 4053 | * <pre>.height(pv.Scale.linear().range(0, 480).by(function(d) d.score))</pre> |
| 4054 | * |
| 4055 | * This is equivalent to: |
| 4056 | * |
| 4057 | * <pre>.height(function(d) d.score * 480)</pre> |
| 4058 | * |
| 4059 | * This method should be used judiciously; it is typically more clear to |
| 4060 | * invoke the scale directly, passing in the value to be scaled. |
| 4061 | * |
| 4062 | * @function |
| 4063 | * @name pv.Scale.quantile.prototype.by |
| 4064 | * @param {function} f an accessor function. |
| 4065 | * @returns {pv.Scale.quantile} a view of this scale by the specified |
| 4066 | * accessor function. |
| 4067 | */ |
| 4068 | scale.by = function(f) { |
| 4069 | function by() { return scale(f.apply(this, arguments)); } |
| 4070 | for (var method in scale) by[method] = scale[method]; |
| 4071 | return by; |
| 4072 | }; |
| 4073 | |
| 4074 | scale.domain.apply(scale, arguments); |
| 4075 | return scale; |
| 4076 | }; |
| 4077 | /** |
| 4078 | * Returns a histogram operator for the specified data, with an optional |
| 4079 | * accessor function. If the data specified is not an array of numbers, an |
| 4080 | * accessor function must be specified to map the data to numeric values. |
| 4081 | * |
| 4082 | * @class Represents a histogram operator. |
| 4083 | * |
| 4084 | * @param {array} data an array of numbers or objects. |
| 4085 | * @param {function} [f] an optional accessor function. |
| 4086 | */ |
| 4087 | pv.histogram = function(data, f) { |
| 4088 | var frequency = true; |
| 4089 | return { |
| 4090 | |
| 4091 | /** |
| 4092 | * Returns the computed histogram bins. An optional array of numbers, |
| 4093 | * <tt>ticks</tt>, may be specified as the break points. If the ticks are |
| 4094 | * not specified, default ticks will be computed using a linear scale on the |
| 4095 | * data domain. |
| 4096 | * |
| 4097 | * <p>The returned array contains {@link pv.histogram.Bin}s. The <tt>x</tt> |
| 4098 | * attribute corresponds to the bin's start value (inclusive), while the |
| 4099 | * <tt>dx</tt> attribute stores the bin size (end - start). The <tt>y</tt> |
| 4100 | * attribute stores either the frequency count or probability, depending on |
| 4101 | * how the histogram operator has been configured. |
| 4102 | * |
| 4103 | * <p>The {@link pv.histogram.Bin} objects are themselves arrays, containing |
| 4104 | * the data elements present in each bin, i.e., the elements in the |
| 4105 | * <tt>data</tt> array (prior to invoking the accessor function, if any). |
| 4106 | * For example, if the data represented countries, and the accessor function |
| 4107 | * returned the GDP of each country, the returned bins would be arrays of |
| 4108 | * countries (not GDPs). |
| 4109 | * |
| 4110 | * @function |
| 4111 | * @name pv.histogram.prototype.bins |
| 4112 | * @param {array} [ticks] |
| 4113 | * @returns {array} |
| 4114 | */ /** @private */ |
| 4115 | bins: function(ticks) { |
| 4116 | var x = pv.map(data, f), bins = []; |
| 4117 | |
| 4118 | /* Initialize default ticks. */ |
| 4119 | if (!arguments.length) ticks = pv.Scale.linear(x).ticks(); |
| 4120 | |
| 4121 | /* Initialize the bins. */ |
| 4122 | for (var i = 0; i < ticks.length - 1; i++) { |
| 4123 | var bin = bins[i] = []; |
| 4124 | bin.x = ticks[i]; |
| 4125 | bin.dx = ticks[i + 1] - ticks[i]; |
| 4126 | bin.y = 0; |
| 4127 | } |
| 4128 | |
| 4129 | /* Count the number of samples per bin. */ |
| 4130 | for (var i = 0; i < x.length; i++) { |
| 4131 | var j = pv.search.index(ticks, x[i]) - 1, |
| 4132 | bin = bins[Math.max(0, Math.min(bins.length - 1, j))]; |
| 4133 | bin.y++; |
| 4134 | bin.push(data[i]); |
| 4135 | } |
| 4136 | |
| 4137 | /* Convert frequencies to probabilities. */ |
| 4138 | if (!frequency) for (var i = 0; i < bins.length; i++) { |
| 4139 | bins[i].y /= x.length; |
| 4140 | } |
| 4141 | |
| 4142 | return bins; |
| 4143 | }, |
| 4144 | |
| 4145 | /** |
| 4146 | * Sets or gets whether this histogram operator returns frequencies or |
| 4147 | * probabilities. |
| 4148 | * |
| 4149 | * @function |
| 4150 | * @name pv.histogram.prototype.frequency |
| 4151 | * @param {boolean} [x] |
| 4152 | * @returns {pv.histogram} this. |
| 4153 | */ /** @private */ |
| 4154 | frequency: function(x) { |
| 4155 | if (arguments.length) { |
| 4156 | frequency = Boolean(x); |
| 4157 | return this; |
| 4158 | } |
| 4159 | return frequency; |
| 4160 | } |
| 4161 | }; |
| 4162 | }; |
| 4163 | |
| 4164 | /** |
| 4165 | * @class Represents a bin returned by the {@link pv.histogram} operator. Bins |
| 4166 | * are themselves arrays containing the data elements present in the given bin |
| 4167 | * (prior to the accessor function being invoked to convert the data object to a |
| 4168 | * numeric value). These bin arrays have additional attributes with meta |
| 4169 | * information about the bin. |
| 4170 | * |
| 4171 | * @name pv.histogram.Bin |
| 4172 | * @extends array |
| 4173 | * @see pv.histogram |
| 4174 | */ |
| 4175 | |
| 4176 | /** |
| 4177 | * The start value of the bin's range. |
| 4178 | * |
| 4179 | * @type number |
| 4180 | * @name pv.histogram.Bin.prototype.x |
| 4181 | */ |
| 4182 | |
| 4183 | /** |
| 4184 | * The magnitude value of the bin's range; end - start. |
| 4185 | * |
| 4186 | * @type number |
| 4187 | * @name pv.histogram.Bin.prototype.dx |
| 4188 | */ |
| 4189 | |
| 4190 | /** |
| 4191 | * The frequency or probability of the bin, depending on how the histogram |
| 4192 | * operator was configured. |
| 4193 | * |
| 4194 | * @type number |
| 4195 | * @name pv.histogram.Bin.prototype.y |
| 4196 | */ |
| 4197 | /** |
| 4198 | * Returns the {@link pv.Color} for the specified color format string. Colors |
| 4199 | * may have an associated opacity, or alpha channel. Color formats are specified |
| 4200 | * by CSS Color Modular Level 3, using either in RGB or HSL color space. For |
| 4201 | * example:<ul> |
| 4202 | * |
| 4203 | * <li>#f00 // #rgb |
| 4204 | * <li>#ff0000 // #rrggbb |
| 4205 | * <li>rgb(255, 0, 0) |
| 4206 | * <li>rgb(100%, 0%, 0%) |
| 4207 | * <li>hsl(0, 100%, 50%) |
| 4208 | * <li>rgba(0, 0, 255, 0.5) |
| 4209 | * <li>hsla(120, 100%, 50%, 1) |
| 4210 | * |
| 4211 | * </ul>The SVG 1.0 color keywords names are also supported, such as "aliceblue" |
| 4212 | * and "yellowgreen". The "transparent" keyword is supported for fully- |
| 4213 | * transparent black. |
| 4214 | * |
| 4215 | * <p>If the <tt>format</tt> argument is already an instance of <tt>Color</tt>, |
| 4216 | * the argument is returned with no further processing. |
| 4217 | * |
| 4218 | * @param {string} format the color specification string, such as "#f00". |
| 4219 | * @returns {pv.Color} the corresponding <tt>Color</tt>. |
| 4220 | * @see <a href="http://www.w3.org/TR/SVG/types.html#ColorKeywords">SVG color |
| 4221 | * keywords</a> |
| 4222 | * @see <a href="http://www.w3.org/TR/css3-color/">CSS3 color module</a> |
| 4223 | */ |
| 4224 | pv.color = function(format) { |
| 4225 | if (format.rgb) return format.rgb(); |
| 4226 | |
| 4227 | /* Handle hsl, rgb. */ |
| 4228 | var m1 = /([a-z]+)\((.*)\)/i.exec(format); |
| 4229 | if (m1) { |
| 4230 | var m2 = m1[2].split(","), a = 1; |
| 4231 | switch (m1[1]) { |
| 4232 | case "hsla": |
| 4233 | case "rgba": { |
| 4234 | a = parseFloat(m2[3]); |
| 4235 | if (!a) return pv.Color.transparent; |
| 4236 | break; |
| 4237 | } |
| 4238 | } |
| 4239 | switch (m1[1]) { |
| 4240 | case "hsla": |
| 4241 | case "hsl": { |
| 4242 | var h = parseFloat(m2[0]), // degrees |
| 4243 | s = parseFloat(m2[1]) / 100, // percentage |
| 4244 | l = parseFloat(m2[2]) / 100; // percentage |
| 4245 | return (new pv.Color.Hsl(h, s, l, a)).rgb(); |
| 4246 | } |
| 4247 | case "rgba": |
| 4248 | case "rgb": { |
| 4249 | function parse(c) { // either integer or percentage |
| 4250 | var f = parseFloat(c); |
| 4251 | return (c[c.length - 1] == '%') ? Math.round(f * 2.55) : f; |
| 4252 | } |
| 4253 | var r = parse(m2[0]), g = parse(m2[1]), b = parse(m2[2]); |
| 4254 | return pv.rgb(r, g, b, a); |
| 4255 | } |
| 4256 | } |
| 4257 | } |
| 4258 | |
| 4259 | /* Named colors. */ |
| 4260 | var named = pv.Color.names[format]; |
| 4261 | if (named) return named; |
| 4262 | |
| 4263 | /* Hexadecimal colors: #rgb and #rrggbb. */ |
| 4264 | if (format.charAt(0) == "#") { |
| 4265 | var r, g, b; |
| 4266 | if (format.length == 4) { |
| 4267 | r = format.charAt(1); r += r; |
| 4268 | g = format.charAt(2); g += g; |
| 4269 | b = format.charAt(3); b += b; |
| 4270 | } else if (format.length == 7) { |
| 4271 | r = format.substring(1, 3); |
| 4272 | g = format.substring(3, 5); |
| 4273 | b = format.substring(5, 7); |
| 4274 | } |
| 4275 | return pv.rgb(parseInt(r, 16), parseInt(g, 16), parseInt(b, 16), 1); |
| 4276 | } |
| 4277 | |
| 4278 | /* Otherwise, pass-through unsupported colors. */ |
| 4279 | return new pv.Color(format, 1); |
| 4280 | }; |
| 4281 | |
| 4282 | /** |
| 4283 | * Constructs a color with the specified color format string and opacity. This |
| 4284 | * constructor should not be invoked directly; use {@link pv.color} instead. |
| 4285 | * |
| 4286 | * @class Represents an abstract (possibly translucent) color. The color is |
| 4287 | * divided into two parts: the <tt>color</tt> attribute, an opaque color format |
| 4288 | * string, and the <tt>opacity</tt> attribute, a float in [0, 1]. The color |
| 4289 | * space is dependent on the implementing class; all colors support the |
| 4290 | * {@link #rgb} method to convert to RGB color space for interpolation. |
| 4291 | * |
| 4292 | * <p>See also the <a href="../../api/Color.html">Color guide</a>. |
| 4293 | * |
| 4294 | * @param {string} color an opaque color format string, such as "#f00". |
| 4295 | * @param {number} opacity the opacity, in [0,1]. |
| 4296 | * @see pv.color |
| 4297 | */ |
| 4298 | pv.Color = function(color, opacity) { |
| 4299 | /** |
| 4300 | * An opaque color format string, such as "#f00". |
| 4301 | * |
| 4302 | * @type string |
| 4303 | * @see <a href="http://www.w3.org/TR/SVG/types.html#ColorKeywords">SVG color |
| 4304 | * keywords</a> |
| 4305 | * @see <a href="http://www.w3.org/TR/css3-color/">CSS3 color module</a> |
| 4306 | */ |
| 4307 | this.color = color; |
| 4308 | |
| 4309 | /** |
| 4310 | * The opacity, a float in [0, 1]. |
| 4311 | * |
| 4312 | * @type number |
| 4313 | */ |
| 4314 | this.opacity = opacity; |
| 4315 | }; |
| 4316 | |
| 4317 | /** |
| 4318 | * Returns a new color that is a brighter version of this color. The behavior of |
| 4319 | * this method may vary slightly depending on the underlying color space. |
| 4320 | * Although brighter and darker are inverse operations, the results of a series |
| 4321 | * of invocations of these two methods might be inconsistent because of rounding |
| 4322 | * errors. |
| 4323 | * |
| 4324 | * @param [k] {number} an optional scale factor; defaults to 1. |
| 4325 | * @see #darker |
| 4326 | * @returns {pv.Color} a brighter color. |
| 4327 | */ |
| 4328 | pv.Color.prototype.brighter = function(k) { |
| 4329 | return this.rgb().brighter(k); |
| 4330 | }; |
| 4331 | |
| 4332 | /** |
| 4333 | * Returns a new color that is a brighter version of this color. The behavior of |
| 4334 | * this method may vary slightly depending on the underlying color space. |
| 4335 | * Although brighter and darker are inverse operations, the results of a series |
| 4336 | * of invocations of these two methods might be inconsistent because of rounding |
| 4337 | * errors. |
| 4338 | * |
| 4339 | * @param [k] {number} an optional scale factor; defaults to 1. |
| 4340 | * @see #brighter |
| 4341 | * @returns {pv.Color} a darker color. |
| 4342 | */ |
| 4343 | pv.Color.prototype.darker = function(k) { |
| 4344 | return this.rgb().darker(k); |
| 4345 | }; |
| 4346 | |
| 4347 | /** |
| 4348 | * Constructs a new RGB color with the specified channel values. |
| 4349 | * |
| 4350 | * @param {number} r the red channel, an integer in [0,255]. |
| 4351 | * @param {number} g the green channel, an integer in [0,255]. |
| 4352 | * @param {number} b the blue channel, an integer in [0,255]. |
| 4353 | * @param {number} [a] the alpha channel, a float in [0,1]. |
| 4354 | * @returns pv.Color.Rgb |
| 4355 | */ |
| 4356 | pv.rgb = function(r, g, b, a) { |
| 4357 | return new pv.Color.Rgb(r, g, b, (arguments.length == 4) ? a : 1); |
| 4358 | }; |
| 4359 | |
| 4360 | /** |
| 4361 | * Constructs a new RGB color with the specified channel values. |
| 4362 | * |
| 4363 | * @class Represents a color in RGB space. |
| 4364 | * |
| 4365 | * @param {number} r the red channel, an integer in [0,255]. |
| 4366 | * @param {number} g the green channel, an integer in [0,255]. |
| 4367 | * @param {number} b the blue channel, an integer in [0,255]. |
| 4368 | * @param {number} a the alpha channel, a float in [0,1]. |
| 4369 | * @extends pv.Color |
| 4370 | */ |
| 4371 | pv.Color.Rgb = function(r, g, b, a) { |
| 4372 | pv.Color.call(this, a ? ("rgb(" + r + "," + g + "," + b + ")") : "none", a); |
| 4373 | |
| 4374 | /** |
| 4375 | * The red channel, an integer in [0, 255]. |
| 4376 | * |
| 4377 | * @type number |
| 4378 | */ |
| 4379 | this.r = r; |
| 4380 | |
| 4381 | /** |
| 4382 | * The green channel, an integer in [0, 255]. |
| 4383 | * |
| 4384 | * @type number |
| 4385 | */ |
| 4386 | this.g = g; |
| 4387 | |
| 4388 | /** |
| 4389 | * The blue channel, an integer in [0, 255]. |
| 4390 | * |
| 4391 | * @type number |
| 4392 | */ |
| 4393 | this.b = b; |
| 4394 | |
| 4395 | /** |
| 4396 | * The alpha channel, a float in [0, 1]. |
| 4397 | * |
| 4398 | * @type number |
| 4399 | */ |
| 4400 | this.a = a; |
| 4401 | }; |
| 4402 | pv.Color.Rgb.prototype = pv.extend(pv.Color); |
| 4403 | |
| 4404 | /** |
| 4405 | * Constructs a new RGB color with the same green, blue and alpha channels as |
| 4406 | * this color, with the specified red channel. |
| 4407 | * |
| 4408 | * @param {number} r the red channel, an integer in [0,255]. |
| 4409 | */ |
| 4410 | pv.Color.Rgb.prototype.red = function(r) { |
| 4411 | return pv.rgb(r, this.g, this.b, this.a); |
| 4412 | }; |
| 4413 | |
| 4414 | /** |
| 4415 | * Constructs a new RGB color with the same red, blue and alpha channels as this |
| 4416 | * color, with the specified green channel. |
| 4417 | * |
| 4418 | * @param {number} g the green channel, an integer in [0,255]. |
| 4419 | */ |
| 4420 | pv.Color.Rgb.prototype.green = function(g) { |
| 4421 | return pv.rgb(this.r, g, this.b, this.a); |
| 4422 | }; |
| 4423 | |
| 4424 | /** |
| 4425 | * Constructs a new RGB color with the same red, green and alpha channels as |
| 4426 | * this color, with the specified blue channel. |
| 4427 | * |
| 4428 | * @param {number} b the blue channel, an integer in [0,255]. |
| 4429 | */ |
| 4430 | pv.Color.Rgb.prototype.blue = function(b) { |
| 4431 | return pv.rgb(this.r, this.g, b, this.a); |
| 4432 | }; |
| 4433 | |
| 4434 | /** |
| 4435 | * Constructs a new RGB color with the same red, green and blue channels as this |
| 4436 | * color, with the specified alpha channel. |
| 4437 | * |
| 4438 | * @param {number} a the alpha channel, a float in [0,1]. |
| 4439 | */ |
| 4440 | pv.Color.Rgb.prototype.alpha = function(a) { |
| 4441 | return pv.rgb(this.r, this.g, this.b, a); |
| 4442 | }; |
| 4443 | |
| 4444 | /** |
| 4445 | * Returns the RGB color equivalent to this color. This method is abstract and |
| 4446 | * must be implemented by subclasses. |
| 4447 | * |
| 4448 | * @returns {pv.Color.Rgb} an RGB color. |
| 4449 | * @function |
| 4450 | * @name pv.Color.prototype.rgb |
| 4451 | */ |
| 4452 | |
| 4453 | /** |
| 4454 | * Returns this. |
| 4455 | * |
| 4456 | * @returns {pv.Color.Rgb} this. |
| 4457 | */ |
| 4458 | pv.Color.Rgb.prototype.rgb = function() { return this; }; |
| 4459 | |
| 4460 | /** |
| 4461 | * Returns a new color that is a brighter version of this color. This method |
| 4462 | * applies an arbitrary scale factor to each of the three RGB components of this |
| 4463 | * color to create a brighter version of this color. Although brighter and |
| 4464 | * darker are inverse operations, the results of a series of invocations of |
| 4465 | * these two methods might be inconsistent because of rounding errors. |
| 4466 | * |
| 4467 | * @param [k] {number} an optional scale factor; defaults to 1. |
| 4468 | * @see #darker |
| 4469 | * @returns {pv.Color.Rgb} a brighter color. |
| 4470 | */ |
| 4471 | pv.Color.Rgb.prototype.brighter = function(k) { |
| 4472 | k = Math.pow(0.7, arguments.length ? k : 1); |
| 4473 | var r = this.r, g = this.g, b = this.b, i = 30; |
| 4474 | if (!r && !g && !b) return pv.rgb(i, i, i, this.a); |
| 4475 | if (r && (r < i)) r = i; |
| 4476 | if (g && (g < i)) g = i; |
| 4477 | if (b && (b < i)) b = i; |
| 4478 | return pv.rgb( |
| 4479 | Math.min(255, Math.floor(r / k)), |
| 4480 | Math.min(255, Math.floor(g / k)), |
| 4481 | Math.min(255, Math.floor(b / k)), |
| 4482 | this.a); |
| 4483 | }; |
| 4484 | |
| 4485 | /** |
| 4486 | * Returns a new color that is a darker version of this color. This method |
| 4487 | * applies an arbitrary scale factor to each of the three RGB components of this |
| 4488 | * color to create a darker version of this color. Although brighter and darker |
| 4489 | * are inverse operations, the results of a series of invocations of these two |
| 4490 | * methods might be inconsistent because of rounding errors. |
| 4491 | * |
| 4492 | * @param [k] {number} an optional scale factor; defaults to 1. |
| 4493 | * @see #brighter |
| 4494 | * @returns {pv.Color.Rgb} a darker color. |
| 4495 | */ |
| 4496 | pv.Color.Rgb.prototype.darker = function(k) { |
| 4497 | k = Math.pow(0.7, arguments.length ? k : 1); |
| 4498 | return pv.rgb( |
| 4499 | Math.max(0, Math.floor(k * this.r)), |
| 4500 | Math.max(0, Math.floor(k * this.g)), |
| 4501 | Math.max(0, Math.floor(k * this.b)), |
| 4502 | this.a); |
| 4503 | }; |
| 4504 | |
| 4505 | /** |
| 4506 | * Constructs a new HSL color with the specified values. |
| 4507 | * |
| 4508 | * @param {number} h the hue, an integer in [0, 360]. |
| 4509 | * @param {number} s the saturation, a float in [0, 1]. |
| 4510 | * @param {number} l the lightness, a float in [0, 1]. |
| 4511 | * @param {number} [a] the opacity, a float in [0, 1]. |
| 4512 | * @returns pv.Color.Hsl |
| 4513 | */ |
| 4514 | pv.hsl = function(h, s, l, a) { |
| 4515 | return new pv.Color.Hsl(h, s, l, (arguments.length == 4) ? a : 1); |
| 4516 | }; |
| 4517 | |
| 4518 | /** |
| 4519 | * Constructs a new HSL color with the specified values. |
| 4520 | * |
| 4521 | * @class Represents a color in HSL space. |
| 4522 | * |
| 4523 | * @param {number} h the hue, an integer in [0, 360]. |
| 4524 | * @param {number} s the saturation, a float in [0, 1]. |
| 4525 | * @param {number} l the lightness, a float in [0, 1]. |
| 4526 | * @param {number} a the opacity, a float in [0, 1]. |
| 4527 | * @extends pv.Color |
| 4528 | */ |
| 4529 | pv.Color.Hsl = function(h, s, l, a) { |
| 4530 | pv.Color.call(this, "hsl(" + h + "," + (s * 100) + "%," + (l * 100) + "%)", a); |
| 4531 | |
| 4532 | /** |
| 4533 | * The hue, an integer in [0, 360]. |
| 4534 | * |
| 4535 | * @type number |
| 4536 | */ |
| 4537 | this.h = h; |
| 4538 | |
| 4539 | /** |
| 4540 | * The saturation, a float in [0, 1]. |
| 4541 | * |
| 4542 | * @type number |
| 4543 | */ |
| 4544 | this.s = s; |
| 4545 | |
| 4546 | /** |
| 4547 | * The lightness, a float in [0, 1]. |
| 4548 | * |
| 4549 | * @type number |
| 4550 | */ |
| 4551 | this.l = l; |
| 4552 | |
| 4553 | /** |
| 4554 | * The opacity, a float in [0, 1]. |
| 4555 | * |
| 4556 | * @type number |
| 4557 | */ |
| 4558 | this.a = a; |
| 4559 | }; |
| 4560 | pv.Color.Hsl.prototype = pv.extend(pv.Color); |
| 4561 | |
| 4562 | /** |
| 4563 | * Constructs a new HSL color with the same saturation, lightness and alpha as |
| 4564 | * this color, and the specified hue. |
| 4565 | * |
| 4566 | * @param {number} h the hue, an integer in [0, 360]. |
| 4567 | */ |
| 4568 | pv.Color.Hsl.prototype.hue = function(h) { |
| 4569 | return pv.hsl(h, this.s, this.l, this.a); |
| 4570 | }; |
| 4571 | |
| 4572 | /** |
| 4573 | * Constructs a new HSL color with the same hue, lightness and alpha as this |
| 4574 | * color, and the specified saturation. |
| 4575 | * |
| 4576 | * @param {number} s the saturation, a float in [0, 1]. |
| 4577 | */ |
| 4578 | pv.Color.Hsl.prototype.saturation = function(s) { |
| 4579 | return pv.hsl(this.h, s, this.l, this.a); |
| 4580 | }; |
| 4581 | |
| 4582 | /** |
| 4583 | * Constructs a new HSL color with the same hue, saturation and alpha as this |
| 4584 | * color, and the specified lightness. |
| 4585 | * |
| 4586 | * @param {number} l the lightness, a float in [0, 1]. |
| 4587 | */ |
| 4588 | pv.Color.Hsl.prototype.lightness = function(l) { |
| 4589 | return pv.hsl(this.h, this.s, l, this.a); |
| 4590 | }; |
| 4591 | |
| 4592 | /** |
| 4593 | * Constructs a new HSL color with the same hue, saturation and lightness as |
| 4594 | * this color, and the specified alpha. |
| 4595 | * |
| 4596 | * @param {number} a the opacity, a float in [0, 1]. |
| 4597 | */ |
| 4598 | pv.Color.Hsl.prototype.alpha = function(a) { |
| 4599 | return pv.hsl(this.h, this.s, this.l, a); |
| 4600 | }; |
| 4601 | |
| 4602 | /** |
| 4603 | * Returns the RGB color equivalent to this HSL color. |
| 4604 | * |
| 4605 | * @returns {pv.Color.Rgb} an RGB color. |
| 4606 | */ |
| 4607 | pv.Color.Hsl.prototype.rgb = function() { |
| 4608 | var h = this.h, s = this.s, l = this.l; |
| 4609 | |
| 4610 | /* Some simple corrections for h, s and l. */ |
| 4611 | h = h % 360; if (h < 0) h += 360; |
| 4612 | s = Math.max(0, Math.min(s, 1)); |
| 4613 | l = Math.max(0, Math.min(l, 1)); |
| 4614 | |
| 4615 | /* From FvD 13.37, CSS Color Module Level 3 */ |
| 4616 | var m2 = (l <= .5) ? (l * (1 + s)) : (l + s - l * s); |
| 4617 | var m1 = 2 * l - m2; |
| 4618 | function v(h) { |
| 4619 | if (h > 360) h -= 360; |
| 4620 | else if (h < 0) h += 360; |
| 4621 | if (h < 60) return m1 + (m2 - m1) * h / 60; |
| 4622 | if (h < 180) return m2; |
| 4623 | if (h < 240) return m1 + (m2 - m1) * (240 - h) / 60; |
| 4624 | return m1; |
| 4625 | } |
| 4626 | function vv(h) { |
| 4627 | return Math.round(v(h) * 255); |
| 4628 | } |
| 4629 | |
| 4630 | return pv.rgb(vv(h + 120), vv(h), vv(h - 120), this.a); |
| 4631 | }; |
| 4632 | |
| 4633 | /** |
| 4634 | * @private SVG color keywords, per CSS Color Module Level 3. |
| 4635 | * |
| 4636 | * @see <a href="http://www.w3.org/TR/SVG/types.html#ColorKeywords">SVG color |
| 4637 | * keywords</a> |
| 4638 | */ |
| 4639 | pv.Color.names = { |
| 4640 | aliceblue: "#f0f8ff", |
| 4641 | antiquewhite: "#faebd7", |
| 4642 | aqua: "#00ffff", |
| 4643 | aquamarine: "#7fffd4", |
| 4644 | azure: "#f0ffff", |
| 4645 | beige: "#f5f5dc", |
| 4646 | bisque: "#ffe4c4", |
| 4647 | black: "#000000", |
| 4648 | blanchedalmond: "#ffebcd", |
| 4649 | blue: "#0000ff", |
| 4650 | blueviolet: "#8a2be2", |
| 4651 | brown: "#a52a2a", |
| 4652 | burlywood: "#deb887", |
| 4653 | cadetblue: "#5f9ea0", |
| 4654 | chartreuse: "#7fff00", |
| 4655 | chocolate: "#d2691e", |
| 4656 | coral: "#ff7f50", |
| 4657 | cornflowerblue: "#6495ed", |
| 4658 | cornsilk: "#fff8dc", |
| 4659 | crimson: "#dc143c", |
| 4660 | cyan: "#00ffff", |
| 4661 | darkblue: "#00008b", |
| 4662 | darkcyan: "#008b8b", |
| 4663 | darkgoldenrod: "#b8860b", |
| 4664 | darkgray: "#a9a9a9", |
| 4665 | darkgreen: "#006400", |
| 4666 | darkgrey: "#a9a9a9", |
| 4667 | darkkhaki: "#bdb76b", |
| 4668 | darkmagenta: "#8b008b", |
| 4669 | darkolivegreen: "#556b2f", |
| 4670 | darkorange: "#ff8c00", |
| 4671 | darkorchid: "#9932cc", |
| 4672 | darkred: "#8b0000", |
| 4673 | darksalmon: "#e9967a", |
| 4674 | darkseagreen: "#8fbc8f", |
| 4675 | darkslateblue: "#483d8b", |
| 4676 | darkslategray: "#2f4f4f", |
| 4677 | darkslategrey: "#2f4f4f", |
| 4678 | darkturquoise: "#00ced1", |
| 4679 | darkviolet: "#9400d3", |
| 4680 | deeppink: "#ff1493", |
| 4681 | deepskyblue: "#00bfff", |
| 4682 | dimgray: "#696969", |
| 4683 | dimgrey: "#696969", |
| 4684 | dodgerblue: "#1e90ff", |
| 4685 | firebrick: "#b22222", |
| 4686 | floralwhite: "#fffaf0", |
| 4687 | forestgreen: "#228b22", |
| 4688 | fuchsia: "#ff00ff", |
| 4689 | gainsboro: "#dcdcdc", |
| 4690 | ghostwhite: "#f8f8ff", |
| 4691 | gold: "#ffd700", |
| 4692 | goldenrod: "#daa520", |
| 4693 | gray: "#808080", |
| 4694 | green: "#008000", |
| 4695 | greenyellow: "#adff2f", |
| 4696 | grey: "#808080", |
| 4697 | honeydew: "#f0fff0", |
| 4698 | hotpink: "#ff69b4", |
| 4699 | indianred: "#cd5c5c", |
| 4700 | indigo: "#4b0082", |
| 4701 | ivory: "#fffff0", |
| 4702 | khaki: "#f0e68c", |
| 4703 | lavender: "#e6e6fa", |
| 4704 | lavenderblush: "#fff0f5", |
| 4705 | lawngreen: "#7cfc00", |
| 4706 | lemonchiffon: "#fffacd", |
| 4707 | lightblue: "#add8e6", |
| 4708 | lightcoral: "#f08080", |
| 4709 | lightcyan: "#e0ffff", |
| 4710 | lightgoldenrodyellow: "#fafad2", |
| 4711 | lightgray: "#d3d3d3", |
| 4712 | lightgreen: "#90ee90", |
| 4713 | lightgrey: "#d3d3d3", |
| 4714 | lightpink: "#ffb6c1", |
| 4715 | lightsalmon: "#ffa07a", |
| 4716 | lightseagreen: "#20b2aa", |
| 4717 | lightskyblue: "#87cefa", |
| 4718 | lightslategray: "#778899", |
| 4719 | lightslategrey: "#778899", |
| 4720 | lightsteelblue: "#b0c4de", |
| 4721 | lightyellow: "#ffffe0", |
| 4722 | lime: "#00ff00", |
| 4723 | limegreen: "#32cd32", |
| 4724 | linen: "#faf0e6", |
| 4725 | magenta: "#ff00ff", |
| 4726 | maroon: "#800000", |
| 4727 | mediumaquamarine: "#66cdaa", |
| 4728 | mediumblue: "#0000cd", |
| 4729 | mediumorchid: "#ba55d3", |
| 4730 | mediumpurple: "#9370db", |
| 4731 | mediumseagreen: "#3cb371", |
| 4732 | mediumslateblue: "#7b68ee", |
| 4733 | mediumspringgreen: "#00fa9a", |
| 4734 | mediumturquoise: "#48d1cc", |
| 4735 | mediumvioletred: "#c71585", |
| 4736 | midnightblue: "#191970", |
| 4737 | mintcream: "#f5fffa", |
| 4738 | mistyrose: "#ffe4e1", |
| 4739 | moccasin: "#ffe4b5", |
| 4740 | navajowhite: "#ffdead", |
| 4741 | navy: "#000080", |
| 4742 | oldlace: "#fdf5e6", |
| 4743 | olive: "#808000", |
| 4744 | olivedrab: "#6b8e23", |
| 4745 | orange: "#ffa500", |
| 4746 | orangered: "#ff4500", |
| 4747 | orchid: "#da70d6", |
| 4748 | palegoldenrod: "#eee8aa", |
| 4749 | palegreen: "#98fb98", |
| 4750 | paleturquoise: "#afeeee", |
| 4751 | palevioletred: "#db7093", |
| 4752 | papayawhip: "#ffefd5", |
| 4753 | peachpuff: "#ffdab9", |
| 4754 | peru: "#cd853f", |
| 4755 | pink: "#ffc0cb", |
| 4756 | plum: "#dda0dd", |
| 4757 | powderblue: "#b0e0e6", |
| 4758 | purple: "#800080", |
| 4759 | red: "#ff0000", |
| 4760 | rosybrown: "#bc8f8f", |
| 4761 | royalblue: "#4169e1", |
| 4762 | saddlebrown: "#8b4513", |
| 4763 | salmon: "#fa8072", |
| 4764 | sandybrown: "#f4a460", |
| 4765 | seagreen: "#2e8b57", |
| 4766 | seashell: "#fff5ee", |
| 4767 | sienna: "#a0522d", |
| 4768 | silver: "#c0c0c0", |
| 4769 | skyblue: "#87ceeb", |
| 4770 | slateblue: "#6a5acd", |
| 4771 | slategray: "#708090", |
| 4772 | slategrey: "#708090", |
| 4773 | snow: "#fffafa", |
| 4774 | springgreen: "#00ff7f", |
| 4775 | steelblue: "#4682b4", |
| 4776 | tan: "#d2b48c", |
| 4777 | teal: "#008080", |
| 4778 | thistle: "#d8bfd8", |
| 4779 | tomato: "#ff6347", |
| 4780 | turquoise: "#40e0d0", |
| 4781 | violet: "#ee82ee", |
| 4782 | wheat: "#f5deb3", |
| 4783 | white: "#ffffff", |
| 4784 | whitesmoke: "#f5f5f5", |
| 4785 | yellow: "#ffff00", |
| 4786 | yellowgreen: "#9acd32", |
| 4787 | transparent: pv.Color.transparent = pv.rgb(0, 0, 0, 0) |
| 4788 | }; |
| 4789 | |
| 4790 | /* Initialized named colors. */ |
| 4791 | (function() { |
| 4792 | var names = pv.Color.names; |
| 4793 | for (var name in names) names[name] = pv.color(names[name]); |
| 4794 | })(); |
| 4795 | /** |
| 4796 | * Returns a new categorical color encoding using the specified colors. The |
| 4797 | * arguments to this method are an array of colors; see {@link pv.color}. For |
| 4798 | * example, to create a categorical color encoding using the <tt>species</tt> |
| 4799 | * attribute: |
| 4800 | * |
| 4801 | * <pre>pv.colors("red", "green", "blue").by(function(d) d.species)</pre> |
| 4802 | * |
| 4803 | * The result of this expression can be used as a fill- or stroke-style |
| 4804 | * property. This assumes that the data's <tt>species</tt> attribute is a |
| 4805 | * string. |
| 4806 | * |
| 4807 | * @param {string} colors... categorical colors. |
| 4808 | * @see pv.Scale.ordinal |
| 4809 | * @returns {pv.Scale.ordinal} an ordinal color scale. |
| 4810 | */ |
| 4811 | pv.colors = function() { |
| 4812 | var scale = pv.Scale.ordinal(); |
| 4813 | scale.range.apply(scale, arguments); |
| 4814 | return scale; |
| 4815 | }; |
| 4816 | |
| 4817 | /** |
| 4818 | * A collection of standard color palettes for categorical encoding. |
| 4819 | * |
| 4820 | * @namespace A collection of standard color palettes for categorical encoding. |
| 4821 | */ |
| 4822 | pv.Colors = {}; |
| 4823 | |
| 4824 | /** |
| 4825 | * Returns a new 10-color scheme. The arguments to this constructor are |
| 4826 | * optional, and equivalent to calling {@link pv.Scale.OrdinalScale#domain}. The |
| 4827 | * following colors are used: |
| 4828 | * |
| 4829 | * <div style="background:#1f77b4;">#1f77b4</div> |
| 4830 | * <div style="background:#ff7f0e;">#ff7f0e</div> |
| 4831 | * <div style="background:#2ca02c;">#2ca02c</div> |
| 4832 | * <div style="background:#d62728;">#d62728</div> |
| 4833 | * <div style="background:#9467bd;">#9467bd</div> |
| 4834 | * <div style="background:#8c564b;">#8c564b</div> |
| 4835 | * <div style="background:#e377c2;">#e377c2</div> |
| 4836 | * <div style="background:#7f7f7f;">#7f7f7f</div> |
| 4837 | * <div style="background:#bcbd22;">#bcbd22</div> |
| 4838 | * <div style="background:#17becf;">#17becf</div> |
| 4839 | * |
| 4840 | * @param {number...} domain... domain values. |
| 4841 | * @returns {pv.Scale.ordinal} a new ordinal color scale. |
| 4842 | * @see pv.color |
| 4843 | */ |
| 4844 | pv.Colors.category10 = function() { |
| 4845 | var scale = pv.colors( |
| 4846 | "#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", |
| 4847 | "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"); |
| 4848 | scale.domain.apply(scale, arguments); |
| 4849 | return scale; |
| 4850 | }; |
| 4851 | |
| 4852 | /** |
| 4853 | * Returns a new 20-color scheme. The arguments to this constructor are |
| 4854 | * optional, and equivalent to calling {@link pv.Scale.OrdinalScale#domain}. The |
| 4855 | * following colors are used: |
| 4856 | * |
| 4857 | * <div style="background:#1f77b4;">#1f77b4</div> |
| 4858 | * <div style="background:#aec7e8;">#aec7e8</div> |
| 4859 | * <div style="background:#ff7f0e;">#ff7f0e</div> |
| 4860 | * <div style="background:#ffbb78;">#ffbb78</div> |
| 4861 | * <div style="background:#2ca02c;">#2ca02c</div> |
| 4862 | * <div style="background:#98df8a;">#98df8a</div> |
| 4863 | * <div style="background:#d62728;">#d62728</div> |
| 4864 | * <div style="background:#ff9896;">#ff9896</div> |
| 4865 | * <div style="background:#9467bd;">#9467bd</div> |
| 4866 | * <div style="background:#c5b0d5;">#c5b0d5</div> |
| 4867 | * <div style="background:#8c564b;">#8c564b</div> |
| 4868 | * <div style="background:#c49c94;">#c49c94</div> |
| 4869 | * <div style="background:#e377c2;">#e377c2</div> |
| 4870 | * <div style="background:#f7b6d2;">#f7b6d2</div> |
| 4871 | * <div style="background:#7f7f7f;">#7f7f7f</div> |
| 4872 | * <div style="background:#c7c7c7;">#c7c7c7</div> |
| 4873 | * <div style="background:#bcbd22;">#bcbd22</div> |
| 4874 | * <div style="background:#dbdb8d;">#dbdb8d</div> |
| 4875 | * <div style="background:#17becf;">#17becf</div> |
| 4876 | * <div style="background:#9edae5;">#9edae5</div> |
| 4877 | * |
| 4878 | * @param {number...} domain... domain values. |
| 4879 | * @returns {pv.Scale.ordinal} a new ordinal color scale. |
| 4880 | * @see pv.color |
| 4881 | */ |
| 4882 | pv.Colors.category20 = function() { |
| 4883 | var scale = pv.colors( |
| 4884 | "#1f77b4", "#aec7e8", "#ff7f0e", "#ffbb78", "#2ca02c", |
| 4885 | "#98df8a", "#d62728", "#ff9896", "#9467bd", "#c5b0d5", |
| 4886 | "#8c564b", "#c49c94", "#e377c2", "#f7b6d2", "#7f7f7f", |
| 4887 | "#c7c7c7", "#bcbd22", "#dbdb8d", "#17becf", "#9edae5"); |
| 4888 | scale.domain.apply(scale, arguments); |
| 4889 | return scale; |
| 4890 | }; |
| 4891 | |
| 4892 | /** |
| 4893 | * Returns a new alternative 19-color scheme. The arguments to this constructor |
| 4894 | * are optional, and equivalent to calling |
| 4895 | * {@link pv.Scale.OrdinalScale#domain}. The following colors are used: |
| 4896 | * |
| 4897 | * <div style="background:#9c9ede;">#9c9ede</div> |
| 4898 | * <div style="background:#7375b5;">#7375b5</div> |
| 4899 | * <div style="background:#4a5584;">#4a5584</div> |
| 4900 | * <div style="background:#cedb9c;">#cedb9c</div> |
| 4901 | * <div style="background:#b5cf6b;">#b5cf6b</div> |
| 4902 | * <div style="background:#8ca252;">#8ca252</div> |
| 4903 | * <div style="background:#637939;">#637939</div> |
| 4904 | * <div style="background:#e7cb94;">#e7cb94</div> |
| 4905 | * <div style="background:#e7ba52;">#e7ba52</div> |
| 4906 | * <div style="background:#bd9e39;">#bd9e39</div> |
| 4907 | * <div style="background:#8c6d31;">#8c6d31</div> |
| 4908 | * <div style="background:#e7969c;">#e7969c</div> |
| 4909 | * <div style="background:#d6616b;">#d6616b</div> |
| 4910 | * <div style="background:#ad494a;">#ad494a</div> |
| 4911 | * <div style="background:#843c39;">#843c39</div> |
| 4912 | * <div style="background:#de9ed6;">#de9ed6</div> |
| 4913 | * <div style="background:#ce6dbd;">#ce6dbd</div> |
| 4914 | * <div style="background:#a55194;">#a55194</div> |
| 4915 | * <div style="background:#7b4173;">#7b4173</div> |
| 4916 | * |
| 4917 | * @param {number...} domain... domain values. |
| 4918 | * @returns {pv.Scale.ordinal} a new ordinal color scale. |
| 4919 | * @see pv.color |
| 4920 | */ |
| 4921 | pv.Colors.category19 = function() { |
| 4922 | var scale = pv.colors( |
| 4923 | "#9c9ede", "#7375b5", "#4a5584", "#cedb9c", "#b5cf6b", |
| 4924 | "#8ca252", "#637939", "#e7cb94", "#e7ba52", "#bd9e39", |
| 4925 | "#8c6d31", "#e7969c", "#d6616b", "#ad494a", "#843c39", |
| 4926 | "#de9ed6", "#ce6dbd", "#a55194", "#7b4173"); |
| 4927 | scale.domain.apply(scale, arguments); |
| 4928 | return scale; |
| 4929 | }; |
| 4930 | /** |
| 4931 | * Returns a linear color ramp from the specified <tt>start</tt> color to the |
| 4932 | * specified <tt>end</tt> color. The color arguments may be specified either as |
| 4933 | * <tt>string</tt>s or as {@link pv.Color}s. This is equivalent to: |
| 4934 | * |
| 4935 | * <pre> pv.Scale.linear().domain(0, 1).range(...)</pre> |
| 4936 | * |
| 4937 | * @param {string} start the start color; may be a <tt>pv.Color</tt>. |
| 4938 | * @param {string} end the end color; may be a <tt>pv.Color</tt>. |
| 4939 | * @returns {Function} a color ramp from <tt>start</tt> to <tt>end</tt>. |
| 4940 | * @see pv.Scale.linear |
| 4941 | */ |
| 4942 | pv.ramp = function(start, end) { |
| 4943 | var scale = pv.Scale.linear(); |
| 4944 | scale.range.apply(scale, arguments); |
| 4945 | return scale; |
| 4946 | }; |
| 4947 | /** |
| 4948 | * @private |
| 4949 | * @namespace |
| 4950 | */ |
| 4951 | pv.Scene = pv.SvgScene = { |
| 4952 | /* Various namespaces. */ |
| 4953 | svg: "http://www.w3.org/2000/svg", |
| 4954 | xmlns: "http://www.w3.org/2000/xmlns", |
| 4955 | xlink: "http://www.w3.org/1999/xlink", |
| 4956 | xhtml: "http://www.w3.org/1999/xhtml", |
| 4957 | |
| 4958 | /** The pre-multipled scale, based on any enclosing transforms. */ |
| 4959 | scale: 1, |
| 4960 | |
| 4961 | /** The set of supported events. */ |
| 4962 | events: [ |
| 4963 | "DOMMouseScroll", // for Firefox |
| 4964 | "mousewheel", |
| 4965 | "mousedown", |
| 4966 | "mouseup", |
| 4967 | "mouseover", |
| 4968 | "mouseout", |
| 4969 | "mousemove", |
| 4970 | "click", |
| 4971 | "dblclick" |
| 4972 | ], |
| 4973 | |
| 4974 | /** Implicit values for SVG and CSS properties. */ |
| 4975 | implicit: { |
| 4976 | svg: { |
| 4977 | "shape-rendering": "auto", |
| 4978 | "pointer-events": "painted", |
| 4979 | "x": 0, |
| 4980 | "y": 0, |
| 4981 | "dy": 0, |
| 4982 | "text-anchor": "start", |
| 4983 | "transform": "translate(0,0)", |
| 4984 | "fill": "none", |
| 4985 | "fill-opacity": 1, |
| 4986 | "stroke": "none", |
| 4987 | "stroke-opacity": 1, |
| 4988 | "stroke-width": 1.5, |
| 4989 | "stroke-linejoin": "miter" |
| 4990 | }, |
| 4991 | css: { |
| 4992 | "font": "10px sans-serif" |
| 4993 | } |
| 4994 | } |
| 4995 | }; |
| 4996 | |
| 4997 | /** |
| 4998 | * Updates the display for the specified array of scene nodes. |
| 4999 | * |
| 5000 | * @param scenes {array} an array of scene nodes. |
| 5001 | */ |
| 5002 | pv.SvgScene.updateAll = function(scenes) { |
| 5003 | if (scenes.length |
| 5004 | && scenes[0].reverse |
| 5005 | && (scenes.type != "line") |
| 5006 | && (scenes.type != "area")) { |
| 5007 | var reversed = pv.extend(scenes); |
| 5008 | for (var i = 0, j = scenes.length - 1; j >= 0; i++, j--) { |
| 5009 | reversed[i] = scenes[j]; |
| 5010 | } |
| 5011 | scenes = reversed; |
| 5012 | } |
| 5013 | this.removeSiblings(this[scenes.type](scenes)); |
| 5014 | }; |
| 5015 | |
| 5016 | /** |
| 5017 | * Creates a new SVG element of the specified type. |
| 5018 | * |
| 5019 | * @param type {string} an SVG element type, such as "rect". |
| 5020 | * @returns a new SVG element. |
| 5021 | */ |
| 5022 | pv.SvgScene.create = function(type) { |
| 5023 | return document.createElementNS(this.svg, type); |
| 5024 | }; |
| 5025 | |
| 5026 | /** |
| 5027 | * Expects the element <i>e</i> to be the specified type. If the element does |
| 5028 | * not exist, a new one is created. If the element does exist but is the wrong |
| 5029 | * type, it is replaced with the specified element. |
| 5030 | * |
| 5031 | * @param e the current SVG element. |
| 5032 | * @param type {string} an SVG element type, such as "rect". |
| 5033 | * @param attributes an optional attribute map. |
| 5034 | * @param style an optional style map. |
| 5035 | * @returns a new SVG element. |
| 5036 | */ |
| 5037 | pv.SvgScene.expect = function(e, type, attributes, style) { |
| 5038 | if (e) { |
| 5039 | if (e.tagName == "a") e = e.firstChild; |
| 5040 | if (e.tagName != type) { |
| 5041 | var n = this.create(type); |
| 5042 | e.parentNode.replaceChild(n, e); |
| 5043 | e = n; |
| 5044 | } |
| 5045 | } else { |
| 5046 | e = this.create(type); |
| 5047 | } |
| 5048 | for (var name in attributes) { |
| 5049 | var value = attributes[name]; |
| 5050 | if (value == this.implicit.svg[name]) value = null; |
| 5051 | if (value == null) e.removeAttribute(name); |
| 5052 | else e.setAttribute(name, value); |
| 5053 | } |
| 5054 | for (var name in style) { |
| 5055 | var value = style[name]; |
| 5056 | if (value == this.implicit.css[name]) value = null; |
| 5057 | if (value == null) e.style.removeProperty(name); |
| 5058 | else e.style[name] = value; |
| 5059 | } |
| 5060 | return e; |
| 5061 | }; |
| 5062 | |
| 5063 | /** TODO */ |
| 5064 | pv.SvgScene.append = function(e, scenes, index) { |
| 5065 | e.$scene = {scenes:scenes, index:index}; |
| 5066 | e = this.title(e, scenes[index]); |
| 5067 | if (!e.parentNode) scenes.$g.appendChild(e); |
| 5068 | return e.nextSibling; |
| 5069 | }; |
| 5070 | |
| 5071 | /** |
| 5072 | * Applies a title tooltip to the specified element <tt>e</tt>, using the |
| 5073 | * <tt>title</tt> property of the specified scene node <tt>s</tt>. Note that |
| 5074 | * this implementation does not create an SVG <tt>title</tt> element as a child |
| 5075 | * of <tt>e</tt>; although this is the recommended standard, it is only |
| 5076 | * supported in Opera. Instead, an anchor element is created around the element |
| 5077 | * <tt>e</tt>, and the <tt>xlink:title</tt> attribute is set accordingly. |
| 5078 | * |
| 5079 | * @param e an SVG element. |
| 5080 | * @param s a scene node. |
| 5081 | */ |
| 5082 | pv.SvgScene.title = function(e, s) { |
| 5083 | var a = e.parentNode; |
| 5084 | if (a && (a.tagName != "a")) a = null; |
| 5085 | if (s.title) { |
| 5086 | if (!a) { |
| 5087 | a = this.create("a"); |
| 5088 | if (e.parentNode) e.parentNode.replaceChild(a, e); |
| 5089 | a.appendChild(e); |
| 5090 | } |
| 5091 | a.setAttributeNS(this.xlink, "title", s.title); |
| 5092 | return a; |
| 5093 | } |
| 5094 | if (a) a.parentNode.replaceChild(e, a); |
| 5095 | return e; |
| 5096 | }; |
| 5097 | |
| 5098 | /** TODO */ |
| 5099 | pv.SvgScene.dispatch = pv.listener(function(e) { |
| 5100 | var t = e.target.$scene; |
| 5101 | if (t) { |
| 5102 | var type = e.type; |
| 5103 | |
| 5104 | /* Fixes for mousewheel support on Firefox & Opera. */ |
| 5105 | switch (type) { |
| 5106 | case "DOMMouseScroll": { |
| 5107 | type = "mousewheel"; |
| 5108 | e.wheel = -480 * e.detail; |
| 5109 | break; |
| 5110 | } |
| 5111 | case "mousewheel": { |
| 5112 | e.wheel = (window.opera ? 12 : 1) * e.wheelDelta; |
| 5113 | break; |
| 5114 | } |
| 5115 | } |
| 5116 | |
| 5117 | if (pv.Mark.dispatch(type, t.scenes, t.index)) e.preventDefault(); |
| 5118 | } |
| 5119 | }); |
| 5120 | |
| 5121 | /** @private Remove siblings following element <i>e</i>. */ |
| 5122 | pv.SvgScene.removeSiblings = function(e) { |
| 5123 | while (e) { |
| 5124 | var n = e.nextSibling; |
| 5125 | e.parentNode.removeChild(e); |
| 5126 | e = n; |
| 5127 | } |
| 5128 | }; |
| 5129 | |
| 5130 | /** @private Do nothing when rendering undefined mark types. */ |
| 5131 | pv.SvgScene.undefined = function() {}; |
| 5132 | /** |
| 5133 | * @private Converts the specified b-spline curve segment to a bezier curve |
| 5134 | * compatible with SVG "C". |
| 5135 | * |
| 5136 | * @param p0 the first control point. |
| 5137 | * @param p1 the second control point. |
| 5138 | * @param p2 the third control point. |
| 5139 | * @param p3 the fourth control point. |
| 5140 | */ |
| 5141 | pv.SvgScene.pathBasis = (function() { |
| 5142 | |
| 5143 | /** |
| 5144 | * Matrix to transform basis (b-spline) control points to bezier control |
| 5145 | * points. Derived from FvD 11.2.8. |
| 5146 | */ |
| 5147 | var basis = [ |
| 5148 | [ 1/6, 2/3, 1/6, 0 ], |
| 5149 | [ 0, 2/3, 1/3, 0 ], |
| 5150 | [ 0, 1/3, 2/3, 0 ], |
| 5151 | [ 0, 1/6, 2/3, 1/6 ] |
| 5152 | ]; |
| 5153 | |
| 5154 | /** |
| 5155 | * Returns the point that is the weighted sum of the specified control points, |
| 5156 | * using the specified weights. This method requires that there are four |
| 5157 | * weights and four control points. |
| 5158 | */ |
| 5159 | function weight(w, p0, p1, p2, p3) { |
| 5160 | return { |
| 5161 | x: w[0] * p0.left + w[1] * p1.left + w[2] * p2.left + w[3] * p3.left, |
| 5162 | y: w[0] * p0.top + w[1] * p1.top + w[2] * p2.top + w[3] * p3.top |
| 5163 | }; |
| 5164 | } |
| 5165 | |
| 5166 | var convert = function(p0, p1, p2, p3) { |
| 5167 | var b1 = weight(basis[1], p0, p1, p2, p3), |
| 5168 | b2 = weight(basis[2], p0, p1, p2, p3), |
| 5169 | b3 = weight(basis[3], p0, p1, p2, p3); |
| 5170 | return "C" + b1.x + "," + b1.y |
| 5171 | + "," + b2.x + "," + b2.y |
| 5172 | + "," + b3.x + "," + b3.y; |
| 5173 | }; |
| 5174 | |
| 5175 | convert.segment = function(p0, p1, p2, p3) { |
| 5176 | var b0 = weight(basis[0], p0, p1, p2, p3), |
| 5177 | b1 = weight(basis[1], p0, p1, p2, p3), |
| 5178 | b2 = weight(basis[2], p0, p1, p2, p3), |
| 5179 | b3 = weight(basis[3], p0, p1, p2, p3); |
| 5180 | return "M" + b0.x + "," + b0.y |
| 5181 | + "C" + b1.x + "," + b1.y |
| 5182 | + "," + b2.x + "," + b2.y |
| 5183 | + "," + b3.x + "," + b3.y; |
| 5184 | }; |
| 5185 | |
| 5186 | return convert; |
| 5187 | })(); |
| 5188 | |
| 5189 | /** |
| 5190 | * @private Interpolates the given points using the basis spline interpolation. |
| 5191 | * Returns an SVG path without the leading M instruction to allow path |
| 5192 | * appending. |
| 5193 | * |
| 5194 | * @param points the array of points. |
| 5195 | */ |
| 5196 | pv.SvgScene.curveBasis = function(points) { |
| 5197 | if (points.length <= 2) return ""; |
| 5198 | var path = "", |
| 5199 | p0 = points[0], |
| 5200 | p1 = p0, |
| 5201 | p2 = p0, |
| 5202 | p3 = points[1]; |
| 5203 | path += this.pathBasis(p0, p1, p2, p3); |
| 5204 | for (var i = 2; i < points.length; i++) { |
| 5205 | p0 = p1; |
| 5206 | p1 = p2; |
| 5207 | p2 = p3; |
| 5208 | p3 = points[i]; |
| 5209 | path += this.pathBasis(p0, p1, p2, p3); |
| 5210 | } |
| 5211 | /* Cycle through to get the last point. */ |
| 5212 | path += this.pathBasis(p1, p2, p3, p3); |
| 5213 | path += this.pathBasis(p2, p3, p3, p3); |
| 5214 | return path; |
| 5215 | }; |
| 5216 | |
| 5217 | /** |
| 5218 | * @private Interpolates the given points using the basis spline interpolation. |
| 5219 | * If points.length == tangents.length then a regular Hermite interpolation is |
| 5220 | * performed, if points.length == tangents.length + 2 then the first and last |
| 5221 | * segments are filled in with cubic bazier segments. Returns an array of path |
| 5222 | * strings. |
| 5223 | * |
| 5224 | * @param points the array of points. |
| 5225 | */ |
| 5226 | pv.SvgScene.curveBasisSegments = function(points) { |
| 5227 | if (points.length <= 2) return ""; |
| 5228 | var paths = [], |
| 5229 | p0 = points[0], |
| 5230 | p1 = p0, |
| 5231 | p2 = p0, |
| 5232 | p3 = points[1], |
| 5233 | firstPath = this.pathBasis.segment(p0, p1, p2, p3); |
| 5234 | |
| 5235 | p0 = p1; |
| 5236 | p1 = p2; |
| 5237 | p2 = p3; |
| 5238 | p3 = points[2]; |
| 5239 | paths.push(firstPath + this.pathBasis(p0, p1, p2, p3)); // merge first & second path |
| 5240 | for (var i = 3; i < points.length; i++) { |
| 5241 | p0 = p1; |
| 5242 | p1 = p2; |
| 5243 | p2 = p3; |
| 5244 | p3 = points[i]; |
| 5245 | paths.push(this.pathBasis.segment(p0, p1, p2, p3)); |
| 5246 | } |
| 5247 | |
| 5248 | // merge last & second-to-last path |
| 5249 | paths.push(this.pathBasis.segment(p1, p2, p3, p3) + this.pathBasis(p2, p3, p3, p3)); |
| 5250 | return paths; |
| 5251 | }; |
| 5252 | |
| 5253 | /** |
| 5254 | * @private Interpolates the given points with respective tangents using the cubic |
| 5255 | * Hermite spline interpolation. If points.length == tangents.length then a regular |
| 5256 | * Hermite interpolation is performed, if points.length == tangents.length + 2 then |
| 5257 | * the first and last segments are filled in with cubic bazier segments. |
| 5258 | * Returns an SVG path without the leading M instruction to allow path appending. |
| 5259 | * |
| 5260 | * @param points the array of points. |
| 5261 | * @param tangents the array of tangent vectors. |
| 5262 | */ |
| 5263 | pv.SvgScene.curveHermite = function(points, tangents) { |
| 5264 | if (tangents.length < 1 |
| 5265 | || (points.length != tangents.length |
| 5266 | && points.length != tangents.length + 2)) return ""; |
| 5267 | var quad = points.length != tangents.length, |
| 5268 | path = "", |
| 5269 | p0 = points[0], |
| 5270 | p = points[1], |
| 5271 | t0 = tangents[0], |
| 5272 | t = t0, |
| 5273 | pi = 1; |
| 5274 | |
| 5275 | if (quad) { |
| 5276 | path += "Q" + (p.left - t0.x * 2 / 3) + "," + (p.top - t0.y * 2 / 3) |
| 5277 | + "," + p.left + "," + p.top; |
| 5278 | p0 = points[1]; |
| 5279 | pi = 2; |
| 5280 | } |
| 5281 | |
| 5282 | if (tangents.length > 1) { |
| 5283 | t = tangents[1]; |
| 5284 | p = points[pi]; |
| 5285 | pi++; |
| 5286 | path += "C" + (p0.left + t0.x) + "," + (p0.top + t0.y) |
| 5287 | + "," + (p.left - t.x) + "," + (p.top - t.y) |
| 5288 | + "," + p.left + "," + p.top; |
| 5289 | for (var i = 2; i < tangents.length; i++, pi++) { |
| 5290 | p = points[pi]; |
| 5291 | t = tangents[i]; |
| 5292 | path += "S" + (p.left - t.x) + "," + (p.top - t.y) |
| 5293 | + "," + p.left + "," + p.top; |
| 5294 | } |
| 5295 | } |
| 5296 | |
| 5297 | if (quad) { |
| 5298 | var lp = points[pi]; |
| 5299 | path += "Q" + (p.left + t.x * 2 / 3) + "," + (p.top + t.y * 2 / 3) + "," |
| 5300 | + lp.left + "," + lp.top; |
| 5301 | } |
| 5302 | |
| 5303 | return path; |
| 5304 | }; |
| 5305 | |
| 5306 | /** |
| 5307 | * @private Interpolates the given points with respective tangents using the |
| 5308 | * cubic Hermite spline interpolation. Returns an array of path strings. |
| 5309 | * |
| 5310 | * @param points the array of points. |
| 5311 | * @param tangents the array of tangent vectors. |
| 5312 | */ |
| 5313 | pv.SvgScene.curveHermiteSegments = function(points, tangents) { |
| 5314 | if (tangents.length < 1 |
| 5315 | || (points.length != tangents.length |
| 5316 | && points.length != tangents.length + 2)) return []; |
| 5317 | var quad = points.length != tangents.length, |
| 5318 | paths = [], |
| 5319 | p0 = points[0], |
| 5320 | p = p0, |
| 5321 | t0 = tangents[0], |
| 5322 | t = t0, |
| 5323 | pi = 1; |
| 5324 | |
| 5325 | if (quad) { |
| 5326 | p = points[1]; |
| 5327 | paths.push("M" + p0.left + "," + p0.top |
| 5328 | + "Q" + (p.left - t.x * 2 / 3) + "," + (p.top - t.y * 2 / 3) |
| 5329 | + "," + p.left + "," + p.top); |
| 5330 | pi = 2; |
| 5331 | } |
| 5332 | |
| 5333 | for (var i = 1; i < tangents.length; i++, pi++) { |
| 5334 | p0 = p; |
| 5335 | t0 = t; |
| 5336 | p = points[pi]; |
| 5337 | t = tangents[i]; |
| 5338 | paths.push("M" + p0.left + "," + p0.top |
| 5339 | + "C" + (p0.left + t0.x) + "," + (p0.top + t0.y) |
| 5340 | + "," + (p.left - t.x) + "," + (p.top - t.y) |
| 5341 | + "," + p.left + "," + p.top); |
| 5342 | } |
| 5343 | |
| 5344 | if (quad) { |
| 5345 | var lp = points[pi]; |
| 5346 | paths.push("M" + p.left + "," + p.top |
| 5347 | + "Q" + (p.left + t.x * 2 / 3) + "," + (p.top + t.y * 2 / 3) + "," |
| 5348 | + lp.left + "," + lp.top); |
| 5349 | } |
| 5350 | |
| 5351 | return paths; |
| 5352 | }; |
| 5353 | |
| 5354 | /** |
| 5355 | * @private Computes the tangents for the given points needed for cardinal |
| 5356 | * spline interpolation. Returns an array of tangent vectors. Note: that for n |
| 5357 | * points only the n-2 well defined tangents are returned. |
| 5358 | * |
| 5359 | * @param points the array of points. |
| 5360 | * @param tension the tension of hte cardinal spline. |
| 5361 | */ |
| 5362 | pv.SvgScene.cardinalTangents = function(points, tension) { |
| 5363 | var tangents = [], |
| 5364 | a = (1 - tension) / 2, |
| 5365 | p0 = points[0], |
| 5366 | p1 = points[1], |
| 5367 | p2 = points[2]; |
| 5368 | |
| 5369 | for (var i = 3; i < points.length; i++) { |
| 5370 | tangents.push({x: a * (p2.left - p0.left), y: a * (p2.top - p0.top)}); |
| 5371 | p0 = p1; |
| 5372 | p1 = p2; |
| 5373 | p2 = points[i]; |
| 5374 | } |
| 5375 | |
| 5376 | tangents.push({x: a * (p2.left - p0.left), y: a * (p2.top - p0.top)}); |
| 5377 | return tangents; |
| 5378 | }; |
| 5379 | |
| 5380 | /** |
| 5381 | * @private Interpolates the given points using cardinal spline interpolation. |
| 5382 | * Returns an SVG path without the leading M instruction to allow path |
| 5383 | * appending. |
| 5384 | * |
| 5385 | * @param points the array of points. |
| 5386 | * @param tension the tension of hte cardinal spline. |
| 5387 | */ |
| 5388 | pv.SvgScene.curveCardinal = function(points, tension) { |
| 5389 | if (points.length <= 2) return ""; |
| 5390 | return this.curveHermite(points, this.cardinalTangents(points, tension)); |
| 5391 | }; |
| 5392 | |
| 5393 | /** |
| 5394 | * @private Interpolates the given points using cardinal spline interpolation. |
| 5395 | * Returns an array of path strings. |
| 5396 | * |
| 5397 | * @param points the array of points. |
| 5398 | * @param tension the tension of hte cardinal spline. |
| 5399 | */ |
| 5400 | pv.SvgScene.curveCardinalSegments = function(points, tension) { |
| 5401 | if (points.length <= 2) return ""; |
| 5402 | return this.curveHermiteSegments(points, this.cardinalTangents(points, tension)); |
| 5403 | }; |
| 5404 | |
| 5405 | /** |
| 5406 | * @private Interpolates the given points using Fritsch-Carlson Monotone cubic |
| 5407 | * Hermite interpolation. Returns an array of tangent vectors. |
| 5408 | * |
| 5409 | * @param points the array of points. |
| 5410 | */ |
| 5411 | pv.SvgScene.monotoneTangents = function(points) { |
| 5412 | var tangents = [], |
| 5413 | d = [], |
| 5414 | m = [], |
| 5415 | dx = [], |
| 5416 | k = 0; |
| 5417 | |
| 5418 | /* Compute the slopes of the secant lines between successive points. */ |
| 5419 | for (k = 0; k < points.length-1; k++) { |
| 5420 | d[k] = (points[k+1].top - points[k].top)/(points[k+1].left - points[k].left); |
| 5421 | } |
| 5422 | |
| 5423 | /* Initialize the tangents at every point as the average of the secants. */ |
| 5424 | m[0] = d[0]; |
| 5425 | dx[0] = points[1].left - points[0].left; |
| 5426 | for (k = 1; k < points.length - 1; k++) { |
| 5427 | m[k] = (d[k-1]+d[k])/2; |
| 5428 | dx[k] = (points[k+1].left - points[k-1].left)/2; |
| 5429 | } |
| 5430 | m[k] = d[k-1]; |
| 5431 | dx[k] = (points[k].left - points[k-1].left); |
| 5432 | |
| 5433 | /* Step 3. Very important, step 3. Yep. Wouldn't miss it. */ |
| 5434 | for (k = 0; k < points.length - 1; k++) { |
| 5435 | if (d[k] == 0) { |
| 5436 | m[ k ] = 0; |
| 5437 | m[k+1] = 0; |
| 5438 | } |
| 5439 | } |
| 5440 | |
| 5441 | /* Step 4 + 5. Out of 5 or more steps. */ |
| 5442 | for (k = 0; k < points.length - 1; k++) { |
| 5443 | if ((Math.abs(m[k]) < 1e-5) || (Math.abs(m[k+1]) < 1e-5)) continue; |
| 5444 | var ak = m[k] / d[k], |
| 5445 | bk = m[k + 1] / d[k], |
| 5446 | s = ak * ak + bk * bk; // monotone constant (?) |
| 5447 | if (s > 9) { |
| 5448 | var tk = 3 / Math.sqrt(s); |
| 5449 | m[k] = tk * ak * d[k]; |
| 5450 | m[k + 1] = tk * bk * d[k]; |
| 5451 | } |
| 5452 | } |
| 5453 | |
| 5454 | var len; |
| 5455 | for (var i = 0; i < points.length; i++) { |
| 5456 | len = 1 + m[i] * m[i]; // pv.vector(1, m[i]).norm().times(dx[i]/3) |
| 5457 | tangents.push({x: dx[i] / 3 / len, y: m[i] * dx[i] / 3 / len}); |
| 5458 | } |
| 5459 | |
| 5460 | return tangents; |
| 5461 | }; |
| 5462 | |
| 5463 | /** |
| 5464 | * @private Interpolates the given points using Fritsch-Carlson Monotone cubic |
| 5465 | * Hermite interpolation. Returns an SVG path without the leading M instruction |
| 5466 | * to allow path appending. |
| 5467 | * |
| 5468 | * @param points the array of points. |
| 5469 | */ |
| 5470 | pv.SvgScene.curveMonotone = function(points) { |
| 5471 | if (points.length <= 2) return ""; |
| 5472 | return this.curveHermite(points, this.monotoneTangents(points)); |
| 5473 | } |
| 5474 | |
| 5475 | /** |
| 5476 | * @private Interpolates the given points using Fritsch-Carlson Monotone cubic |
| 5477 | * Hermite interpolation. |
| 5478 | * Returns an array of path strings. |
| 5479 | * |
| 5480 | * @param points the array of points. |
| 5481 | */ |
| 5482 | pv.SvgScene.curveMonotoneSegments = function(points) { |
| 5483 | if (points.length <= 2) return ""; |
| 5484 | return this.curveHermiteSegments(points, this.monotoneTangents(points)); |
| 5485 | }; |
| 5486 | pv.SvgScene.area = function(scenes) { |
| 5487 | var e = scenes.$g.firstChild; |
| 5488 | if (!scenes.length) return e; |
| 5489 | var s = scenes[0]; |
| 5490 | |
| 5491 | /* segmented */ |
| 5492 | if (s.segmented) return this.areaSegment(scenes); |
| 5493 | |
| 5494 | /* visible */ |
| 5495 | if (!s.visible) return e; |
| 5496 | var fill = s.fillStyle, stroke = s.strokeStyle; |
| 5497 | if (!fill.opacity && !stroke.opacity) return e; |
| 5498 | |
| 5499 | /** @private Computes the straight path for the range [i, j]. */ |
| 5500 | function path(i, j) { |
| 5501 | var p1 = [], p2 = []; |
| 5502 | for (var k = j; i <= k; i++, j--) { |
| 5503 | var si = scenes[i], |
| 5504 | sj = scenes[j], |
| 5505 | pi = si.left + "," + si.top, |
| 5506 | pj = (sj.left + sj.width) + "," + (sj.top + sj.height); |
| 5507 | |
| 5508 | /* interpolate */ |
| 5509 | if (i < k) { |
| 5510 | var sk = scenes[i + 1], sl = scenes[j - 1]; |
| 5511 | switch (s.interpolate) { |
| 5512 | case "step-before": { |
| 5513 | pi += "V" + sk.top; |
| 5514 | pj += "H" + (sl.left + sl.width); |
| 5515 | break; |
| 5516 | } |
| 5517 | case "step-after": { |
| 5518 | pi += "H" + sk.left; |
| 5519 | pj += "V" + (sl.top + sl.height); |
| 5520 | break; |
| 5521 | } |
| 5522 | } |
| 5523 | } |
| 5524 | |
| 5525 | p1.push(pi); |
| 5526 | p2.push(pj); |
| 5527 | } |
| 5528 | return p1.concat(p2).join("L"); |
| 5529 | } |
| 5530 | |
| 5531 | /** @private Computes the curved path for the range [i, j]. */ |
| 5532 | function pathCurve(i, j) { |
| 5533 | var pointsT = [], pointsB = [], pathT, pathB; |
| 5534 | |
| 5535 | for (var k = j; i <= k; i++, j--) { |
| 5536 | var sj = scenes[j]; |
| 5537 | pointsT.push(scenes[i]); |
| 5538 | pointsB.push({left: sj.left + sj.width, top: sj.top + sj.height}); |
| 5539 | } |
| 5540 | |
| 5541 | if (s.interpolate == "basis") { |
| 5542 | pathT = pv.SvgScene.curveBasis(pointsT); |
| 5543 | pathB = pv.SvgScene.curveBasis(pointsB); |
| 5544 | } else if (s.interpolate == "cardinal") { |
| 5545 | pathT = pv.SvgScene.curveCardinal(pointsT, s.tension); |
| 5546 | pathB = pv.SvgScene.curveCardinal(pointsB, s.tension); |
| 5547 | } else { // monotone |
| 5548 | pathT = pv.SvgScene.curveMonotone(pointsT); |
| 5549 | pathB = pv.SvgScene.curveMonotone(pointsB); |
| 5550 | } |
| 5551 | |
| 5552 | return pointsT[0].left + "," + pointsT[0].top + pathT |
| 5553 | + "L" + pointsB[0].left + "," + pointsB[0].top + pathB; |
| 5554 | } |
| 5555 | |
| 5556 | /* points */ |
| 5557 | var d = [], si, sj; |
| 5558 | for (var i = 0; i < scenes.length; i++) { |
| 5559 | si = scenes[i]; if (!si.width && !si.height) continue; |
| 5560 | for (var j = i + 1; j < scenes.length; j++) { |
| 5561 | sj = scenes[j]; if (!sj.width && !sj.height) break; |
| 5562 | } |
| 5563 | if (i && (s.interpolate != "step-after")) i--; |
| 5564 | if ((j < scenes.length) && (s.interpolate != "step-before")) j++; |
| 5565 | d.push(((j - i > 2 |
| 5566 | && (s.interpolate == "basis" |
| 5567 | || s.interpolate == "cardinal" |
| 5568 | || s.interpolate == "monotone")) |
| 5569 | ? pathCurve : path)(i, j - 1)); |
| 5570 | i = j - 1; |
| 5571 | } |
| 5572 | if (!d.length) return e; |
| 5573 | |
| 5574 | e = this.expect(e, "path", { |
| 5575 | "shape-rendering": s.antialias ? null : "crispEdges", |
| 5576 | "pointer-events": s.events, |
| 5577 | "cursor": s.cursor, |
| 5578 | "d": "M" + d.join("ZM") + "Z", |
| 5579 | "fill": fill.color, |
| 5580 | "fill-opacity": fill.opacity || null, |
| 5581 | "stroke": stroke.color, |
| 5582 | "stroke-opacity": stroke.opacity || null, |
| 5583 | "stroke-width": stroke.opacity ? s.lineWidth / this.scale : null |
| 5584 | }); |
| 5585 | return this.append(e, scenes, 0); |
| 5586 | }; |
| 5587 | |
| 5588 | pv.SvgScene.areaSegment = function(scenes) { |
| 5589 | var e = scenes.$g.firstChild, s = scenes[0], pathsT, pathsB; |
| 5590 | if (s.interpolate == "basis" |
| 5591 | || s.interpolate == "cardinal" |
| 5592 | || s.interpolate == "monotone") { |
| 5593 | var pointsT = [], pointsB = []; |
| 5594 | |
| 5595 | for (var i = 0, n = scenes.length; i < n; i++) { |
| 5596 | var sj = scenes[n - i - 1]; |
| 5597 | pointsT.push(scenes[i]); |
| 5598 | pointsB.push({left: sj.left + sj.width, top: sj.top + sj.height}); |
| 5599 | } |
| 5600 | |
| 5601 | if (s.interpolate == "basis") { |
| 5602 | pathsT = this.curveBasisSegments(pointsT); |
| 5603 | pathsB = this.curveBasisSegments(pointsB); |
| 5604 | } else if (s.interpolate == "cardinal") { |
| 5605 | pathsT = this.curveCardinalSegments(pointsT, s.tension); |
| 5606 | pathsB = this.curveCardinalSegments(pointsB, s.tension); |
| 5607 | } else { // monotone |
| 5608 | pathsT = this.curveMonotoneSegments(pointsT); |
| 5609 | pathsB = this.curveMonotoneSegments(pointsB); |
| 5610 | } |
| 5611 | } |
| 5612 | |
| 5613 | for (var i = 0, n = scenes.length - 1; i < n; i++) { |
| 5614 | var s1 = scenes[i], s2 = scenes[i + 1]; |
| 5615 | |
| 5616 | /* visible */ |
| 5617 | if (!s1.visible || !s2.visible) continue; |
| 5618 | var fill = s1.fillStyle, stroke = s1.strokeStyle; |
| 5619 | if (!fill.opacity && !stroke.opacity) continue; |
| 5620 | |
| 5621 | var d; |
| 5622 | if (pathsT) { |
| 5623 | var pathT = pathsT[i], |
| 5624 | pathB = "L" + pathsB[n - i - 1].substr(1); |
| 5625 | |
| 5626 | d = pathT + pathB + "Z"; |
| 5627 | } else { |
| 5628 | /* interpolate */ |
| 5629 | var si = s1, sj = s2; |
| 5630 | switch (s1.interpolate) { |
| 5631 | case "step-before": si = s2; break; |
| 5632 | case "step-after": sj = s1; break; |
| 5633 | } |
| 5634 | |
| 5635 | /* path */ |
| 5636 | d = "M" + s1.left + "," + si.top |
| 5637 | + "L" + s2.left + "," + sj.top |
| 5638 | + "L" + (s2.left + s2.width) + "," + (sj.top + sj.height) |
| 5639 | + "L" + (s1.left + s1.width) + "," + (si.top + si.height) |
| 5640 | + "Z"; |
| 5641 | } |
| 5642 | |
| 5643 | e = this.expect(e, "path", { |
| 5644 | "shape-rendering": s1.antialias ? null : "crispEdges", |
| 5645 | "pointer-events": s1.events, |
| 5646 | "cursor": s1.cursor, |
| 5647 | "d": d, |
| 5648 | "fill": fill.color, |
| 5649 | "fill-opacity": fill.opacity || null, |
| 5650 | "stroke": stroke.color, |
| 5651 | "stroke-opacity": stroke.opacity || null, |
| 5652 | "stroke-width": stroke.opacity ? s1.lineWidth / this.scale : null |
| 5653 | }); |
| 5654 | e = this.append(e, scenes, i); |
| 5655 | } |
| 5656 | return e; |
| 5657 | }; |
| 5658 | pv.SvgScene.bar = function(scenes) { |
| 5659 | var e = scenes.$g.firstChild; |
| 5660 | for (var i = 0; i < scenes.length; i++) { |
| 5661 | var s = scenes[i]; |
| 5662 | |
| 5663 | /* visible */ |
| 5664 | if (!s.visible) continue; |
| 5665 | var fill = s.fillStyle, stroke = s.strokeStyle; |
| 5666 | if (!fill.opacity && !stroke.opacity) continue; |
| 5667 | |
| 5668 | e = this.expect(e, "rect", { |
| 5669 | "shape-rendering": s.antialias ? null : "crispEdges", |
| 5670 | "pointer-events": s.events, |
| 5671 | "cursor": s.cursor, |
| 5672 | "x": s.left, |
| 5673 | "y": s.top, |
| 5674 | "width": Math.max(1E-10, s.width), |
| 5675 | "height": Math.max(1E-10, s.height), |
| 5676 | "fill": fill.color, |
| 5677 | "fill-opacity": fill.opacity || null, |
| 5678 | "stroke": stroke.color, |
| 5679 | "stroke-opacity": stroke.opacity || null, |
| 5680 | "stroke-width": stroke.opacity ? s.lineWidth / this.scale : null |
| 5681 | }); |
| 5682 | e = this.append(e, scenes, i); |
| 5683 | } |
| 5684 | return e; |
| 5685 | }; |
| 5686 | pv.SvgScene.dot = function(scenes) { |
| 5687 | var e = scenes.$g.firstChild; |
| 5688 | for (var i = 0; i < scenes.length; i++) { |
| 5689 | var s = scenes[i]; |
| 5690 | |
| 5691 | /* visible */ |
| 5692 | if (!s.visible) continue; |
| 5693 | var fill = s.fillStyle, stroke = s.strokeStyle; |
| 5694 | if (!fill.opacity && !stroke.opacity) continue; |
| 5695 | |
| 5696 | /* points */ |
| 5697 | var radius = s.radius, path = null; |
| 5698 | switch (s.shape) { |
| 5699 | case "cross": { |
| 5700 | path = "M" + -radius + "," + -radius |
| 5701 | + "L" + radius + "," + radius |
| 5702 | + "M" + radius + "," + -radius |
| 5703 | + "L" + -radius + "," + radius; |
| 5704 | break; |
| 5705 | } |
| 5706 | case "triangle": { |
| 5707 | var h = radius, w = radius * 1.1547; // 2 / Math.sqrt(3) |
| 5708 | path = "M0," + h |
| 5709 | + "L" + w +"," + -h |
| 5710 | + " " + -w + "," + -h |
| 5711 | + "Z"; |
| 5712 | break; |
| 5713 | } |
| 5714 | case "diamond": { |
| 5715 | radius *= Math.SQRT2; |
| 5716 | path = "M0," + -radius |
| 5717 | + "L" + radius + ",0" |
| 5718 | + " 0," + radius |
| 5719 | + " " + -radius + ",0" |
| 5720 | + "Z"; |
| 5721 | break; |
| 5722 | } |
| 5723 | case "square": { |
| 5724 | path = "M" + -radius + "," + -radius |
| 5725 | + "L" + radius + "," + -radius |
| 5726 | + " " + radius + "," + radius |
| 5727 | + " " + -radius + "," + radius |
| 5728 | + "Z"; |
| 5729 | break; |
| 5730 | } |
| 5731 | case "tick": { |
| 5732 | path = "M0,0L0," + -s.size; |
| 5733 | break; |
| 5734 | } |
| 5735 | case "bar": { |
| 5736 | path = "M0," + (s.size / 2) + "L0," + -(s.size / 2); |
| 5737 | break; |
| 5738 | } |
| 5739 | } |
| 5740 | |
| 5741 | /* Use <circle> for circles, <path> for everything else. */ |
| 5742 | var svg = { |
| 5743 | "shape-rendering": s.antialias ? null : "crispEdges", |
| 5744 | "pointer-events": s.events, |
| 5745 | "cursor": s.cursor, |
| 5746 | "fill": fill.color, |
| 5747 | "fill-opacity": fill.opacity || null, |
| 5748 | "stroke": stroke.color, |
| 5749 | "stroke-opacity": stroke.opacity || null, |
| 5750 | "stroke-width": stroke.opacity ? s.lineWidth / this.scale : null |
| 5751 | }; |
| 5752 | if (path) { |
| 5753 | svg.transform = "translate(" + s.left + "," + s.top + ")"; |
| 5754 | if (s.angle) svg.transform += " rotate(" + 180 * s.angle / Math.PI + ")"; |
| 5755 | svg.d = path; |
| 5756 | e = this.expect(e, "path", svg); |
| 5757 | } else { |
| 5758 | svg.cx = s.left; |
| 5759 | svg.cy = s.top; |
| 5760 | svg.r = radius; |
| 5761 | e = this.expect(e, "circle", svg); |
| 5762 | } |
| 5763 | e = this.append(e, scenes, i); |
| 5764 | } |
| 5765 | return e; |
| 5766 | }; |
| 5767 | pv.SvgScene.image = function(scenes) { |
| 5768 | var e = scenes.$g.firstChild; |
| 5769 | for (var i = 0; i < scenes.length; i++) { |
| 5770 | var s = scenes[i]; |
| 5771 | |
| 5772 | /* visible */ |
| 5773 | if (!s.visible) continue; |
| 5774 | |
| 5775 | /* fill */ |
| 5776 | e = this.fill(e, scenes, i); |
| 5777 | |
| 5778 | /* image */ |
| 5779 | if (s.image) { |
| 5780 | e = this.expect(e, "foreignObject", { |
| 5781 | "cursor": s.cursor, |
| 5782 | "x": s.left, |
| 5783 | "y": s.top, |
| 5784 | "width": s.width, |
| 5785 | "height": s.height |
| 5786 | }); |
| 5787 | var c = e.firstChild || e.appendChild(document.createElementNS(this.xhtml, "canvas")); |
| 5788 | c.$scene = {scenes:scenes, index:i}; |
| 5789 | c.style.width = s.width; |
| 5790 | c.style.height = s.height; |
| 5791 | c.width = s.imageWidth; |
| 5792 | c.height = s.imageHeight; |
| 5793 | c.getContext("2d").putImageData(s.image, 0, 0); |
| 5794 | } else { |
| 5795 | e = this.expect(e, "image", { |
| 5796 | "preserveAspectRatio": "none", |
| 5797 | "cursor": s.cursor, |
| 5798 | "x": s.left, |
| 5799 | "y": s.top, |
| 5800 | "width": s.width, |
| 5801 | "height": s.height |
| 5802 | }); |
| 5803 | e.setAttributeNS(this.xlink, "href", s.url); |
| 5804 | } |
| 5805 | e = this.append(e, scenes, i); |
| 5806 | |
| 5807 | /* stroke */ |
| 5808 | e = this.stroke(e, scenes, i); |
| 5809 | } |
| 5810 | return e; |
| 5811 | }; |
| 5812 | pv.SvgScene.label = function(scenes) { |
| 5813 | var e = scenes.$g.firstChild; |
| 5814 | for (var i = 0; i < scenes.length; i++) { |
| 5815 | var s = scenes[i]; |
| 5816 | |
| 5817 | /* visible */ |
| 5818 | if (!s.visible) continue; |
| 5819 | var fill = s.textStyle; |
| 5820 | if (!fill.opacity || !s.text) continue; |
| 5821 | |
| 5822 | /* text-baseline, text-align */ |
| 5823 | var x = 0, y = 0, dy = 0, anchor = "start"; |
| 5824 | switch (s.textBaseline) { |
| 5825 | case "middle": dy = ".35em"; break; |
| 5826 | case "top": dy = ".71em"; y = s.textMargin; break; |
| 5827 | case "bottom": y = "-" + s.textMargin; break; |
| 5828 | } |
| 5829 | switch (s.textAlign) { |
| 5830 | case "right": anchor = "end"; x = "-" + s.textMargin; break; |
| 5831 | case "center": anchor = "middle"; break; |
| 5832 | case "left": x = s.textMargin; break; |
| 5833 | } |
| 5834 | |
| 5835 | e = this.expect(e, "text", { |
| 5836 | "pointer-events": s.events, |
| 5837 | "cursor": s.cursor, |
| 5838 | "x": x, |
| 5839 | "y": y, |
| 5840 | "dy": dy, |
| 5841 | "transform": "translate(" + s.left + "," + s.top + ")" |
| 5842 | + (s.textAngle ? " rotate(" + 180 * s.textAngle / Math.PI + ")" : "") |
| 5843 | + (this.scale != 1 ? " scale(" + 1 / this.scale + ")" : ""), |
| 5844 | "fill": fill.color, |
| 5845 | "fill-opacity": fill.opacity || null, |
| 5846 | "text-anchor": anchor |
| 5847 | }, { |
| 5848 | "font": s.font, |
| 5849 | "text-shadow": s.textShadow, |
| 5850 | "text-decoration": s.textDecoration |
| 5851 | }); |
| 5852 | if (e.firstChild) e.firstChild.nodeValue = s.text; |
| 5853 | else e.appendChild(document.createTextNode(s.text)); |
| 5854 | e = this.append(e, scenes, i); |
| 5855 | } |
| 5856 | return e; |
| 5857 | }; |
| 5858 | pv.SvgScene.line = function(scenes) { |
| 5859 | var e = scenes.$g.firstChild; |
| 5860 | if (scenes.length < 2) return e; |
| 5861 | var s = scenes[0]; |
| 5862 | |
| 5863 | /* segmented */ |
| 5864 | if (s.segmented) return this.lineSegment(scenes); |
| 5865 | |
| 5866 | /* visible */ |
| 5867 | if (!s.visible) return e; |
| 5868 | var fill = s.fillStyle, stroke = s.strokeStyle; |
| 5869 | if (!fill.opacity && !stroke.opacity) return e; |
| 5870 | |
| 5871 | /* points */ |
| 5872 | var d = "M" + s.left + "," + s.top; |
| 5873 | |
| 5874 | if (scenes.length > 2 && (s.interpolate == "basis" || s.interpolate == "cardinal" || s.interpolate == "monotone")) { |
| 5875 | switch (s.interpolate) { |
| 5876 | case "basis": d += this.curveBasis(scenes); break; |
| 5877 | case "cardinal": d += this.curveCardinal(scenes, s.tension); break; |
| 5878 | case "monotone": d += this.curveMonotone(scenes); break; |
| 5879 | } |
| 5880 | } else { |
| 5881 | for (var i = 1; i < scenes.length; i++) { |
| 5882 | d += this.pathSegment(scenes[i - 1], scenes[i]); |
| 5883 | } |
| 5884 | } |
| 5885 | |
| 5886 | e = this.expect(e, "path", { |
| 5887 | "shape-rendering": s.antialias ? null : "crispEdges", |
| 5888 | "pointer-events": s.events, |
| 5889 | "cursor": s.cursor, |
| 5890 | "d": d, |
| 5891 | "fill": fill.color, |
| 5892 | "fill-opacity": fill.opacity || null, |
| 5893 | "stroke": stroke.color, |
| 5894 | "stroke-opacity": stroke.opacity || null, |
| 5895 | "stroke-width": stroke.opacity ? s.lineWidth / this.scale : null, |
| 5896 | "stroke-linejoin": s.lineJoin |
| 5897 | }); |
| 5898 | return this.append(e, scenes, 0); |
| 5899 | }; |
| 5900 | |
| 5901 | pv.SvgScene.lineSegment = function(scenes) { |
| 5902 | var e = scenes.$g.firstChild; |
| 5903 | |
| 5904 | var s = scenes[0]; |
| 5905 | var paths; |
| 5906 | switch (s.interpolate) { |
| 5907 | case "basis": paths = this.curveBasisSegments(scenes); break; |
| 5908 | case "cardinal": paths = this.curveCardinalSegments(scenes, s.tension); break; |
| 5909 | case "monotone": paths = this.curveMonotoneSegments(scenes); break; |
| 5910 | } |
| 5911 | |
| 5912 | for (var i = 0, n = scenes.length - 1; i < n; i++) { |
| 5913 | var s1 = scenes[i], s2 = scenes[i + 1]; |
| 5914 | |
| 5915 | /* visible */ |
| 5916 | if (!s1.visible || !s2.visible) continue; |
| 5917 | var stroke = s1.strokeStyle, fill = pv.Color.transparent; |
| 5918 | if (!stroke.opacity) continue; |
| 5919 | |
| 5920 | /* interpolate */ |
| 5921 | var d; |
| 5922 | if ((s1.interpolate == "linear") && (s1.lineJoin == "miter")) { |
| 5923 | fill = stroke; |
| 5924 | stroke = pv.Color.transparent; |
| 5925 | d = this.pathJoin(scenes[i - 1], s1, s2, scenes[i + 2]); |
| 5926 | } else if(paths) { |
| 5927 | d = paths[i]; |
| 5928 | } else { |
| 5929 | d = "M" + s1.left + "," + s1.top + this.pathSegment(s1, s2); |
| 5930 | } |
| 5931 | |
| 5932 | e = this.expect(e, "path", { |
| 5933 | "shape-rendering": s1.antialias ? null : "crispEdges", |
| 5934 | "pointer-events": s1.events, |
| 5935 | "cursor": s1.cursor, |
| 5936 | "d": d, |
| 5937 | "fill": fill.color, |
| 5938 | "fill-opacity": fill.opacity || null, |
| 5939 | "stroke": stroke.color, |
| 5940 | "stroke-opacity": stroke.opacity || null, |
| 5941 | "stroke-width": stroke.opacity ? s1.lineWidth / this.scale : null, |
| 5942 | "stroke-linejoin": s1.lineJoin |
| 5943 | }); |
| 5944 | e = this.append(e, scenes, i); |
| 5945 | } |
| 5946 | return e; |
| 5947 | }; |
| 5948 | |
| 5949 | /** @private Returns the path segment for the specified points. */ |
| 5950 | pv.SvgScene.pathSegment = function(s1, s2) { |
| 5951 | var l = 1; // sweep-flag |
| 5952 | switch (s1.interpolate) { |
| 5953 | case "polar-reverse": |
| 5954 | l = 0; |
| 5955 | case "polar": { |
| 5956 | var dx = s2.left - s1.left, |
| 5957 | dy = s2.top - s1.top, |
| 5958 | e = 1 - s1.eccentricity, |
| 5959 | r = Math.sqrt(dx * dx + dy * dy) / (2 * e); |
| 5960 | if ((e <= 0) || (e > 1)) break; // draw a straight line |
| 5961 | return "A" + r + "," + r + " 0 0," + l + " " + s2.left + "," + s2.top; |
| 5962 | } |
| 5963 | case "step-before": return "V" + s2.top + "H" + s2.left; |
| 5964 | case "step-after": return "H" + s2.left + "V" + s2.top; |
| 5965 | } |
| 5966 | return "L" + s2.left + "," + s2.top; |
| 5967 | }; |
| 5968 | |
| 5969 | /** @private Line-line intersection, per Akenine-Moller 16.16.1. */ |
| 5970 | pv.SvgScene.lineIntersect = function(o1, d1, o2, d2) { |
| 5971 | return o1.plus(d1.times(o2.minus(o1).dot(d2.perp()) / d1.dot(d2.perp()))); |
| 5972 | } |
| 5973 | |
| 5974 | /** @private Returns the miter join path for the specified points. */ |
| 5975 | pv.SvgScene.pathJoin = function(s0, s1, s2, s3) { |
| 5976 | /* |
| 5977 | * P1-P2 is the current line segment. V is a vector that is perpendicular to |
| 5978 | * the line segment, and has length lineWidth / 2. ABCD forms the initial |
| 5979 | * bounding box of the line segment (i.e., the line segment if we were to do |
| 5980 | * no joins). |
| 5981 | */ |
| 5982 | var p1 = pv.vector(s1.left, s1.top), |
| 5983 | p2 = pv.vector(s2.left, s2.top), |
| 5984 | p = p2.minus(p1), |
| 5985 | v = p.perp().norm(), |
| 5986 | w = v.times(s1.lineWidth / (2 * this.scale)), |
| 5987 | a = p1.plus(w), |
| 5988 | b = p2.plus(w), |
| 5989 | c = p2.minus(w), |
| 5990 | d = p1.minus(w); |
| 5991 | |
| 5992 | /* |
| 5993 | * Start join. P0 is the previous line segment's start point. We define the |
| 5994 | * cutting plane as the average of the vector perpendicular to P0-P1, and |
| 5995 | * the vector perpendicular to P1-P2. This insures that the cross-section of |
| 5996 | * the line on the cutting plane is equal if the line-width is unchanged. |
| 5997 | * Note that we don't implement miter limits, so these can get wild. |
| 5998 | */ |
| 5999 | if (s0 && s0.visible) { |
| 6000 | var v1 = p1.minus(s0.left, s0.top).perp().norm().plus(v); |
| 6001 | d = this.lineIntersect(p1, v1, d, p); |
| 6002 | a = this.lineIntersect(p1, v1, a, p); |
| 6003 | } |
| 6004 | |
| 6005 | /* Similarly, for end join. */ |
| 6006 | if (s3 && s3.visible) { |
| 6007 | var v2 = pv.vector(s3.left, s3.top).minus(p2).perp().norm().plus(v); |
| 6008 | c = this.lineIntersect(p2, v2, c, p); |
| 6009 | b = this.lineIntersect(p2, v2, b, p); |
| 6010 | } |
| 6011 | |
| 6012 | return "M" + a.x + "," + a.y |
| 6013 | + "L" + b.x + "," + b.y |
| 6014 | + " " + c.x + "," + c.y |
| 6015 | + " " + d.x + "," + d.y; |
| 6016 | }; |
| 6017 | pv.SvgScene.panel = function(scenes) { |
| 6018 | var g = scenes.$g, e = g && g.firstChild; |
| 6019 | for (var i = 0; i < scenes.length; i++) { |
| 6020 | var s = scenes[i]; |
| 6021 | |
| 6022 | /* visible */ |
| 6023 | if (!s.visible) continue; |
| 6024 | |
| 6025 | /* svg */ |
| 6026 | if (!scenes.parent) { |
| 6027 | s.canvas.style.display = "inline-block"; |
| 6028 | if (g && (g.parentNode != s.canvas)) { |
| 6029 | g = s.canvas.firstChild; |
| 6030 | e = g && g.firstChild; |
| 6031 | } |
| 6032 | if (!g) { |
| 6033 | g = s.canvas.appendChild(this.create("svg")); |
| 6034 | g.setAttribute("font-size", "10px"); |
| 6035 | g.setAttribute("font-family", "sans-serif"); |
| 6036 | g.setAttribute("fill", "none"); |
| 6037 | g.setAttribute("stroke", "none"); |
| 6038 | g.setAttribute("stroke-width", 1.5); |
| 6039 | for (var j = 0; j < this.events.length; j++) { |
| 6040 | g.addEventListener(this.events[j], this.dispatch, false); |
| 6041 | } |
| 6042 | e = g.firstChild; |
| 6043 | } |
| 6044 | scenes.$g = g; |
| 6045 | g.setAttribute("width", s.width + s.left + s.right); |
| 6046 | g.setAttribute("height", s.height + s.top + s.bottom); |
| 6047 | } |
| 6048 | |
| 6049 | /* clip (nest children) */ |
| 6050 | if (s.overflow == "hidden") { |
| 6051 | var id = pv.id().toString(36), |
| 6052 | c = this.expect(e, "g", {"clip-path": "url(#" + id + ")"}); |
| 6053 | if (!c.parentNode) g.appendChild(c); |
| 6054 | scenes.$g = g = c; |
| 6055 | e = c.firstChild; |
| 6056 | |
| 6057 | e = this.expect(e, "clipPath", {"id": id}); |
| 6058 | var r = e.firstChild || e.appendChild(this.create("rect")); |
| 6059 | r.setAttribute("x", s.left); |
| 6060 | r.setAttribute("y", s.top); |
| 6061 | r.setAttribute("width", s.width); |
| 6062 | r.setAttribute("height", s.height); |
| 6063 | if (!e.parentNode) g.appendChild(e); |
| 6064 | e = e.nextSibling; |
| 6065 | } |
| 6066 | |
| 6067 | /* fill */ |
| 6068 | e = this.fill(e, scenes, i); |
| 6069 | |
| 6070 | /* transform (push) */ |
| 6071 | var k = this.scale, |
| 6072 | t = s.transform, |
| 6073 | x = s.left + t.x, |
| 6074 | y = s.top + t.y; |
| 6075 | this.scale *= t.k; |
| 6076 | |
| 6077 | /* children */ |
| 6078 | for (var j = 0; j < s.children.length; j++) { |
| 6079 | s.children[j].$g = e = this.expect(e, "g", { |
| 6080 | "transform": "translate(" + x + "," + y + ")" |
| 6081 | + (t.k != 1 ? " scale(" + t.k + ")" : "") |
| 6082 | }); |
| 6083 | this.updateAll(s.children[j]); |
| 6084 | if (!e.parentNode) g.appendChild(e); |
| 6085 | e = e.nextSibling; |
| 6086 | } |
| 6087 | |
| 6088 | /* transform (pop) */ |
| 6089 | this.scale = k; |
| 6090 | |
| 6091 | /* stroke */ |
| 6092 | e = this.stroke(e, scenes, i); |
| 6093 | |
| 6094 | /* clip (restore group) */ |
| 6095 | if (s.overflow == "hidden") { |
| 6096 | scenes.$g = g = c.parentNode; |
| 6097 | e = c.nextSibling; |
| 6098 | } |
| 6099 | } |
| 6100 | return e; |
| 6101 | }; |
| 6102 | |
| 6103 | pv.SvgScene.fill = function(e, scenes, i) { |
| 6104 | var s = scenes[i], fill = s.fillStyle; |
| 6105 | if (fill.opacity || s.events == "all") { |
| 6106 | e = this.expect(e, "rect", { |
| 6107 | "shape-rendering": s.antialias ? null : "crispEdges", |
| 6108 | "pointer-events": s.events, |
| 6109 | "cursor": s.cursor, |
| 6110 | "x": s.left, |
| 6111 | "y": s.top, |
| 6112 | "width": s.width, |
| 6113 | "height": s.height, |
| 6114 | "fill": fill.color, |
| 6115 | "fill-opacity": fill.opacity, |
| 6116 | "stroke": null |
| 6117 | }); |
| 6118 | e = this.append(e, scenes, i); |
| 6119 | } |
| 6120 | return e; |
| 6121 | }; |
| 6122 | |
| 6123 | pv.SvgScene.stroke = function(e, scenes, i) { |
| 6124 | var s = scenes[i], stroke = s.strokeStyle; |
| 6125 | if (stroke.opacity || s.events == "all") { |
| 6126 | e = this.expect(e, "rect", { |
| 6127 | "shape-rendering": s.antialias ? null : "crispEdges", |
| 6128 | "pointer-events": s.events == "all" ? "stroke" : s.events, |
| 6129 | "cursor": s.cursor, |
| 6130 | "x": s.left, |
| 6131 | "y": s.top, |
| 6132 | "width": Math.max(1E-10, s.width), |
| 6133 | "height": Math.max(1E-10, s.height), |
| 6134 | "fill": null, |
| 6135 | "stroke": stroke.color, |
| 6136 | "stroke-opacity": stroke.opacity, |
| 6137 | "stroke-width": s.lineWidth / this.scale |
| 6138 | }); |
| 6139 | e = this.append(e, scenes, i); |
| 6140 | } |
| 6141 | return e; |
| 6142 | }; |
| 6143 | pv.SvgScene.rule = function(scenes) { |
| 6144 | var e = scenes.$g.firstChild; |
| 6145 | for (var i = 0; i < scenes.length; i++) { |
| 6146 | var s = scenes[i]; |
| 6147 | |
| 6148 | /* visible */ |
| 6149 | if (!s.visible) continue; |
| 6150 | var stroke = s.strokeStyle; |
| 6151 | if (!stroke.opacity) continue; |
| 6152 | |
| 6153 | e = this.expect(e, "line", { |
| 6154 | "shape-rendering": s.antialias ? null : "crispEdges", |
| 6155 | "pointer-events": s.events, |
| 6156 | "cursor": s.cursor, |
| 6157 | "x1": s.left, |
| 6158 | "y1": s.top, |
| 6159 | "x2": s.left + s.width, |
| 6160 | "y2": s.top + s.height, |
| 6161 | "stroke": stroke.color, |
| 6162 | "stroke-opacity": stroke.opacity, |
| 6163 | "stroke-width": s.lineWidth / this.scale |
| 6164 | }); |
| 6165 | e = this.append(e, scenes, i); |
| 6166 | } |
| 6167 | return e; |
| 6168 | }; |
| 6169 | pv.SvgScene.wedge = function(scenes) { |
| 6170 | var e = scenes.$g.firstChild; |
| 6171 | for (var i = 0; i < scenes.length; i++) { |
| 6172 | var s = scenes[i]; |
| 6173 | |
| 6174 | /* visible */ |
| 6175 | if (!s.visible) continue; |
| 6176 | var fill = s.fillStyle, stroke = s.strokeStyle; |
| 6177 | if (!fill.opacity && !stroke.opacity) continue; |
| 6178 | |
| 6179 | /* points */ |
| 6180 | var r1 = s.innerRadius, r2 = s.outerRadius, a = Math.abs(s.angle), p; |
| 6181 | if (a >= 2 * Math.PI) { |
| 6182 | if (r1) { |
| 6183 | p = "M0," + r2 |
| 6184 | + "A" + r2 + "," + r2 + " 0 1,1 0," + (-r2) |
| 6185 | + "A" + r2 + "," + r2 + " 0 1,1 0," + r2 |
| 6186 | + "M0," + r1 |
| 6187 | + "A" + r1 + "," + r1 + " 0 1,1 0," + (-r1) |
| 6188 | + "A" + r1 + "," + r1 + " 0 1,1 0," + r1 |
| 6189 | + "Z"; |
| 6190 | } else { |
| 6191 | p = "M0," + r2 |
| 6192 | + "A" + r2 + "," + r2 + " 0 1,1 0," + (-r2) |
| 6193 | + "A" + r2 + "," + r2 + " 0 1,1 0," + r2 |
| 6194 | + "Z"; |
| 6195 | } |
| 6196 | } else { |
| 6197 | var sa = Math.min(s.startAngle, s.endAngle), |
| 6198 | ea = Math.max(s.startAngle, s.endAngle), |
| 6199 | c1 = Math.cos(sa), c2 = Math.cos(ea), |
| 6200 | s1 = Math.sin(sa), s2 = Math.sin(ea); |
| 6201 | if (r1) { |
| 6202 | p = "M" + r2 * c1 + "," + r2 * s1 |
| 6203 | + "A" + r2 + "," + r2 + " 0 " |
| 6204 | + ((a < Math.PI) ? "0" : "1") + ",1 " |
| 6205 | + r2 * c2 + "," + r2 * s2 |
| 6206 | + "L" + r1 * c2 + "," + r1 * s2 |
| 6207 | + "A" + r1 + "," + r1 + " 0 " |
| 6208 | + ((a < Math.PI) ? "0" : "1") + ",0 " |
| 6209 | + r1 * c1 + "," + r1 * s1 + "Z"; |
| 6210 | } else { |
| 6211 | p = "M" + r2 * c1 + "," + r2 * s1 |
| 6212 | + "A" + r2 + "," + r2 + " 0 " |
| 6213 | + ((a < Math.PI) ? "0" : "1") + ",1 " |
| 6214 | + r2 * c2 + "," + r2 * s2 + "L0,0Z"; |
| 6215 | } |
| 6216 | } |
| 6217 | |
| 6218 | e = this.expect(e, "path", { |
| 6219 | "shape-rendering": s.antialias ? null : "crispEdges", |
| 6220 | "pointer-events": s.events, |
| 6221 | "cursor": s.cursor, |
| 6222 | "transform": "translate(" + s.left + "," + s.top + ")", |
| 6223 | "d": p, |
| 6224 | "fill": fill.color, |
| 6225 | "fill-rule": "evenodd", |
| 6226 | "fill-opacity": fill.opacity || null, |
| 6227 | "stroke": stroke.color, |
| 6228 | "stroke-opacity": stroke.opacity || null, |
| 6229 | "stroke-width": stroke.opacity ? s.lineWidth / this.scale : null |
| 6230 | }); |
| 6231 | e = this.append(e, scenes, i); |
| 6232 | } |
| 6233 | return e; |
| 6234 | }; |
| 6235 | /** |
| 6236 | * Constructs a new mark with default properties. Marks, with the exception of |
| 6237 | * the root panel, are not typically constructed directly; instead, they are |
| 6238 | * added to a panel or an existing mark via {@link pv.Mark#add}. |
| 6239 | * |
| 6240 | * @class Represents a data-driven graphical mark. The <tt>Mark</tt> class is |
| 6241 | * the base class for all graphical marks in Protovis; it does not provide any |
| 6242 | * specific rendering functionality, but together with {@link Panel} establishes |
| 6243 | * the core framework. |
| 6244 | * |
| 6245 | * <p>Concrete mark types include familiar visual elements such as bars, lines |
| 6246 | * and labels. Although a bar mark may be used to construct a bar chart, marks |
| 6247 | * know nothing about charts; it is only through their specification and |
| 6248 | * composition that charts are produced. These building blocks permit many |
| 6249 | * combinatorial possibilities. |
| 6250 | * |
| 6251 | * <p>Marks are associated with <b>data</b>: a mark is generated once per |
| 6252 | * associated datum, mapping the datum to visual <b>properties</b> such as |
| 6253 | * position and color. Thus, a single mark specification represents a set of |
| 6254 | * visual elements that share the same data and visual encoding. The type of |
| 6255 | * mark defines the names of properties and their meaning. A property may be |
| 6256 | * static, ignoring the associated datum and returning a constant; or, it may be |
| 6257 | * dynamic, derived from the associated datum or index. Such dynamic encodings |
| 6258 | * can be specified succinctly using anonymous functions. Special properties |
| 6259 | * called event handlers can be registered to add interactivity. |
| 6260 | * |
| 6261 | * <p>Protovis uses <b>inheritance</b> to simplify the specification of related |
| 6262 | * marks: a new mark can be derived from an existing mark, inheriting its |
| 6263 | * properties. The new mark can then override properties to specify new |
| 6264 | * behavior, potentially in terms of the old behavior. In this way, the old mark |
| 6265 | * serves as the <b>prototype</b> for the new mark. Most mark types share the |
| 6266 | * same basic properties for consistency and to facilitate inheritance. |
| 6267 | * |
| 6268 | * <p>The prioritization of redundant properties is as follows:<ol> |
| 6269 | * |
| 6270 | * <li>If the <tt>width</tt> property is not specified (i.e., null), its value |
| 6271 | * is the width of the parent panel, minus this mark's left and right margins; |
| 6272 | * the left and right margins are zero if not specified. |
| 6273 | * |
| 6274 | * <li>Otherwise, if the <tt>right</tt> margin is not specified, its value is |
| 6275 | * the width of the parent panel, minus this mark's width and left margin; the |
| 6276 | * left margin is zero if not specified. |
| 6277 | * |
| 6278 | * <li>Otherwise, if the <tt>left</tt> property is not specified, its value is |
| 6279 | * the width of the parent panel, minus this mark's width and the right margin. |
| 6280 | * |
| 6281 | * </ol>This prioritization is then duplicated for the <tt>height</tt>, |
| 6282 | * <tt>bottom</tt> and <tt>top</tt> properties, respectively. |
| 6283 | * |
| 6284 | * <p>While most properties are <i>variable</i>, some mark types, such as lines |
| 6285 | * and areas, generate a single visual element rather than a distinct visual |
| 6286 | * element per datum. With these marks, some properties may be <b>fixed</b>. |
| 6287 | * Fixed properties can vary per mark, but not <i>per datum</i>! These |
| 6288 | * properties are evaluated solely for the first (0-index) datum, and typically |
| 6289 | * are specified as a constant. However, it is valid to use a function if the |
| 6290 | * property varies between panels or is dynamically generated. |
| 6291 | * |
| 6292 | * <p>See also the <a href="../../api/">Protovis guide</a>. |
| 6293 | */ |
| 6294 | pv.Mark = function() { |
| 6295 | /* |
| 6296 | * TYPE 0 constant defs |
| 6297 | * TYPE 1 function defs |
| 6298 | * TYPE 2 constant properties |
| 6299 | * TYPE 3 function properties |
| 6300 | * in order of evaluation! |
| 6301 | */ |
| 6302 | this.$properties = []; |
| 6303 | this.$handlers = {}; |
| 6304 | }; |
| 6305 | |
| 6306 | /** @private Records which properties are defined on this mark type. */ |
| 6307 | pv.Mark.prototype.properties = {}; |
| 6308 | |
| 6309 | /** @private Records the cast function for each property. */ |
| 6310 | pv.Mark.cast = {}; |
| 6311 | |
| 6312 | /** |
| 6313 | * @private Defines and registers a property method for the property with the |
| 6314 | * given name. This method should be called on a mark class prototype to define |
| 6315 | * each exposed property. (Note this refers to the JavaScript |
| 6316 | * <tt>prototype</tt>, not the Protovis mark prototype, which is the {@link |
| 6317 | * #proto} field.) |
| 6318 | * |
| 6319 | * <p>The created property method supports several modes of invocation: <ol> |
| 6320 | * |
| 6321 | * <li>If invoked with a <tt>Function</tt> argument, this function is evaluated |
| 6322 | * for each associated datum. The return value of the function is used as the |
| 6323 | * computed property value. The context of the function (<tt>this</tt>) is this |
| 6324 | * mark. The arguments to the function are the associated data of this mark and |
| 6325 | * any enclosing panels. For example, a linear encoding of numerical data to |
| 6326 | * height is specified as |
| 6327 | * |
| 6328 | * <pre>m.height(function(d) d * 100);</pre> |
| 6329 | * |
| 6330 | * The expression <tt>d * 100</tt> will be evaluated for the height property of |
| 6331 | * each mark instance. The return value of the property method (e.g., |
| 6332 | * <tt>m.height</tt>) is this mark (<tt>m</tt>)).<p> |
| 6333 | * |
| 6334 | * <li>If invoked with a non-function argument, the property is treated as a |
| 6335 | * constant. The return value of the property method (e.g., <tt>m.height</tt>) |
| 6336 | * is this mark.<p> |
| 6337 | * |
| 6338 | * <li>If invoked with no arguments, the computed property value for the current |
| 6339 | * mark instance in the scene graph is returned. This facilitates <i>property |
| 6340 | * chaining</i>, where one mark's properties are defined in terms of another's. |
| 6341 | * For example, to offset a mark's location from its prototype, you might say |
| 6342 | * |
| 6343 | * <pre>m.top(function() this.proto.top() + 10);</pre> |
| 6344 | * |
| 6345 | * Note that the index of the mark being evaluated (in the above example, |
| 6346 | * <tt>this.proto</tt>) is inherited from the <tt>Mark</tt> class and set by |
| 6347 | * this mark. So, if the fifth element's top property is being evaluated, the |
| 6348 | * fifth instance of <tt>this.proto</tt> will similarly be queried for the value |
| 6349 | * of its top property. If the mark being evaluated has a different number of |
| 6350 | * instances, or its data is unrelated, the behavior of this method is |
| 6351 | * undefined. In these cases it may be better to index the <tt>scene</tt> |
| 6352 | * explicitly to specify the exact instance. |
| 6353 | * |
| 6354 | * </ol><p>Property names should follow standard JavaScript method naming |
| 6355 | * conventions, using lowerCamel-style capitalization. |
| 6356 | * |
| 6357 | * <p>In addition to creating the property method, every property is registered |
| 6358 | * in the {@link #properties} map on the <tt>prototype</tt>. Although this is an |
| 6359 | * instance field, it is considered immutable and shared by all instances of a |
| 6360 | * given mark type. The <tt>properties</tt> map can be queried to see if a mark |
| 6361 | * type defines a particular property, such as width or height. |
| 6362 | * |
| 6363 | * @param {string} name the property name. |
| 6364 | * @param {function} [cast] the cast function for this property. |
| 6365 | */ |
| 6366 | pv.Mark.prototype.property = function(name, cast) { |
| 6367 | if (!this.hasOwnProperty("properties")) { |
| 6368 | this.properties = pv.extend(this.properties); |
| 6369 | } |
| 6370 | this.properties[name] = true; |
| 6371 | |
| 6372 | /* |
| 6373 | * Define the setter-getter globally, since the default behavior should be the |
| 6374 | * same for all properties, and since the Protovis inheritance chain is |
| 6375 | * independent of the JavaScript inheritance chain. For example, anchors |
| 6376 | * define a "name" property that is evaluated on derived marks, even though |
| 6377 | * those marks don't normally have a name. |
| 6378 | */ |
| 6379 | pv.Mark.prototype.propertyMethod(name, false, pv.Mark.cast[name] = cast); |
| 6380 | return this; |
| 6381 | }; |
| 6382 | |
| 6383 | /** |
| 6384 | * @private Defines a setter-getter for the specified property. |
| 6385 | * |
| 6386 | * <p>If a cast function has been assigned to the specified property name, the |
| 6387 | * property function is wrapped by the cast function, or, if a constant is |
| 6388 | * specified, the constant is immediately cast. Note, however, that if the |
| 6389 | * property value is null, the cast function is not invoked. |
| 6390 | * |
| 6391 | * @param {string} name the property name. |
| 6392 | * @param {boolean} [def] whether is a property or a def. |
| 6393 | * @param {function} [cast] the cast function for this property. |
| 6394 | */ |
| 6395 | pv.Mark.prototype.propertyMethod = function(name, def, cast) { |
| 6396 | if (!cast) cast = pv.Mark.cast[name]; |
| 6397 | this[name] = function(v) { |
| 6398 | |
| 6399 | /* If this is a def, use it rather than property. */ |
| 6400 | if (def && this.scene) { |
| 6401 | var defs = this.scene.defs; |
| 6402 | if (arguments.length) { |
| 6403 | defs[name] = { |
| 6404 | id: (v == null) ? 0 : pv.id(), |
| 6405 | value: ((v != null) && cast) ? cast(v) : v |
| 6406 | }; |
| 6407 | return this; |
| 6408 | } |
| 6409 | return defs[name] ? defs[name].value : null; |
| 6410 | } |
| 6411 | |
| 6412 | /* If arguments are specified, set the property value. */ |
| 6413 | if (arguments.length) { |
| 6414 | var type = !def << 1 | (typeof v == "function"); |
| 6415 | this.propertyValue(name, (type & 1 && cast) ? function() { |
| 6416 | var x = v.apply(this, arguments); |
| 6417 | return (x != null) ? cast(x) : null; |
| 6418 | } : (((v != null) && cast) ? cast(v) : v)).type = type; |
| 6419 | return this; |
| 6420 | } |
| 6421 | |
| 6422 | return this.instance()[name]; |
| 6423 | }; |
| 6424 | }; |
| 6425 | |
| 6426 | /** @private Sets the value of the property <i>name</i> to <i>v</i>. */ |
| 6427 | pv.Mark.prototype.propertyValue = function(name, v) { |
| 6428 | var properties = this.$properties, p = {name: name, id: pv.id(), value: v}; |
| 6429 | for (var i = 0; i < properties.length; i++) { |
| 6430 | if (properties[i].name == name) { |
| 6431 | properties.splice(i, 1); |
| 6432 | break; |
| 6433 | } |
| 6434 | } |
| 6435 | properties.push(p); |
| 6436 | return p; |
| 6437 | }; |
| 6438 | |
| 6439 | /* Define all global properties. */ |
| 6440 | pv.Mark.prototype |
| 6441 | .property("data") |
| 6442 | .property("visible", Boolean) |
| 6443 | .property("left", Number) |
| 6444 | .property("right", Number) |
| 6445 | .property("top", Number) |
| 6446 | .property("bottom", Number) |
| 6447 | .property("cursor", String) |
| 6448 | .property("title", String) |
| 6449 | .property("reverse", Boolean) |
| 6450 | .property("antialias", Boolean) |
| 6451 | .property("events", String); |
| 6452 | |
| 6453 | /** |
| 6454 | * The mark type; a lower camelCase name. The type name controls rendering |
| 6455 | * behavior, and unless the rendering engine is extended, must be one of the |
| 6456 | * built-in concrete mark types: area, bar, dot, image, label, line, panel, |
| 6457 | * rule, or wedge. |
| 6458 | * |
| 6459 | * @type string |
| 6460 | * @name pv.Mark.prototype.type |
| 6461 | */ |
| 6462 | |
| 6463 | /** |
| 6464 | * The mark prototype, possibly undefined, from which to inherit property |
| 6465 | * functions. The mark prototype is not necessarily of the same type as this |
| 6466 | * mark. Any properties defined on this mark will override properties inherited |
| 6467 | * either from the prototype or from the type-specific defaults. |
| 6468 | * |
| 6469 | * @type pv.Mark |
| 6470 | * @name pv.Mark.prototype.proto |
| 6471 | */ |
| 6472 | |
| 6473 | /** |
| 6474 | * The enclosing parent panel. The parent panel is generally undefined only for |
| 6475 | * the root panel; however, it is possible to create "offscreen" marks that are |
| 6476 | * used only for inheritance purposes. |
| 6477 | * |
| 6478 | * @type pv.Panel |
| 6479 | * @name pv.Mark.prototype.parent |
| 6480 | */ |
| 6481 | |
| 6482 | /** |
| 6483 | * The child index. -1 if the enclosing parent panel is null; otherwise, the |
| 6484 | * zero-based index of this mark into the parent panel's <tt>children</tt> array. |
| 6485 | * |
| 6486 | * @type number |
| 6487 | */ |
| 6488 | pv.Mark.prototype.childIndex = -1; |
| 6489 | |
| 6490 | /** |
| 6491 | * The mark index. The value of this field depends on which instance (i.e., |
| 6492 | * which element of the data array) is currently being evaluated. During the |
| 6493 | * build phase, the index is incremented over each datum; when handling events, |
| 6494 | * the index is set to the instance that triggered the event. |
| 6495 | * |
| 6496 | * @type number |
| 6497 | */ |
| 6498 | pv.Mark.prototype.index = -1; |
| 6499 | |
| 6500 | /** |
| 6501 | * The current scale factor, based on any enclosing transforms. The current |
| 6502 | * scale can be used to create scale-independent graphics. For example, to |
| 6503 | * define a dot that has a radius of 10 irrespective of any zooming, say: |
| 6504 | * |
| 6505 | * <pre>dot.radius(function() 10 / this.scale)</pre> |
| 6506 | * |
| 6507 | * Note that the stroke width and font size are defined irrespective of scale |
| 6508 | * (i.e., in screen space) already. Also note that when a transform is applied |
| 6509 | * to a panel, the scale affects only the child marks, not the panel itself. |
| 6510 | * |
| 6511 | * @type number |
| 6512 | * @see pv.Panel#transform |
| 6513 | */ |
| 6514 | pv.Mark.prototype.scale = 1; |
| 6515 | |
| 6516 | /** |
| 6517 | * @private The scene graph. The scene graph is an array of objects; each object |
| 6518 | * (or "node") corresponds to an instance of this mark and an element in the |
| 6519 | * data array. The scene graph can be traversed to lookup previously-evaluated |
| 6520 | * properties. |
| 6521 | * |
| 6522 | * @name pv.Mark.prototype.scene |
| 6523 | */ |
| 6524 | |
| 6525 | /** |
| 6526 | * The root parent panel. This may be undefined for "offscreen" marks that are |
| 6527 | * created for inheritance purposes only. |
| 6528 | * |
| 6529 | * @type pv.Panel |
| 6530 | * @name pv.Mark.prototype.root |
| 6531 | */ |
| 6532 | |
| 6533 | /** |
| 6534 | * The data property; an array of objects. The size of the array determines the |
| 6535 | * number of marks that will be instantiated; each element in the array will be |
| 6536 | * passed to property functions to compute the property values. Typically, the |
| 6537 | * data property is specified as a constant array, such as |
| 6538 | * |
| 6539 | * <pre>m.data([1, 2, 3, 4, 5]);</pre> |
| 6540 | * |
| 6541 | * However, it is perfectly acceptable to define the data property as a |
| 6542 | * function. This function might compute the data dynamically, allowing |
| 6543 | * different data to be used per enclosing panel. For instance, in the stacked |
| 6544 | * area graph example (see {@link #scene}), the data function on the area mark |
| 6545 | * dereferences each series. |
| 6546 | * |
| 6547 | * @type array |
| 6548 | * @name pv.Mark.prototype.data |
| 6549 | */ |
| 6550 | |
| 6551 | /** |
| 6552 | * The visible property; a boolean determining whether or not the mark instance |
| 6553 | * is visible. If a mark instance is not visible, its other properties will not |
| 6554 | * be evaluated. Similarly, for panels no child marks will be rendered. |
| 6555 | * |
| 6556 | * @type boolean |
| 6557 | * @name pv.Mark.prototype.visible |
| 6558 | */ |
| 6559 | |
| 6560 | /** |
| 6561 | * The left margin; the distance, in pixels, between the left edge of the |
| 6562 | * enclosing panel and the left edge of this mark. Note that in some cases this |
| 6563 | * property may be redundant with the right property, or with the conjunction of |
| 6564 | * right and width. |
| 6565 | * |
| 6566 | * @type number |
| 6567 | * @name pv.Mark.prototype.left |
| 6568 | */ |
| 6569 | |
| 6570 | /** |
| 6571 | * The right margin; the distance, in pixels, between the right edge of the |
| 6572 | * enclosing panel and the right edge of this mark. Note that in some cases this |
| 6573 | * property may be redundant with the left property, or with the conjunction of |
| 6574 | * left and width. |
| 6575 | * |
| 6576 | * @type number |
| 6577 | * @name pv.Mark.prototype.right |
| 6578 | */ |
| 6579 | |
| 6580 | /** |
| 6581 | * The top margin; the distance, in pixels, between the top edge of the |
| 6582 | * enclosing panel and the top edge of this mark. Note that in some cases this |
| 6583 | * property may be redundant with the bottom property, or with the conjunction |
| 6584 | * of bottom and height. |
| 6585 | * |
| 6586 | * @type number |
| 6587 | * @name pv.Mark.prototype.top |
| 6588 | */ |
| 6589 | |
| 6590 | /** |
| 6591 | * The bottom margin; the distance, in pixels, between the bottom edge of the |
| 6592 | * enclosing panel and the bottom edge of this mark. Note that in some cases |
| 6593 | * this property may be redundant with the top property, or with the conjunction |
| 6594 | * of top and height. |
| 6595 | * |
| 6596 | * @type number |
| 6597 | * @name pv.Mark.prototype.bottom |
| 6598 | */ |
| 6599 | |
| 6600 | /** |
| 6601 | * The cursor property; corresponds to the CSS cursor property. This is |
| 6602 | * typically used in conjunction with event handlers to indicate interactivity. |
| 6603 | * |
| 6604 | * @type string |
| 6605 | * @name pv.Mark.prototype.cursor |
| 6606 | * @see <a href="http://www.w3.org/TR/CSS2/ui.html#propdef-cursor">CSS2 cursor</a> |
| 6607 | */ |
| 6608 | |
| 6609 | /** |
| 6610 | * The title property; corresponds to the HTML/SVG title property, allowing the |
| 6611 | * general of simple plain text tooltips. |
| 6612 | * |
| 6613 | * @type string |
| 6614 | * @name pv.Mark.prototype.title |
| 6615 | */ |
| 6616 | |
| 6617 | /** |
| 6618 | * The events property; corresponds to the SVG pointer-events property, |
| 6619 | * specifying how the mark should participate in mouse events. The default value |
| 6620 | * is "painted". Supported values are: |
| 6621 | * |
| 6622 | * <p>"painted": The given mark may receive events when the mouse is over a |
| 6623 | * "painted" area. The painted areas are the interior (i.e., fill) of the mark |
| 6624 | * if a 'fillStyle' is specified, and the perimeter (i.e., stroke) of the mark |
| 6625 | * if a 'strokeStyle' is specified. |
| 6626 | * |
| 6627 | * <p>"all": The given mark may receive events when the mouse is over either the |
| 6628 | * interior (i.e., fill) or the perimeter (i.e., stroke) of the mark, regardless |
| 6629 | * of the specified fillStyle and strokeStyle. |
| 6630 | * |
| 6631 | * <p>"none": The given mark may not receive events. |
| 6632 | * |
| 6633 | * @type string |
| 6634 | * @name pv.Mark.prototype.events |
| 6635 | */ |
| 6636 | |
| 6637 | /** |
| 6638 | * The reverse property; a boolean determining whether marks are ordered from |
| 6639 | * front-to-back or back-to-front. SVG does not support explicit z-ordering; |
| 6640 | * shapes are rendered in the order they appear. Thus, by default, marks are |
| 6641 | * rendered in data order. Setting the reverse property to false reverses the |
| 6642 | * order in which they are rendered; however, the properties are still evaluated |
| 6643 | * (i.e., built) in forward order. |
| 6644 | * |
| 6645 | * @type boolean |
| 6646 | * @name pv.Mark.prototype.reverse |
| 6647 | */ |
| 6648 | |
| 6649 | /** |
| 6650 | * Default properties for all mark types. By default, the data array is the |
| 6651 | * parent data as a single-element array; if the data property is not specified, |
| 6652 | * this causes each mark to be instantiated as a singleton with the parents |
| 6653 | * datum. The visible property is true by default, and the reverse property is |
| 6654 | * false. |
| 6655 | * |
| 6656 | * @type pv.Mark |
| 6657 | */ |
| 6658 | pv.Mark.prototype.defaults = new pv.Mark() |
| 6659 | .data(function(d) { return [d]; }) |
| 6660 | .visible(true) |
| 6661 | .antialias(true) |
| 6662 | .events("painted"); |
| 6663 | |
| 6664 | /** |
| 6665 | * Sets the prototype of this mark to the specified mark. Any properties not |
| 6666 | * defined on this mark may be inherited from the specified prototype mark, or |
| 6667 | * its prototype, and so on. The prototype mark need not be the same type of |
| 6668 | * mark as this mark. (Note that for inheritance to be useful, properties with |
| 6669 | * the same name on different mark types should have equivalent meaning.) |
| 6670 | * |
| 6671 | * @param {pv.Mark} proto the new prototype. |
| 6672 | * @returns {pv.Mark} this mark. |
| 6673 | * @see #add |
| 6674 | */ |
| 6675 | pv.Mark.prototype.extend = function(proto) { |
| 6676 | this.proto = proto; |
| 6677 | return this; |
| 6678 | }; |
| 6679 | |
| 6680 | /** |
| 6681 | * Adds a new mark of the specified type to the enclosing parent panel, whilst |
| 6682 | * simultaneously setting the prototype of the new mark to be this mark. |
| 6683 | * |
| 6684 | * @param {function} type the type of mark to add; a constructor, such as |
| 6685 | * <tt>pv.Bar</tt>. |
| 6686 | * @returns {pv.Mark} the new mark. |
| 6687 | * @see #extend |
| 6688 | */ |
| 6689 | pv.Mark.prototype.add = function(type) { |
| 6690 | return this.parent.add(type).extend(this); |
| 6691 | }; |
| 6692 | |
| 6693 | /** |
| 6694 | * Defines a custom property on this mark. Custom properties are currently |
| 6695 | * fixed, in that they are initialized once per mark set (i.e., per parent panel |
| 6696 | * instance). Custom properties can be used to store local state for the mark, |
| 6697 | * such as data needed by other properties (e.g., a custom scale) or interaction |
| 6698 | * state. |
| 6699 | * |
| 6700 | * <p>WARNING We plan on changing this feature in a future release to define |
| 6701 | * standard properties, as opposed to <i>fixed</i> properties that behave |
| 6702 | * idiosyncratically within event handlers. Furthermore, we recommend storing |
| 6703 | * state in an external data structure, rather than tying it to the |
| 6704 | * visualization specification as with defs. |
| 6705 | * |
| 6706 | * @param {string} name the name of the local variable. |
| 6707 | * @param {function} [v] an optional initializer; may be a constant or a |
| 6708 | * function. |
| 6709 | */ |
| 6710 | pv.Mark.prototype.def = function(name, v) { |
| 6711 | this.propertyMethod(name, true); |
| 6712 | return this[name](arguments.length > 1 ? v : null); |
| 6713 | }; |
| 6714 | |
| 6715 | /** |
| 6716 | * Returns an anchor with the specified name. All marks support the five |
| 6717 | * standard anchor names:<ul> |
| 6718 | * |
| 6719 | * <li>top |
| 6720 | * <li>left |
| 6721 | * <li>center |
| 6722 | * <li>bottom |
| 6723 | * <li>right |
| 6724 | * |
| 6725 | * </ul>In addition to positioning properties (left, right, top bottom), the |
| 6726 | * anchors support text rendering properties (text-align, text-baseline). Text is |
| 6727 | * rendered to appear inside the mark by default. |
| 6728 | * |
| 6729 | * <p>To facilitate stacking, anchors are defined in terms of their opposite |
| 6730 | * edge. For example, the top anchor defines the bottom property, such that the |
| 6731 | * mark extends upwards; the bottom anchor instead defines the top property, |
| 6732 | * such that the mark extends downwards. See also {@link pv.Layout.Stack}. |
| 6733 | * |
| 6734 | * <p>While anchor names are typically constants, the anchor name is a true |
| 6735 | * property, which means you can specify a function to compute the anchor name |
| 6736 | * dynamically. See the {@link pv.Anchor#name} property for details. |
| 6737 | * |
| 6738 | * @param {string} name the anchor name; either a string or a property function. |
| 6739 | * @returns {pv.Anchor} the new anchor. |
| 6740 | */ |
| 6741 | pv.Mark.prototype.anchor = function(name) { |
| 6742 | var target = this, scene; |
| 6743 | |
| 6744 | /* Default anchor name. */ |
| 6745 | if (!name) name = "center"; |
| 6746 | |
| 6747 | /** @private Find the instances of target that match source. */ |
| 6748 | function instances(source) { |
| 6749 | var mark = target, index = []; |
| 6750 | |
| 6751 | /* Mirrored descent. */ |
| 6752 | while (!(scene = mark.scene)) { |
| 6753 | source = source.parent; |
| 6754 | index.push({index: source.index, childIndex: mark.childIndex}); |
| 6755 | mark = mark.parent; |
| 6756 | } |
| 6757 | while (index.length) { |
| 6758 | var i = index.pop(); |
| 6759 | scene = scene[i.index].children[i.childIndex]; |
| 6760 | } |
| 6761 | |
| 6762 | /* |
| 6763 | * When the anchor target is also an ancestor, as in the case of adding |
| 6764 | * to a panel anchor, only generate one instance per panel. Also, set |
| 6765 | * the margins to zero, since they are offset by the enclosing panel. |
| 6766 | */ |
| 6767 | if (target.hasOwnProperty("index")) { |
| 6768 | var s = pv.extend(scene[target.index]); |
| 6769 | s.right = s.top = s.left = s.bottom = 0; |
| 6770 | return [s]; |
| 6771 | } |
| 6772 | return scene; |
| 6773 | } |
| 6774 | |
| 6775 | return new pv.Anchor(this) |
| 6776 | .name(name) |
| 6777 | .def("$mark.anchor", function() { |
| 6778 | scene = this.scene.target = instances(this); |
| 6779 | }) |
| 6780 | .data(function() { |
| 6781 | return scene.map(function(s) { return s.data; }); |
| 6782 | }) |
| 6783 | .visible(function() { |
| 6784 | return scene[this.index].visible; |
| 6785 | }) |
| 6786 | .left(function() { |
| 6787 | var s = scene[this.index], w = s.width || 0; |
| 6788 | switch (this.name()) { |
| 6789 | case "bottom": |
| 6790 | case "top": |
| 6791 | case "center": return s.left + w / 2; |
| 6792 | case "left": return null; |
| 6793 | } |
| 6794 | return s.left + w; |
| 6795 | }) |
| 6796 | .top(function() { |
| 6797 | var s = scene[this.index], h = s.height || 0; |
| 6798 | switch (this.name()) { |
| 6799 | case "left": |
| 6800 | case "right": |
| 6801 | case "center": return s.top + h / 2; |
| 6802 | case "top": return null; |
| 6803 | } |
| 6804 | return s.top + h; |
| 6805 | }) |
| 6806 | .right(function() { |
| 6807 | var s = scene[this.index]; |
| 6808 | return this.name() == "left" ? s.right + (s.width || 0) : null; |
| 6809 | }) |
| 6810 | .bottom(function() { |
| 6811 | var s = scene[this.index]; |
| 6812 | return this.name() == "top" ? s.bottom + (s.height || 0) : null; |
| 6813 | }) |
| 6814 | .textAlign(function() { |
| 6815 | switch (this.name()) { |
| 6816 | case "bottom": |
| 6817 | case "top": |
| 6818 | case "center": return "center"; |
| 6819 | case "right": return "right"; |
| 6820 | } |
| 6821 | return "left"; |
| 6822 | }) |
| 6823 | .textBaseline(function() { |
| 6824 | switch (this.name()) { |
| 6825 | case "right": |
| 6826 | case "left": |
| 6827 | case "center": return "middle"; |
| 6828 | case "top": return "top"; |
| 6829 | } |
| 6830 | return "bottom"; |
| 6831 | }); |
| 6832 | }; |
| 6833 | |
| 6834 | /** |
| 6835 | * Returns the anchor target of this mark, if it is derived from an anchor; |
| 6836 | * otherwise returns null. For example, if a label is derived from a bar anchor, |
| 6837 | * |
| 6838 | * <pre>bar.anchor("top").add(pv.Label);</pre> |
| 6839 | * |
| 6840 | * then property functions on the label can refer to the bar via the |
| 6841 | * <tt>anchorTarget</tt> method. This method is also useful for mark types |
| 6842 | * defining properties on custom anchors. |
| 6843 | * |
| 6844 | * @returns {pv.Mark} the anchor target of this mark; possibly null. |
| 6845 | */ |
| 6846 | pv.Mark.prototype.anchorTarget = function() { |
| 6847 | return this.proto.anchorTarget(); |
| 6848 | }; |
| 6849 | |
| 6850 | /** |
| 6851 | * Alias for setting the left, right, top and bottom properties simultaneously. |
| 6852 | * |
| 6853 | * @see #left |
| 6854 | * @see #right |
| 6855 | * @see #top |
| 6856 | * @see #bottom |
| 6857 | * @returns {pv.Mark} this. |
| 6858 | */ |
| 6859 | pv.Mark.prototype.margin = function(n) { |
| 6860 | return this.left(n).right(n).top(n).bottom(n); |
| 6861 | }; |
| 6862 | |
| 6863 | /** |
| 6864 | * @private Returns the current instance of this mark in the scene graph. This |
| 6865 | * is typically equivalent to <tt>this.scene[this.index]</tt>, however if the |
| 6866 | * scene or index is unset, the default instance of the mark is returned. If no |
| 6867 | * default is set, the default is the last instance. Similarly, if the scene or |
| 6868 | * index of the parent panel is unset, the default instance of this mark in the |
| 6869 | * last instance of the enclosing panel is returned, and so on. |
| 6870 | * |
| 6871 | * @returns a node in the scene graph. |
| 6872 | */ |
| 6873 | pv.Mark.prototype.instance = function(defaultIndex) { |
| 6874 | var scene = this.scene || this.parent.instance(-1).children[this.childIndex], |
| 6875 | index = !arguments.length || this.hasOwnProperty("index") ? this.index : defaultIndex; |
| 6876 | return scene[index < 0 ? scene.length - 1 : index]; |
| 6877 | }; |
| 6878 | |
| 6879 | /** |
| 6880 | * @private Returns the first instance of this mark in the scene graph. This |
| 6881 | * method can only be called when the mark is bound to the scene graph (for |
| 6882 | * example, from an event handler, or within a property function). |
| 6883 | * |
| 6884 | * @returns a node in the scene graph. |
| 6885 | */ |
| 6886 | pv.Mark.prototype.first = function() { |
| 6887 | return this.scene[0]; |
| 6888 | }; |
| 6889 | |
| 6890 | /** |
| 6891 | * @private Returns the last instance of this mark in the scene graph. This |
| 6892 | * method can only be called when the mark is bound to the scene graph (for |
| 6893 | * example, from an event handler, or within a property function). In addition, |
| 6894 | * note that mark instances are built sequentially, so the last instance of this |
| 6895 | * mark may not yet be constructed. |
| 6896 | * |
| 6897 | * @returns a node in the scene graph. |
| 6898 | */ |
| 6899 | pv.Mark.prototype.last = function() { |
| 6900 | return this.scene[this.scene.length - 1]; |
| 6901 | }; |
| 6902 | |
| 6903 | /** |
| 6904 | * @private Returns the previous instance of this mark in the scene graph, or |
| 6905 | * null if this is the first instance. |
| 6906 | * |
| 6907 | * @returns a node in the scene graph, or null. |
| 6908 | */ |
| 6909 | pv.Mark.prototype.sibling = function() { |
| 6910 | return (this.index == 0) ? null : this.scene[this.index - 1]; |
| 6911 | }; |
| 6912 | |
| 6913 | /** |
| 6914 | * @private Returns the current instance in the scene graph of this mark, in the |
| 6915 | * previous instance of the enclosing parent panel. May return null if this |
| 6916 | * instance could not be found. |
| 6917 | * |
| 6918 | * @returns a node in the scene graph, or null. |
| 6919 | */ |
| 6920 | pv.Mark.prototype.cousin = function() { |
| 6921 | var p = this.parent, s = p && p.sibling(); |
| 6922 | return (s && s.children) ? s.children[this.childIndex][this.index] : null; |
| 6923 | }; |
| 6924 | |
| 6925 | /** |
| 6926 | * Renders this mark, including recursively rendering all child marks if this is |
| 6927 | * a panel. This method finds all instances of this mark and renders them. This |
| 6928 | * method descends recursively to the level of the mark to be rendered, finding |
| 6929 | * all visible instances of the mark. After the marks are rendered, the scene |
| 6930 | * and index attributes are removed from the mark to restore them to a clean |
| 6931 | * state. |
| 6932 | * |
| 6933 | * <p>If an enclosing panel has an index property set (as is the case inside in |
| 6934 | * an event handler), then only instances of this mark inside the given instance |
| 6935 | * of the panel will be rendered; otherwise, all visible instances of the mark |
| 6936 | * will be rendered. |
| 6937 | */ |
| 6938 | pv.Mark.prototype.render = function() { |
| 6939 | var parent = this.parent, |
| 6940 | stack = pv.Mark.stack; |
| 6941 | |
| 6942 | /* For the first render, take it from the top. */ |
| 6943 | if (parent && !this.root.scene) { |
| 6944 | this.root.render(); |
| 6945 | return; |
| 6946 | } |
| 6947 | |
| 6948 | /* Record the path to this mark. */ |
| 6949 | var indexes = []; |
| 6950 | for (var mark = this; mark.parent; mark = mark.parent) { |
| 6951 | indexes.unshift(mark.childIndex); |
| 6952 | } |
| 6953 | |
| 6954 | /** @private */ |
| 6955 | function render(mark, depth, scale) { |
| 6956 | mark.scale = scale; |
| 6957 | if (depth < indexes.length) { |
| 6958 | stack.unshift(null); |
| 6959 | if (mark.hasOwnProperty("index")) { |
| 6960 | renderInstance(mark, depth, scale); |
| 6961 | } else { |
| 6962 | for (var i = 0, n = mark.scene.length; i < n; i++) { |
| 6963 | mark.index = i; |
| 6964 | renderInstance(mark, depth, scale); |
| 6965 | } |
| 6966 | delete mark.index; |
| 6967 | } |
| 6968 | stack.shift(); |
| 6969 | } else { |
| 6970 | mark.build(); |
| 6971 | |
| 6972 | /* |
| 6973 | * In the update phase, the scene is rendered by creating and updating |
| 6974 | * elements and attributes in the SVG image. No properties are evaluated |
| 6975 | * during the update phase; instead the values computed previously in the |
| 6976 | * build phase are simply translated into SVG. The update phase is |
| 6977 | * decoupled (see pv.Scene) to allow different rendering engines. |
| 6978 | */ |
| 6979 | pv.Scene.scale = scale; |
| 6980 | pv.Scene.updateAll(mark.scene); |
| 6981 | } |
| 6982 | delete mark.scale; |
| 6983 | } |
| 6984 | |
| 6985 | /** |
| 6986 | * @private Recursively renders the current instance of the specified mark. |
| 6987 | * This is slightly tricky because `index` and `scene` properties may or may |
| 6988 | * not already be set; if they are set, it means we are rendering only a |
| 6989 | * specific instance; if they are unset, we are rendering all instances. |
| 6990 | * Furthermore, we must preserve the original context of these properties when |
| 6991 | * rendering completes. |
| 6992 | * |
| 6993 | * <p>Another tricky aspect is that the `scene` attribute should be set for |
| 6994 | * any preceding children, so as to allow property chaining. This is |
| 6995 | * consistent with first-pass rendering. |
| 6996 | */ |
| 6997 | function renderInstance(mark, depth, scale) { |
| 6998 | var s = mark.scene[mark.index], i; |
| 6999 | if (s.visible) { |
| 7000 | var childIndex = indexes[depth], |
| 7001 | child = mark.children[childIndex]; |
| 7002 | |
| 7003 | /* Set preceding child scenes. */ |
| 7004 | for (i = 0; i < childIndex; i++) { |
| 7005 | mark.children[i].scene = s.children[i]; |
| 7006 | } |
| 7007 | |
| 7008 | /* Set current child scene, if necessary. */ |
| 7009 | stack[0] = s.data; |
| 7010 | if (child.scene) { |
| 7011 | render(child, depth + 1, scale * s.transform.k); |
| 7012 | } else { |
| 7013 | child.scene = s.children[childIndex]; |
| 7014 | render(child, depth + 1, scale * s.transform.k); |
| 7015 | delete child.scene; |
| 7016 | } |
| 7017 | |
| 7018 | /* Clear preceding child scenes. */ |
| 7019 | for (i = 0; i < childIndex; i++) { |
| 7020 | delete mark.children[i].scene; |
| 7021 | } |
| 7022 | } |
| 7023 | } |
| 7024 | |
| 7025 | /* Bind this mark's property definitions. */ |
| 7026 | this.bind(); |
| 7027 | |
| 7028 | /* The render context is the first ancestor with an explicit index. */ |
| 7029 | while (parent && !parent.hasOwnProperty("index")) parent = parent.parent; |
| 7030 | |
| 7031 | /* Recursively render all instances of this mark. */ |
| 7032 | this.context( |
| 7033 | parent ? parent.scene : undefined, |
| 7034 | parent ? parent.index : -1, |
| 7035 | function() { render(this.root, 0, 1); }); |
| 7036 | }; |
| 7037 | |
| 7038 | /** @private Stores the current data stack. */ |
| 7039 | pv.Mark.stack = []; |
| 7040 | |
| 7041 | /** |
| 7042 | * @private In the bind phase, inherited property definitions are cached so they |
| 7043 | * do not need to be queried during build. |
| 7044 | */ |
| 7045 | pv.Mark.prototype.bind = function() { |
| 7046 | var seen = {}, types = [[], [], [], []], data, visible; |
| 7047 | |
| 7048 | /** Scans the proto chain for the specified mark. */ |
| 7049 | function bind(mark) { |
| 7050 | do { |
| 7051 | var properties = mark.$properties; |
| 7052 | for (var i = properties.length - 1; i >= 0 ; i--) { |
| 7053 | var p = properties[i]; |
| 7054 | if (!(p.name in seen)) { |
| 7055 | seen[p.name] = p; |
| 7056 | switch (p.name) { |
| 7057 | case "data": data = p; break; |
| 7058 | case "visible": visible = p; break; |
| 7059 | default: types[p.type].push(p); break; |
| 7060 | } |
| 7061 | } |
| 7062 | } |
| 7063 | } while (mark = mark.proto); |
| 7064 | } |
| 7065 | |
| 7066 | /* Scan the proto chain for all defined properties. */ |
| 7067 | bind(this); |
| 7068 | bind(this.defaults); |
| 7069 | types[1].reverse(); |
| 7070 | types[3].reverse(); |
| 7071 | |
| 7072 | /* Any undefined properties are null. */ |
| 7073 | var mark = this; |
| 7074 | do for (var name in mark.properties) { |
| 7075 | if (!(name in seen)) { |
| 7076 | types[2].push(seen[name] = {name: name, type: 2, value: null}); |
| 7077 | } |
| 7078 | } while (mark = mark.proto); |
| 7079 | |
| 7080 | /* Define setter-getter for inherited defs. */ |
| 7081 | var defs = types[0].concat(types[1]); |
| 7082 | for (var i = 0; i < defs.length; i++) { |
| 7083 | this.propertyMethod(defs[i].name, true); |
| 7084 | } |
| 7085 | |
| 7086 | /* Setup binds to evaluate constants before functions. */ |
| 7087 | this.binds = { |
| 7088 | properties: seen, |
| 7089 | data: data, |
| 7090 | defs: defs, |
| 7091 | required: [visible], |
| 7092 | optional: pv.blend(types) |
| 7093 | }; |
| 7094 | }; |
| 7095 | |
| 7096 | /** |
| 7097 | * @private Evaluates properties and computes implied properties. Properties are |
| 7098 | * stored in the {@link #scene} array for each instance of this mark. |
| 7099 | * |
| 7100 | * <p>As marks are built recursively, the {@link #index} property is updated to |
| 7101 | * match the current index into the data array for each mark. Note that the |
| 7102 | * index property is only set for the mark currently being built and its |
| 7103 | * enclosing parent panels. The index property for other marks is unset, but is |
| 7104 | * inherited from the global <tt>Mark</tt> class prototype. This allows mark |
| 7105 | * properties to refer to properties on other marks <i>in the same panel</i> |
| 7106 | * conveniently; however, in general it is better to reference mark instances |
| 7107 | * specifically through the scene graph rather than depending on the magical |
| 7108 | * behavior of {@link #index}. |
| 7109 | * |
| 7110 | * <p>The root scene array has a special property, <tt>data</tt>, which stores |
| 7111 | * the current data stack. The first element in this stack is the current datum, |
| 7112 | * followed by the datum of the enclosing parent panel, and so on. The data |
| 7113 | * stack should not be accessed directly; instead, property functions are passed |
| 7114 | * the current data stack as arguments. |
| 7115 | * |
| 7116 | * <p>The evaluation of the <tt>data</tt> and <tt>visible</tt> properties is |
| 7117 | * special. The <tt>data</tt> property is evaluated first; unlike the other |
| 7118 | * properties, the data stack is from the parent panel, rather than the current |
| 7119 | * mark, since the data is not defined until the data property is evaluated. |
| 7120 | * The <tt>visisble</tt> property is subsequently evaluated for each instance; |
| 7121 | * only if true will the {@link #buildInstance} method be called, evaluating |
| 7122 | * other properties and recursively building the scene graph. |
| 7123 | * |
| 7124 | * <p>If this mark is being re-built, any old instances of this mark that no |
| 7125 | * longer exist (because the new data array contains fewer elements) will be |
| 7126 | * cleared using {@link #clearInstance}. |
| 7127 | * |
| 7128 | * @param parent the instance of the parent panel from the scene graph. |
| 7129 | */ |
| 7130 | pv.Mark.prototype.build = function() { |
| 7131 | var scene = this.scene, stack = pv.Mark.stack; |
| 7132 | if (!scene) { |
| 7133 | scene = this.scene = []; |
| 7134 | scene.mark = this; |
| 7135 | scene.type = this.type; |
| 7136 | scene.childIndex = this.childIndex; |
| 7137 | if (this.parent) { |
| 7138 | scene.parent = this.parent.scene; |
| 7139 | scene.parentIndex = this.parent.index; |
| 7140 | } |
| 7141 | } |
| 7142 | |
| 7143 | /* Evaluate defs. */ |
| 7144 | if (this.binds.defs.length) { |
| 7145 | var defs = scene.defs; |
| 7146 | if (!defs) scene.defs = defs = {}; |
| 7147 | for (var i = 0; i < this.binds.defs.length; i++) { |
| 7148 | var p = this.binds.defs[i], d = defs[p.name]; |
| 7149 | if (!d || (p.id > d.id)) { |
| 7150 | defs[p.name] = { |
| 7151 | id: 0, // this def will be re-evaluated on next build |
| 7152 | value: (p.type & 1) ? p.value.apply(this, stack) : p.value |
| 7153 | }; |
| 7154 | } |
| 7155 | } |
| 7156 | } |
| 7157 | |
| 7158 | /* Evaluate special data property. */ |
| 7159 | var data = this.binds.data; |
| 7160 | data = data.type & 1 ? data.value.apply(this, stack) : data.value; |
| 7161 | |
| 7162 | /* Create, update and delete scene nodes. */ |
| 7163 | stack.unshift(null); |
| 7164 | scene.length = data.length; |
| 7165 | for (var i = 0; i < data.length; i++) { |
| 7166 | pv.Mark.prototype.index = this.index = i; |
| 7167 | var s = scene[i]; |
| 7168 | if (!s) scene[i] = s = {}; |
| 7169 | s.data = stack[0] = data[i]; |
| 7170 | this.buildInstance(s); |
| 7171 | } |
| 7172 | pv.Mark.prototype.index = -1; |
| 7173 | delete this.index; |
| 7174 | stack.shift(); |
| 7175 | |
| 7176 | return this; |
| 7177 | }; |
| 7178 | |
| 7179 | /** |
| 7180 | * @private Evaluates the specified array of properties for the specified |
| 7181 | * instance <tt>s</tt> in the scene graph. |
| 7182 | * |
| 7183 | * @param s a node in the scene graph; the instance of the mark to build. |
| 7184 | * @param properties an array of properties. |
| 7185 | */ |
| 7186 | pv.Mark.prototype.buildProperties = function(s, properties) { |
| 7187 | for (var i = 0, n = properties.length; i < n; i++) { |
| 7188 | var p = properties[i], v = p.value; // assume case 2 (constant) |
| 7189 | switch (p.type) { |
| 7190 | case 0: |
| 7191 | case 1: v = this.scene.defs[p.name].value; break; |
| 7192 | case 3: v = v.apply(this, pv.Mark.stack); break; |
| 7193 | } |
| 7194 | s[p.name] = v; |
| 7195 | } |
| 7196 | }; |
| 7197 | |
| 7198 | /** |
| 7199 | * @private Evaluates all of the properties for this mark for the specified |
| 7200 | * instance <tt>s</tt> in the scene graph. The set of properties to evaluate is |
| 7201 | * retrieved from the {@link #properties} array for this mark type (see {@link |
| 7202 | * #type}). After these properties are evaluated, any <b>implied</b> properties |
| 7203 | * may be computed by the mark and set on the scene graph; see |
| 7204 | * {@link #buildImplied}. |
| 7205 | * |
| 7206 | * <p>For panels, this method recursively builds the scene graph for all child |
| 7207 | * marks as well. In general, this method should not need to be overridden by |
| 7208 | * concrete mark types. |
| 7209 | * |
| 7210 | * @param s a node in the scene graph; the instance of the mark to build. |
| 7211 | */ |
| 7212 | pv.Mark.prototype.buildInstance = function(s) { |
| 7213 | this.buildProperties(s, this.binds.required); |
| 7214 | if (s.visible) { |
| 7215 | this.buildProperties(s, this.binds.optional); |
| 7216 | this.buildImplied(s); |
| 7217 | } |
| 7218 | }; |
| 7219 | |
| 7220 | /** |
| 7221 | * @private Computes the implied properties for this mark for the specified |
| 7222 | * instance <tt>s</tt> in the scene graph. Implied properties are those with |
| 7223 | * dependencies on multiple other properties; for example, the width property |
| 7224 | * may be implied if the left and right properties are set. This method can be |
| 7225 | * overridden by concrete mark types to define new implied properties, if |
| 7226 | * necessary. |
| 7227 | * |
| 7228 | * @param s a node in the scene graph; the instance of the mark to build. |
| 7229 | */ |
| 7230 | pv.Mark.prototype.buildImplied = function(s) { |
| 7231 | var l = s.left; |
| 7232 | var r = s.right; |
| 7233 | var t = s.top; |
| 7234 | var b = s.bottom; |
| 7235 | |
| 7236 | /* Assume width and height are zero if not supported by this mark type. */ |
| 7237 | var p = this.properties; |
| 7238 | var w = p.width ? s.width : 0; |
| 7239 | var h = p.height ? s.height : 0; |
| 7240 | |
| 7241 | /* Compute implied width, right and left. */ |
| 7242 | var width = this.parent ? this.parent.width() : (w + l + r); |
| 7243 | if (w == null) { |
| 7244 | w = width - (r = r || 0) - (l = l || 0); |
| 7245 | } else if (r == null) { |
| 7246 | r = width - w - (l = l || 0); |
| 7247 | } else if (l == null) { |
| 7248 | l = width - w - (r = r || 0); |
| 7249 | } |
| 7250 | |
| 7251 | /* Compute implied height, bottom and top. */ |
| 7252 | var height = this.parent ? this.parent.height() : (h + t + b); |
| 7253 | if (h == null) { |
| 7254 | h = height - (t = t || 0) - (b = b || 0); |
| 7255 | } else if (b == null) { |
| 7256 | b = height - h - (t = t || 0); |
| 7257 | } else if (t == null) { |
| 7258 | t = height - h - (b = b || 0); |
| 7259 | } |
| 7260 | |
| 7261 | s.left = l; |
| 7262 | s.right = r; |
| 7263 | s.top = t; |
| 7264 | s.bottom = b; |
| 7265 | |
| 7266 | /* Only set width and height if they are supported by this mark type. */ |
| 7267 | if (p.width) s.width = w; |
| 7268 | if (p.height) s.height = h; |
| 7269 | |
| 7270 | /* Set any null colors to pv.Color.transparent. */ |
| 7271 | if (p.textStyle && !s.textStyle) s.textStyle = pv.Color.transparent; |
| 7272 | if (p.fillStyle && !s.fillStyle) s.fillStyle = pv.Color.transparent; |
| 7273 | if (p.strokeStyle && !s.strokeStyle) s.strokeStyle = pv.Color.transparent; |
| 7274 | }; |
| 7275 | |
| 7276 | /** |
| 7277 | * Returns the current location of the mouse (cursor) relative to this mark's |
| 7278 | * parent. The <i>x</i> coordinate corresponds to the left margin, while the |
| 7279 | * <i>y</i> coordinate corresponds to the top margin. |
| 7280 | * |
| 7281 | * @returns {pv.Vector} the mouse location. |
| 7282 | */ |
| 7283 | pv.Mark.prototype.mouse = function() { |
| 7284 | |
| 7285 | /* Compute xy-coordinates relative to the panel. */ |
| 7286 | var x = pv.event.pageX || 0, |
| 7287 | y = pv.event.pageY || 0, |
| 7288 | n = this.root.canvas(); |
| 7289 | do { |
| 7290 | x -= n.offsetLeft; |
| 7291 | y -= n.offsetTop; |
| 7292 | } while (n = n.offsetParent); |
| 7293 | |
| 7294 | /* Compute the inverse transform of all enclosing panels. */ |
| 7295 | var t = pv.Transform.identity, |
| 7296 | p = this.properties.transform ? this : this.parent, |
| 7297 | pz = []; |
| 7298 | do { pz.push(p); } while (p = p.parent); |
| 7299 | while (p = pz.pop()) t = t.translate(p.left(), p.top()).times(p.transform()); |
| 7300 | t = t.invert(); |
| 7301 | |
| 7302 | return pv.vector(x * t.k + t.x, y * t.k + t.y); |
| 7303 | }; |
| 7304 | |
| 7305 | /** |
| 7306 | * Registers an event handler for the specified event type with this mark. When |
| 7307 | * an event of the specified type is triggered, the specified handler will be |
| 7308 | * invoked. The handler is invoked in a similar method to property functions: |
| 7309 | * the context is <tt>this</tt> mark instance, and the arguments are the full |
| 7310 | * data stack. Event handlers can use property methods to manipulate the display |
| 7311 | * properties of the mark: |
| 7312 | * |
| 7313 | * <pre>m.event("click", function() this.fillStyle("red"));</pre> |
| 7314 | * |
| 7315 | * Alternatively, the external data can be manipulated and the visualization |
| 7316 | * redrawn: |
| 7317 | * |
| 7318 | * <pre>m.event("click", function(d) { |
| 7319 | * data = all.filter(function(k) k.name == d); |
| 7320 | * vis.render(); |
| 7321 | * });</pre> |
| 7322 | * |
| 7323 | * The return value of the event handler determines which mark gets re-rendered. |
| 7324 | * Use defs ({@link #def}) to set temporary state from event handlers. |
| 7325 | * |
| 7326 | * <p>The complete set of event types is defined by SVG; see the reference |
| 7327 | * below. The set of supported event types is:<ul> |
| 7328 | * |
| 7329 | * <li>click |
| 7330 | * <li>mousedown |
| 7331 | * <li>mouseup |
| 7332 | * <li>mouseover |
| 7333 | * <li>mousemove |
| 7334 | * <li>mouseout |
| 7335 | * |
| 7336 | * </ul>Since Protovis does not specify any concept of focus, it does not |
| 7337 | * support key events; these should be handled outside the visualization using |
| 7338 | * standard JavaScript. In the future, support for interaction may be extended |
| 7339 | * to support additional event types, particularly those most relevant to |
| 7340 | * interactive visualization, such as selection. |
| 7341 | * |
| 7342 | * <p>TODO In the current implementation, event handlers are not inherited from |
| 7343 | * prototype marks. They must be defined explicitly on each interactive mark. In |
| 7344 | * addition, only one event handler for a given event type can be defined; when |
| 7345 | * specifying multiple event handlers for the same type, only the last one will |
| 7346 | * be used. |
| 7347 | * |
| 7348 | * @see <a href="http://www.w3.org/TR/SVGTiny12/interact.html#SVGEvents">SVG events</a> |
| 7349 | * @param {string} type the event type. |
| 7350 | * @param {function} handler the event handler. |
| 7351 | * @returns {pv.Mark} this. |
| 7352 | */ |
| 7353 | pv.Mark.prototype.event = function(type, handler) { |
| 7354 | this.$handlers[type] = pv.functor(handler); |
| 7355 | return this; |
| 7356 | }; |
| 7357 | |
| 7358 | /** @private Evaluates the function <i>f</i> with the specified context. */ |
| 7359 | pv.Mark.prototype.context = function(scene, index, f) { |
| 7360 | var proto = pv.Mark.prototype, |
| 7361 | stack = pv.Mark.stack, |
| 7362 | oscene = pv.Mark.scene, |
| 7363 | oindex = proto.index; |
| 7364 | |
| 7365 | /** @private Sets the context. */ |
| 7366 | function apply(scene, index) { |
| 7367 | pv.Mark.scene = scene; |
| 7368 | proto.index = index; |
| 7369 | if (!scene) return; |
| 7370 | |
| 7371 | var that = scene.mark, |
| 7372 | mark = that, |
| 7373 | ancestors = []; |
| 7374 | |
| 7375 | /* Set ancestors' scene and index; populate data stack. */ |
| 7376 | do { |
| 7377 | ancestors.push(mark); |
| 7378 | stack.push(scene[index].data); |
| 7379 | mark.index = index; |
| 7380 | mark.scene = scene; |
| 7381 | index = scene.parentIndex; |
| 7382 | scene = scene.parent; |
| 7383 | } while (mark = mark.parent); |
| 7384 | |
| 7385 | /* Set ancestors' scale; requires top-down. */ |
| 7386 | for (var i = ancestors.length - 1, k = 1; i > 0; i--) { |
| 7387 | mark = ancestors[i]; |
| 7388 | mark.scale = k; |
| 7389 | k *= mark.scene[mark.index].transform.k; |
| 7390 | } |
| 7391 | |
| 7392 | /* Set children's scene and scale. */ |
| 7393 | if (that.children) for (var i = 0, n = that.children.length; i < n; i++) { |
| 7394 | mark = that.children[i]; |
| 7395 | mark.scene = that.scene[that.index].children[i]; |
| 7396 | mark.scale = k; |
| 7397 | } |
| 7398 | } |
| 7399 | |
| 7400 | /** @private Clears the context. */ |
| 7401 | function clear(scene, index) { |
| 7402 | if (!scene) return; |
| 7403 | var that = scene.mark, |
| 7404 | mark; |
| 7405 | |
| 7406 | /* Reset children. */ |
| 7407 | if (that.children) for (var i = 0, n = that.children.length; i < n; i++) { |
| 7408 | mark = that.children[i]; |
| 7409 | delete mark.scene; |
| 7410 | delete mark.scale; |
| 7411 | } |
| 7412 | |
| 7413 | /* Reset ancestors. */ |
| 7414 | mark = that; |
| 7415 | do { |
| 7416 | stack.pop(); |
| 7417 | if (mark.parent) { |
| 7418 | delete mark.scene; |
| 7419 | delete mark.scale; |
| 7420 | } |
| 7421 | delete mark.index; |
| 7422 | } while (mark = mark.parent); |
| 7423 | } |
| 7424 | |
| 7425 | /* Context switch, invoke the function, then switch back. */ |
| 7426 | clear(oscene, oindex); |
| 7427 | apply(scene, index); |
| 7428 | try { |
| 7429 | f.apply(this, stack); |
| 7430 | } finally { |
| 7431 | clear(scene, index); |
| 7432 | apply(oscene, oindex); |
| 7433 | } |
| 7434 | }; |
| 7435 | |
| 7436 | /** @private Execute the event listener, then re-render. */ |
| 7437 | pv.Mark.dispatch = function(type, scene, index) { |
| 7438 | var m = scene.mark, p = scene.parent, l = m.$handlers[type]; |
| 7439 | if (!l) return p && pv.Mark.dispatch(type, p, scene.parentIndex); |
| 7440 | m.context(scene, index, function() { |
| 7441 | m = l.apply(m, pv.Mark.stack); |
| 7442 | if (m && m.render) m.render(); |
| 7443 | }); |
| 7444 | return true; |
| 7445 | }; |
| 7446 | /** |
| 7447 | * Constructs a new mark anchor with default properties. |
| 7448 | * |
| 7449 | * @class Represents an anchor on a given mark. An anchor is itself a mark, but |
| 7450 | * without a visual representation. It serves only to provide useful default |
| 7451 | * properties that can be inherited by other marks. Each type of mark can define |
| 7452 | * any number of named anchors for convenience. If the concrete mark type does |
| 7453 | * not define an anchor implementation specifically, one will be inherited from |
| 7454 | * the mark's parent class. |
| 7455 | * |
| 7456 | * <p>For example, the bar mark provides anchors for its four sides: left, |
| 7457 | * right, top and bottom. Adding a label to the top anchor of a bar, |
| 7458 | * |
| 7459 | * <pre>bar.anchor("top").add(pv.Label);</pre> |
| 7460 | * |
| 7461 | * will render a text label on the top edge of the bar; the top anchor defines |
| 7462 | * the appropriate position properties (top and left), as well as text-rendering |
| 7463 | * properties for convenience (textAlign and textBaseline). |
| 7464 | * |
| 7465 | * <p>Note that anchors do not <i>inherit</i> from their targets; the positional |
| 7466 | * properties are copied from the scene graph, which guarantees that the anchors |
| 7467 | * are positioned correctly, even if the positional properties are not defined |
| 7468 | * deterministically. (In addition, it also improves performance by avoiding |
| 7469 | * re-evaluating expensive properties.) If you want the anchor to inherit from |
| 7470 | * the target, use {@link pv.Mark#extend} before adding. For example: |
| 7471 | * |
| 7472 | * <pre>bar.anchor("top").extend(bar).add(pv.Label);</pre> |
| 7473 | * |
| 7474 | * The anchor defines it's own positional properties, but other properties (such |
| 7475 | * as the title property, say) can be inherited using the above idiom. Also note |
| 7476 | * that you can override positional properties in the anchor for custom |
| 7477 | * behavior. |
| 7478 | * |
| 7479 | * @extends pv.Mark |
| 7480 | * @param {pv.Mark} target the anchor target. |
| 7481 | */ |
| 7482 | pv.Anchor = function(target) { |
| 7483 | pv.Mark.call(this); |
| 7484 | this.target = target; |
| 7485 | this.parent = target.parent; |
| 7486 | }; |
| 7487 | |
| 7488 | pv.Anchor.prototype = pv.extend(pv.Mark) |
| 7489 | .property("name", String); |
| 7490 | |
| 7491 | /** |
| 7492 | * The anchor name. The set of supported anchor names is dependent on the |
| 7493 | * concrete mark type; see the mark type for details. For example, bars support |
| 7494 | * left, right, top and bottom anchors. |
| 7495 | * |
| 7496 | * <p>While anchor names are typically constants, the anchor name is a true |
| 7497 | * property, which means you can specify a function to compute the anchor name |
| 7498 | * dynamically. For instance, if you wanted to alternate top and bottom anchors, |
| 7499 | * saying |
| 7500 | * |
| 7501 | * <pre>m.anchor(function() (this.index % 2) ? "top" : "bottom").add(pv.Dot);</pre> |
| 7502 | * |
| 7503 | * would have the desired effect. |
| 7504 | * |
| 7505 | * @type string |
| 7506 | * @name pv.Anchor.prototype.name |
| 7507 | */ |
| 7508 | |
| 7509 | /** |
| 7510 | * Returns the anchor target of this mark, if it is derived from an anchor; |
| 7511 | * otherwise returns null. For example, if a label is derived from a bar anchor, |
| 7512 | * |
| 7513 | * <pre>bar.anchor("top").add(pv.Label);</pre> |
| 7514 | * |
| 7515 | * then property functions on the label can refer to the bar via the |
| 7516 | * <tt>anchorTarget</tt> method. This method is also useful for mark types |
| 7517 | * defining properties on custom anchors. |
| 7518 | * |
| 7519 | * @returns {pv.Mark} the anchor target of this mark; possibly null. |
| 7520 | */ |
| 7521 | pv.Anchor.prototype.anchorTarget = function() { |
| 7522 | return this.target; |
| 7523 | }; |
| 7524 | /** |
| 7525 | * Constructs a new area mark with default properties. Areas are not typically |
| 7526 | * constructed directly, but by adding to a panel or an existing mark via |
| 7527 | * {@link pv.Mark#add}. |
| 7528 | * |
| 7529 | * @class Represents an area mark: the solid area between two series of |
| 7530 | * connected line segments. Unsurprisingly, areas are used most frequently for |
| 7531 | * area charts. |
| 7532 | * |
| 7533 | * <p>Just as a line represents a polyline, the <tt>Area</tt> mark type |
| 7534 | * represents a <i>polygon</i>. However, an area is not an arbitrary polygon; |
| 7535 | * vertices are paired either horizontally or vertically into parallel |
| 7536 | * <i>spans</i>, and each span corresponds to an associated datum. Either the |
| 7537 | * width or the height must be specified, but not both; this determines whether |
| 7538 | * the area is horizontally-oriented or vertically-oriented. Like lines, areas |
| 7539 | * can be stroked and filled with arbitrary colors. |
| 7540 | * |
| 7541 | * <p>See also the <a href="../../api/Area.html">Area guide</a>. |
| 7542 | * |
| 7543 | * @extends pv.Mark |
| 7544 | */ |
| 7545 | pv.Area = function() { |
| 7546 | pv.Mark.call(this); |
| 7547 | }; |
| 7548 | |
| 7549 | pv.Area.prototype = pv.extend(pv.Mark) |
| 7550 | .property("width", Number) |
| 7551 | .property("height", Number) |
| 7552 | .property("lineWidth", Number) |
| 7553 | .property("strokeStyle", pv.color) |
| 7554 | .property("fillStyle", pv.color) |
| 7555 | .property("segmented", Boolean) |
| 7556 | .property("interpolate", String) |
| 7557 | .property("tension", Number); |
| 7558 | |
| 7559 | pv.Area.prototype.type = "area"; |
| 7560 | |
| 7561 | /** |
| 7562 | * The width of a given span, in pixels; used for horizontal spans. If the width |
| 7563 | * is specified, the height property should be 0 (the default). Either the top |
| 7564 | * or bottom property should be used to space the spans vertically, typically as |
| 7565 | * a multiple of the index. |
| 7566 | * |
| 7567 | * @type number |
| 7568 | * @name pv.Area.prototype.width |
| 7569 | */ |
| 7570 | |
| 7571 | /** |
| 7572 | * The height of a given span, in pixels; used for vertical spans. If the height |
| 7573 | * is specified, the width property should be 0 (the default). Either the left |
| 7574 | * or right property should be used to space the spans horizontally, typically |
| 7575 | * as a multiple of the index. |
| 7576 | * |
| 7577 | * @type number |
| 7578 | * @name pv.Area.prototype.height |
| 7579 | */ |
| 7580 | |
| 7581 | /** |
| 7582 | * The width of stroked lines, in pixels; used in conjunction with |
| 7583 | * <tt>strokeStyle</tt> to stroke the perimeter of the area. Unlike the |
| 7584 | * {@link Line} mark type, the entire perimeter is stroked, rather than just one |
| 7585 | * edge. The default value of this property is 1.5, but since the default stroke |
| 7586 | * style is null, area marks are not stroked by default. |
| 7587 | * |
| 7588 | * <p>This property is <i>fixed</i> for non-segmented areas. See |
| 7589 | * {@link pv.Mark}. |
| 7590 | * |
| 7591 | * @type number |
| 7592 | * @name pv.Area.prototype.lineWidth |
| 7593 | */ |
| 7594 | |
| 7595 | /** |
| 7596 | * The style of stroked lines; used in conjunction with <tt>lineWidth</tt> to |
| 7597 | * stroke the perimeter of the area. Unlike the {@link Line} mark type, the |
| 7598 | * entire perimeter is stroked, rather than just one edge. The default value of |
| 7599 | * this property is null, meaning areas are not stroked by default. |
| 7600 | * |
| 7601 | * <p>This property is <i>fixed</i> for non-segmented areas. See |
| 7602 | * {@link pv.Mark}. |
| 7603 | * |
| 7604 | * @type string |
| 7605 | * @name pv.Area.prototype.strokeStyle |
| 7606 | * @see pv.color |
| 7607 | */ |
| 7608 | |
| 7609 | /** |
| 7610 | * The area fill style; if non-null, the interior of the polygon forming the |
| 7611 | * area is filled with the specified color. The default value of this property |
| 7612 | * is a categorical color. |
| 7613 | * |
| 7614 | * <p>This property is <i>fixed</i> for non-segmented areas. See |
| 7615 | * {@link pv.Mark}. |
| 7616 | * |
| 7617 | * @type string |
| 7618 | * @name pv.Area.prototype.fillStyle |
| 7619 | * @see pv.color |
| 7620 | */ |
| 7621 | |
| 7622 | /** |
| 7623 | * Whether the area is segmented; whether variations in fill style, stroke |
| 7624 | * style, and the other properties are treated as fixed. Rendering segmented |
| 7625 | * areas is noticeably slower than non-segmented areas. |
| 7626 | * |
| 7627 | * <p>This property is <i>fixed</i>. See {@link pv.Mark}. |
| 7628 | * |
| 7629 | * @type boolean |
| 7630 | * @name pv.Area.prototype.segmented |
| 7631 | */ |
| 7632 | |
| 7633 | /** |
| 7634 | * How to interpolate between values. Linear interpolation ("linear") is the |
| 7635 | * default, producing a straight line between points. For piecewise constant |
| 7636 | * functions (i.e., step functions), either "step-before" or "step-after" can be |
| 7637 | * specified. To draw open uniform b-splines, specify "basis". To draw cardinal |
| 7638 | * splines, specify "cardinal"; see also {@link #tension}. |
| 7639 | * |
| 7640 | * <p>This property is <i>fixed</i>. See {@link pv.Mark}. |
| 7641 | * |
| 7642 | * @type string |
| 7643 | * @name pv.Area.prototype.interpolate |
| 7644 | */ |
| 7645 | |
| 7646 | /** |
| 7647 | * The tension of cardinal splines; used in conjunction with |
| 7648 | * interpolate("cardinal"). A value between 0 and 1 draws cardinal splines with |
| 7649 | * the given tension. In some sense, the tension can be interpreted as the |
| 7650 | * "length" of the tangent; a tension of 1 will yield all zero tangents (i.e., |
| 7651 | * linear interpolation), and a tension of 0 yields a Catmull-Rom spline. The |
| 7652 | * default value is 0.7. |
| 7653 | * |
| 7654 | * <p>This property is <i>fixed</i>. See {@link pv.Mark}. |
| 7655 | * |
| 7656 | * @type number |
| 7657 | * @name pv.Area.prototype.tension |
| 7658 | */ |
| 7659 | |
| 7660 | /** |
| 7661 | * Default properties for areas. By default, there is no stroke and the fill |
| 7662 | * style is a categorical color. |
| 7663 | * |
| 7664 | * @type pv.Area |
| 7665 | */ |
| 7666 | pv.Area.prototype.defaults = new pv.Area() |
| 7667 | .extend(pv.Mark.prototype.defaults) |
| 7668 | .lineWidth(1.5) |
| 7669 | .fillStyle(pv.Colors.category20().by(pv.parent)) |
| 7670 | .interpolate("linear") |
| 7671 | .tension(.7); |
| 7672 | |
| 7673 | /** @private Sets width and height to zero if null. */ |
| 7674 | pv.Area.prototype.buildImplied = function(s) { |
| 7675 | if (s.height == null) s.height = 0; |
| 7676 | if (s.width == null) s.width = 0; |
| 7677 | pv.Mark.prototype.buildImplied.call(this, s); |
| 7678 | }; |
| 7679 | |
| 7680 | /** @private Records which properties may be fixed. */ |
| 7681 | pv.Area.fixed = { |
| 7682 | lineWidth: 1, |
| 7683 | lineJoin: 1, |
| 7684 | strokeStyle: 1, |
| 7685 | fillStyle: 1, |
| 7686 | segmented: 1, |
| 7687 | interpolate: 1, |
| 7688 | tension: 1 |
| 7689 | }; |
| 7690 | |
| 7691 | /** |
| 7692 | * @private Make segmented required, such that this fixed property is always |
| 7693 | * evaluated, even if the first segment is not visible. Also cache which |
| 7694 | * properties are normally fixed. |
| 7695 | */ |
| 7696 | pv.Area.prototype.bind = function() { |
| 7697 | pv.Mark.prototype.bind.call(this); |
| 7698 | var binds = this.binds, |
| 7699 | required = binds.required, |
| 7700 | optional = binds.optional; |
| 7701 | for (var i = 0, n = optional.length; i < n; i++) { |
| 7702 | var p = optional[i]; |
| 7703 | p.fixed = p.name in pv.Area.fixed; |
| 7704 | if (p.name == "segmented") { |
| 7705 | required.push(p); |
| 7706 | optional.splice(i, 1); |
| 7707 | i--; |
| 7708 | n--; |
| 7709 | } |
| 7710 | } |
| 7711 | |
| 7712 | /* Cache the original arrays so they can be restored on build. */ |
| 7713 | this.binds.$required = required; |
| 7714 | this.binds.$optional = optional; |
| 7715 | }; |
| 7716 | |
| 7717 | /** |
| 7718 | * @private Override the default build behavior such that fixed properties are |
| 7719 | * determined dynamically, based on the value of the (always) fixed segmented |
| 7720 | * property. Any fixed properties are only evaluated on the first instance, |
| 7721 | * although their values are propagated to subsequent instances, so that they |
| 7722 | * are available for property chaining and the like. |
| 7723 | */ |
| 7724 | pv.Area.prototype.buildInstance = function(s) { |
| 7725 | var binds = this.binds; |
| 7726 | |
| 7727 | /* Handle fixed properties on secondary instances. */ |
| 7728 | if (this.index) { |
| 7729 | var fixed = binds.fixed; |
| 7730 | |
| 7731 | /* Determine which properties are fixed. */ |
| 7732 | if (!fixed) { |
| 7733 | fixed = binds.fixed = []; |
| 7734 | function f(p) { return !p.fixed || (fixed.push(p), false); } |
| 7735 | binds.required = binds.required.filter(f); |
| 7736 | if (!this.scene[0].segmented) binds.optional = binds.optional.filter(f); |
| 7737 | } |
| 7738 | |
| 7739 | /* Copy fixed property values from the first instance. */ |
| 7740 | for (var i = 0, n = fixed.length; i < n; i++) { |
| 7741 | var p = fixed[i].name; |
| 7742 | s[p] = this.scene[0][p]; |
| 7743 | } |
| 7744 | } |
| 7745 | |
| 7746 | /* Evaluate all properties on the first instance. */ |
| 7747 | else { |
| 7748 | binds.required = binds.$required; |
| 7749 | binds.optional = binds.$optional; |
| 7750 | binds.fixed = null; |
| 7751 | } |
| 7752 | |
| 7753 | pv.Mark.prototype.buildInstance.call(this, s); |
| 7754 | }; |
| 7755 | |
| 7756 | /** |
| 7757 | * Constructs a new area anchor with default properties. Areas support five |
| 7758 | * different anchors:<ul> |
| 7759 | * |
| 7760 | * <li>top |
| 7761 | * <li>left |
| 7762 | * <li>center |
| 7763 | * <li>bottom |
| 7764 | * <li>right |
| 7765 | * |
| 7766 | * </ul>In addition to positioning properties (left, right, top bottom), the |
| 7767 | * anchors support text rendering properties (text-align, text-baseline). Text |
| 7768 | * is rendered to appear inside the area. The area anchor also propagates the |
| 7769 | * interpolate, eccentricity, and tension properties such that an anchored area |
| 7770 | * or line will match positions between control points. |
| 7771 | * |
| 7772 | * <p>For consistency with the other mark types, the anchor positions are |
| 7773 | * defined in terms of their opposite edge. For example, the top anchor defines |
| 7774 | * the bottom property, such that an area added to the top anchor grows upward. |
| 7775 | * |
| 7776 | * @param {string} name the anchor name; either a string or a property function. |
| 7777 | * @returns {pv.Anchor} |
| 7778 | */ |
| 7779 | pv.Area.prototype.anchor = function(name) { |
| 7780 | var scene; |
| 7781 | return pv.Mark.prototype.anchor.call(this, name) |
| 7782 | .def("$area.anchor", function() { |
| 7783 | scene = this.scene.target; |
| 7784 | }) |
| 7785 | .interpolate(function() { |
| 7786 | return scene[this.index].interpolate; |
| 7787 | }) |
| 7788 | .eccentricity(function() { |
| 7789 | return scene[this.index].eccentricity; |
| 7790 | }) |
| 7791 | .tension(function() { |
| 7792 | return scene[this.index].tension; |
| 7793 | }); |
| 7794 | }; |
| 7795 | /** |
| 7796 | * Constructs a new bar mark with default properties. Bars are not typically |
| 7797 | * constructed directly, but by adding to a panel or an existing mark via |
| 7798 | * {@link pv.Mark#add}. |
| 7799 | * |
| 7800 | * @class Represents a bar: an axis-aligned rectangle that can be stroked and |
| 7801 | * filled. Bars are used for many chart types, including bar charts, histograms |
| 7802 | * and Gantt charts. Bars can also be used as decorations, for example to draw a |
| 7803 | * frame border around a panel; in fact, a panel is a special type (a subclass) |
| 7804 | * of bar. |
| 7805 | * |
| 7806 | * <p>Bars can be positioned in several ways. Most commonly, one of the four |
| 7807 | * corners is fixed using two margins, and then the width and height properties |
| 7808 | * determine the extent of the bar relative to this fixed location. For example, |
| 7809 | * using the bottom and left properties fixes the bottom-left corner; the width |
| 7810 | * then extends to the right, while the height extends to the top. As an |
| 7811 | * alternative to the four corners, a bar can be positioned exclusively using |
| 7812 | * margins; this is convenient as an inset from the containing panel, for |
| 7813 | * example. See {@link pv.Mark} for details on the prioritization of redundant |
| 7814 | * positioning properties. |
| 7815 | * |
| 7816 | * <p>See also the <a href="../../api/Bar.html">Bar guide</a>. |
| 7817 | * |
| 7818 | * @extends pv.Mark |
| 7819 | */ |
| 7820 | pv.Bar = function() { |
| 7821 | pv.Mark.call(this); |
| 7822 | }; |
| 7823 | |
| 7824 | pv.Bar.prototype = pv.extend(pv.Mark) |
| 7825 | .property("width", Number) |
| 7826 | .property("height", Number) |
| 7827 | .property("lineWidth", Number) |
| 7828 | .property("strokeStyle", pv.color) |
| 7829 | .property("fillStyle", pv.color); |
| 7830 | |
| 7831 | pv.Bar.prototype.type = "bar"; |
| 7832 | |
| 7833 | /** |
| 7834 | * The width of the bar, in pixels. If the left position is specified, the bar |
| 7835 | * extends rightward from the left edge; if the right position is specified, the |
| 7836 | * bar extends leftward from the right edge. |
| 7837 | * |
| 7838 | * @type number |
| 7839 | * @name pv.Bar.prototype.width |
| 7840 | */ |
| 7841 | |
| 7842 | /** |
| 7843 | * The height of the bar, in pixels. If the bottom position is specified, the |
| 7844 | * bar extends upward from the bottom edge; if the top position is specified, |
| 7845 | * the bar extends downward from the top edge. |
| 7846 | * |
| 7847 | * @type number |
| 7848 | * @name pv.Bar.prototype.height |
| 7849 | */ |
| 7850 | |
| 7851 | /** |
| 7852 | * The width of stroked lines, in pixels; used in conjunction with |
| 7853 | * <tt>strokeStyle</tt> to stroke the bar's border. |
| 7854 | * |
| 7855 | * @type number |
| 7856 | * @name pv.Bar.prototype.lineWidth |
| 7857 | */ |
| 7858 | |
| 7859 | /** |
| 7860 | * The style of stroked lines; used in conjunction with <tt>lineWidth</tt> to |
| 7861 | * stroke the bar's border. The default value of this property is null, meaning |
| 7862 | * bars are not stroked by default. |
| 7863 | * |
| 7864 | * @type string |
| 7865 | * @name pv.Bar.prototype.strokeStyle |
| 7866 | * @see pv.color |
| 7867 | */ |
| 7868 | |
| 7869 | /** |
| 7870 | * The bar fill style; if non-null, the interior of the bar is filled with the |
| 7871 | * specified color. The default value of this property is a categorical color. |
| 7872 | * |
| 7873 | * @type string |
| 7874 | * @name pv.Bar.prototype.fillStyle |
| 7875 | * @see pv.color |
| 7876 | */ |
| 7877 | |
| 7878 | /** |
| 7879 | * Default properties for bars. By default, there is no stroke and the fill |
| 7880 | * style is a categorical color. |
| 7881 | * |
| 7882 | * @type pv.Bar |
| 7883 | */ |
| 7884 | pv.Bar.prototype.defaults = new pv.Bar() |
| 7885 | .extend(pv.Mark.prototype.defaults) |
| 7886 | .lineWidth(1.5) |
| 7887 | .fillStyle(pv.Colors.category20().by(pv.parent)); |
| 7888 | /** |
| 7889 | * Constructs a new dot mark with default properties. Dots are not typically |
| 7890 | * constructed directly, but by adding to a panel or an existing mark via |
| 7891 | * {@link pv.Mark#add}. |
| 7892 | * |
| 7893 | * @class Represents a dot; a dot is simply a sized glyph centered at a given |
| 7894 | * point that can also be stroked and filled. The <tt>size</tt> property is |
| 7895 | * proportional to the area of the rendered glyph to encourage meaningful visual |
| 7896 | * encodings. Dots can visually encode up to eight dimensions of data, though |
| 7897 | * this may be unwise due to integrality. See {@link pv.Mark} for details on the |
| 7898 | * prioritization of redundant positioning properties. |
| 7899 | * |
| 7900 | * <p>See also the <a href="../../api/Dot.html">Dot guide</a>. |
| 7901 | * |
| 7902 | * @extends pv.Mark |
| 7903 | */ |
| 7904 | pv.Dot = function() { |
| 7905 | pv.Mark.call(this); |
| 7906 | }; |
| 7907 | |
| 7908 | pv.Dot.prototype = pv.extend(pv.Mark) |
| 7909 | .property("size", Number) |
| 7910 | .property("radius", Number) |
| 7911 | .property("shape", String) |
| 7912 | .property("angle", Number) |
| 7913 | .property("lineWidth", Number) |
| 7914 | .property("strokeStyle", pv.color) |
| 7915 | .property("fillStyle", pv.color); |
| 7916 | |
| 7917 | pv.Dot.prototype.type = "dot"; |
| 7918 | |
| 7919 | /** |
| 7920 | * The size of the dot, in square pixels. Square pixels are used such that the |
| 7921 | * area of the dot is linearly proportional to the value of the size property, |
| 7922 | * facilitating representative encodings. |
| 7923 | * |
| 7924 | * @see #radius |
| 7925 | * @type number |
| 7926 | * @name pv.Dot.prototype.size |
| 7927 | */ |
| 7928 | |
| 7929 | /** |
| 7930 | * The radius of the dot, in pixels. This is an alternative to using |
| 7931 | * {@link #size}. |
| 7932 | * |
| 7933 | * @see #size |
| 7934 | * @type number |
| 7935 | * @name pv.Dot.prototype.radius |
| 7936 | */ |
| 7937 | |
| 7938 | /** |
| 7939 | * The shape name. Several shapes are supported:<ul> |
| 7940 | * |
| 7941 | * <li>cross |
| 7942 | * <li>triangle |
| 7943 | * <li>diamond |
| 7944 | * <li>square |
| 7945 | * <li>circle |
| 7946 | * <li>tick |
| 7947 | * <li>bar |
| 7948 | * |
| 7949 | * </ul>These shapes can be further changed using the {@link #angle} property; |
| 7950 | * for instance, a cross can be turned into a plus by rotating. Similarly, the |
| 7951 | * tick, which is vertical by default, can be rotated horizontally. Note that |
| 7952 | * some shapes (cross and tick) do not have interior areas, and thus do not |
| 7953 | * support fill style meaningfully. |
| 7954 | * |
| 7955 | * <p>Note: it may be more natural to use the {@link pv.Rule} mark for |
| 7956 | * horizontal and vertical ticks. The tick shape is only necessary if angled |
| 7957 | * ticks are needed. |
| 7958 | * |
| 7959 | * @type string |
| 7960 | * @name pv.Dot.prototype.shape |
| 7961 | */ |
| 7962 | |
| 7963 | /** |
| 7964 | * The rotation angle, in radians. Used to rotate shapes, such as to turn a |
| 7965 | * cross into a plus. |
| 7966 | * |
| 7967 | * @type number |
| 7968 | * @name pv.Dot.prototype.angle |
| 7969 | */ |
| 7970 | |
| 7971 | /** |
| 7972 | * The width of stroked lines, in pixels; used in conjunction with |
| 7973 | * <tt>strokeStyle</tt> to stroke the dot's shape. |
| 7974 | * |
| 7975 | * @type number |
| 7976 | * @name pv.Dot.prototype.lineWidth |
| 7977 | */ |
| 7978 | |
| 7979 | /** |
| 7980 | * The style of stroked lines; used in conjunction with <tt>lineWidth</tt> to |
| 7981 | * stroke the dot's shape. The default value of this property is a categorical |
| 7982 | * color. |
| 7983 | * |
| 7984 | * @type string |
| 7985 | * @name pv.Dot.prototype.strokeStyle |
| 7986 | * @see pv.color |
| 7987 | */ |
| 7988 | |
| 7989 | /** |
| 7990 | * The fill style; if non-null, the interior of the dot is filled with the |
| 7991 | * specified color. The default value of this property is null, meaning dots are |
| 7992 | * not filled by default. |
| 7993 | * |
| 7994 | * @type string |
| 7995 | * @name pv.Dot.prototype.fillStyle |
| 7996 | * @see pv.color |
| 7997 | */ |
| 7998 | |
| 7999 | /** |
| 8000 | * Default properties for dots. By default, there is no fill and the stroke |
| 8001 | * style is a categorical color. The default shape is "circle" with size 20. |
| 8002 | * |
| 8003 | * @type pv.Dot |
| 8004 | */ |
| 8005 | pv.Dot.prototype.defaults = new pv.Dot() |
| 8006 | .extend(pv.Mark.prototype.defaults) |
| 8007 | .size(20) |
| 8008 | .shape("circle") |
| 8009 | .lineWidth(1.5) |
| 8010 | .strokeStyle(pv.Colors.category10().by(pv.parent)); |
| 8011 | |
| 8012 | /** |
| 8013 | * Constructs a new dot anchor with default properties. Dots support five |
| 8014 | * different anchors:<ul> |
| 8015 | * |
| 8016 | * <li>top |
| 8017 | * <li>left |
| 8018 | * <li>center |
| 8019 | * <li>bottom |
| 8020 | * <li>right |
| 8021 | * |
| 8022 | * </ul>In addition to positioning properties (left, right, top bottom), the |
| 8023 | * anchors support text rendering properties (text-align, text-baseline). Text is |
| 8024 | * rendered to appear outside the dot. Note that this behavior is different from |
| 8025 | * other mark anchors, which default to rendering text <i>inside</i> the mark. |
| 8026 | * |
| 8027 | * <p>For consistency with the other mark types, the anchor positions are |
| 8028 | * defined in terms of their opposite edge. For example, the top anchor defines |
| 8029 | * the bottom property, such that a bar added to the top anchor grows upward. |
| 8030 | * |
| 8031 | * @param {string} name the anchor name; either a string or a property function. |
| 8032 | * @returns {pv.Anchor} |
| 8033 | */ |
| 8034 | pv.Dot.prototype.anchor = function(name) { |
| 8035 | var scene; |
| 8036 | return pv.Mark.prototype.anchor.call(this, name) |
| 8037 | .def("$wedge.anchor", function() { |
| 8038 | scene = this.scene.target; |
| 8039 | }) |
| 8040 | .left(function() { |
| 8041 | var s = scene[this.index]; |
| 8042 | switch (this.name()) { |
| 8043 | case "bottom": |
| 8044 | case "top": |
| 8045 | case "center": return s.left; |
| 8046 | case "left": return null; |
| 8047 | } |
| 8048 | return s.left + s.radius; |
| 8049 | }) |
| 8050 | .right(function() { |
| 8051 | var s = scene[this.index]; |
| 8052 | return this.name() == "left" ? s.right + s.radius : null; |
| 8053 | }) |
| 8054 | .top(function() { |
| 8055 | var s = scene[this.index]; |
| 8056 | switch (this.name()) { |
| 8057 | case "left": |
| 8058 | case "right": |
| 8059 | case "center": return s.top; |
| 8060 | case "top": return null; |
| 8061 | } |
| 8062 | return s.top + s.radius; |
| 8063 | }) |
| 8064 | .bottom(function() { |
| 8065 | var s = scene[this.index]; |
| 8066 | return this.name() == "top" ? s.bottom + s.radius : null; |
| 8067 | }) |
| 8068 | .textAlign(function() { |
| 8069 | switch (this.name()) { |
| 8070 | case "left": return "right"; |
| 8071 | case "bottom": |
| 8072 | case "top": |
| 8073 | case "center": return "center"; |
| 8074 | } |
| 8075 | return "left"; |
| 8076 | }) |
| 8077 | .textBaseline(function() { |
| 8078 | switch (this.name()) { |
| 8079 | case "right": |
| 8080 | case "left": |
| 8081 | case "center": return "middle"; |
| 8082 | case "bottom": return "top"; |
| 8083 | } |
| 8084 | return "bottom"; |
| 8085 | }); |
| 8086 | }; |
| 8087 | |
| 8088 | /** @private Sets radius based on size or vice versa. */ |
| 8089 | pv.Dot.prototype.buildImplied = function(s) { |
| 8090 | if (s.radius == null) s.radius = Math.sqrt(s.size); |
| 8091 | else if (s.size == null) s.size = s.radius * s.radius; |
| 8092 | pv.Mark.prototype.buildImplied.call(this, s); |
| 8093 | }; |
| 8094 | /** |
| 8095 | * Constructs a new label mark with default properties. Labels are not typically |
| 8096 | * constructed directly, but by adding to a panel or an existing mark via |
| 8097 | * {@link pv.Mark#add}. |
| 8098 | * |
| 8099 | * @class Represents a text label, allowing textual annotation of other marks or |
| 8100 | * arbitrary text within the visualization. The character data must be plain |
| 8101 | * text (unicode), though the text can be styled using the {@link #font} |
| 8102 | * property. If rich text is needed, external HTML elements can be overlaid on |
| 8103 | * the canvas by hand. |
| 8104 | * |
| 8105 | * <p>Labels are positioned using the box model, similarly to {@link Dot}. Thus, |
| 8106 | * a label has no width or height, but merely a text anchor location. The text |
| 8107 | * is positioned relative to this anchor location based on the |
| 8108 | * {@link #textAlign}, {@link #textBaseline} and {@link #textMargin} properties. |
| 8109 | * Furthermore, the text may be rotated using {@link #textAngle}. |
| 8110 | * |
| 8111 | * <p>Labels ignore events, so as to not interfere with event handlers on |
| 8112 | * underlying marks, such as bars. In the future, we may support event handlers |
| 8113 | * on labels. |
| 8114 | * |
| 8115 | * <p>See also the <a href="../../api/Label.html">Label guide</a>. |
| 8116 | * |
| 8117 | * @extends pv.Mark |
| 8118 | */ |
| 8119 | pv.Label = function() { |
| 8120 | pv.Mark.call(this); |
| 8121 | }; |
| 8122 | |
| 8123 | pv.Label.prototype = pv.extend(pv.Mark) |
| 8124 | .property("text", String) |
| 8125 | .property("font", String) |
| 8126 | .property("textAngle", Number) |
| 8127 | .property("textStyle", pv.color) |
| 8128 | .property("textAlign", String) |
| 8129 | .property("textBaseline", String) |
| 8130 | .property("textMargin", Number) |
| 8131 | .property("textDecoration", String) |
| 8132 | .property("textShadow", String); |
| 8133 | |
| 8134 | pv.Label.prototype.type = "label"; |
| 8135 | |
| 8136 | /** |
| 8137 | * The character data to render; a string. The default value of the text |
| 8138 | * property is the identity function, meaning the label's associated datum will |
| 8139 | * be rendered using its <tt>toString</tt>. |
| 8140 | * |
| 8141 | * @type string |
| 8142 | * @name pv.Label.prototype.text |
| 8143 | */ |
| 8144 | |
| 8145 | /** |
| 8146 | * The font format, per the CSS Level 2 specification. The default font is "10px |
| 8147 | * sans-serif", for consistency with the HTML 5 canvas element specification. |
| 8148 | * Note that since text is not wrapped, any line-height property will be |
| 8149 | * ignored. The other font-style, font-variant, font-weight, font-size and |
| 8150 | * font-family properties are supported. |
| 8151 | * |
| 8152 | * @see <a href="http://www.w3.org/TR/CSS2/fonts.html#font-shorthand">CSS2 fonts</a> |
| 8153 | * @type string |
| 8154 | * @name pv.Label.prototype.font |
| 8155 | */ |
| 8156 | |
| 8157 | /** |
| 8158 | * The rotation angle, in radians. Text is rotated clockwise relative to the |
| 8159 | * anchor location. For example, with the default left alignment, an angle of |
| 8160 | * Math.PI / 2 causes text to proceed downwards. The default angle is zero. |
| 8161 | * |
| 8162 | * @type number |
| 8163 | * @name pv.Label.prototype.textAngle |
| 8164 | */ |
| 8165 | |
| 8166 | /** |
| 8167 | * The text color. The name "textStyle" is used for consistency with "fillStyle" |
| 8168 | * and "strokeStyle", although it might be better to rename this property (and |
| 8169 | * perhaps use the same name as "strokeStyle"). The default color is black. |
| 8170 | * |
| 8171 | * @type string |
| 8172 | * @name pv.Label.prototype.textStyle |
| 8173 | * @see pv.color |
| 8174 | */ |
| 8175 | |
| 8176 | /** |
| 8177 | * The horizontal text alignment. One of:<ul> |
| 8178 | * |
| 8179 | * <li>left |
| 8180 | * <li>center |
| 8181 | * <li>right |
| 8182 | * |
| 8183 | * </ul>The default horizontal alignment is left. |
| 8184 | * |
| 8185 | * @type string |
| 8186 | * @name pv.Label.prototype.textAlign |
| 8187 | */ |
| 8188 | |
| 8189 | /** |
| 8190 | * The vertical text alignment. One of:<ul> |
| 8191 | * |
| 8192 | * <li>top |
| 8193 | * <li>middle |
| 8194 | * <li>bottom |
| 8195 | * |
| 8196 | * </ul>The default vertical alignment is bottom. |
| 8197 | * |
| 8198 | * @type string |
| 8199 | * @name pv.Label.prototype.textBaseline |
| 8200 | */ |
| 8201 | |
| 8202 | /** |
| 8203 | * The text margin; may be specified in pixels, or in font-dependent units (such |
| 8204 | * as ".1ex"). The margin can be used to pad text away from its anchor location, |
| 8205 | * in a direction dependent on the horizontal and vertical alignment |
| 8206 | * properties. For example, if the text is left- and middle-aligned, the margin |
| 8207 | * shifts the text to the right. The default margin is 3 pixels. |
| 8208 | * |
| 8209 | * @type number |
| 8210 | * @name pv.Label.prototype.textMargin |
| 8211 | */ |
| 8212 | |
| 8213 | /** |
| 8214 | * A list of shadow effects to be applied to text, per the CSS Text Level 3 |
| 8215 | * text-shadow property. An example specification is "0.1em 0.1em 0.1em |
| 8216 | * rgba(0,0,0,.5)"; the first length is the horizontal offset, the second the |
| 8217 | * vertical offset, and the third the blur radius. |
| 8218 | * |
| 8219 | * @see <a href="http://www.w3.org/TR/css3-text/#text-shadow">CSS3 text</a> |
| 8220 | * @type string |
| 8221 | * @name pv.Label.prototype.textShadow |
| 8222 | */ |
| 8223 | |
| 8224 | /** |
| 8225 | * A list of decoration to be applied to text, per the CSS Text Level 3 |
| 8226 | * text-decoration property. An example specification is "underline". |
| 8227 | * |
| 8228 | * @see <a href="http://www.w3.org/TR/css3-text/#text-decoration">CSS3 text</a> |
| 8229 | * @type string |
| 8230 | * @name pv.Label.prototype.textDecoration |
| 8231 | */ |
| 8232 | |
| 8233 | /** |
| 8234 | * Default properties for labels. See the individual properties for the default |
| 8235 | * values. |
| 8236 | * |
| 8237 | * @type pv.Label |
| 8238 | */ |
| 8239 | pv.Label.prototype.defaults = new pv.Label() |
| 8240 | .extend(pv.Mark.prototype.defaults) |
| 8241 | .events("none") |
| 8242 | .text(pv.identity) |
| 8243 | .font("10px sans-serif") |
| 8244 | .textAngle(0) |
| 8245 | .textStyle("black") |
| 8246 | .textAlign("left") |
| 8247 | .textBaseline("bottom") |
| 8248 | .textMargin(3); |
| 8249 | /** |
| 8250 | * Constructs a new line mark with default properties. Lines are not typically |
| 8251 | * constructed directly, but by adding to a panel or an existing mark via |
| 8252 | * {@link pv.Mark#add}. |
| 8253 | * |
| 8254 | * @class Represents a series of connected line segments, or <i>polyline</i>, |
| 8255 | * that can be stroked with a configurable color and thickness. Each |
| 8256 | * articulation point in the line corresponds to a datum; for <i>n</i> points, |
| 8257 | * <i>n</i>-1 connected line segments are drawn. The point is positioned using |
| 8258 | * the box model. Arbitrary paths are also possible, allowing radar plots and |
| 8259 | * other custom visualizations. |
| 8260 | * |
| 8261 | * <p>Like areas, lines can be stroked and filled with arbitrary colors. In most |
| 8262 | * cases, lines are only stroked, but the fill style can be used to construct |
| 8263 | * arbitrary polygons. |
| 8264 | * |
| 8265 | * <p>See also the <a href="../../api/Line.html">Line guide</a>. |
| 8266 | * |
| 8267 | * @extends pv.Mark |
| 8268 | */ |
| 8269 | pv.Line = function() { |
| 8270 | pv.Mark.call(this); |
| 8271 | }; |
| 8272 | |
| 8273 | pv.Line.prototype = pv.extend(pv.Mark) |
| 8274 | .property("lineWidth", Number) |
| 8275 | .property("lineJoin", String) |
| 8276 | .property("strokeStyle", pv.color) |
| 8277 | .property("fillStyle", pv.color) |
| 8278 | .property("segmented", Boolean) |
| 8279 | .property("interpolate", String) |
| 8280 | .property("eccentricity", Number) |
| 8281 | .property("tension", Number); |
| 8282 | |
| 8283 | pv.Line.prototype.type = "line"; |
| 8284 | |
| 8285 | /** |
| 8286 | * The width of stroked lines, in pixels; used in conjunction with |
| 8287 | * <tt>strokeStyle</tt> to stroke the line. |
| 8288 | * |
| 8289 | * @type number |
| 8290 | * @name pv.Line.prototype.lineWidth |
| 8291 | */ |
| 8292 | |
| 8293 | /** |
| 8294 | * The style of stroked lines; used in conjunction with <tt>lineWidth</tt> to |
| 8295 | * stroke the line. The default value of this property is a categorical color. |
| 8296 | * |
| 8297 | * @type string |
| 8298 | * @name pv.Line.prototype.strokeStyle |
| 8299 | * @see pv.color |
| 8300 | */ |
| 8301 | |
| 8302 | /** |
| 8303 | * The type of corners where two lines meet. Accepted values are "bevel", |
| 8304 | * "round" and "miter". The default value is "miter". |
| 8305 | * |
| 8306 | * <p>For segmented lines, only "miter" joins and "linear" interpolation are |
| 8307 | * currently supported. Any other value, including null, will disable joins, |
| 8308 | * producing disjoint line segments. Note that the miter joins must be computed |
| 8309 | * manually (at least in the current SVG renderer); since this calculation may |
| 8310 | * be expensive and unnecessary for small lines, specifying null can improve |
| 8311 | * performance significantly. |
| 8312 | * |
| 8313 | * <p>This property is <i>fixed</i>. See {@link pv.Mark}. |
| 8314 | * |
| 8315 | * @type string |
| 8316 | * @name pv.Line.prototype.lineJoin |
| 8317 | */ |
| 8318 | |
| 8319 | /** |
| 8320 | * The line fill style; if non-null, the interior of the line is closed and |
| 8321 | * filled with the specified color. The default value of this property is a |
| 8322 | * null, meaning that lines are not filled by default. |
| 8323 | * |
| 8324 | * <p>This property is <i>fixed</i>. See {@link pv.Mark}. |
| 8325 | * |
| 8326 | * @type string |
| 8327 | * @name pv.Line.prototype.fillStyle |
| 8328 | * @see pv.color |
| 8329 | */ |
| 8330 | |
| 8331 | /** |
| 8332 | * Whether the line is segmented; whether variations in stroke style, line width |
| 8333 | * and the other properties are treated as fixed. Rendering segmented lines is |
| 8334 | * noticeably slower than non-segmented lines. |
| 8335 | * |
| 8336 | * <p>This property is <i>fixed</i>. See {@link pv.Mark}. |
| 8337 | * |
| 8338 | * @type boolean |
| 8339 | * @name pv.Line.prototype.segmented |
| 8340 | */ |
| 8341 | |
| 8342 | /** |
| 8343 | * How to interpolate between values. Linear interpolation ("linear") is the |
| 8344 | * default, producing a straight line between points. For piecewise constant |
| 8345 | * functions (i.e., step functions), either "step-before" or "step-after" can be |
| 8346 | * specified. To draw a clockwise circular arc between points, specify "polar"; |
| 8347 | * to draw a counterclockwise circular arc between points, specify |
| 8348 | * "polar-reverse". To draw open uniform b-splines, specify "basis". To draw |
| 8349 | * cardinal splines, specify "cardinal"; see also {@link #tension}. |
| 8350 | * |
| 8351 | * <p>This property is <i>fixed</i>. See {@link pv.Mark}. |
| 8352 | * |
| 8353 | * @type string |
| 8354 | * @name pv.Line.prototype.interpolate |
| 8355 | */ |
| 8356 | |
| 8357 | /** |
| 8358 | * The eccentricity of polar line segments; used in conjunction with |
| 8359 | * interpolate("polar"). The default value of 0 means that line segments are |
| 8360 | * drawn as circular arcs. A value of 1 draws a straight line. A value between 0 |
| 8361 | * and 1 draws an elliptical arc with the given eccentricity. |
| 8362 | * |
| 8363 | * @type number |
| 8364 | * @name pv.Line.prototype.eccentricity |
| 8365 | */ |
| 8366 | |
| 8367 | /** |
| 8368 | * The tension of cardinal splines; used in conjunction with |
| 8369 | * interpolate("cardinal"). A value between 0 and 1 draws cardinal splines with |
| 8370 | * the given tension. In some sense, the tension can be interpreted as the |
| 8371 | * "length" of the tangent; a tension of 1 will yield all zero tangents (i.e., |
| 8372 | * linear interpolation), and a tension of 0 yields a Catmull-Rom spline. The |
| 8373 | * default value is 0.7. |
| 8374 | * |
| 8375 | * <p>This property is <i>fixed</i>. See {@link pv.Mark}. |
| 8376 | * |
| 8377 | * @type number |
| 8378 | * @name pv.Line.prototype.tension |
| 8379 | */ |
| 8380 | |
| 8381 | /** |
| 8382 | * Default properties for lines. By default, there is no fill and the stroke |
| 8383 | * style is a categorical color. The default interpolation is linear. |
| 8384 | * |
| 8385 | * @type pv.Line |
| 8386 | */ |
| 8387 | pv.Line.prototype.defaults = new pv.Line() |
| 8388 | .extend(pv.Mark.prototype.defaults) |
| 8389 | .lineJoin("miter") |
| 8390 | .lineWidth(1.5) |
| 8391 | .strokeStyle(pv.Colors.category10().by(pv.parent)) |
| 8392 | .interpolate("linear") |
| 8393 | .eccentricity(0) |
| 8394 | .tension(.7); |
| 8395 | |
| 8396 | /** @private Reuse Area's implementation for segmented bind & build. */ |
| 8397 | pv.Line.prototype.bind = pv.Area.prototype.bind; |
| 8398 | pv.Line.prototype.buildInstance = pv.Area.prototype.buildInstance; |
| 8399 | |
| 8400 | /** |
| 8401 | * Constructs a new line anchor with default properties. Lines support five |
| 8402 | * different anchors:<ul> |
| 8403 | * |
| 8404 | * <li>top |
| 8405 | * <li>left |
| 8406 | * <li>center |
| 8407 | * <li>bottom |
| 8408 | * <li>right |
| 8409 | * |
| 8410 | * </ul>In addition to positioning properties (left, right, top bottom), the |
| 8411 | * anchors support text rendering properties (text-align, text-baseline). Text is |
| 8412 | * rendered to appear outside the line. Note that this behavior is different |
| 8413 | * from other mark anchors, which default to rendering text <i>inside</i> the |
| 8414 | * mark. |
| 8415 | * |
| 8416 | * <p>For consistency with the other mark types, the anchor positions are |
| 8417 | * defined in terms of their opposite edge. For example, the top anchor defines |
| 8418 | * the bottom property, such that a bar added to the top anchor grows upward. |
| 8419 | * |
| 8420 | * @param {string} name the anchor name; either a string or a property function. |
| 8421 | * @returns {pv.Anchor} |
| 8422 | */ |
| 8423 | pv.Line.prototype.anchor = function(name) { |
| 8424 | return pv.Area.prototype.anchor.call(this, name) |
| 8425 | .textAlign(function(d) { |
| 8426 | switch (this.name()) { |
| 8427 | case "left": return "right"; |
| 8428 | case "bottom": |
| 8429 | case "top": |
| 8430 | case "center": return "center"; |
| 8431 | case "right": return "left"; |
| 8432 | } |
| 8433 | }) |
| 8434 | .textBaseline(function(d) { |
| 8435 | switch (this.name()) { |
| 8436 | case "right": |
| 8437 | case "left": |
| 8438 | case "center": return "middle"; |
| 8439 | case "top": return "bottom"; |
| 8440 | case "bottom": return "top"; |
| 8441 | } |
| 8442 | }); |
| 8443 | }; |
| 8444 | /** |
| 8445 | * Constructs a new rule with default properties. Rules are not typically |
| 8446 | * constructed directly, but by adding to a panel or an existing mark via |
| 8447 | * {@link pv.Mark#add}. |
| 8448 | * |
| 8449 | * @class Represents a horizontal or vertical rule. Rules are frequently used |
| 8450 | * for axes and grid lines. For example, specifying only the bottom property |
| 8451 | * draws horizontal rules, while specifying only the left draws vertical |
| 8452 | * rules. Rules can also be used as thin bars. The visual style is controlled in |
| 8453 | * the same manner as lines. |
| 8454 | * |
| 8455 | * <p>Rules are positioned exclusively the standard box model properties. The |
| 8456 | * following combinations of properties are supported: |
| 8457 | * |
| 8458 | * <table> |
| 8459 | * <thead><th style="width:12em;">Properties</th><th>Orientation</th></thead> |
| 8460 | * <tbody> |
| 8461 | * <tr><td>left</td><td>vertical</td></tr> |
| 8462 | * <tr><td>right</td><td>vertical</td></tr> |
| 8463 | * <tr><td>left, bottom, top</td><td>vertical</td></tr> |
| 8464 | * <tr><td>right, bottom, top</td><td>vertical</td></tr> |
| 8465 | * <tr><td>top</td><td>horizontal</td></tr> |
| 8466 | * <tr><td>bottom</td><td>horizontal</td></tr> |
| 8467 | * <tr><td>top, left, right</td><td>horizontal</td></tr> |
| 8468 | * <tr><td>bottom, left, right</td><td>horizontal</td></tr> |
| 8469 | * <tr><td>left, top, height</td><td>vertical</td></tr> |
| 8470 | * <tr><td>left, bottom, height</td><td>vertical</td></tr> |
| 8471 | * <tr><td>right, top, height</td><td>vertical</td></tr> |
| 8472 | * <tr><td>right, bottom, height</td><td>vertical</td></tr> |
| 8473 | * <tr><td>left, top, width</td><td>horizontal</td></tr> |
| 8474 | * <tr><td>left, bottom, width</td><td>horizontal</td></tr> |
| 8475 | * <tr><td>right, top, width</td><td>horizontal</td></tr> |
| 8476 | * <tr><td>right, bottom, width</td><td>horizontal</td></tr> |
| 8477 | * </tbody> |
| 8478 | * </table> |
| 8479 | * |
| 8480 | * <p>Small rules can be used as tick marks; alternatively, a {@link Dot} with |
| 8481 | * the "tick" shape can be used. |
| 8482 | * |
| 8483 | * <p>See also the <a href="../../api/Rule.html">Rule guide</a>. |
| 8484 | * |
| 8485 | * @see pv.Line |
| 8486 | * @extends pv.Mark |
| 8487 | */ |
| 8488 | pv.Rule = function() { |
| 8489 | pv.Mark.call(this); |
| 8490 | }; |
| 8491 | |
| 8492 | pv.Rule.prototype = pv.extend(pv.Mark) |
| 8493 | .property("width", Number) |
| 8494 | .property("height", Number) |
| 8495 | .property("lineWidth", Number) |
| 8496 | .property("strokeStyle", pv.color); |
| 8497 | |
| 8498 | pv.Rule.prototype.type = "rule"; |
| 8499 | |
| 8500 | /** |
| 8501 | * The width of the rule, in pixels. If the left position is specified, the rule |
| 8502 | * extends rightward from the left edge; if the right position is specified, the |
| 8503 | * rule extends leftward from the right edge. |
| 8504 | * |
| 8505 | * @type number |
| 8506 | * @name pv.Rule.prototype.width |
| 8507 | */ |
| 8508 | |
| 8509 | /** |
| 8510 | * The height of the rule, in pixels. If the bottom position is specified, the |
| 8511 | * rule extends upward from the bottom edge; if the top position is specified, |
| 8512 | * the rule extends downward from the top edge. |
| 8513 | * |
| 8514 | * @type number |
| 8515 | * @name pv.Rule.prototype.height |
| 8516 | */ |
| 8517 | |
| 8518 | /** |
| 8519 | * The width of stroked lines, in pixels; used in conjunction with |
| 8520 | * <tt>strokeStyle</tt> to stroke the rule. The default value is 1 pixel. |
| 8521 | * |
| 8522 | * @type number |
| 8523 | * @name pv.Rule.prototype.lineWidth |
| 8524 | */ |
| 8525 | |
| 8526 | /** |
| 8527 | * The style of stroked lines; used in conjunction with <tt>lineWidth</tt> to |
| 8528 | * stroke the rule. The default value of this property is black. |
| 8529 | * |
| 8530 | * @type string |
| 8531 | * @name pv.Rule.prototype.strokeStyle |
| 8532 | * @see pv.color |
| 8533 | */ |
| 8534 | |
| 8535 | /** |
| 8536 | * Default properties for rules. By default, a single-pixel black line is |
| 8537 | * stroked. |
| 8538 | * |
| 8539 | * @type pv.Rule |
| 8540 | */ |
| 8541 | pv.Rule.prototype.defaults = new pv.Rule() |
| 8542 | .extend(pv.Mark.prototype.defaults) |
| 8543 | .lineWidth(1) |
| 8544 | .strokeStyle("black") |
| 8545 | .antialias(false); |
| 8546 | |
| 8547 | /** |
| 8548 | * Constructs a new rule anchor with default properties. Rules support five |
| 8549 | * different anchors:<ul> |
| 8550 | * |
| 8551 | * <li>top |
| 8552 | * <li>left |
| 8553 | * <li>center |
| 8554 | * <li>bottom |
| 8555 | * <li>right |
| 8556 | * |
| 8557 | * </ul>In addition to positioning properties (left, right, top bottom), the |
| 8558 | * anchors support text rendering properties (text-align, text-baseline). Text is |
| 8559 | * rendered to appear outside the rule. Note that this behavior is different |
| 8560 | * from other mark anchors, which default to rendering text <i>inside</i> the |
| 8561 | * mark. |
| 8562 | * |
| 8563 | * <p>For consistency with the other mark types, the anchor positions are |
| 8564 | * defined in terms of their opposite edge. For example, the top anchor defines |
| 8565 | * the bottom property, such that a bar added to the top anchor grows upward. |
| 8566 | * |
| 8567 | * @param {string} name the anchor name; either a string or a property function. |
| 8568 | * @returns {pv.Anchor} |
| 8569 | */ |
| 8570 | pv.Rule.prototype.anchor = pv.Line.prototype.anchor; |
| 8571 | |
| 8572 | /** @private Sets width or height based on orientation. */ |
| 8573 | pv.Rule.prototype.buildImplied = function(s) { |
| 8574 | var l = s.left, r = s.right, t = s.top, b = s.bottom; |
| 8575 | |
| 8576 | /* Determine horizontal or vertical orientation. */ |
| 8577 | if ((s.width != null) |
| 8578 | || ((l == null) && (r == null)) |
| 8579 | || ((r != null) && (l != null))) { |
| 8580 | s.height = 0; |
| 8581 | } else { |
| 8582 | s.width = 0; |
| 8583 | } |
| 8584 | |
| 8585 | pv.Mark.prototype.buildImplied.call(this, s); |
| 8586 | }; |
| 8587 | /** |
| 8588 | * Constructs a new, empty panel with default properties. Panels, with the |
| 8589 | * exception of the root panel, are not typically constructed directly; instead, |
| 8590 | * they are added to an existing panel or mark via {@link pv.Mark#add}. |
| 8591 | * |
| 8592 | * @class Represents a container mark. Panels allow repeated or nested |
| 8593 | * structures, commonly used in small multiple displays where a small |
| 8594 | * visualization is tiled to facilitate comparison across one or more |
| 8595 | * dimensions. Other types of visualizations may benefit from repeated and |
| 8596 | * possibly overlapping structure as well, such as stacked area charts. Panels |
| 8597 | * can also offset the position of marks to provide padding from surrounding |
| 8598 | * content. |
| 8599 | * |
| 8600 | * <p>All Protovis displays have at least one panel; this is the root panel to |
| 8601 | * which marks are rendered. The box model properties (four margins, width and |
| 8602 | * height) are used to offset the positions of contained marks. The data |
| 8603 | * property determines the panel count: a panel is generated once per associated |
| 8604 | * datum. When nested panels are used, property functions can declare additional |
| 8605 | * arguments to access the data associated with enclosing panels. |
| 8606 | * |
| 8607 | * <p>Panels can be rendered inline, facilitating the creation of sparklines. |
| 8608 | * This allows designers to reuse browser layout features, such as text flow and |
| 8609 | * tables; designers can also overlay HTML elements such as rich text and |
| 8610 | * images. |
| 8611 | * |
| 8612 | * <p>All panels have a <tt>children</tt> array (possibly empty) containing the |
| 8613 | * child marks in the order they were added. Panels also have a <tt>root</tt> |
| 8614 | * field which points to the root (outermost) panel; the root panel's root field |
| 8615 | * points to itself. |
| 8616 | * |
| 8617 | * <p>See also the <a href="../../api/">Protovis guide</a>. |
| 8618 | * |
| 8619 | * @extends pv.Bar |
| 8620 | */ |
| 8621 | pv.Panel = function() { |
| 8622 | pv.Bar.call(this); |
| 8623 | |
| 8624 | /** |
| 8625 | * The child marks; zero or more {@link pv.Mark}s in the order they were |
| 8626 | * added. |
| 8627 | * |
| 8628 | * @see #add |
| 8629 | * @type pv.Mark[] |
| 8630 | */ |
| 8631 | this.children = []; |
| 8632 | this.root = this; |
| 8633 | |
| 8634 | /** |
| 8635 | * The internal $dom field is set by the Protovis loader; see lang/init.js. It |
| 8636 | * refers to the script element that contains the Protovis specification, so |
| 8637 | * that the panel knows where in the DOM to insert the generated SVG element. |
| 8638 | * |
| 8639 | * @private |
| 8640 | */ |
| 8641 | this.$dom = pv.$ && pv.$.s; |
| 8642 | }; |
| 8643 | |
| 8644 | pv.Panel.prototype = pv.extend(pv.Bar) |
| 8645 | .property("transform") |
| 8646 | .property("overflow", String) |
| 8647 | .property("canvas", function(c) { |
| 8648 | return (typeof c == "string") |
| 8649 | ? document.getElementById(c) |
| 8650 | : c; // assume that c is the passed-in element |
| 8651 | }); |
| 8652 | |
| 8653 | pv.Panel.prototype.type = "panel"; |
| 8654 | |
| 8655 | /** |
| 8656 | * The canvas element; either the string ID of the canvas element in the current |
| 8657 | * document, or a reference to the canvas element itself. If null, a canvas |
| 8658 | * element will be created and inserted into the document at the location of the |
| 8659 | * script element containing the current Protovis specification. This property |
| 8660 | * only applies to root panels and is ignored on nested panels. |
| 8661 | * |
| 8662 | * <p>Note: the "canvas" element here refers to a <tt>div</tt> (or other suitable |
| 8663 | * HTML container element), <i>not</i> a <tt>canvas</tt> element. The name of |
| 8664 | * this property is a historical anachronism from the first implementation that |
| 8665 | * used HTML 5 canvas, rather than SVG. |
| 8666 | * |
| 8667 | * @type string |
| 8668 | * @name pv.Panel.prototype.canvas |
| 8669 | */ |
| 8670 | |
| 8671 | /** |
| 8672 | * Specifies whether child marks are clipped when they overflow this panel. |
| 8673 | * This affects the clipping of all this panel's descendant marks. |
| 8674 | * |
| 8675 | * @type string |
| 8676 | * @name pv.Panel.prototype.overflow |
| 8677 | * @see <a href="http://www.w3.org/TR/CSS2/visufx.html#overflow">CSS2</a> |
| 8678 | */ |
| 8679 | |
| 8680 | /** |
| 8681 | * The transform to be applied to child marks. The default transform is |
| 8682 | * identity, which has no effect. Note that the panel's own fill and stroke are |
| 8683 | * not affected by the transform, and panel's transform only affects the |
| 8684 | * <tt>scale</tt> of child marks, not the panel itself. |
| 8685 | * |
| 8686 | * @type pv.Transform |
| 8687 | * @name pv.Panel.prototype.transform |
| 8688 | * @see pv.Mark#scale |
| 8689 | */ |
| 8690 | |
| 8691 | /** |
| 8692 | * Default properties for panels. By default, the margins are zero, the fill |
| 8693 | * style is transparent. |
| 8694 | * |
| 8695 | * @type pv.Panel |
| 8696 | */ |
| 8697 | pv.Panel.prototype.defaults = new pv.Panel() |
| 8698 | .extend(pv.Bar.prototype.defaults) |
| 8699 | .fillStyle(null) // override Bar default |
| 8700 | .overflow("visible"); |
| 8701 | |
| 8702 | /** |
| 8703 | * Returns an anchor with the specified name. This method is overridden such |
| 8704 | * that adding to a panel's anchor adds to the panel, rather than to the panel's |
| 8705 | * parent. |
| 8706 | * |
| 8707 | * @param {string} name the anchor name; either a string or a property function. |
| 8708 | * @returns {pv.Anchor} the new anchor. |
| 8709 | */ |
| 8710 | pv.Panel.prototype.anchor = function(name) { |
| 8711 | var anchor = pv.Bar.prototype.anchor.call(this, name); |
| 8712 | anchor.parent = this; |
| 8713 | return anchor; |
| 8714 | }; |
| 8715 | |
| 8716 | /** |
| 8717 | * Adds a new mark of the specified type to this panel. Unlike the normal |
| 8718 | * {@link Mark#add} behavior, adding a mark to a panel does not cause the mark |
| 8719 | * to inherit from the panel. Since the contained marks are offset by the panel |
| 8720 | * margins already, inheriting properties is generally undesirable; of course, |
| 8721 | * it is always possible to change this behavior by calling {@link Mark#extend} |
| 8722 | * explicitly. |
| 8723 | * |
| 8724 | * @param {function} type the type of the new mark to add. |
| 8725 | * @returns {pv.Mark} the new mark. |
| 8726 | */ |
| 8727 | pv.Panel.prototype.add = function(type) { |
| 8728 | var child = new type(); |
| 8729 | child.parent = this; |
| 8730 | child.root = this.root; |
| 8731 | child.childIndex = this.children.length; |
| 8732 | this.children.push(child); |
| 8733 | return child; |
| 8734 | }; |
| 8735 | |
| 8736 | /** @private Bind this panel, then any child marks recursively. */ |
| 8737 | pv.Panel.prototype.bind = function() { |
| 8738 | pv.Mark.prototype.bind.call(this); |
| 8739 | for (var i = 0; i < this.children.length; i++) { |
| 8740 | this.children[i].bind(); |
| 8741 | } |
| 8742 | }; |
| 8743 | |
| 8744 | /** |
| 8745 | * @private Evaluates all of the properties for this panel for the specified |
| 8746 | * instance <tt>s</tt> in the scene graph, including recursively building the |
| 8747 | * scene graph for child marks. |
| 8748 | * |
| 8749 | * @param s a node in the scene graph; the instance of the panel to build. |
| 8750 | * @see Mark#scene |
| 8751 | */ |
| 8752 | pv.Panel.prototype.buildInstance = function(s) { |
| 8753 | pv.Bar.prototype.buildInstance.call(this, s); |
| 8754 | if (!s.visible) return; |
| 8755 | if (!s.children) s.children = []; |
| 8756 | |
| 8757 | /* |
| 8758 | * Multiply the current scale factor by this panel's transform. Also clear the |
| 8759 | * default index as we recurse into child marks; it will be reset to the |
| 8760 | * current index when the next panel instance is built. |
| 8761 | */ |
| 8762 | var scale = this.scale * s.transform.k, child, n = this.children.length; |
| 8763 | pv.Mark.prototype.index = -1; |
| 8764 | |
| 8765 | /* |
| 8766 | * Build each child, passing in the parent (this panel) scene graph node. The |
| 8767 | * child mark's scene is initialized from the corresponding entry in the |
| 8768 | * existing scene graph, such that properties from the previous build can be |
| 8769 | * reused; this is largely to facilitate the recycling of SVG elements. |
| 8770 | */ |
| 8771 | for (var i = 0; i < n; i++) { |
| 8772 | child = this.children[i]; |
| 8773 | child.scene = s.children[i]; // possibly undefined |
| 8774 | child.scale = scale; |
| 8775 | child.build(); |
| 8776 | } |
| 8777 | |
| 8778 | /* |
| 8779 | * Once the child marks have been built, the new scene graph nodes are removed |
| 8780 | * from the child marks and placed into the scene graph. The nodes cannot |
| 8781 | * remain on the child nodes because this panel (or a parent panel) may be |
| 8782 | * instantiated multiple times! |
| 8783 | */ |
| 8784 | for (var i = 0; i < n; i++) { |
| 8785 | child = this.children[i]; |
| 8786 | s.children[i] = child.scene; |
| 8787 | delete child.scene; |
| 8788 | delete child.scale; |
| 8789 | } |
| 8790 | |
| 8791 | /* Delete any expired child scenes. */ |
| 8792 | s.children.length = n; |
| 8793 | }; |
| 8794 | |
| 8795 | /** |
| 8796 | * @private Computes the implied properties for this panel for the specified |
| 8797 | * instance <tt>s</tt> in the scene graph. Panels have two implied |
| 8798 | * properties:<ul> |
| 8799 | * |
| 8800 | * <li>The <tt>canvas</tt> property references the DOM element, typically a DIV, |
| 8801 | * that contains the SVG element that is used to display the visualization. This |
| 8802 | * property may be specified as a string, referring to the unique ID of the |
| 8803 | * element in the DOM. The string is converted to a reference to the DOM |
| 8804 | * element. The width and height of the SVG element is inferred from this DOM |
| 8805 | * element. If no canvas property is specified, a new SVG element is created and |
| 8806 | * inserted into the document, using the panel dimensions; see |
| 8807 | * {@link #createCanvas}. |
| 8808 | * |
| 8809 | * <li>The <tt>children</tt> array, while not a property per se, contains the |
| 8810 | * scene graph for each child mark. This array is initialized to be empty, and |
| 8811 | * is populated above in {@link #buildInstance}. |
| 8812 | * |
| 8813 | * </ul>The current implementation creates the SVG element, if necessary, during |
| 8814 | * the build phase; in the future, it may be preferrable to move this to the |
| 8815 | * update phase, although then the canvas property would be undefined. In |
| 8816 | * addition, DOM inspection is necessary to define the implied width and height |
| 8817 | * properties that may be inferred from the DOM. |
| 8818 | * |
| 8819 | * @param s a node in the scene graph; the instance of the panel to build. |
| 8820 | */ |
| 8821 | pv.Panel.prototype.buildImplied = function(s) { |
| 8822 | if (!this.parent) { |
| 8823 | var c = s.canvas; |
| 8824 | if (c) { |
| 8825 | /* Clear the container if it's not associated with this panel. */ |
| 8826 | if (c.$panel != this) { |
| 8827 | c.$panel = this; |
| 8828 | while (c.lastChild) c.removeChild(c.lastChild); |
| 8829 | } |
| 8830 | |
| 8831 | /* If width and height weren't specified, inspect the container. */ |
| 8832 | var w, h; |
| 8833 | if (s.width == null) { |
| 8834 | w = parseFloat(pv.css(c, "width")); |
| 8835 | s.width = w - s.left - s.right; |
| 8836 | } |
| 8837 | if (s.height == null) { |
| 8838 | h = parseFloat(pv.css(c, "height")); |
| 8839 | s.height = h - s.top - s.bottom; |
| 8840 | } |
| 8841 | } else { |
| 8842 | var cache = this.$canvas || (this.$canvas = []); |
| 8843 | if (!(c = cache[this.index])) { |
| 8844 | c = cache[this.index] = document.createElement("span"); |
| 8845 | if (this.$dom) { // script element for text/javascript+protovis |
| 8846 | this.$dom.parentNode.insertBefore(c, this.$dom); |
| 8847 | } else { // find the last element in the body |
| 8848 | var n = document.body; |
| 8849 | while (n.lastChild && n.lastChild.tagName) n = n.lastChild; |
| 8850 | if (n != document.body) n = n.parentNode; |
| 8851 | n.appendChild(c); |
| 8852 | } |
| 8853 | } |
| 8854 | } |
| 8855 | s.canvas = c; |
| 8856 | } |
| 8857 | if (!s.transform) s.transform = pv.Transform.identity; |
| 8858 | pv.Mark.prototype.buildImplied.call(this, s); |
| 8859 | }; |
| 8860 | /** |
| 8861 | * Constructs a new image with default properties. Images are not typically |
| 8862 | * constructed directly, but by adding to a panel or an existing mark via |
| 8863 | * {@link pv.Mark#add}. |
| 8864 | * |
| 8865 | * @class Represents an image, either a static resource or a dynamically- |
| 8866 | * generated pixel buffer. Images share the same layout and style properties as |
| 8867 | * bars. The external image resource is specified via the {@link #url} |
| 8868 | * property. The optional fill, if specified, appears beneath the image, while |
| 8869 | * the optional stroke appears above the image. |
| 8870 | * |
| 8871 | * <p>Dynamic images such as heatmaps are supported using the {@link #image} |
| 8872 | * psuedo-property. This function is passed the <i>x</i> and <i>y</i> index, in |
| 8873 | * addition to the current data stack. The return value is a {@link pv.Color}, |
| 8874 | * or null for transparent. A string can also be returned, which will be parsed |
| 8875 | * into a color; however, it is typically much faster to return an object with |
| 8876 | * <tt>r</tt>, <tt>g</tt>, <tt>b</tt> and <tt>a</tt> attributes, to avoid the |
| 8877 | * cost of parsing and object instantiation. |
| 8878 | * |
| 8879 | * <p>See {@link pv.Bar} for details on positioning properties. |
| 8880 | * |
| 8881 | * @extends pv.Bar |
| 8882 | */ |
| 8883 | pv.Image = function() { |
| 8884 | pv.Bar.call(this); |
| 8885 | }; |
| 8886 | |
| 8887 | pv.Image.prototype = pv.extend(pv.Bar) |
| 8888 | .property("url", String) |
| 8889 | .property("imageWidth", Number) |
| 8890 | .property("imageHeight", Number); |
| 8891 | |
| 8892 | pv.Image.prototype.type = "image"; |
| 8893 | |
| 8894 | /** |
| 8895 | * The URL of the image to display. The set of supported image types is |
| 8896 | * browser-dependent; PNG and JPEG are recommended. |
| 8897 | * |
| 8898 | * @type string |
| 8899 | * @name pv.Image.prototype.url |
| 8900 | */ |
| 8901 | |
| 8902 | /** |
| 8903 | * The width of the image in pixels. For static images, this property is |
| 8904 | * computed implicitly from the loaded image resources. For dynamic images, this |
| 8905 | * property can be used to specify the width of the pixel buffer; otherwise, the |
| 8906 | * value is derived from the <tt>width</tt> property. |
| 8907 | * |
| 8908 | * @type number |
| 8909 | * @name pv.Image.prototype.imageWidth |
| 8910 | */ |
| 8911 | |
| 8912 | /** |
| 8913 | * The height of the image in pixels. For static images, this property is |
| 8914 | * computed implicitly from the loaded image resources. For dynamic images, this |
| 8915 | * property can be used to specify the height of the pixel buffer; otherwise, the |
| 8916 | * value is derived from the <tt>height</tt> property. |
| 8917 | * |
| 8918 | * @type number |
| 8919 | * @name pv.Image.prototype.imageHeight |
| 8920 | */ |
| 8921 | |
| 8922 | /** |
| 8923 | * Default properties for images. By default, there is no stroke or fill style. |
| 8924 | * |
| 8925 | * @type pv.Image |
| 8926 | */ |
| 8927 | pv.Image.prototype.defaults = new pv.Image() |
| 8928 | .extend(pv.Bar.prototype.defaults) |
| 8929 | .fillStyle(null); |
| 8930 | |
| 8931 | /** |
| 8932 | * Specifies the dynamic image function. By default, no image function is |
| 8933 | * specified and the <tt>url</tt> property is used to load a static image |
| 8934 | * resource. If an image function is specified, it will be invoked for each |
| 8935 | * pixel in the image, based on the related <tt>imageWidth</tt> and |
| 8936 | * <tt>imageHeight</tt> properties. |
| 8937 | * |
| 8938 | * <p>For example, given a two-dimensional array <tt>heatmap</tt>, containing |
| 8939 | * numbers in the range [0, 1] in row-major order, a simple monochrome heatmap |
| 8940 | * image can be specified as: |
| 8941 | * |
| 8942 | * <pre>vis.add(pv.Image) |
| 8943 | * .imageWidth(heatmap[0].length) |
| 8944 | * .imageHeight(heatmap.length) |
| 8945 | * .image(pv.ramp("white", "black").by(function(x, y) heatmap[y][x]));</pre> |
| 8946 | * |
| 8947 | * For fastest performance, use an ordinal scale which caches the fixed color |
| 8948 | * palette, or return an object literal with <tt>r</tt>, <tt>g</tt>, <tt>b</tt> |
| 8949 | * and <tt>a</tt> attributes. A {@link pv.Color} or string can also be returned, |
| 8950 | * though this typically results in slower performance. |
| 8951 | * |
| 8952 | * @param {function} f the new sizing function. |
| 8953 | * @returns {pv.Layout.Pack} this. |
| 8954 | */ |
| 8955 | pv.Image.prototype.image = function(f) { |
| 8956 | /** @private */ |
| 8957 | this.$image = function() { |
| 8958 | var c = f.apply(this, arguments); |
| 8959 | return c == null ? pv.Color.transparent |
| 8960 | : typeof c == "string" ? pv.color(c) |
| 8961 | : c; |
| 8962 | }; |
| 8963 | return this; |
| 8964 | }; |
| 8965 | |
| 8966 | /** @private Scan the proto chain for an image function. */ |
| 8967 | pv.Image.prototype.bind = function() { |
| 8968 | pv.Bar.prototype.bind.call(this); |
| 8969 | var binds = this.binds, mark = this; |
| 8970 | do { |
| 8971 | binds.image = mark.$image; |
| 8972 | } while (!binds.image && (mark = mark.proto)); |
| 8973 | }; |
| 8974 | |
| 8975 | /** @private */ |
| 8976 | pv.Image.prototype.buildImplied = function(s) { |
| 8977 | pv.Bar.prototype.buildImplied.call(this, s); |
| 8978 | if (!s.visible) return; |
| 8979 | |
| 8980 | /* Compute the implied image dimensions. */ |
| 8981 | if (s.imageWidth == null) s.imageWidth = s.width; |
| 8982 | if (s.imageHeight == null) s.imageHeight = s.height; |
| 8983 | |
| 8984 | /* Compute the pixel values. */ |
| 8985 | if ((s.url == null) && this.binds.image) { |
| 8986 | |
| 8987 | /* Cache the canvas element to reuse across renders. */ |
| 8988 | var canvas = this.$canvas || (this.$canvas = document.createElement("canvas")), |
| 8989 | context = canvas.getContext("2d"), |
| 8990 | w = s.imageWidth, |
| 8991 | h = s.imageHeight, |
| 8992 | stack = pv.Mark.stack, |
| 8993 | data; |
| 8994 | |
| 8995 | /* Evaluate the image function, storing into a CanvasPixelArray. */ |
| 8996 | canvas.width = w; |
| 8997 | canvas.height = h; |
| 8998 | data = (s.image = context.createImageData(w, h)).data; |
| 8999 | stack.unshift(null, null); |
| 9000 | for (var y = 0, p = 0; y < h; y++) { |
| 9001 | stack[1] = y; |
| 9002 | for (var x = 0; x < w; x++) { |
| 9003 | stack[0] = x; |
| 9004 | var color = this.binds.image.apply(this, stack); |
| 9005 | data[p++] = color.r; |
| 9006 | data[p++] = color.g; |
| 9007 | data[p++] = color.b; |
| 9008 | data[p++] = 255 * color.a; |
| 9009 | } |
| 9010 | } |
| 9011 | stack.splice(0, 2); |
| 9012 | } |
| 9013 | }; |
| 9014 | /** |
| 9015 | * Constructs a new wedge with default properties. Wedges are not typically |
| 9016 | * constructed directly, but by adding to a panel or an existing mark via |
| 9017 | * {@link pv.Mark#add}. |
| 9018 | * |
| 9019 | * @class Represents a wedge, or pie slice. Specified in terms of start and end |
| 9020 | * angle, inner and outer radius, wedges can be used to construct donut charts |
| 9021 | * and polar bar charts as well. If the {@link #angle} property is used, the end |
| 9022 | * angle is implied by adding this value to start angle. By default, the start |
| 9023 | * angle is the previously-generated wedge's end angle. This design allows |
| 9024 | * explicit control over the wedge placement if desired, while offering |
| 9025 | * convenient defaults for the construction of radial graphs. |
| 9026 | * |
| 9027 | * <p>The center point of the circle is positioned using the standard box model. |
| 9028 | * The wedge can be stroked and filled, similar to {@link pv.Bar}. |
| 9029 | * |
| 9030 | * <p>See also the <a href="../../api/Wedge.html">Wedge guide</a>. |
| 9031 | * |
| 9032 | * @extends pv.Mark |
| 9033 | */ |
| 9034 | pv.Wedge = function() { |
| 9035 | pv.Mark.call(this); |
| 9036 | }; |
| 9037 | |
| 9038 | pv.Wedge.prototype = pv.extend(pv.Mark) |
| 9039 | .property("startAngle", Number) |
| 9040 | .property("endAngle", Number) |
| 9041 | .property("angle", Number) |
| 9042 | .property("innerRadius", Number) |
| 9043 | .property("outerRadius", Number) |
| 9044 | .property("lineWidth", Number) |
| 9045 | .property("strokeStyle", pv.color) |
| 9046 | .property("fillStyle", pv.color); |
| 9047 | |
| 9048 | pv.Wedge.prototype.type = "wedge"; |
| 9049 | |
| 9050 | /** |
| 9051 | * The start angle of the wedge, in radians. The start angle is measured |
| 9052 | * clockwise from the 3 o'clock position. The default value of this property is |
| 9053 | * the end angle of the previous instance (the {@link Mark#sibling}), or -PI / 2 |
| 9054 | * for the first wedge; for pie and donut charts, typically only the |
| 9055 | * {@link #angle} property needs to be specified. |
| 9056 | * |
| 9057 | * @type number |
| 9058 | * @name pv.Wedge.prototype.startAngle |
| 9059 | */ |
| 9060 | |
| 9061 | /** |
| 9062 | * The end angle of the wedge, in radians. If not specified, the end angle is |
| 9063 | * implied as the start angle plus the {@link #angle}. |
| 9064 | * |
| 9065 | * @type number |
| 9066 | * @name pv.Wedge.prototype.endAngle |
| 9067 | */ |
| 9068 | |
| 9069 | /** |
| 9070 | * The angular span of the wedge, in radians. This property is used if end angle |
| 9071 | * is not specified. |
| 9072 | * |
| 9073 | * @type number |
| 9074 | * @name pv.Wedge.prototype.angle |
| 9075 | */ |
| 9076 | |
| 9077 | /** |
| 9078 | * The inner radius of the wedge, in pixels. The default value of this property |
| 9079 | * is zero; a positive value will produce a donut slice rather than a pie slice. |
| 9080 | * The inner radius can vary per-wedge. |
| 9081 | * |
| 9082 | * @type number |
| 9083 | * @name pv.Wedge.prototype.innerRadius |
| 9084 | */ |
| 9085 | |
| 9086 | /** |
| 9087 | * The outer radius of the wedge, in pixels. This property is required. For |
| 9088 | * pies, only this radius is required; for donuts, the inner radius must be |
| 9089 | * specified as well. The outer radius can vary per-wedge. |
| 9090 | * |
| 9091 | * @type number |
| 9092 | * @name pv.Wedge.prototype.outerRadius |
| 9093 | */ |
| 9094 | |
| 9095 | /** |
| 9096 | * The width of stroked lines, in pixels; used in conjunction with |
| 9097 | * <tt>strokeStyle</tt> to stroke the wedge's border. |
| 9098 | * |
| 9099 | * @type number |
| 9100 | * @name pv.Wedge.prototype.lineWidth |
| 9101 | */ |
| 9102 | |
| 9103 | /** |
| 9104 | * The style of stroked lines; used in conjunction with <tt>lineWidth</tt> to |
| 9105 | * stroke the wedge's border. The default value of this property is null, |
| 9106 | * meaning wedges are not stroked by default. |
| 9107 | * |
| 9108 | * @type string |
| 9109 | * @name pv.Wedge.prototype.strokeStyle |
| 9110 | * @see pv.color |
| 9111 | */ |
| 9112 | |
| 9113 | /** |
| 9114 | * The wedge fill style; if non-null, the interior of the wedge is filled with |
| 9115 | * the specified color. The default value of this property is a categorical |
| 9116 | * color. |
| 9117 | * |
| 9118 | * @type string |
| 9119 | * @name pv.Wedge.prototype.fillStyle |
| 9120 | * @see pv.color |
| 9121 | */ |
| 9122 | |
| 9123 | /** |
| 9124 | * Default properties for wedges. By default, there is no stroke and the fill |
| 9125 | * style is a categorical color. |
| 9126 | * |
| 9127 | * @type pv.Wedge |
| 9128 | */ |
| 9129 | pv.Wedge.prototype.defaults = new pv.Wedge() |
| 9130 | .extend(pv.Mark.prototype.defaults) |
| 9131 | .startAngle(function() { |
| 9132 | var s = this.sibling(); |
| 9133 | return s ? s.endAngle : -Math.PI / 2; |
| 9134 | }) |
| 9135 | .innerRadius(0) |
| 9136 | .lineWidth(1.5) |
| 9137 | .strokeStyle(null) |
| 9138 | .fillStyle(pv.Colors.category20().by(pv.index)); |
| 9139 | |
| 9140 | /** |
| 9141 | * Returns the mid-radius of the wedge, which is defined as half-way between the |
| 9142 | * inner and outer radii. |
| 9143 | * |
| 9144 | * @see #innerRadius |
| 9145 | * @see #outerRadius |
| 9146 | * @returns {number} the mid-radius, in pixels. |
| 9147 | */ |
| 9148 | pv.Wedge.prototype.midRadius = function() { |
| 9149 | return (this.innerRadius() + this.outerRadius()) / 2; |
| 9150 | }; |
| 9151 | |
| 9152 | /** |
| 9153 | * Returns the mid-angle of the wedge, which is defined as half-way between the |
| 9154 | * start and end angles. |
| 9155 | * |
| 9156 | * @see #startAngle |
| 9157 | * @see #endAngle |
| 9158 | * @returns {number} the mid-angle, in radians. |
| 9159 | */ |
| 9160 | pv.Wedge.prototype.midAngle = function() { |
| 9161 | return (this.startAngle() + this.endAngle()) / 2; |
| 9162 | }; |
| 9163 | |
| 9164 | /** |
| 9165 | * Constructs a new wedge anchor with default properties. Wedges support five |
| 9166 | * different anchors:<ul> |
| 9167 | * |
| 9168 | * <li>outer |
| 9169 | * <li>inner |
| 9170 | * <li>center |
| 9171 | * <li>start |
| 9172 | * <li>end |
| 9173 | * |
| 9174 | * </ul>In addition to positioning properties (left, right, top bottom), the |
| 9175 | * anchors support text rendering properties (text-align, text-baseline, |
| 9176 | * textAngle). Text is rendered to appear inside the wedge. |
| 9177 | * |
| 9178 | * @param {string} name the anchor name; either a string or a property function. |
| 9179 | * @returns {pv.Anchor} |
| 9180 | */ |
| 9181 | pv.Wedge.prototype.anchor = function(name) { |
| 9182 | function partial(s) { return s.innerRadius || s.angle < 2 * Math.PI; } |
| 9183 | function midRadius(s) { return (s.innerRadius + s.outerRadius) / 2; } |
| 9184 | function midAngle(s) { return (s.startAngle + s.endAngle) / 2; } |
| 9185 | var scene; |
| 9186 | return pv.Mark.prototype.anchor.call(this, name) |
| 9187 | .def("$wedge.anchor", function() { |
| 9188 | scene = this.scene.target; |
| 9189 | }) |
| 9190 | .left(function() { |
| 9191 | var s = scene[this.index]; |
| 9192 | if (partial(s)) switch (this.name()) { |
| 9193 | case "outer": return s.left + s.outerRadius * Math.cos(midAngle(s)); |
| 9194 | case "inner": return s.left + s.innerRadius * Math.cos(midAngle(s)); |
| 9195 | case "start": return s.left + midRadius(s) * Math.cos(s.startAngle); |
| 9196 | case "center": return s.left + midRadius(s) * Math.cos(midAngle(s)); |
| 9197 | case "end": return s.left + midRadius(s) * Math.cos(s.endAngle); |
| 9198 | } |
| 9199 | return s.left; |
| 9200 | }) |
| 9201 | .top(function() { |
| 9202 | var s = scene[this.index]; |
| 9203 | if (partial(s)) switch (this.name()) { |
| 9204 | case "outer": return s.top + s.outerRadius * Math.sin(midAngle(s)); |
| 9205 | case "inner": return s.top + s.innerRadius * Math.sin(midAngle(s)); |
| 9206 | case "start": return s.top + midRadius(s) * Math.sin(s.startAngle); |
| 9207 | case "center": return s.top + midRadius(s) * Math.sin(midAngle(s)); |
| 9208 | case "end": return s.top + midRadius(s) * Math.sin(s.endAngle); |
| 9209 | } |
| 9210 | return s.top; |
| 9211 | }) |
| 9212 | .textAlign(function() { |
| 9213 | var s = scene[this.index]; |
| 9214 | if (partial(s)) switch (this.name()) { |
| 9215 | case "outer": return pv.Wedge.upright(midAngle(s)) ? "right" : "left"; |
| 9216 | case "inner": return pv.Wedge.upright(midAngle(s)) ? "left" : "right"; |
| 9217 | } |
| 9218 | return "center"; |
| 9219 | }) |
| 9220 | .textBaseline(function() { |
| 9221 | var s = scene[this.index]; |
| 9222 | if (partial(s)) switch (this.name()) { |
| 9223 | case "start": return pv.Wedge.upright(s.startAngle) ? "top" : "bottom"; |
| 9224 | case "end": return pv.Wedge.upright(s.endAngle) ? "bottom" : "top"; |
| 9225 | } |
| 9226 | return "middle"; |
| 9227 | }) |
| 9228 | .textAngle(function() { |
| 9229 | var s = scene[this.index], a = 0; |
| 9230 | if (partial(s)) switch (this.name()) { |
| 9231 | case "center": |
| 9232 | case "inner": |
| 9233 | case "outer": a = midAngle(s); break; |
| 9234 | case "start": a = s.startAngle; break; |
| 9235 | case "end": a = s.endAngle; break; |
| 9236 | } |
| 9237 | return pv.Wedge.upright(a) ? a : (a + Math.PI); |
| 9238 | }); |
| 9239 | }; |
| 9240 | |
| 9241 | /** |
| 9242 | * Returns true if the specified angle is considered "upright", as in, text |
| 9243 | * rendered at that angle would appear upright. If the angle is not upright, |
| 9244 | * text is rotated 180 degrees to be upright, and the text alignment properties |
| 9245 | * are correspondingly changed. |
| 9246 | * |
| 9247 | * @param {number} angle an angle, in radius. |
| 9248 | * @returns {boolean} true if the specified angle is upright. |
| 9249 | */ |
| 9250 | pv.Wedge.upright = function(angle) { |
| 9251 | angle = angle % (2 * Math.PI); |
| 9252 | angle = (angle < 0) ? (2 * Math.PI + angle) : angle; |
| 9253 | return (angle < Math.PI / 2) || (angle >= 3 * Math.PI / 2); |
| 9254 | }; |
| 9255 | |
| 9256 | /** @private Sets angle based on endAngle or vice versa. */ |
| 9257 | pv.Wedge.prototype.buildImplied = function(s) { |
| 9258 | if (s.angle == null) s.angle = s.endAngle - s.startAngle; |
| 9259 | else if (s.endAngle == null) s.endAngle = s.startAngle + s.angle; |
| 9260 | pv.Mark.prototype.buildImplied.call(this, s); |
| 9261 | }; |
| 9262 | /** |
| 9263 | * Abstract; not implemented. There is no explicit constructor; this class |
| 9264 | * merely serves to document the attributes that are used on particles in |
| 9265 | * physics simulations. |
| 9266 | * |
| 9267 | * @class A weighted particle that can participate in a force simulation. |
| 9268 | * |
| 9269 | * @name pv.Particle |
| 9270 | */ |
| 9271 | |
| 9272 | /** |
| 9273 | * The next particle in the simulation. Particles form a singly-linked list. |
| 9274 | * |
| 9275 | * @field |
| 9276 | * @type pv.Particle |
| 9277 | * @name pv.Particle.prototype.next |
| 9278 | */ |
| 9279 | |
| 9280 | /** |
| 9281 | * The <i>x</i>-position of the particle. |
| 9282 | * |
| 9283 | * @field |
| 9284 | * @type number |
| 9285 | * @name pv.Particle.prototype.x |
| 9286 | */ |
| 9287 | |
| 9288 | /** |
| 9289 | * The <i>y</i>-position of the particle. |
| 9290 | * |
| 9291 | * @field |
| 9292 | * @type number |
| 9293 | * @name pv.Particle.prototype.y |
| 9294 | */ |
| 9295 | |
| 9296 | /** |
| 9297 | * The <i>x</i>-velocity of the particle. |
| 9298 | * |
| 9299 | * @field |
| 9300 | * @type number |
| 9301 | * @name pv.Particle.prototype.vx |
| 9302 | */ |
| 9303 | |
| 9304 | /** |
| 9305 | * The <i>y</i>-velocity of the particle. |
| 9306 | * |
| 9307 | * @field |
| 9308 | * @type number |
| 9309 | * @name pv.Particle.prototype.vy |
| 9310 | */ |
| 9311 | |
| 9312 | /** |
| 9313 | * The <i>x</i>-position of the particle at -dt. |
| 9314 | * |
| 9315 | * @field |
| 9316 | * @type number |
| 9317 | * @name pv.Particle.prototype.px |
| 9318 | */ |
| 9319 | |
| 9320 | /** |
| 9321 | * The <i>y</i>-position of the particle at -dt. |
| 9322 | * |
| 9323 | * @field |
| 9324 | * @type number |
| 9325 | * @name pv.Particle.prototype.py |
| 9326 | */ |
| 9327 | |
| 9328 | /** |
| 9329 | * The <i>x</i>-force on the particle. |
| 9330 | * |
| 9331 | * @field |
| 9332 | * @type number |
| 9333 | * @name pv.Particle.prototype.fx |
| 9334 | */ |
| 9335 | |
| 9336 | /** |
| 9337 | * The <i>y</i>-force on the particle. |
| 9338 | * |
| 9339 | * @field |
| 9340 | * @type number |
| 9341 | * @name pv.Particle.prototype.fy |
| 9342 | */ |
| 9343 | /** |
| 9344 | * Constructs a new empty simulation. |
| 9345 | * |
| 9346 | * @param {array} particles |
| 9347 | * @returns {pv.Simulation} a new simulation for the specified particles. |
| 9348 | * @see pv.Simulation |
| 9349 | */ |
| 9350 | pv.simulation = function(particles) { |
| 9351 | return new pv.Simulation(particles); |
| 9352 | }; |
| 9353 | |
| 9354 | /** |
| 9355 | * Constructs a new simulation for the specified particles. |
| 9356 | * |
| 9357 | * @class Represents a particle simulation. Particles are massive points in |
| 9358 | * two-dimensional space. Forces can be applied to these particles, causing them |
| 9359 | * to move. Constraints can also be applied to restrict particle movement, for |
| 9360 | * example, constraining particles to a fixed position, or simulating collision |
| 9361 | * between circular particles with area. |
| 9362 | * |
| 9363 | * <p>The simulation uses <a |
| 9364 | * href="http://en.wikipedia.org/wiki/Verlet_integration">Position Verlet</a> |
| 9365 | * integration, due to the ease with which <a |
| 9366 | * href="http://www.teknikus.dk/tj/gdc2001.htm">geometric constraints</a> can be |
| 9367 | * implemented. For each time step, Verlet integration is performed, new forces |
| 9368 | * are accumulated, and then constraints are applied. |
| 9369 | * |
| 9370 | * <p>The simulation makes two simplifying assumptions: all particles are |
| 9371 | * equal-mass, and the time step of the simulation is fixed. It would be easy to |
| 9372 | * incorporate variable-mass particles as a future enhancement. Variable time |
| 9373 | * steps are also possible, but are likely to introduce instability in the |
| 9374 | * simulation. |
| 9375 | * |
| 9376 | * <p>This class can be used directly to simulate particle interaction. |
| 9377 | * Alternatively, for network diagrams, see {@link pv.Layout.Force}. |
| 9378 | * |
| 9379 | * @param {array} particles an array of {@link pv.Particle}s to simulate. |
| 9380 | * @see pv.Layout.Force |
| 9381 | * @see pv.Force |
| 9382 | * @see pv.Constraint |
| 9383 | */ |
| 9384 | pv.Simulation = function(particles) { |
| 9385 | for (var i = 0; i < particles.length; i++) this.particle(particles[i]); |
| 9386 | }; |
| 9387 | |
| 9388 | /** |
| 9389 | * The particles in the simulation. Particles are stored as a linked list; this |
| 9390 | * field represents the first particle in the simulation. |
| 9391 | * |
| 9392 | * @field |
| 9393 | * @type pv.Particle |
| 9394 | * @name pv.Simulation.prototype.particles |
| 9395 | */ |
| 9396 | |
| 9397 | /** |
| 9398 | * The forces in the simulation. Forces are stored as a linked list; this field |
| 9399 | * represents the first force in the simulation. |
| 9400 | * |
| 9401 | * @field |
| 9402 | * @type pv.Force |
| 9403 | * @name pv.Simulation.prototype.forces |
| 9404 | */ |
| 9405 | |
| 9406 | /** |
| 9407 | * The constraints in the simulation. Constraints are stored as a linked list; |
| 9408 | * this field represents the first constraint in the simulation. |
| 9409 | * |
| 9410 | * @field |
| 9411 | * @type pv.Constraint |
| 9412 | * @name pv.Simulation.prototype.constraints |
| 9413 | */ |
| 9414 | |
| 9415 | /** |
| 9416 | * Adds the specified particle to the simulation. |
| 9417 | * |
| 9418 | * @param {pv.Particle} p the new particle. |
| 9419 | * @returns {pv.Simulation} this. |
| 9420 | */ |
| 9421 | pv.Simulation.prototype.particle = function(p) { |
| 9422 | p.next = this.particles; |
| 9423 | /* Default velocities and forces to zero if unset. */ |
| 9424 | if (isNaN(p.px)) p.px = p.x; |
| 9425 | if (isNaN(p.py)) p.py = p.y; |
| 9426 | if (isNaN(p.fx)) p.fx = 0; |
| 9427 | if (isNaN(p.fy)) p.fy = 0; |
| 9428 | this.particles = p; |
| 9429 | return this; |
| 9430 | }; |
| 9431 | |
| 9432 | /** |
| 9433 | * Adds the specified force to the simulation. |
| 9434 | * |
| 9435 | * @param {pv.Force} f the new force. |
| 9436 | * @returns {pv.Simulation} this. |
| 9437 | */ |
| 9438 | pv.Simulation.prototype.force = function(f) { |
| 9439 | f.next = this.forces; |
| 9440 | this.forces = f; |
| 9441 | return this; |
| 9442 | }; |
| 9443 | |
| 9444 | /** |
| 9445 | * Adds the specified constraint to the simulation. |
| 9446 | * |
| 9447 | * @param {pv.Constraint} c the new constraint. |
| 9448 | * @returns {pv.Simulation} this. |
| 9449 | */ |
| 9450 | pv.Simulation.prototype.constraint = function(c) { |
| 9451 | c.next = this.constraints; |
| 9452 | this.constraints = c; |
| 9453 | return this; |
| 9454 | }; |
| 9455 | |
| 9456 | /** |
| 9457 | * Apply constraints, and then set the velocities to zero. |
| 9458 | * |
| 9459 | * @returns {pv.Simulation} this. |
| 9460 | */ |
| 9461 | pv.Simulation.prototype.stabilize = function(n) { |
| 9462 | var c; |
| 9463 | if (!arguments.length) n = 3; // TODO use cooling schedule |
| 9464 | for (var i = 0; i < n; i++) { |
| 9465 | var q = new pv.Quadtree(this.particles); |
| 9466 | for (c = this.constraints; c; c = c.next) c.apply(this.particles, q); |
| 9467 | } |
| 9468 | for (var p = this.particles; p; p = p.next) { |
| 9469 | p.px = p.x; |
| 9470 | p.py = p.y; |
| 9471 | } |
| 9472 | return this; |
| 9473 | }; |
| 9474 | |
| 9475 | /** |
| 9476 | * Advances the simulation one time-step. |
| 9477 | */ |
| 9478 | pv.Simulation.prototype.step = function() { |
| 9479 | var p, f, c; |
| 9480 | |
| 9481 | /* |
| 9482 | * Assumptions: |
| 9483 | * - The mass (m) of every particles is 1. |
| 9484 | * - The time step (dt) is 1. |
| 9485 | */ |
| 9486 | |
| 9487 | /* Position Verlet integration. */ |
| 9488 | for (p = this.particles; p; p = p.next) { |
| 9489 | var px = p.px, py = p.py; |
| 9490 | p.px = p.x; |
| 9491 | p.py = p.y; |
| 9492 | p.x += p.vx = ((p.x - px) + p.fx); |
| 9493 | p.y += p.vy = ((p.y - py) + p.fy); |
| 9494 | } |
| 9495 | |
| 9496 | /* Apply constraints, then accumulate new forces. */ |
| 9497 | var q = new pv.Quadtree(this.particles); |
| 9498 | for (c = this.constraints; c; c = c.next) c.apply(this.particles, q); |
| 9499 | for (p = this.particles; p; p = p.next) p.fx = p.fy = 0; |
| 9500 | for (f = this.forces; f; f = f.next) f.apply(this.particles, q); |
| 9501 | }; |
| 9502 | /** |
| 9503 | * Constructs a new quadtree for the specified array of particles. |
| 9504 | * |
| 9505 | * @class Represents a quadtree: a two-dimensional recursive spatial |
| 9506 | * subdivision. This particular implementation uses square partitions, dividing |
| 9507 | * each square into four equally-sized squares. Each particle exists in a unique |
| 9508 | * node; if multiple particles are in the same position, some particles may be |
| 9509 | * stored on internal nodes rather than leaf nodes. |
| 9510 | * |
| 9511 | * <p>This quadtree can be used to accelerate various spatial operations, such |
| 9512 | * as the Barnes-Hut approximation for computing n-body forces, or collision |
| 9513 | * detection. |
| 9514 | * |
| 9515 | * @see pv.Force.charge |
| 9516 | * @see pv.Constraint.collision |
| 9517 | * @param {pv.Particle} particles the linked list of particles. |
| 9518 | */ |
| 9519 | pv.Quadtree = function(particles) { |
| 9520 | var p; |
| 9521 | |
| 9522 | /* Compute bounds. */ |
| 9523 | var x1 = Number.POSITIVE_INFINITY, y1 = x1, |
| 9524 | x2 = Number.NEGATIVE_INFINITY, y2 = x2; |
| 9525 | for (p = particles; p; p = p.next) { |
| 9526 | if (p.x < x1) x1 = p.x; |
| 9527 | if (p.y < y1) y1 = p.y; |
| 9528 | if (p.x > x2) x2 = p.x; |
| 9529 | if (p.y > y2) y2 = p.y; |
| 9530 | } |
| 9531 | |
| 9532 | /* Squarify the bounds. */ |
| 9533 | var dx = x2 - x1, dy = y2 - y1; |
| 9534 | if (dx > dy) y2 = y1 + dx; |
| 9535 | else x2 = x1 + dy; |
| 9536 | this.xMin = x1; |
| 9537 | this.yMin = y1; |
| 9538 | this.xMax = x2; |
| 9539 | this.yMax = y2; |
| 9540 | |
| 9541 | /** |
| 9542 | * @ignore Recursively inserts the specified particle <i>p</i> at the node |
| 9543 | * <i>n</i> or one of its descendants. The bounds are defined by [<i>x1</i>, |
| 9544 | * <i>x2</i>] and [<i>y1</i>, <i>y2</i>]. |
| 9545 | */ |
| 9546 | function insert(n, p, x1, y1, x2, y2) { |
| 9547 | if (isNaN(p.x) || isNaN(p.y)) return; // ignore invalid particles |
| 9548 | if (n.leaf) { |
| 9549 | if (n.p) { |
| 9550 | /* |
| 9551 | * If the particle at this leaf node is at the same position as the new |
| 9552 | * particle we are adding, we leave the particle associated with the |
| 9553 | * internal node while adding the new particle to a child node. This |
| 9554 | * avoids infinite recursion. |
| 9555 | */ |
| 9556 | if ((Math.abs(n.p.x - p.x) + Math.abs(n.p.y - p.y)) < .01) { |
| 9557 | insertChild(n, p, x1, y1, x2, y2); |
| 9558 | } else { |
| 9559 | var v = n.p; |
| 9560 | n.p = null; |
| 9561 | insertChild(n, v, x1, y1, x2, y2); |
| 9562 | insertChild(n, p, x1, y1, x2, y2); |
| 9563 | } |
| 9564 | } else { |
| 9565 | n.p = p; |
| 9566 | } |
| 9567 | } else { |
| 9568 | insertChild(n, p, x1, y1, x2, y2); |
| 9569 | } |
| 9570 | } |
| 9571 | |
| 9572 | /** |
| 9573 | * @ignore Recursively inserts the specified particle <i>p</i> into a |
| 9574 | * descendant of node <i>n</i>. The bounds are defined by [<i>x1</i>, |
| 9575 | * <i>x2</i>] and [<i>y1</i>, <i>y2</i>]. |
| 9576 | */ |
| 9577 | function insertChild(n, p, x1, y1, x2, y2) { |
| 9578 | /* Compute the split point, and the quadrant in which to insert p. */ |
| 9579 | var sx = (x1 + x2) * .5, |
| 9580 | sy = (y1 + y2) * .5, |
| 9581 | right = p.x >= sx, |
| 9582 | bottom = p.y >= sy; |
| 9583 | |
| 9584 | /* Recursively insert into the child node. */ |
| 9585 | n.leaf = false; |
| 9586 | switch ((bottom << 1) + right) { |
| 9587 | case 0: n = n.c1 || (n.c1 = new pv.Quadtree.Node()); break; |
| 9588 | case 1: n = n.c2 || (n.c2 = new pv.Quadtree.Node()); break; |
| 9589 | case 2: n = n.c3 || (n.c3 = new pv.Quadtree.Node()); break; |
| 9590 | case 3: n = n.c4 || (n.c4 = new pv.Quadtree.Node()); break; |
| 9591 | } |
| 9592 | |
| 9593 | /* Update the bounds as we recurse. */ |
| 9594 | if (right) x1 = sx; else x2 = sx; |
| 9595 | if (bottom) y1 = sy; else y2 = sy; |
| 9596 | insert(n, p, x1, y1, x2, y2); |
| 9597 | } |
| 9598 | |
| 9599 | /* Insert all particles. */ |
| 9600 | this.root = new pv.Quadtree.Node(); |
| 9601 | for (p = particles; p; p = p.next) insert(this.root, p, x1, y1, x2, y2); |
| 9602 | }; |
| 9603 | |
| 9604 | /** |
| 9605 | * The root node of the quadtree. |
| 9606 | * |
| 9607 | * @type pv.Quadtree.Node |
| 9608 | * @name pv.Quadtree.prototype.root |
| 9609 | */ |
| 9610 | |
| 9611 | /** |
| 9612 | * The minimum x-coordinate value of all contained particles. |
| 9613 | * |
| 9614 | * @type number |
| 9615 | * @name pv.Quadtree.prototype.xMin |
| 9616 | */ |
| 9617 | |
| 9618 | /** |
| 9619 | * The maximum x-coordinate value of all contained particles. |
| 9620 | * |
| 9621 | * @type number |
| 9622 | * @name pv.Quadtree.prototype.xMax |
| 9623 | */ |
| 9624 | |
| 9625 | /** |
| 9626 | * The minimum y-coordinate value of all contained particles. |
| 9627 | * |
| 9628 | * @type number |
| 9629 | * @name pv.Quadtree.prototype.yMin |
| 9630 | */ |
| 9631 | |
| 9632 | /** |
| 9633 | * The maximum y-coordinate value of all contained particles. |
| 9634 | * |
| 9635 | * @type number |
| 9636 | * @name pv.Quadtree.prototype.yMax |
| 9637 | */ |
| 9638 | |
| 9639 | /** |
| 9640 | * Constructs a new node. |
| 9641 | * |
| 9642 | * @class A node in a quadtree. |
| 9643 | * |
| 9644 | * @see pv.Quadtree |
| 9645 | */ |
| 9646 | pv.Quadtree.Node = function() { |
| 9647 | /* |
| 9648 | * Prepopulating all attributes significantly increases performance! Also, |
| 9649 | * letting the language interpreter manage garbage collection was moderately |
| 9650 | * faster than creating a cache pool. |
| 9651 | */ |
| 9652 | this.leaf = true; |
| 9653 | this.c1 = null; |
| 9654 | this.c2 = null; |
| 9655 | this.c3 = null; |
| 9656 | this.c4 = null; |
| 9657 | this.p = null; |
| 9658 | }; |
| 9659 | |
| 9660 | /** |
| 9661 | * True if this node is a leaf node; i.e., it has no children. Note that both |
| 9662 | * leaf nodes and non-leaf (internal) nodes may have associated particles. If |
| 9663 | * this is a non-leaf node, then at least one of {@link #c1}, {@link #c2}, |
| 9664 | * {@link #c3} or {@link #c4} is guaranteed to be non-null. |
| 9665 | * |
| 9666 | * @type boolean |
| 9667 | * @name pv.Quadtree.Node.prototype.leaf |
| 9668 | */ |
| 9669 | |
| 9670 | /** |
| 9671 | * The particle associated with this node, if any. |
| 9672 | * |
| 9673 | * @type pv.Particle |
| 9674 | * @name pv.Quadtree.Node.prototype.p |
| 9675 | */ |
| 9676 | |
| 9677 | /** |
| 9678 | * The child node for the second quadrant, if any. |
| 9679 | * |
| 9680 | * @type pv.Quadtree.Node |
| 9681 | * @name pv.Quadtree.Node.prototype.c2 |
| 9682 | */ |
| 9683 | |
| 9684 | /** |
| 9685 | * The child node for the third quadrant, if any. |
| 9686 | * |
| 9687 | * @type pv.Quadtree.Node |
| 9688 | * @name pv.Quadtree.Node.prototype.c3 |
| 9689 | */ |
| 9690 | |
| 9691 | /** |
| 9692 | * The child node for the fourth quadrant, if any. |
| 9693 | * |
| 9694 | * @type pv.Quadtree.Node |
| 9695 | * @name pv.Quadtree.Node.prototype.c4 |
| 9696 | */ |
| 9697 | /** |
| 9698 | * Abstract; see an implementing class. |
| 9699 | * |
| 9700 | * @class Represents a force that acts on particles. Note that this interface |
| 9701 | * does not specify how to bind a force to specific particles; in general, |
| 9702 | * forces are applied globally to all particles. However, some forces may be |
| 9703 | * applied to specific particles or between particles, such as spring forces, |
| 9704 | * through additional specialization. |
| 9705 | * |
| 9706 | * @see pv.Simulation |
| 9707 | * @see pv.Particle |
| 9708 | * @see pv.Force.charge |
| 9709 | * @see pv.Force.drag |
| 9710 | * @see pv.Force.spring |
| 9711 | */ |
| 9712 | pv.Force = {}; |
| 9713 | |
| 9714 | /** |
| 9715 | * Applies this force to the specified particles. |
| 9716 | * |
| 9717 | * @function |
| 9718 | * @name pv.Force.prototype.apply |
| 9719 | * @param {pv.Particle} particles particles to which to apply this force. |
| 9720 | * @param {pv.Quadtree} q a quadtree for spatial acceleration. |
| 9721 | */ |
| 9722 | /** |
| 9723 | * Constructs a new charge force, with an optional charge constant. The charge |
| 9724 | * constant can be negative for repulsion (e.g., particles with electrical |
| 9725 | * charge of equal sign), or positive for attraction (e.g., massive particles |
| 9726 | * with mutual gravity). The default charge constant is -40. |
| 9727 | * |
| 9728 | * @class An n-body force, as defined by Coulomb's law or Newton's law of |
| 9729 | * gravitation, inversely proportional to the square of the distance between |
| 9730 | * particles. Note that the force is independent of the <i>mass</i> of the |
| 9731 | * associated particles, and that the particles do not have charges of varying |
| 9732 | * magnitude; instead, the attraction or repulsion of all particles is globally |
| 9733 | * specified as the charge {@link #constant}. |
| 9734 | * |
| 9735 | * <p>This particular implementation uses the Barnes-Hut algorithm. For details, |
| 9736 | * see <a |
| 9737 | * href="http://www.nature.com/nature/journal/v324/n6096/abs/324446a0.html">"A |
| 9738 | * hierarchical O(N log N) force-calculation algorithm"</a>, J. Barnes & |
| 9739 | * P. Hut, <i>Nature</i> 1986. |
| 9740 | * |
| 9741 | * @name pv.Force.charge |
| 9742 | * @param {number} [k] the charge constant. |
| 9743 | */ |
| 9744 | pv.Force.charge = function(k) { |
| 9745 | var min = 2, // minimum distance at which to observe forces |
| 9746 | min1 = 1 / min, |
| 9747 | max = 500, // maximum distance at which to observe forces |
| 9748 | max1 = 1 / max, |
| 9749 | theta = .9, // Barnes-Hut theta approximation constant |
| 9750 | force = {}; |
| 9751 | |
| 9752 | if (!arguments.length) k = -40; // default charge constant (repulsion) |
| 9753 | |
| 9754 | /** |
| 9755 | * Sets or gets the charge constant. If an argument is specified, it is the |
| 9756 | * new charge constant. The charge constant can be negative for repulsion |
| 9757 | * (e.g., particles with electrical charge of equal sign), or positive for |
| 9758 | * attraction (e.g., massive particles with mutual gravity). The default |
| 9759 | * charge constant is -40. |
| 9760 | * |
| 9761 | * @function |
| 9762 | * @name pv.Force.charge.prototype.constant |
| 9763 | * @param {number} x the charge constant. |
| 9764 | * @returns {pv.Force.charge} this. |
| 9765 | */ |
| 9766 | force.constant = function(x) { |
| 9767 | if (arguments.length) { |
| 9768 | k = Number(x); |
| 9769 | return force; |
| 9770 | } |
| 9771 | return k; |
| 9772 | }; |
| 9773 | |
| 9774 | /** |
| 9775 | * Sets or gets the domain; specifies the minimum and maximum domain within |
| 9776 | * which charge forces are applied. A minimum distance threshold avoids |
| 9777 | * applying forces that are two strong (due to granularity of the simulation's |
| 9778 | * numeric integration). A maximum distance threshold improves performance by |
| 9779 | * skipping force calculations for particles that are far apart. |
| 9780 | * |
| 9781 | * <p>The default domain is [2, 500]. |
| 9782 | * |
| 9783 | * @function |
| 9784 | * @name pv.Force.charge.prototype.domain |
| 9785 | * @param {number} a |
| 9786 | * @param {number} b |
| 9787 | * @returns {pv.Force.charge} this. |
| 9788 | */ |
| 9789 | force.domain = function(a, b) { |
| 9790 | if (arguments.length) { |
| 9791 | min = Number(a); |
| 9792 | min1 = 1 / min; |
| 9793 | max = Number(b); |
| 9794 | max1 = 1 / max; |
| 9795 | return force; |
| 9796 | } |
| 9797 | return [min, max]; |
| 9798 | }; |
| 9799 | |
| 9800 | /** |
| 9801 | * Sets or gets the Barnes-Hut approximation factor. The Barnes-Hut |
| 9802 | * approximation criterion is the ratio of the size of the quadtree node to |
| 9803 | * the distance from the point to the node's center of mass is beneath some |
| 9804 | * threshold. |
| 9805 | * |
| 9806 | * @function |
| 9807 | * @name pv.Force.charge.prototype.theta |
| 9808 | * @param {number} x the new Barnes-Hut approximation factor. |
| 9809 | * @returns {pv.Force.charge} this. |
| 9810 | */ |
| 9811 | force.theta = function(x) { |
| 9812 | if (arguments.length) { |
| 9813 | theta = Number(x); |
| 9814 | return force; |
| 9815 | } |
| 9816 | return theta; |
| 9817 | }; |
| 9818 | |
| 9819 | /** |
| 9820 | * @ignore Recursively computes the center of charge for each node in the |
| 9821 | * quadtree. This is equivalent to the center of mass, assuming that all |
| 9822 | * particles have unit weight. |
| 9823 | */ |
| 9824 | function accumulate(n) { |
| 9825 | var cx = 0, cy = 0; |
| 9826 | n.cn = 0; |
| 9827 | function accumulateChild(c) { |
| 9828 | accumulate(c); |
| 9829 | n.cn += c.cn; |
| 9830 | cx += c.cn * c.cx; |
| 9831 | cy += c.cn * c.cy; |
| 9832 | } |
| 9833 | if (!n.leaf) { |
| 9834 | if (n.c1) accumulateChild(n.c1); |
| 9835 | if (n.c2) accumulateChild(n.c2); |
| 9836 | if (n.c3) accumulateChild(n.c3); |
| 9837 | if (n.c4) accumulateChild(n.c4); |
| 9838 | } |
| 9839 | if (n.p) { |
| 9840 | n.cn += k; |
| 9841 | cx += k * n.p.x; |
| 9842 | cy += k * n.p.y; |
| 9843 | } |
| 9844 | n.cx = cx / n.cn; |
| 9845 | n.cy = cy / n.cn; |
| 9846 | } |
| 9847 | |
| 9848 | /** |
| 9849 | * @ignore Recursively computes forces on the given particle using the given |
| 9850 | * quadtree node. The Barnes-Hut approximation criterion is the ratio of the |
| 9851 | * size of the quadtree node to the distance from the point to the node's |
| 9852 | * center of mass is beneath some threshold. |
| 9853 | */ |
| 9854 | function forces(n, p, x1, y1, x2, y2) { |
| 9855 | var dx = n.cx - p.x, |
| 9856 | dy = n.cy - p.y, |
| 9857 | dn = 1 / Math.sqrt(dx * dx + dy * dy); |
| 9858 | |
| 9859 | /* Barnes-Hut criterion. */ |
| 9860 | if ((n.leaf && (n.p != p)) || ((x2 - x1) * dn < theta)) { |
| 9861 | if (dn < max1) return; |
| 9862 | if (dn > min1) dn = min1; |
| 9863 | var kc = n.cn * dn * dn * dn, |
| 9864 | fx = dx * kc, |
| 9865 | fy = dy * kc; |
| 9866 | p.fx += fx; |
| 9867 | p.fy += fy; |
| 9868 | } else if (!n.leaf) { |
| 9869 | var sx = (x1 + x2) * .5, sy = (y1 + y2) * .5; |
| 9870 | if (n.c1) forces(n.c1, p, x1, y1, sx, sy); |
| 9871 | if (n.c2) forces(n.c2, p, sx, y1, x2, sy); |
| 9872 | if (n.c3) forces(n.c3, p, x1, sy, sx, y2); |
| 9873 | if (n.c4) forces(n.c4, p, sx, sy, x2, y2); |
| 9874 | if (dn < max1) return; |
| 9875 | if (dn > min1) dn = min1; |
| 9876 | if (n.p && (n.p != p)) { |
| 9877 | var kc = k * dn * dn * dn, |
| 9878 | fx = dx * kc, |
| 9879 | fy = dy * kc; |
| 9880 | p.fx += fx; |
| 9881 | p.fy += fy; |
| 9882 | } |
| 9883 | } |
| 9884 | } |
| 9885 | |
| 9886 | /** |
| 9887 | * Applies this force to the specified particles. The force is applied between |
| 9888 | * all pairs of particles within the domain, using the specified quadtree to |
| 9889 | * accelerate n-body force calculation using the Barnes-Hut approximation |
| 9890 | * criterion. |
| 9891 | * |
| 9892 | * @function |
| 9893 | * @name pv.Force.charge.prototype.apply |
| 9894 | * @param {pv.Particle} particles particles to which to apply this force. |
| 9895 | * @param {pv.Quadtree} q a quadtree for spatial acceleration. |
| 9896 | */ |
| 9897 | force.apply = function(particles, q) { |
| 9898 | accumulate(q.root); |
| 9899 | for (var p = particles; p; p = p.next) { |
| 9900 | forces(q.root, p, q.xMin, q.yMin, q.xMax, q.yMax); |
| 9901 | } |
| 9902 | }; |
| 9903 | |
| 9904 | return force; |
| 9905 | }; |
| 9906 | /** |
| 9907 | * Constructs a new drag force with the specified constant. |
| 9908 | * |
| 9909 | * @class Implements a drag force, simulating friction. The drag force is |
| 9910 | * applied in the opposite direction of the particle's velocity. Since Position |
| 9911 | * Verlet integration does not track velocities explicitly, the error term with |
| 9912 | * this estimate of velocity is fairly high, so the drag force may be |
| 9913 | * inaccurate. |
| 9914 | * |
| 9915 | * @extends pv.Force |
| 9916 | * @param {number} k the drag constant. |
| 9917 | * @see #constant |
| 9918 | */ |
| 9919 | pv.Force.drag = function(k) { |
| 9920 | var force = {}; |
| 9921 | |
| 9922 | if (!arguments.length) k = .1; // default drag constant |
| 9923 | |
| 9924 | /** |
| 9925 | * Sets or gets the drag constant, in the range [0,1]. The default drag |
| 9926 | * constant is 0.1. The drag forces scales linearly with the particle's |
| 9927 | * velocity based on the given drag constant. |
| 9928 | * |
| 9929 | * @function |
| 9930 | * @name pv.Force.drag.prototype.constant |
| 9931 | * @param {number} x the new drag constant. |
| 9932 | * @returns {pv.Force.drag} this, or the current drag constant. |
| 9933 | */ |
| 9934 | force.constant = function(x) { |
| 9935 | if (arguments.length) { k = x; return force; } |
| 9936 | return k; |
| 9937 | }; |
| 9938 | |
| 9939 | /** |
| 9940 | * Applies this force to the specified particles. |
| 9941 | * |
| 9942 | * @function |
| 9943 | * @name pv.Force.drag.prototype.apply |
| 9944 | * @param {pv.Particle} particles particles to which to apply this force. |
| 9945 | */ |
| 9946 | force.apply = function(particles) { |
| 9947 | if (k) for (var p = particles; p; p = p.next) { |
| 9948 | p.fx -= k * p.vx; |
| 9949 | p.fy -= k * p.vy; |
| 9950 | } |
| 9951 | }; |
| 9952 | |
| 9953 | return force; |
| 9954 | }; |
| 9955 | /** |
| 9956 | * Constructs a new spring force with the specified constant. The links |
| 9957 | * associated with this spring force must be specified before the spring force |
| 9958 | * can be applied. |
| 9959 | * |
| 9960 | * @class Implements a spring force, per Hooke's law. The spring force can be |
| 9961 | * configured with a tension constant, rest length, and damping factor. The |
| 9962 | * tension and damping will automatically be normalized using the inverse square |
| 9963 | * root of the maximum link degree of attached nodes; this makes springs weaker |
| 9964 | * between nodes of high link degree. |
| 9965 | * |
| 9966 | * <p>Unlike other forces (such as charge and drag forces) which may be applied |
| 9967 | * globally, spring forces are only applied between linked particles. Therefore, |
| 9968 | * an array of links must be specified before this force can be applied; the |
| 9969 | * links should be an array of {@link pv.Layout.Network.Link}s. See also |
| 9970 | * {@link pv.Layout.Force} for an example of using spring and charge forces for |
| 9971 | * network layout. |
| 9972 | * |
| 9973 | * @extends pv.Force |
| 9974 | * @param {number} k the spring constant. |
| 9975 | * @see #constant |
| 9976 | * @see #links |
| 9977 | */ |
| 9978 | pv.Force.spring = function(k) { |
| 9979 | var d = .1, // default damping factor |
| 9980 | l = 20, // default rest length |
| 9981 | links, // links on which to apply spring forces |
| 9982 | kl, // per-spring normalization |
| 9983 | force = {}; |
| 9984 | |
| 9985 | if (!arguments.length) k = .1; // default spring constant (tension) |
| 9986 | |
| 9987 | /** |
| 9988 | * Sets or gets the links associated with this spring force. Unlike other |
| 9989 | * forces (such as charge and drag forces) which may be applied globally, |
| 9990 | * spring forces are only applied between linked particles. Therefore, an |
| 9991 | * array of links must be specified before this force can be applied; the |
| 9992 | * links should be an array of {@link pv.Layout.Network.Link}s. |
| 9993 | * |
| 9994 | * @function |
| 9995 | * @name pv.Force.spring.prototype.links |
| 9996 | * @param {array} x the new array of links. |
| 9997 | * @returns {pv.Force.spring} this, or the current array of links. |
| 9998 | */ |
| 9999 | force.links = function(x) { |
| 10000 | if (arguments.length) { |
| 10001 | links = x; |
| 10002 | kl = x.map(function(l) { |
| 10003 | return 1 / Math.sqrt(Math.max( |
| 10004 | l.sourceNode.linkDegree, |
| 10005 | l.targetNode.linkDegree)); |
| 10006 | }); |
| 10007 | return force; |
| 10008 | } |
| 10009 | return links; |
| 10010 | }; |
| 10011 | |
| 10012 | /** |
| 10013 | * Sets or gets the spring constant. The default value is 0.1; greater values |
| 10014 | * will result in stronger tension. The spring tension is automatically |
| 10015 | * normalized using the inverse square root of the maximum link degree of |
| 10016 | * attached nodes. |
| 10017 | * |
| 10018 | * @function |
| 10019 | * @name pv.Force.spring.prototype.constant |
| 10020 | * @param {number} x the new spring constant. |
| 10021 | * @returns {pv.Force.spring} this, or the current spring constant. |
| 10022 | */ |
| 10023 | force.constant = function(x) { |
| 10024 | if (arguments.length) { |
| 10025 | k = Number(x); |
| 10026 | return force; |
| 10027 | } |
| 10028 | return k; |
| 10029 | }; |
| 10030 | |
| 10031 | /** |
| 10032 | * The spring damping factor, in the range [0,1]. Damping functions |
| 10033 | * identically to drag forces, damping spring bounciness by applying a force |
| 10034 | * in the opposite direction of attached nodes' velocities. The default value |
| 10035 | * is 0.1. The spring damping is automatically normalized using the inverse |
| 10036 | * square root of the maximum link degree of attached nodes. |
| 10037 | * |
| 10038 | * @function |
| 10039 | * @name pv.Force.spring.prototype.damping |
| 10040 | * @param {number} x the new spring damping factor. |
| 10041 | * @returns {pv.Force.spring} this, or the current spring damping factor. |
| 10042 | */ |
| 10043 | force.damping = function(x) { |
| 10044 | if (arguments.length) { |
| 10045 | d = Number(x); |
| 10046 | return force; |
| 10047 | } |
| 10048 | return d; |
| 10049 | }; |
| 10050 | |
| 10051 | /** |
| 10052 | * The spring rest length. The default value is 20 pixels. |
| 10053 | * |
| 10054 | * @function |
| 10055 | * @name pv.Force.spring.prototype.length |
| 10056 | * @param {number} x the new spring rest length. |
| 10057 | * @returns {pv.Force.spring} this, or the current spring rest length. |
| 10058 | */ |
| 10059 | force.length = function(x) { |
| 10060 | if (arguments.length) { |
| 10061 | l = Number(x); |
| 10062 | return force; |
| 10063 | } |
| 10064 | return l; |
| 10065 | }; |
| 10066 | |
| 10067 | /** |
| 10068 | * Applies this force to the specified particles. |
| 10069 | * |
| 10070 | * @function |
| 10071 | * @name pv.Force.spring.prototype.apply |
| 10072 | * @param {pv.Particle} particles particles to which to apply this force. |
| 10073 | */ |
| 10074 | force.apply = function(particles) { |
| 10075 | for (var i = 0; i < links.length; i++) { |
| 10076 | var a = links[i].sourceNode, |
| 10077 | b = links[i].targetNode, |
| 10078 | dx = a.x - b.x, |
| 10079 | dy = a.y - b.y, |
| 10080 | dn = Math.sqrt(dx * dx + dy * dy), |
| 10081 | dd = dn ? (1 / dn) : 1, |
| 10082 | ks = k * kl[i], // normalized tension |
| 10083 | kd = d * kl[i], // normalized damping |
| 10084 | kk = (ks * (dn - l) + kd * (dx * (a.vx - b.vx) + dy * (a.vy - b.vy)) * dd) * dd, |
| 10085 | fx = -kk * (dn ? dx : (.01 * (.5 - Math.random()))), |
| 10086 | fy = -kk * (dn ? dy : (.01 * (.5 - Math.random()))); |
| 10087 | a.fx += fx; |
| 10088 | a.fy += fy; |
| 10089 | b.fx -= fx; |
| 10090 | b.fy -= fy; |
| 10091 | } |
| 10092 | }; |
| 10093 | |
| 10094 | return force; |
| 10095 | }; |
| 10096 | /** |
| 10097 | * Abstract; see an implementing class. |
| 10098 | * |
| 10099 | * @class Represents a constraint that acts on particles. Note that this |
| 10100 | * interface does not specify how to bind a constraint to specific particles; in |
| 10101 | * general, constraints are applied globally to all particles. However, some |
| 10102 | * constraints may be applied to specific particles or between particles, such |
| 10103 | * as position constraints, through additional specialization. |
| 10104 | * |
| 10105 | * @see pv.Simulation |
| 10106 | * @see pv.Particle |
| 10107 | * @see pv.Constraint.bound |
| 10108 | * @see pv.Constraint.collision |
| 10109 | * @see pv.Constraint.position |
| 10110 | */ |
| 10111 | pv.Constraint = {}; |
| 10112 | |
| 10113 | /** |
| 10114 | * Applies this constraint to the specified particles. |
| 10115 | * |
| 10116 | * @function |
| 10117 | * @name pv.Constraint.prototype.apply |
| 10118 | * @param {pv.Particle} particles particles to which to apply this constraint. |
| 10119 | * @param {pv.Quadtree} q a quadtree for spatial acceleration. |
| 10120 | * @returns {pv.Constraint} this. |
| 10121 | */ |
| 10122 | /** |
| 10123 | * Constructs a new collision constraint. The default search radius is 10, and |
| 10124 | * the default repeat count is 1. A radius function must be specified to compute |
| 10125 | * the radius of particles. |
| 10126 | * |
| 10127 | * @class Constraints circles to avoid overlap. Each particle is treated as a |
| 10128 | * circle, with the radius of the particle computed using a specified function. |
| 10129 | * For example, if the particle has an <tt>r</tt> attribute storing the radius, |
| 10130 | * the radius <tt>function(d) d.r</tt> specifies a collision constraint using |
| 10131 | * this radius. The radius function is passed each {@link pv.Particle} as the |
| 10132 | * first argument. |
| 10133 | * |
| 10134 | * <p>To accelerate collision detection, this implementation uses a quadtree and |
| 10135 | * a search radius. The search radius is computed as the maximum radius of all |
| 10136 | * particles in the simulation. |
| 10137 | * |
| 10138 | * @see pv.Constraint |
| 10139 | * @param {function} radius the radius function. |
| 10140 | */ |
| 10141 | pv.Constraint.collision = function(radius) { |
| 10142 | var n = 1, // number of times to repeat the constraint |
| 10143 | r1, |
| 10144 | px1, |
| 10145 | py1, |
| 10146 | px2, |
| 10147 | py2, |
| 10148 | constraint = {}; |
| 10149 | |
| 10150 | if (!arguments.length) r1 = 10; // default search radius |
| 10151 | |
| 10152 | /** |
| 10153 | * Sets or gets the repeat count. If the repeat count is greater than 1, the |
| 10154 | * constraint will be applied repeatedly; this is a form of the Gauss-Seidel |
| 10155 | * method for constraints relaxation. Repeating the collision constraint makes |
| 10156 | * the constraint have more of an effect when there is a potential for many |
| 10157 | * co-occurring collisions. |
| 10158 | * |
| 10159 | * @function |
| 10160 | * @name pv.Constraint.collision.prototype.repeat |
| 10161 | * @param {number} x the number of times to repeat this constraint. |
| 10162 | * @returns {pv.Constraint.collision} this. |
| 10163 | */ |
| 10164 | constraint.repeat = function(x) { |
| 10165 | if (arguments.length) { |
| 10166 | n = Number(x); |
| 10167 | return constraint; |
| 10168 | } |
| 10169 | return n; |
| 10170 | }; |
| 10171 | |
| 10172 | /** @private */ |
| 10173 | function constrain(n, p, x1, y1, x2, y2) { |
| 10174 | if (!n.leaf) { |
| 10175 | var sx = (x1 + x2) * .5, |
| 10176 | sy = (y1 + y2) * .5, |
| 10177 | top = sy > py1, |
| 10178 | bottom = sy < py2, |
| 10179 | left = sx > px1, |
| 10180 | right = sx < px2; |
| 10181 | if (top) { |
| 10182 | if (n.c1 && left) constrain(n.c1, p, x1, y1, sx, sy); |
| 10183 | if (n.c2 && right) constrain(n.c2, p, sx, y1, x2, sy); |
| 10184 | } |
| 10185 | if (bottom) { |
| 10186 | if (n.c3 && left) constrain(n.c3, p, x1, sy, sx, y2); |
| 10187 | if (n.c4 && right) constrain(n.c4, p, sx, sy, x2, y2); |
| 10188 | } |
| 10189 | } |
| 10190 | if (n.p && (n.p != p)) { |
| 10191 | var dx = p.x - n.p.x, |
| 10192 | dy = p.y - n.p.y, |
| 10193 | l = Math.sqrt(dx * dx + dy * dy), |
| 10194 | d = r1 + radius(n.p); |
| 10195 | if (l < d) { |
| 10196 | var k = (l - d) / l * .5; |
| 10197 | dx *= k; |
| 10198 | dy *= k; |
| 10199 | p.x -= dx; |
| 10200 | p.y -= dy; |
| 10201 | n.p.x += dx; |
| 10202 | n.p.y += dy; |
| 10203 | } |
| 10204 | } |
| 10205 | } |
| 10206 | |
| 10207 | /** |
| 10208 | * Applies this constraint to the specified particles. |
| 10209 | * |
| 10210 | * @function |
| 10211 | * @name pv.Constraint.collision.prototype.apply |
| 10212 | * @param {pv.Particle} particles particles to which to apply this constraint. |
| 10213 | * @param {pv.Quadtree} q a quadtree for spatial acceleration. |
| 10214 | */ |
| 10215 | constraint.apply = function(particles, q) { |
| 10216 | var p, r, max = -Infinity; |
| 10217 | for (p = particles; p; p = p.next) { |
| 10218 | r = radius(p); |
| 10219 | if (r > max) max = r; |
| 10220 | } |
| 10221 | for (var i = 0; i < n; i++) { |
| 10222 | for (p = particles; p; p = p.next) { |
| 10223 | r = (r1 = radius(p)) + max; |
| 10224 | px1 = p.x - r; |
| 10225 | px2 = p.x + r; |
| 10226 | py1 = p.y - r; |
| 10227 | py2 = p.y + r; |
| 10228 | constrain(q.root, p, q.xMin, q.yMin, q.xMax, q.yMax); |
| 10229 | } |
| 10230 | } |
| 10231 | }; |
| 10232 | |
| 10233 | return constraint; |
| 10234 | }; |
| 10235 | /** |
| 10236 | * Constructs a default position constraint using the <tt>fix</tt> attribute. |
| 10237 | * An optional position function can be specified to determine how the fixed |
| 10238 | * position per-particle is determined. |
| 10239 | * |
| 10240 | * @class Constraints particles to a fixed position. The fixed position per |
| 10241 | * particle is determined using a given position function, which defaults to |
| 10242 | * <tt>function(d) d.fix</tt>. |
| 10243 | * |
| 10244 | * <p>If the position function returns null, then no position constraint is |
| 10245 | * applied to the given particle. Otherwise, the particle's position is set to |
| 10246 | * the returned position, as expressed by a {@link pv.Vector}. (Note: the |
| 10247 | * position does not need to be an instance of <tt>pv.Vector</tt>, but simply an |
| 10248 | * object with <tt>x</tt> and <tt>y</tt> attributes.) |
| 10249 | * |
| 10250 | * <p>This constraint also supports a configurable alpha parameter, which |
| 10251 | * defaults to 1. If the alpha parameter is in the range [0,1], then rather than |
| 10252 | * setting the particle's new position directly to the position returned by the |
| 10253 | * supplied position function, the particle's position is interpolated towards |
| 10254 | * the fixed position. This results is a smooth (exponential) drift towards the |
| 10255 | * fixed position, which can increase the stability of the physics simulation. |
| 10256 | * In addition, the alpha parameter can be decayed over time, relaxing the |
| 10257 | * position constraint, which helps to stabilize on an optimal solution. |
| 10258 | * |
| 10259 | * @param {function} [f] the position function. |
| 10260 | */ |
| 10261 | pv.Constraint.position = function(f) { |
| 10262 | var a = 1, // default alpha |
| 10263 | constraint = {}; |
| 10264 | |
| 10265 | if (!arguments.length) /** @ignore */ f = function(p) { return p.fix; }; |
| 10266 | |
| 10267 | /** |
| 10268 | * Sets or gets the alpha parameter for position interpolation. If the alpha |
| 10269 | * parameter is in the range [0,1], then rather than setting the particle's |
| 10270 | * new position directly to the position returned by the supplied position |
| 10271 | * function, the particle's position is interpolated towards the fixed |
| 10272 | * position. |
| 10273 | * |
| 10274 | * @function |
| 10275 | * @name pv.Constraint.position.prototype.alpha |
| 10276 | * @param {number} x the new alpha parameter, in the range [0,1]. |
| 10277 | * @returns {pv.Constraint.position} this. |
| 10278 | */ |
| 10279 | constraint.alpha = function(x) { |
| 10280 | if (arguments.length) { |
| 10281 | a = Number(x); |
| 10282 | return constraint; |
| 10283 | } |
| 10284 | return a; |
| 10285 | }; |
| 10286 | |
| 10287 | /** |
| 10288 | * Applies this constraint to the specified particles. |
| 10289 | * |
| 10290 | * @function |
| 10291 | * @name pv.Constraint.position.prototype.apply |
| 10292 | * @param {pv.Particle} particles particles to which to apply this constraint. |
| 10293 | */ |
| 10294 | constraint.apply = function(particles) { |
| 10295 | for (var p = particles; p; p = p.next) { |
| 10296 | var v = f(p); |
| 10297 | if (v) { |
| 10298 | p.x += (v.x - p.x) * a; |
| 10299 | p.y += (v.y - p.y) * a; |
| 10300 | p.fx = p.fy = p.vx = p.vy = 0; |
| 10301 | } |
| 10302 | } |
| 10303 | }; |
| 10304 | |
| 10305 | return constraint; |
| 10306 | }; |
| 10307 | /** |
| 10308 | * Constructs a new bound constraint. Before the constraint can be used, the |
| 10309 | * {@link #x} and {@link #y} methods must be call to specify the bounds. |
| 10310 | * |
| 10311 | * @class Constrains particles to within fixed rectangular bounds. For example, |
| 10312 | * this constraint can be used to constrain particles in a physics simulation |
| 10313 | * within the bounds of an enclosing panel. |
| 10314 | * |
| 10315 | * <p>Note that the current implementation treats particles as points, with no |
| 10316 | * area. If the particles are rendered as dots, be sure to include some |
| 10317 | * additional padding to inset the bounds such that the edges of the dots do not |
| 10318 | * get clipped by the panel bounds. If the particles have different radii, this |
| 10319 | * constraint would need to be extended using a radius function, similar to |
| 10320 | * {@link pv.Constraint.collision}. |
| 10321 | * |
| 10322 | * @see pv.Layout.Force |
| 10323 | * @extends pv.Constraint |
| 10324 | */ |
| 10325 | pv.Constraint.bound = function() { |
| 10326 | var constraint = {}, |
| 10327 | x, |
| 10328 | y; |
| 10329 | |
| 10330 | /** |
| 10331 | * Sets or gets the bounds on the x-coordinate. |
| 10332 | * |
| 10333 | * @function |
| 10334 | * @name pv.Constraint.bound.prototype.x |
| 10335 | * @param {number} min the minimum allowed x-coordinate. |
| 10336 | * @param {number} max the maximum allowed x-coordinate. |
| 10337 | * @returns {pv.Constraint.bound} this. |
| 10338 | */ |
| 10339 | constraint.x = function(min, max) { |
| 10340 | if (arguments.length) { |
| 10341 | x = {min: Math.min(min, max), max: Math.max(min, max)}; |
| 10342 | return this; |
| 10343 | } |
| 10344 | return x; |
| 10345 | }; |
| 10346 | |
| 10347 | /** |
| 10348 | * Sets or gets the bounds on the y-coordinate. |
| 10349 | * |
| 10350 | * @function |
| 10351 | * @name pv.Constraint.bound.prototype.y |
| 10352 | * @param {number} min the minimum allowed y-coordinate. |
| 10353 | * @param {number} max the maximum allowed y-coordinate. |
| 10354 | * @returns {pv.Constraint.bound} this. |
| 10355 | */ |
| 10356 | constraint.y = function(min, max) { |
| 10357 | if (arguments.length) { |
| 10358 | y = {min: Math.min(min, max), max: Math.max(min, max)}; |
| 10359 | return this; |
| 10360 | } |
| 10361 | return y; |
| 10362 | }; |
| 10363 | |
| 10364 | /** |
| 10365 | * Applies this constraint to the specified particles. |
| 10366 | * |
| 10367 | * @function |
| 10368 | * @name pv.Constraint.bound.prototype.apply |
| 10369 | * @param {pv.Particle} particles particles to which to apply this constraint. |
| 10370 | */ |
| 10371 | constraint.apply = function(particles) { |
| 10372 | if (x) for (var p = particles; p; p = p.next) { |
| 10373 | p.x = p.x < x.min ? x.min : (p.x > x.max ? x.max : p.x); |
| 10374 | } |
| 10375 | if (y) for (var p = particles; p; p = p.next) { |
| 10376 | p.y = p.y < y.min ? y.min : (p.y > y.max ? y.max : p.y); |
| 10377 | } |
| 10378 | }; |
| 10379 | |
| 10380 | return constraint; |
| 10381 | }; |
| 10382 | /** |
| 10383 | * Constructs a new, empty layout with default properties. Layouts are not |
| 10384 | * typically constructed directly; instead, a concrete subclass is added to an |
| 10385 | * existing panel via {@link pv.Mark#add}. |
| 10386 | * |
| 10387 | * @class Represents an abstract layout, encapsulating a visualization technique |
| 10388 | * such as a streamgraph or treemap. Layouts are themselves containers, |
| 10389 | * extending from {@link pv.Panel}, and defining a set of mark prototypes as |
| 10390 | * children. These mark prototypes provide default properties that together |
| 10391 | * implement the given visualization technique. |
| 10392 | * |
| 10393 | * <p>Layouts do not initially contain any marks; any exported marks (such as a |
| 10394 | * network layout's <tt>link</tt> and <tt>node</tt>) are intended to be used as |
| 10395 | * prototypes. By adding a concrete mark, such as a {@link pv.Bar}, to the |
| 10396 | * appropriate mark prototype, the mark is added to the layout and inherits the |
| 10397 | * given properties. This approach allows further customization of the layout, |
| 10398 | * either by choosing a different mark type to add, or more simply by overriding |
| 10399 | * some of the layout's defined properties. |
| 10400 | * |
| 10401 | * <p>Each concrete layout, such as treemap or circle-packing, has different |
| 10402 | * behavior and may export different mark prototypes, depending on what marks |
| 10403 | * are typically needed to render the desired visualization. Therefore it is |
| 10404 | * important to understand how each layout is structured, such that the provided |
| 10405 | * mark prototypes are used appropriately. |
| 10406 | * |
| 10407 | * <p>In addition to the mark prototypes, layouts may define custom properties |
| 10408 | * that affect the overall behavior of the layout. For example, a treemap layout |
| 10409 | * might use a property to specify which layout algorithm to use. These |
| 10410 | * properties are just like other mark properties, and can be defined as |
| 10411 | * constants or as functions. As with panels, the data property can be used to |
| 10412 | * replicate layouts, and properties can be defined to in terms of layout data. |
| 10413 | * |
| 10414 | * @extends pv.Panel |
| 10415 | */ |
| 10416 | pv.Layout = function() { |
| 10417 | pv.Panel.call(this); |
| 10418 | }; |
| 10419 | |
| 10420 | pv.Layout.prototype = pv.extend(pv.Panel); |
| 10421 | |
| 10422 | /** |
| 10423 | * @private Defines a local property with the specified name and cast. Note that |
| 10424 | * although the property method is only defined locally, the cast function is |
| 10425 | * global, which is necessary since properties are inherited! |
| 10426 | * |
| 10427 | * @param {string} name the property name. |
| 10428 | * @param {function} [cast] the cast function for this property. |
| 10429 | */ |
| 10430 | pv.Layout.prototype.property = function(name, cast) { |
| 10431 | if (!this.hasOwnProperty("properties")) { |
| 10432 | this.properties = pv.extend(this.properties); |
| 10433 | } |
| 10434 | this.properties[name] = true; |
| 10435 | this.propertyMethod(name, false, pv.Mark.cast[name] = cast); |
| 10436 | return this; |
| 10437 | }; |
| 10438 | /** |
| 10439 | * Constructs a new, empty network layout. Layouts are not typically constructed |
| 10440 | * directly; instead, they are added to an existing panel via |
| 10441 | * {@link pv.Mark#add}. |
| 10442 | * |
| 10443 | * @class Represents an abstract layout for network diagrams. This class |
| 10444 | * provides the basic structure for both node-link diagrams (such as |
| 10445 | * force-directed graph layout) and space-filling network diagrams (such as |
| 10446 | * sunbursts and treemaps). Note that "network" here is a general term that |
| 10447 | * includes hierarchical structures; a tree is represented using links from |
| 10448 | * child to parent. |
| 10449 | * |
| 10450 | * <p>Network layouts require the graph data structure to be defined using two |
| 10451 | * properties:<ul> |
| 10452 | * |
| 10453 | * <li><tt>nodes</tt> - an array of objects representing nodes. Objects in this |
| 10454 | * array must conform to the {@link pv.Layout.Network.Node} interface; which is |
| 10455 | * to say, be careful to avoid naming collisions with automatic attributes such |
| 10456 | * as <tt>index</tt> and <tt>linkDegree</tt>. If the nodes property is defined |
| 10457 | * as an array of primitives, such as numbers or strings, these primitives are |
| 10458 | * automatically wrapped in an object; the resulting object's <tt>nodeValue</tt> |
| 10459 | * attribute points to the original primitive value. |
| 10460 | * |
| 10461 | * <p><li><tt>links</tt> - an array of objects representing links. Objects in |
| 10462 | * this array must conform to the {@link pv.Layout.Network.Link} interface; at a |
| 10463 | * minimum, either <tt>source</tt> and <tt>target</tt> indexes or |
| 10464 | * <tt>sourceNode</tt> and <tt>targetNode</tt> references must be set. Note that |
| 10465 | * if the links property is defined after the nodes property, the links can be |
| 10466 | * defined in terms of <tt>this.nodes()</tt>. |
| 10467 | * |
| 10468 | * </ul> |
| 10469 | * |
| 10470 | * <p>Three standard mark prototypes are provided:<ul> |
| 10471 | * |
| 10472 | * <li><tt>node</tt> - for rendering nodes; typically a {@link pv.Dot}. The node |
| 10473 | * mark is added directly to the layout, with the data property defined via the |
| 10474 | * layout's <tt>nodes</tt> property. Properties such as <tt>strokeStyle</tt> and |
| 10475 | * <tt>fillStyle</tt> can be overridden to compute properties from node data |
| 10476 | * dynamically. |
| 10477 | * |
| 10478 | * <p><li><tt>link</tt> - for rendering links; typically a {@link pv.Line}. The |
| 10479 | * link mark is added to a child panel, whose data property is defined as |
| 10480 | * layout's <tt>links</tt> property. The link's data property is then a |
| 10481 | * two-element array of the source node and target node. Thus, poperties such as |
| 10482 | * <tt>strokeStyle</tt> and <tt>fillStyle</tt> can be overridden to compute |
| 10483 | * properties from either the node data (the first argument) or the link data |
| 10484 | * (the second argument; the parent panel data) dynamically. |
| 10485 | * |
| 10486 | * <p><li><tt>label</tt> - for rendering node labels; typically a |
| 10487 | * {@link pv.Label}. The label mark is added directly to the layout, with the |
| 10488 | * data property defined via the layout's <tt>nodes</tt> property. Properties |
| 10489 | * such as <tt>strokeStyle</tt> and <tt>fillStyle</tt> can be overridden to |
| 10490 | * compute properties from node data dynamically. |
| 10491 | * |
| 10492 | * </ul>Note that some network implementations may not support all three |
| 10493 | * standard mark prototypes; for example, space-filling hierarchical layouts |
| 10494 | * typically do not use a <tt>link</tt> prototype, as the parent-child links are |
| 10495 | * implied by the structure of the space-filling <tt>node</tt> marks. Check the |
| 10496 | * specific network layout for implementation details. |
| 10497 | * |
| 10498 | * <p>Network layout properties, including <tt>nodes</tt> and <tt>links</tt>, |
| 10499 | * are typically cached rather than re-evaluated with every call to render. This |
| 10500 | * is a performance optimization, as network layout algorithms can be |
| 10501 | * expensive. If the network structure changes, call {@link #reset} to clear the |
| 10502 | * cache before rendering. Note that although the network layout properties are |
| 10503 | * cached, child mark properties, such as the marks used to render the nodes and |
| 10504 | * links, <i>are not</i>. Therefore, non-structural changes to the network |
| 10505 | * layout, such as changing the color of a mark on mouseover, do not need to |
| 10506 | * reset the layout. |
| 10507 | * |
| 10508 | * @see pv.Layout.Hierarchy |
| 10509 | * @see pv.Layout.Force |
| 10510 | * @see pv.Layout.Matrix |
| 10511 | * @see pv.Layout.Arc |
| 10512 | * @see pv.Layout.Rollup |
| 10513 | * @extends pv.Layout |
| 10514 | */ |
| 10515 | pv.Layout.Network = function() { |
| 10516 | pv.Layout.call(this); |
| 10517 | var that = this; |
| 10518 | |
| 10519 | /* @private Version tracking to cache layout state, improving performance. */ |
| 10520 | this.$id = pv.id(); |
| 10521 | |
| 10522 | /** |
| 10523 | * The node prototype. This prototype is intended to be used with a Dot mark |
| 10524 | * in conjunction with the link prototype. |
| 10525 | * |
| 10526 | * @type pv.Mark |
| 10527 | * @name pv.Layout.Network.prototype.node |
| 10528 | */ |
| 10529 | (this.node = new pv.Mark() |
| 10530 | .data(function() { return that.nodes(); }) |
| 10531 | .strokeStyle("#1f77b4") |
| 10532 | .fillStyle("#fff") |
| 10533 | .left(function(n) { return n.x; }) |
| 10534 | .top(function(n) { return n.y; })).parent = this; |
| 10535 | |
| 10536 | /** |
| 10537 | * The link prototype, which renders edges between source nodes and target |
| 10538 | * nodes. This prototype is intended to be used with a Line mark in |
| 10539 | * conjunction with the node prototype. |
| 10540 | * |
| 10541 | * @type pv.Mark |
| 10542 | * @name pv.Layout.Network.prototype.link |
| 10543 | */ |
| 10544 | this.link = new pv.Mark() |
| 10545 | .extend(this.node) |
| 10546 | .data(function(p) { return [p.sourceNode, p.targetNode]; }) |
| 10547 | .fillStyle(null) |
| 10548 | .lineWidth(function(d, p) { return p.linkValue * 1.5; }) |
| 10549 | .strokeStyle("rgba(0,0,0,.2)"); |
| 10550 | |
| 10551 | this.link.add = function(type) { |
| 10552 | return that.add(pv.Panel) |
| 10553 | .data(function() { return that.links(); }) |
| 10554 | .add(type) |
| 10555 | .extend(this); |
| 10556 | }; |
| 10557 | |
| 10558 | /** |
| 10559 | * The node label prototype, which renders the node name adjacent to the node. |
| 10560 | * This prototype is provided as an alternative to using the anchor on the |
| 10561 | * node mark; it is primarily intended to be used with radial node-link |
| 10562 | * layouts, since it provides a convenient mechanism to set the text angle. |
| 10563 | * |
| 10564 | * @type pv.Mark |
| 10565 | * @name pv.Layout.Network.prototype.label |
| 10566 | */ |
| 10567 | (this.label = new pv.Mark() |
| 10568 | .extend(this.node) |
| 10569 | .textMargin(7) |
| 10570 | .textBaseline("middle") |
| 10571 | .text(function(n) { return n.nodeName || n.nodeValue; }) |
| 10572 | .textAngle(function(n) { |
| 10573 | var a = n.midAngle; |
| 10574 | return pv.Wedge.upright(a) ? a : (a + Math.PI); |
| 10575 | }) |
| 10576 | .textAlign(function(n) { |
| 10577 | return pv.Wedge.upright(n.midAngle) ? "left" : "right"; |
| 10578 | })).parent = this; |
| 10579 | }; |
| 10580 | |
| 10581 | /** |
| 10582 | * @class Represents a node in a network layout. There is no explicit |
| 10583 | * constructor; this class merely serves to document the attributes that are |
| 10584 | * used on nodes in network layouts. (Note that hierarchical nodes place |
| 10585 | * additional requirements on node representation, vis {@link pv.Dom.Node}.) |
| 10586 | * |
| 10587 | * @see pv.Layout.Network |
| 10588 | * @name pv.Layout.Network.Node |
| 10589 | */ |
| 10590 | |
| 10591 | /** |
| 10592 | * The node index, zero-based. This attribute is populated automatically based |
| 10593 | * on the index in the array returned by the <tt>nodes</tt> property. |
| 10594 | * |
| 10595 | * @type number |
| 10596 | * @name pv.Layout.Network.Node.prototype.index |
| 10597 | */ |
| 10598 | |
| 10599 | /** |
| 10600 | * The link degree; the sum of link values for all incoming and outgoing links. |
| 10601 | * This attribute is populated automatically. |
| 10602 | * |
| 10603 | * @type number |
| 10604 | * @name pv.Layout.Network.Node.prototype.linkDegree |
| 10605 | */ |
| 10606 | |
| 10607 | /** |
| 10608 | * The node name; optional. If present, this attribute will be used to provide |
| 10609 | * the text for node labels. If not present, the label text will fallback to the |
| 10610 | * <tt>nodeValue</tt> attribute. |
| 10611 | * |
| 10612 | * @type string |
| 10613 | * @name pv.Layout.Network.Node.prototype.nodeName |
| 10614 | */ |
| 10615 | |
| 10616 | /** |
| 10617 | * The node value; optional. If present, and no <tt>nodeName</tt> attribute is |
| 10618 | * present, the node value will be used as the label text. This attribute is |
| 10619 | * also automatically populated if the nodes are specified as an array of |
| 10620 | * primitives, such as strings or numbers. |
| 10621 | * |
| 10622 | * @type object |
| 10623 | * @name pv.Layout.Network.Node.prototype.nodeValue |
| 10624 | */ |
| 10625 | |
| 10626 | /** |
| 10627 | * @class Represents a link in a network layout. There is no explicit |
| 10628 | * constructor; this class merely serves to document the attributes that are |
| 10629 | * used on links in network layouts. For hierarchical layouts, this class is |
| 10630 | * used to represent the parent-child links. |
| 10631 | * |
| 10632 | * @see pv.Layout.Network |
| 10633 | * @name pv.Layout.Network.Link |
| 10634 | */ |
| 10635 | |
| 10636 | /** |
| 10637 | * The link value, or weight; optional. If not specified (or not a number), the |
| 10638 | * default value of 1 is used. |
| 10639 | * |
| 10640 | * @type number |
| 10641 | * @name pv.Layout.Network.Link.prototype.linkValue |
| 10642 | */ |
| 10643 | |
| 10644 | /** |
| 10645 | * The link's source node. If not set, this value will be derived from the |
| 10646 | * <tt>source</tt> attribute index. |
| 10647 | * |
| 10648 | * @type pv.Layout.Network.Node |
| 10649 | * @name pv.Layout.Network.Link.prototype.sourceNode |
| 10650 | */ |
| 10651 | |
| 10652 | /** |
| 10653 | * The link's target node. If not set, this value will be derived from the |
| 10654 | * <tt>target</tt> attribute index. |
| 10655 | * |
| 10656 | * @type pv.Layout.Network.Node |
| 10657 | * @name pv.Layout.Network.Link.prototype.targetNode |
| 10658 | */ |
| 10659 | |
| 10660 | /** |
| 10661 | * Alias for <tt>sourceNode</tt>, as expressed by the index of the source node. |
| 10662 | * This attribute is not populated automatically, but may be used as a more |
| 10663 | * convenient identification of the link's source, for example in a static JSON |
| 10664 | * representation. |
| 10665 | * |
| 10666 | * @type number |
| 10667 | * @name pv.Layout.Network.Link.prototype.source |
| 10668 | */ |
| 10669 | |
| 10670 | /** |
| 10671 | * Alias for <tt>targetNode</tt>, as expressed by the index of the target node. |
| 10672 | * This attribute is not populated automatically, but may be used as a more |
| 10673 | * convenient identification of the link's target, for example in a static JSON |
| 10674 | * representation. |
| 10675 | * |
| 10676 | * @type number |
| 10677 | * @name pv.Layout.Network.Link.prototype.target |
| 10678 | */ |
| 10679 | |
| 10680 | /** |
| 10681 | * Alias for <tt>linkValue</tt>. This attribute is not populated automatically, |
| 10682 | * but may be used instead of the <tt>linkValue</tt> attribute when specifying |
| 10683 | * links. |
| 10684 | * |
| 10685 | * @type number |
| 10686 | * @name pv.Layout.Network.Link.prototype.value |
| 10687 | */ |
| 10688 | |
| 10689 | /** @private Transform nodes and links on cast. */ |
| 10690 | pv.Layout.Network.prototype = pv.extend(pv.Layout) |
| 10691 | .property("nodes", function(v) { |
| 10692 | return v.map(function(d, i) { |
| 10693 | if (typeof d != "object") d = {nodeValue: d}; |
| 10694 | d.index = i; |
| 10695 | d.linkDegree = 0; |
| 10696 | return d; |
| 10697 | }); |
| 10698 | }) |
| 10699 | .property("links", function(v) { |
| 10700 | return v.map(function(d) { |
| 10701 | if (isNaN(d.linkValue)) d.linkValue = isNaN(d.value) ? 1 : d.value; |
| 10702 | return d; |
| 10703 | }); |
| 10704 | }); |
| 10705 | |
| 10706 | /** |
| 10707 | * Resets the cache, such that changes to layout property definitions will be |
| 10708 | * visible on subsequent render. Unlike normal marks (and normal layouts), |
| 10709 | * properties associated with network layouts are not automatically re-evaluated |
| 10710 | * on render; the properties are cached, and any expensive layout algorithms are |
| 10711 | * only run after the layout is explicitly reset. |
| 10712 | * |
| 10713 | * @returns {pv.Layout.Network} this. |
| 10714 | */ |
| 10715 | pv.Layout.Network.prototype.reset = function() { |
| 10716 | this.$id = pv.id(); |
| 10717 | return this; |
| 10718 | }; |
| 10719 | |
| 10720 | /** @private Skip evaluating properties if cached. */ |
| 10721 | pv.Layout.Network.prototype.buildProperties = function(s, properties) { |
| 10722 | if ((s.$id || 0) < this.$id) { |
| 10723 | pv.Layout.prototype.buildProperties.call(this, s, properties); |
| 10724 | } |
| 10725 | }; |
| 10726 | |
| 10727 | /** @private Compute link degrees; map source and target indexes to nodes. */ |
| 10728 | pv.Layout.Network.prototype.buildImplied = function(s) { |
| 10729 | pv.Layout.prototype.buildImplied.call(this, s); |
| 10730 | if (s.$id >= this.$id) return true; |
| 10731 | s.$id = this.$id; |
| 10732 | s.links.forEach(function(d) { |
| 10733 | var v = d.linkValue; |
| 10734 | (d.sourceNode || (d.sourceNode = s.nodes[d.source])).linkDegree += v; |
| 10735 | (d.targetNode || (d.targetNode = s.nodes[d.target])).linkDegree += v; |
| 10736 | }); |
| 10737 | }; |
| 10738 | /** |
| 10739 | * Constructs a new, empty hierarchy layout. Layouts are not typically |
| 10740 | * constructed directly; instead, they are added to an existing panel via |
| 10741 | * {@link pv.Mark#add}. |
| 10742 | * |
| 10743 | * @class Represents an abstract layout for hierarchy diagrams. This class is a |
| 10744 | * specialization of {@link pv.Layout.Network}, providing the basic structure |
| 10745 | * for both hierarchical node-link diagrams (such as Reingold-Tilford trees) and |
| 10746 | * space-filling hierarchy diagrams (such as sunbursts and treemaps). |
| 10747 | * |
| 10748 | * <p>Unlike general network layouts, the <tt>links</tt> property need not be |
| 10749 | * defined explicitly. Instead, the links are computed implicitly from the |
| 10750 | * <tt>parentNode</tt> attribute of the node objects, as defined by the |
| 10751 | * <tt>nodes</tt> property. This implementation is also available as |
| 10752 | * {@link #links}, for reuse with non-hierarchical layouts; for example, to |
| 10753 | * render a tree using force-directed layout. |
| 10754 | * |
| 10755 | * <p>Correspondingly, the <tt>nodes</tt> property is represented as a union of |
| 10756 | * {@link pv.Layout.Network.Node} and {@link pv.Dom.Node}. To construct a node |
| 10757 | * hierarchy from a simple JSON map, use the {@link pv.Dom} operator; this |
| 10758 | * operator also provides an easy way to sort nodes before passing them to the |
| 10759 | * layout. |
| 10760 | * |
| 10761 | * <p>For more details on how to use this layout, see |
| 10762 | * {@link pv.Layout.Network}. |
| 10763 | * |
| 10764 | * @see pv.Layout.Cluster |
| 10765 | * @see pv.Layout.Partition |
| 10766 | * @see pv.Layout.Tree |
| 10767 | * @see pv.Layout.Treemap |
| 10768 | * @see pv.Layout.Indent |
| 10769 | * @see pv.Layout.Pack |
| 10770 | * @extends pv.Layout.Network |
| 10771 | */ |
| 10772 | pv.Layout.Hierarchy = function() { |
| 10773 | pv.Layout.Network.call(this); |
| 10774 | this.link.strokeStyle("#ccc"); |
| 10775 | }; |
| 10776 | |
| 10777 | pv.Layout.Hierarchy.prototype = pv.extend(pv.Layout.Network); |
| 10778 | |
| 10779 | /** @private Compute the implied links. (Links are null by default.) */ |
| 10780 | pv.Layout.Hierarchy.prototype.buildImplied = function(s) { |
| 10781 | if (!s.links) s.links = pv.Layout.Hierarchy.links.call(this); |
| 10782 | pv.Layout.Network.prototype.buildImplied.call(this, s); |
| 10783 | }; |
| 10784 | |
| 10785 | /** The implied links; computes links using the <tt>parentNode</tt> attribute. */ |
| 10786 | pv.Layout.Hierarchy.links = function() { |
| 10787 | return this.nodes() |
| 10788 | .filter(function(n) { return n.parentNode; }) |
| 10789 | .map(function(n) { |
| 10790 | return { |
| 10791 | sourceNode: n, |
| 10792 | targetNode: n.parentNode, |
| 10793 | linkValue: 1 |
| 10794 | }; |
| 10795 | }); |
| 10796 | }; |
| 10797 | |
| 10798 | /** @private Provides standard node-link layout based on breadth & depth. */ |
| 10799 | pv.Layout.Hierarchy.NodeLink = { |
| 10800 | |
| 10801 | /** @private */ |
| 10802 | buildImplied: function(s) { |
| 10803 | var nodes = s.nodes, |
| 10804 | orient = s.orient, |
| 10805 | horizontal = /^(top|bottom)$/.test(orient), |
| 10806 | w = s.width, |
| 10807 | h = s.height; |
| 10808 | |
| 10809 | /* Compute default inner and outer radius. */ |
| 10810 | if (orient == "radial") { |
| 10811 | var ir = s.innerRadius, or = s.outerRadius; |
| 10812 | if (ir == null) ir = 0; |
| 10813 | if (or == null) or = Math.min(w, h) / 2; |
| 10814 | } |
| 10815 | |
| 10816 | /** @private Returns the radius of the given node. */ |
| 10817 | function radius(n) { |
| 10818 | return n.parentNode ? (n.depth * (or - ir) + ir) : 0; |
| 10819 | } |
| 10820 | |
| 10821 | /** @private Returns the angle of the given node. */ |
| 10822 | function midAngle(n) { |
| 10823 | return (n.parentNode ? (n.breadth - .25) * 2 * Math.PI : 0); |
| 10824 | } |
| 10825 | |
| 10826 | /** @private */ |
| 10827 | function x(n) { |
| 10828 | switch (orient) { |
| 10829 | case "left": return n.depth * w; |
| 10830 | case "right": return w - n.depth * w; |
| 10831 | case "top": return n.breadth * w; |
| 10832 | case "bottom": return w - n.breadth * w; |
| 10833 | case "radial": return w / 2 + radius(n) * Math.cos(n.midAngle); |
| 10834 | } |
| 10835 | } |
| 10836 | |
| 10837 | /** @private */ |
| 10838 | function y(n) { |
| 10839 | switch (orient) { |
| 10840 | case "left": return n.breadth * h; |
| 10841 | case "right": return h - n.breadth * h; |
| 10842 | case "top": return n.depth * h; |
| 10843 | case "bottom": return h - n.depth * h; |
| 10844 | case "radial": return h / 2 + radius(n) * Math.sin(n.midAngle); |
| 10845 | } |
| 10846 | } |
| 10847 | |
| 10848 | for (var i = 0; i < nodes.length; i++) { |
| 10849 | var n = nodes[i]; |
| 10850 | n.midAngle = orient == "radial" ? midAngle(n) |
| 10851 | : horizontal ? Math.PI / 2 : 0; |
| 10852 | n.x = x(n); |
| 10853 | n.y = y(n); |
| 10854 | if (n.firstChild) n.midAngle += Math.PI; |
| 10855 | } |
| 10856 | } |
| 10857 | }; |
| 10858 | |
| 10859 | /** @private Provides standard space-filling layout based on breadth & depth. */ |
| 10860 | pv.Layout.Hierarchy.Fill = { |
| 10861 | |
| 10862 | /** @private */ |
| 10863 | constructor: function() { |
| 10864 | this.node |
| 10865 | .strokeStyle("#fff") |
| 10866 | .fillStyle("#ccc") |
| 10867 | .width(function(n) { return n.dx; }) |
| 10868 | .height(function(n) { return n.dy; }) |
| 10869 | .innerRadius(function(n) { return n.innerRadius; }) |
| 10870 | .outerRadius(function(n) { return n.outerRadius; }) |
| 10871 | .startAngle(function(n) { return n.startAngle; }) |
| 10872 | .angle(function(n) { return n.angle; }); |
| 10873 | |
| 10874 | this.label |
| 10875 | .textAlign("center") |
| 10876 | .left(function(n) { return n.x + (n.dx / 2); }) |
| 10877 | .top(function(n) { return n.y + (n.dy / 2); }); |
| 10878 | |
| 10879 | /* Hide unsupported link. */ |
| 10880 | delete this.link; |
| 10881 | }, |
| 10882 | |
| 10883 | /** @private */ |
| 10884 | buildImplied: function(s) { |
| 10885 | var nodes = s.nodes, |
| 10886 | orient = s.orient, |
| 10887 | horizontal = /^(top|bottom)$/.test(orient), |
| 10888 | w = s.width, |
| 10889 | h = s.height, |
| 10890 | depth = -nodes[0].minDepth; |
| 10891 | |
| 10892 | /* Compute default inner and outer radius. */ |
| 10893 | if (orient == "radial") { |
| 10894 | var ir = s.innerRadius, or = s.outerRadius; |
| 10895 | if (ir == null) ir = 0; |
| 10896 | if (ir) depth *= 2; // use full depth step for root |
| 10897 | if (or == null) or = Math.min(w, h) / 2; |
| 10898 | } |
| 10899 | |
| 10900 | /** @private Scales the specified depth for a space-filling layout. */ |
| 10901 | function scale(d, depth) { |
| 10902 | return (d + depth) / (1 + depth); |
| 10903 | } |
| 10904 | |
| 10905 | /** @private */ |
| 10906 | function x(n) { |
| 10907 | switch (orient) { |
| 10908 | case "left": return scale(n.minDepth, depth) * w; |
| 10909 | case "right": return (1 - scale(n.maxDepth, depth)) * w; |
| 10910 | case "top": return n.minBreadth * w; |
| 10911 | case "bottom": return (1 - n.maxBreadth) * w; |
| 10912 | case "radial": return w / 2; |
| 10913 | } |
| 10914 | } |
| 10915 | |
| 10916 | /** @private */ |
| 10917 | function y(n) { |
| 10918 | switch (orient) { |
| 10919 | case "left": return n.minBreadth * h; |
| 10920 | case "right": return (1 - n.maxBreadth) * h; |
| 10921 | case "top": return scale(n.minDepth, depth) * h; |
| 10922 | case "bottom": return (1 - scale(n.maxDepth, depth)) * h; |
| 10923 | case "radial": return h / 2; |
| 10924 | } |
| 10925 | } |
| 10926 | |
| 10927 | /** @private */ |
| 10928 | function dx(n) { |
| 10929 | switch (orient) { |
| 10930 | case "left": |
| 10931 | case "right": return (n.maxDepth - n.minDepth) / (1 + depth) * w; |
| 10932 | case "top": |
| 10933 | case "bottom": return (n.maxBreadth - n.minBreadth) * w; |
| 10934 | case "radial": return n.parentNode ? (n.innerRadius + n.outerRadius) * Math.cos(n.midAngle) : 0; |
| 10935 | } |
| 10936 | } |
| 10937 | |
| 10938 | /** @private */ |
| 10939 | function dy(n) { |
| 10940 | switch (orient) { |
| 10941 | case "left": |
| 10942 | case "right": return (n.maxBreadth - n.minBreadth) * h; |
| 10943 | case "top": |
| 10944 | case "bottom": return (n.maxDepth - n.minDepth) / (1 + depth) * h; |
| 10945 | case "radial": return n.parentNode ? (n.innerRadius + n.outerRadius) * Math.sin(n.midAngle) : 0; |
| 10946 | } |
| 10947 | } |
| 10948 | |
| 10949 | /** @private */ |
| 10950 | function innerRadius(n) { |
| 10951 | return Math.max(0, scale(n.minDepth, depth / 2)) * (or - ir) + ir; |
| 10952 | } |
| 10953 | |
| 10954 | /** @private */ |
| 10955 | function outerRadius(n) { |
| 10956 | return scale(n.maxDepth, depth / 2) * (or - ir) + ir; |
| 10957 | } |
| 10958 | |
| 10959 | /** @private */ |
| 10960 | function startAngle(n) { |
| 10961 | return (n.parentNode ? n.minBreadth - .25 : 0) * 2 * Math.PI; |
| 10962 | } |
| 10963 | |
| 10964 | /** @private */ |
| 10965 | function angle(n) { |
| 10966 | return (n.parentNode ? n.maxBreadth - n.minBreadth : 1) * 2 * Math.PI; |
| 10967 | } |
| 10968 | |
| 10969 | for (var i = 0; i < nodes.length; i++) { |
| 10970 | var n = nodes[i]; |
| 10971 | n.x = x(n); |
| 10972 | n.y = y(n); |
| 10973 | if (orient == "radial") { |
| 10974 | n.innerRadius = innerRadius(n); |
| 10975 | n.outerRadius = outerRadius(n); |
| 10976 | n.startAngle = startAngle(n); |
| 10977 | n.angle = angle(n); |
| 10978 | n.midAngle = n.startAngle + n.angle / 2; |
| 10979 | } else { |
| 10980 | n.midAngle = horizontal ? -Math.PI / 2 : 0; |
| 10981 | } |
| 10982 | n.dx = dx(n); |
| 10983 | n.dy = dy(n); |
| 10984 | } |
| 10985 | } |
| 10986 | }; |
| 10987 | /** |
| 10988 | * Constructs a new, empty grid layout. Layouts are not typically constructed |
| 10989 | * directly; instead, they are added to an existing panel via |
| 10990 | * {@link pv.Mark#add}. |
| 10991 | * |
| 10992 | * @class Implements a grid layout with regularly-sized rows and columns. The |
| 10993 | * number of rows and columns are determined from their respective |
| 10994 | * properties. For example, the 2×3 array: |
| 10995 | * |
| 10996 | * <pre>1 2 3 |
| 10997 | * 4 5 6</pre> |
| 10998 | * |
| 10999 | * can be represented using the <tt>rows</tt> property as: |
| 11000 | * |
| 11001 | * <pre>[[1, 2, 3], [4, 5, 6]]</pre> |
| 11002 | * |
| 11003 | * If your data is in column-major order, you can equivalently use the |
| 11004 | * <tt>columns</tt> property. If the <tt>rows</tt> property is an array, it |
| 11005 | * takes priority over the <tt>columns</tt> property. The data is implicitly |
| 11006 | * transposed, as if the {@link pv.transpose} operator were applied. |
| 11007 | * |
| 11008 | * <p>This layout exports a single <tt>cell</tt> mark prototype, which is |
| 11009 | * intended to be used with a bar, panel, layout, or subclass thereof. The data |
| 11010 | * property of the cell prototype is defined as the elements in the array. For |
| 11011 | * example, if the array is a two-dimensional array of values in the range |
| 11012 | * [0,1], a simple heatmap can be generated as: |
| 11013 | * |
| 11014 | * <pre>vis.add(pv.Layout.Grid) |
| 11015 | * .rows(arrays) |
| 11016 | * .cell.add(pv.Bar) |
| 11017 | * .fillStyle(pv.ramp("white", "black"))</pre> |
| 11018 | * |
| 11019 | * The grid subdivides the full width and height of the parent panel into equal |
| 11020 | * rectangles. Note, however, that for large, interactive, or animated heatmaps, |
| 11021 | * you may see significantly better performance through dynamic {@link pv.Image} |
| 11022 | * generation. |
| 11023 | * |
| 11024 | * <p>For irregular grids using value-based spatial partitioning, see {@link |
| 11025 | * pv.Layout.Treemap}. |
| 11026 | * |
| 11027 | * @extends pv.Layout |
| 11028 | */ |
| 11029 | pv.Layout.Grid = function() { |
| 11030 | pv.Layout.call(this); |
| 11031 | var that = this; |
| 11032 | |
| 11033 | /** |
| 11034 | * The cell prototype. This prototype is intended to be used with a bar, |
| 11035 | * panel, or layout (or subclass thereof) to render the grid cells. |
| 11036 | * |
| 11037 | * @type pv.Mark |
| 11038 | * @name pv.Layout.Grid.prototype.cell |
| 11039 | */ |
| 11040 | (this.cell = new pv.Mark() |
| 11041 | .data(function() { |
| 11042 | return that.scene[that.index].$grid; |
| 11043 | }) |
| 11044 | .width(function() { |
| 11045 | return that.width() / that.cols(); |
| 11046 | }) |
| 11047 | .height(function() { |
| 11048 | return that.height() / that.rows(); |
| 11049 | }) |
| 11050 | .left(function() { |
| 11051 | return this.width() * (this.index % that.cols()); |
| 11052 | }) |
| 11053 | .top(function() { |
| 11054 | return this.height() * Math.floor(this.index / that.cols()); |
| 11055 | })).parent = this; |
| 11056 | }; |
| 11057 | |
| 11058 | pv.Layout.Grid.prototype = pv.extend(pv.Layout) |
| 11059 | .property("rows") |
| 11060 | .property("cols"); |
| 11061 | |
| 11062 | /** |
| 11063 | * Default properties for grid layouts. By default, there is one row and one |
| 11064 | * column, and the data is the propagated to the child cell. |
| 11065 | * |
| 11066 | * @type pv.Layout.Grid |
| 11067 | */ |
| 11068 | pv.Layout.Grid.prototype.defaults = new pv.Layout.Grid() |
| 11069 | .extend(pv.Layout.prototype.defaults) |
| 11070 | .rows(1) |
| 11071 | .cols(1); |
| 11072 | |
| 11073 | /** @private */ |
| 11074 | pv.Layout.Grid.prototype.buildImplied = function(s) { |
| 11075 | pv.Layout.prototype.buildImplied.call(this, s); |
| 11076 | var r = s.rows, c = s.cols; |
| 11077 | if (typeof c == "object") r = pv.transpose(c); |
| 11078 | if (typeof r == "object") { |
| 11079 | s.$grid = pv.blend(r); |
| 11080 | s.rows = r.length; |
| 11081 | s.cols = r[0] ? r[0].length : 0; |
| 11082 | } else { |
| 11083 | s.$grid = pv.repeat([s.data], r * c); |
| 11084 | } |
| 11085 | }; |
| 11086 | |
| 11087 | /** |
| 11088 | * The number of rows. This property can also be specified as the data in |
| 11089 | * row-major order; in this case, the rows property is implicitly set to the |
| 11090 | * length of the array, and the cols property is set to the length of the first |
| 11091 | * element in the array. |
| 11092 | * |
| 11093 | * @type number |
| 11094 | * @name pv.Layout.Grid.prototype.rows |
| 11095 | */ |
| 11096 | |
| 11097 | /** |
| 11098 | * The number of columns. This property can also be specified as the data in |
| 11099 | * column-major order; in this case, the cols property is implicitly set to the |
| 11100 | * length of the array, and the rows property is set to the length of the first |
| 11101 | * element in the array. |
| 11102 | * |
| 11103 | * @type number |
| 11104 | * @name pv.Layout.Grid.prototype.cols |
| 11105 | */ |
| 11106 | /** |
| 11107 | * Constructs a new, empty stack layout. Layouts are not typically constructed |
| 11108 | * directly; instead, they are added to an existing panel via |
| 11109 | * {@link pv.Mark#add}. |
| 11110 | * |
| 11111 | * @class Implements a layout for stacked visualizations, ranging from simple |
| 11112 | * stacked bar charts to more elaborate "streamgraphs" composed of stacked |
| 11113 | * areas. Stack layouts uses length as a visual encoding, as opposed to |
| 11114 | * position, as the layers do not share an aligned axis. |
| 11115 | * |
| 11116 | * <p>Marks can be stacked vertically or horizontally. For example, |
| 11117 | * |
| 11118 | * <pre>vis.add(pv.Layout.Stack) |
| 11119 | * .layers([[1, 1.2, 1.7, 1.5, 1.7], |
| 11120 | * [.5, 1, .8, 1.1, 1.3], |
| 11121 | * [.2, .5, .8, .9, 1]]) |
| 11122 | * .x(function() this.index * 35) |
| 11123 | * .y(function(d) d * 40) |
| 11124 | * .layer.add(pv.Area);</pre> |
| 11125 | * |
| 11126 | * specifies a vertically-stacked area chart, using the default "bottom-left" |
| 11127 | * orientation with "zero" offset. This visualization can be easily changed into |
| 11128 | * a streamgraph using the "wiggle" offset, which attempts to minimize change in |
| 11129 | * slope weighted by layer thickness. See the {@link #offset} property for more |
| 11130 | * supported streamgraph algorithms. |
| 11131 | * |
| 11132 | * <p>In the simplest case, the layer data can be specified as a two-dimensional |
| 11133 | * array of numbers. The <tt>x</tt> and <tt>y</tt> psuedo-properties are used to |
| 11134 | * define the thickness of each layer at the given position, respectively; in |
| 11135 | * the above example of the "bottom-left" orientation, the <tt>x</tt> and |
| 11136 | * <tt>y</tt> psuedo-properties are equivalent to the <tt>left</tt> and |
| 11137 | * <tt>height</tt> properties that you might use if you implemented a stacked |
| 11138 | * area by hand. |
| 11139 | * |
| 11140 | * <p>The advantage of using the stack layout is that the baseline, i.e., the |
| 11141 | * <tt>bottom</tt> property is computed automatically using the specified offset |
| 11142 | * algorithm. In addition, the order of layers can be computed using a built-in |
| 11143 | * algorithm via the <tt>order</tt> property. |
| 11144 | * |
| 11145 | * <p>With the exception of the "expand" <tt>offset</tt>, the stack layout does |
| 11146 | * not perform any automatic scaling of data; the values returned from |
| 11147 | * <tt>x</tt> and <tt>y</tt> specify pixel sizes. To simplify scaling math, use |
| 11148 | * this layout in conjunction with {@link pv.Scale.linear} or similar. |
| 11149 | * |
| 11150 | * <p>In other cases, the <tt>values</tt> psuedo-property can be used to define |
| 11151 | * the data more flexibly. As with a typical panel & area, the |
| 11152 | * <tt>layers</tt> property corresponds to the data in the enclosing panel, |
| 11153 | * while the <tt>values</tt> psuedo-property corresponds to the data for the |
| 11154 | * area within the panel. For example, given an array of data values: |
| 11155 | * |
| 11156 | * <pre>var crimea = [ |
| 11157 | * { date: "4/1854", wounds: 0, other: 110, disease: 110 }, |
| 11158 | * { date: "5/1854", wounds: 0, other: 95, disease: 105 }, |
| 11159 | * { date: "6/1854", wounds: 0, other: 40, disease: 95 }, |
| 11160 | * ...</pre> |
| 11161 | * |
| 11162 | * and a corresponding array of series names: |
| 11163 | * |
| 11164 | * <pre>var causes = ["wounds", "other", "disease"];</pre> |
| 11165 | * |
| 11166 | * Separate layers can be defined for each cause like so: |
| 11167 | * |
| 11168 | * <pre>vis.add(pv.Layout.Stack) |
| 11169 | * .layers(causes) |
| 11170 | * .values(crimea) |
| 11171 | * .x(function(d) x(d.date)) |
| 11172 | * .y(function(d, p) y(d[p])) |
| 11173 | * .layer.add(pv.Area) |
| 11174 | * ...</pre> |
| 11175 | * |
| 11176 | * As with the panel & area case, the datum that is passed to the |
| 11177 | * psuedo-properties <tt>x</tt> and <tt>y</tt> are the values (an element in |
| 11178 | * <tt>crimea</tt>); the second argument is the layer data (a string in |
| 11179 | * <tt>causes</tt>). Additional arguments specify the data of enclosing panels, |
| 11180 | * if any. |
| 11181 | * |
| 11182 | * @extends pv.Layout |
| 11183 | */ |
| 11184 | pv.Layout.Stack = function() { |
| 11185 | pv.Layout.call(this); |
| 11186 | var that = this, |
| 11187 | /** @ignore */ none = function() { return null; }, |
| 11188 | prop = {t: none, l: none, r: none, b: none, w: none, h: none}, |
| 11189 | values, |
| 11190 | buildImplied = that.buildImplied; |
| 11191 | |
| 11192 | /** @private Proxy the given property on the layer. */ |
| 11193 | function proxy(name) { |
| 11194 | return function() { |
| 11195 | return prop[name](this.parent.index, this.index); |
| 11196 | }; |
| 11197 | } |
| 11198 | |
| 11199 | /** @private Compute the layout! */ |
| 11200 | this.buildImplied = function(s) { |
| 11201 | buildImplied.call(this, s); |
| 11202 | |
| 11203 | var data = s.layers, |
| 11204 | n = data.length, |
| 11205 | m, |
| 11206 | orient = s.orient, |
| 11207 | horizontal = /^(top|bottom)\b/.test(orient), |
| 11208 | h = this.parent[horizontal ? "height" : "width"](), |
| 11209 | x = [], |
| 11210 | y = [], |
| 11211 | dy = []; |
| 11212 | |
| 11213 | /* |
| 11214 | * Iterate over the data, evaluating the values, x and y functions. The |
| 11215 | * context in which the x and y psuedo-properties are evaluated is a |
| 11216 | * pseudo-mark that is a grandchild of this layout. |
| 11217 | */ |
| 11218 | var stack = pv.Mark.stack, o = {parent: {parent: this}}; |
| 11219 | stack.unshift(null); |
| 11220 | values = []; |
| 11221 | for (var i = 0; i < n; i++) { |
| 11222 | dy[i] = []; |
| 11223 | y[i] = []; |
| 11224 | o.parent.index = i; |
| 11225 | stack[0] = data[i]; |
| 11226 | values[i] = this.$values.apply(o.parent, stack); |
| 11227 | if (!i) m = values[i].length; |
| 11228 | stack.unshift(null); |
| 11229 | for (var j = 0; j < m; j++) { |
| 11230 | stack[0] = values[i][j]; |
| 11231 | o.index = j; |
| 11232 | if (!i) x[j] = this.$x.apply(o, stack); |
| 11233 | dy[i][j] = this.$y.apply(o, stack); |
| 11234 | } |
| 11235 | stack.shift(); |
| 11236 | } |
| 11237 | stack.shift(); |
| 11238 | |
| 11239 | /* order */ |
| 11240 | var index; |
| 11241 | switch (s.order) { |
| 11242 | case "inside-out": { |
| 11243 | var max = dy.map(function(v) { return pv.max.index(v); }), |
| 11244 | map = pv.range(n).sort(function(a, b) { return max[a] - max[b]; }), |
| 11245 | sums = dy.map(function(v) { return pv.sum(v); }), |
| 11246 | top = 0, |
| 11247 | bottom = 0, |
| 11248 | tops = [], |
| 11249 | bottoms = []; |
| 11250 | for (var i = 0; i < n; i++) { |
| 11251 | var j = map[i]; |
| 11252 | if (top < bottom) { |
| 11253 | top += sums[j]; |
| 11254 | tops.push(j); |
| 11255 | } else { |
| 11256 | bottom += sums[j]; |
| 11257 | bottoms.push(j); |
| 11258 | } |
| 11259 | } |
| 11260 | index = bottoms.reverse().concat(tops); |
| 11261 | break; |
| 11262 | } |
| 11263 | case "reverse": index = pv.range(n - 1, -1, -1); break; |
| 11264 | default: index = pv.range(n); break; |
| 11265 | } |
| 11266 | |
| 11267 | /* offset */ |
| 11268 | switch (s.offset) { |
| 11269 | case "silohouette": { |
| 11270 | for (var j = 0; j < m; j++) { |
| 11271 | var o = 0; |
| 11272 | for (var i = 0; i < n; i++) o += dy[i][j]; |
| 11273 | y[index[0]][j] = (h - o) / 2; |
| 11274 | } |
| 11275 | break; |
| 11276 | } |
| 11277 | case "wiggle": { |
| 11278 | var o = 0; |
| 11279 | for (var i = 0; i < n; i++) o += dy[i][0]; |
| 11280 | y[index[0]][0] = o = (h - o) / 2; |
| 11281 | for (var j = 1; j < m; j++) { |
| 11282 | var s1 = 0, s2 = 0, dx = x[j] - x[j - 1]; |
| 11283 | for (var i = 0; i < n; i++) s1 += dy[i][j]; |
| 11284 | for (var i = 0; i < n; i++) { |
| 11285 | var s3 = (dy[index[i]][j] - dy[index[i]][j - 1]) / (2 * dx); |
| 11286 | for (var k = 0; k < i; k++) { |
| 11287 | s3 += (dy[index[k]][j] - dy[index[k]][j - 1]) / dx; |
| 11288 | } |
| 11289 | s2 += s3 * dy[index[i]][j]; |
| 11290 | } |
| 11291 | y[index[0]][j] = o -= s1 ? s2 / s1 * dx : 0; |
| 11292 | } |
| 11293 | break; |
| 11294 | } |
| 11295 | case "expand": { |
| 11296 | for (var j = 0; j < m; j++) { |
| 11297 | y[index[0]][j] = 0; |
| 11298 | var k = 0; |
| 11299 | for (var i = 0; i < n; i++) k += dy[i][j]; |
| 11300 | if (k) { |
| 11301 | k = h / k; |
| 11302 | for (var i = 0; i < n; i++) dy[i][j] *= k; |
| 11303 | } else { |
| 11304 | k = h / n; |
| 11305 | for (var i = 0; i < n; i++) dy[i][j] = k; |
| 11306 | } |
| 11307 | } |
| 11308 | break; |
| 11309 | } |
| 11310 | default: { |
| 11311 | for (var j = 0; j < m; j++) y[index[0]][j] = 0; |
| 11312 | break; |
| 11313 | } |
| 11314 | } |
| 11315 | |
| 11316 | /* Propagate the offset to the other series. */ |
| 11317 | for (var j = 0; j < m; j++) { |
| 11318 | var o = y[index[0]][j]; |
| 11319 | for (var i = 1; i < n; i++) { |
| 11320 | o += dy[index[i - 1]][j]; |
| 11321 | y[index[i]][j] = o; |
| 11322 | } |
| 11323 | } |
| 11324 | |
| 11325 | /* Find the property definitions for dynamic substitution. */ |
| 11326 | var i = orient.indexOf("-"), |
| 11327 | pdy = horizontal ? "h" : "w", |
| 11328 | px = i < 0 ? (horizontal ? "l" : "b") : orient.charAt(i + 1), |
| 11329 | py = orient.charAt(0); |
| 11330 | for (var p in prop) prop[p] = none; |
| 11331 | prop[px] = function(i, j) { return x[j]; }; |
| 11332 | prop[py] = function(i, j) { return y[i][j]; }; |
| 11333 | prop[pdy] = function(i, j) { return dy[i][j]; }; |
| 11334 | }; |
| 11335 | |
| 11336 | /** |
| 11337 | * The layer prototype. This prototype is intended to be used with an area, |
| 11338 | * bar or panel mark (or subclass thereof). Other mark types may be possible, |
| 11339 | * though note that the stack layout is not currently designed to support |
| 11340 | * radial stacked visualizations using wedges. |
| 11341 | * |
| 11342 | * <p>The layer is not a direct child of the stack layout; a hidden panel is |
| 11343 | * used to replicate layers. |
| 11344 | * |
| 11345 | * @type pv.Mark |
| 11346 | * @name pv.Layout.Stack.prototype.layer |
| 11347 | */ |
| 11348 | this.layer = new pv.Mark() |
| 11349 | .data(function() { return values[this.parent.index]; }) |
| 11350 | .top(proxy("t")) |
| 11351 | .left(proxy("l")) |
| 11352 | .right(proxy("r")) |
| 11353 | .bottom(proxy("b")) |
| 11354 | .width(proxy("w")) |
| 11355 | .height(proxy("h")); |
| 11356 | |
| 11357 | this.layer.add = function(type) { |
| 11358 | return that.add(pv.Panel) |
| 11359 | .data(function() { return that.layers(); }) |
| 11360 | .add(type) |
| 11361 | .extend(this); |
| 11362 | }; |
| 11363 | }; |
| 11364 | |
| 11365 | pv.Layout.Stack.prototype = pv.extend(pv.Layout) |
| 11366 | .property("orient", String) |
| 11367 | .property("offset", String) |
| 11368 | .property("order", String) |
| 11369 | .property("layers"); |
| 11370 | |
| 11371 | /** |
| 11372 | * Default properties for stack layouts. The default orientation is |
| 11373 | * "bottom-left", the default offset is "zero", and the default layers is |
| 11374 | * <tt>[[]]</tt>. |
| 11375 | * |
| 11376 | * @type pv.Layout.Stack |
| 11377 | */ |
| 11378 | pv.Layout.Stack.prototype.defaults = new pv.Layout.Stack() |
| 11379 | .extend(pv.Layout.prototype.defaults) |
| 11380 | .orient("bottom-left") |
| 11381 | .offset("zero") |
| 11382 | .layers([[]]); |
| 11383 | |
| 11384 | /** @private */ |
| 11385 | pv.Layout.Stack.prototype.$x |
| 11386 | = /** @private */ pv.Layout.Stack.prototype.$y |
| 11387 | = function() { return 0; }; |
| 11388 | |
| 11389 | /** |
| 11390 | * The x psuedo-property; determines the position of the value within the layer. |
| 11391 | * This typically corresponds to the independent variable. For example, with the |
| 11392 | * default "bottom-left" orientation, this function defines the "left" property. |
| 11393 | * |
| 11394 | * @param {function} f the x function. |
| 11395 | * @returns {pv.Layout.Stack} this. |
| 11396 | */ |
| 11397 | pv.Layout.Stack.prototype.x = function(f) { |
| 11398 | /** @private */ this.$x = pv.functor(f); |
| 11399 | return this; |
| 11400 | }; |
| 11401 | |
| 11402 | /** |
| 11403 | * The y psuedo-property; determines the thickness of the layer at the given |
| 11404 | * value. This typically corresponds to the dependent variable. For example, |
| 11405 | * with the default "bottom-left" orientation, this function defines the |
| 11406 | * "height" property. |
| 11407 | * |
| 11408 | * @param {function} f the y function. |
| 11409 | * @returns {pv.Layout.Stack} this. |
| 11410 | */ |
| 11411 | pv.Layout.Stack.prototype.y = function(f) { |
| 11412 | /** @private */ this.$y = pv.functor(f); |
| 11413 | return this; |
| 11414 | }; |
| 11415 | |
| 11416 | /** @private The default value function; identity. */ |
| 11417 | pv.Layout.Stack.prototype.$values = pv.identity; |
| 11418 | |
| 11419 | /** |
| 11420 | * The values function; determines the values for a given layer. The default |
| 11421 | * value is the identity function, which assumes that the layers property is |
| 11422 | * specified as a two-dimensional (i.e., nested) array. |
| 11423 | * |
| 11424 | * @param {function} f the values function. |
| 11425 | * @returns {pv.Layout.Stack} this. |
| 11426 | */ |
| 11427 | pv.Layout.Stack.prototype.values = function(f) { |
| 11428 | this.$values = pv.functor(f); |
| 11429 | return this; |
| 11430 | }; |
| 11431 | |
| 11432 | /** |
| 11433 | * The layer data in row-major order. The value of this property is typically a |
| 11434 | * two-dimensional (i.e., nested) array, but any array can be used, provided the |
| 11435 | * values psuedo-property is defined accordingly. |
| 11436 | * |
| 11437 | * @type array[] |
| 11438 | * @name pv.Layout.Stack.prototype.layers |
| 11439 | */ |
| 11440 | |
| 11441 | /** |
| 11442 | * The layer orientation. The following values are supported:<ul> |
| 11443 | * |
| 11444 | * <li>bottom-left == bottom |
| 11445 | * <li>bottom-right |
| 11446 | * <li>top-left == top |
| 11447 | * <li>top-right |
| 11448 | * <li>left-top |
| 11449 | * <li>left-bottom == left |
| 11450 | * <li>right-top |
| 11451 | * <li>right-bottom == right |
| 11452 | * |
| 11453 | * </ul>. The default value is "bottom-left", which means that the layers will |
| 11454 | * be built from the bottom-up, and the values within layers will be laid out |
| 11455 | * from left-to-right. |
| 11456 | * |
| 11457 | * <p>Note that with non-zero baselines, some orientations may give similar |
| 11458 | * results. For example, offset("silohouette") centers the layers, resulting in |
| 11459 | * a streamgraph. Thus, the orientations "bottom-left" and "top-left" will |
| 11460 | * produce similar results, differing only in the layer order. |
| 11461 | * |
| 11462 | * @type string |
| 11463 | * @name pv.Layout.Stack.prototype.orient |
| 11464 | */ |
| 11465 | |
| 11466 | /** |
| 11467 | * The layer order. The following values are supported:<ul> |
| 11468 | * |
| 11469 | * <li><i>null</i> - use given layer order. |
| 11470 | * <li>inside-out - sort by maximum value, with balanced order. |
| 11471 | * <li>reverse - use reverse of given layer order. |
| 11472 | * |
| 11473 | * </ul>For details on the inside-out order algorithm, refer to "Stacked Graphs |
| 11474 | * -- Geometry & Aesthetics" by L. Byron and M. Wattenberg, IEEE TVCG |
| 11475 | * November/December 2008. |
| 11476 | * |
| 11477 | * @type string |
| 11478 | * @name pv.Layout.Stack.prototype.order |
| 11479 | */ |
| 11480 | |
| 11481 | /** |
| 11482 | * The layer offset; the y-position of the bottom of the lowest layer. The |
| 11483 | * following values are supported:<ul> |
| 11484 | * |
| 11485 | * <li>zero - use a zero baseline, i.e., the y-axis. |
| 11486 | * <li>silohouette - center the stream, i.e., ThemeRiver. |
| 11487 | * <li>wiggle - minimize weighted change in slope. |
| 11488 | * <li>expand - expand layers to fill the enclosing layout dimensions. |
| 11489 | * |
| 11490 | * </ul>For details on these offset algorithms, refer to "Stacked Graphs -- |
| 11491 | * Geometry & Aesthetics" by L. Byron and M. Wattenberg, IEEE TVCG |
| 11492 | * November/December 2008. |
| 11493 | * |
| 11494 | * @type string |
| 11495 | * @name pv.Layout.Stack.prototype.offset |
| 11496 | */ |
| 11497 | /** |
| 11498 | * Constructs a new, empty treemap layout. Layouts are not typically |
| 11499 | * constructed directly; instead, they are added to an existing panel via |
| 11500 | * {@link pv.Mark#add}. |
| 11501 | * |
| 11502 | * @class Implements a space-filling rectangular layout, with the hierarchy |
| 11503 | * represented via containment. Treemaps represent nodes as boxes, with child |
| 11504 | * nodes placed within parent boxes. The size of each box is proportional to the |
| 11505 | * size of the node in the tree. This particular algorithm is taken from Bruls, |
| 11506 | * D.M., C. Huizing, and J.J. van Wijk, <a |
| 11507 | * href="http://www.win.tue.nl/~vanwijk/stm.pdf">"Squarified Treemaps"</a> in |
| 11508 | * <i>Data Visualization 2000, Proceedings of the Joint Eurographics and IEEE |
| 11509 | * TCVG Sumposium on Visualization</i>, 2000, pp. 33-42. |
| 11510 | * |
| 11511 | * <p>The meaning of the exported mark prototypes changes slightly in the |
| 11512 | * space-filling implementation:<ul> |
| 11513 | * |
| 11514 | * <li><tt>node</tt> - for rendering nodes; typically a {@link pv.Bar}. The node |
| 11515 | * data is populated with <tt>dx</tt> and <tt>dy</tt> attributes, in addition to |
| 11516 | * the standard <tt>x</tt> and <tt>y</tt> position attributes. |
| 11517 | * |
| 11518 | * <p><li><tt>leaf</tt> - for rendering leaf nodes only, with no fill or stroke |
| 11519 | * style by default; typically a {@link pv.Panel} or another layout! |
| 11520 | * |
| 11521 | * <p><li><tt>link</tt> - unsupported; undefined. Links are encoded implicitly |
| 11522 | * in the arrangement of the space-filling nodes. |
| 11523 | * |
| 11524 | * <p><li><tt>label</tt> - for rendering node labels; typically a |
| 11525 | * {@link pv.Label}. |
| 11526 | * |
| 11527 | * </ul>For more details on how to use this layout, see |
| 11528 | * {@link pv.Layout.Hierarchy}. |
| 11529 | * |
| 11530 | * @extends pv.Layout.Hierarchy |
| 11531 | */ |
| 11532 | pv.Layout.Treemap = function() { |
| 11533 | pv.Layout.Hierarchy.call(this); |
| 11534 | |
| 11535 | this.node |
| 11536 | .strokeStyle("#fff") |
| 11537 | .fillStyle("rgba(31, 119, 180, .25)") |
| 11538 | .width(function(n) { return n.dx; }) |
| 11539 | .height(function(n) { return n.dy; }); |
| 11540 | |
| 11541 | this.label |
| 11542 | .visible(function(n) { return !n.firstChild; }) |
| 11543 | .left(function(n) { return n.x + (n.dx / 2); }) |
| 11544 | .top(function(n) { return n.y + (n.dy / 2); }) |
| 11545 | .textAlign("center") |
| 11546 | .textAngle(function(n) { return n.dx > n.dy ? 0 : -Math.PI / 2; }); |
| 11547 | |
| 11548 | (this.leaf = new pv.Mark() |
| 11549 | .extend(this.node) |
| 11550 | .fillStyle(null) |
| 11551 | .strokeStyle(null) |
| 11552 | .visible(function(n) { return !n.firstChild; })).parent = this; |
| 11553 | |
| 11554 | /* Hide unsupported link. */ |
| 11555 | delete this.link; |
| 11556 | }; |
| 11557 | |
| 11558 | pv.Layout.Treemap.prototype = pv.extend(pv.Layout.Hierarchy) |
| 11559 | .property("round", Boolean) |
| 11560 | .property("paddingLeft", Number) |
| 11561 | .property("paddingRight", Number) |
| 11562 | .property("paddingTop", Number) |
| 11563 | .property("paddingBottom", Number) |
| 11564 | .property("mode", String) |
| 11565 | .property("order", String); |
| 11566 | |
| 11567 | /** |
| 11568 | * Default propertiess for treemap layouts. The default mode is "squarify" and |
| 11569 | * the default order is "ascending". |
| 11570 | * |
| 11571 | * @type pv.Layout.Treemap |
| 11572 | */ |
| 11573 | pv.Layout.Treemap.prototype.defaults = new pv.Layout.Treemap() |
| 11574 | .extend(pv.Layout.Hierarchy.prototype.defaults) |
| 11575 | .mode("squarify") // squarify, slice-and-dice, slice, dice |
| 11576 | .order("ascending"); // ascending, descending, reverse, null |
| 11577 | |
| 11578 | /** |
| 11579 | * Whether node sizes should be rounded to integer values. This has a similar |
| 11580 | * effect to setting <tt>antialias(false)</tt> for node values, but allows the |
| 11581 | * treemap algorithm to accumulate error related to pixel rounding. |
| 11582 | * |
| 11583 | * @type boolean |
| 11584 | * @name pv.Layout.Treemap.prototype.round |
| 11585 | */ |
| 11586 | |
| 11587 | /** |
| 11588 | * The left inset between parent add child in pixels. Defaults to 0. |
| 11589 | * |
| 11590 | * @type number |
| 11591 | * @name pv.Layout.Treemap.prototype.paddingLeft |
| 11592 | * @see #padding |
| 11593 | */ |
| 11594 | |
| 11595 | /** |
| 11596 | * The right inset between parent add child in pixels. Defaults to 0. |
| 11597 | * |
| 11598 | * @type number |
| 11599 | * @name pv.Layout.Treemap.prototype.paddingRight |
| 11600 | * @see #padding |
| 11601 | */ |
| 11602 | |
| 11603 | /** |
| 11604 | * The top inset between parent and child in pixels. Defaults to 0. |
| 11605 | * |
| 11606 | * @type number |
| 11607 | * @name pv.Layout.Treemap.prototype.paddingTop |
| 11608 | * @see #padding |
| 11609 | */ |
| 11610 | |
| 11611 | /** |
| 11612 | * The bottom inset between parent and child in pixels. Defaults to 0. |
| 11613 | * |
| 11614 | * @type number |
| 11615 | * @name pv.Layout.Treemap.prototype.paddingBottom |
| 11616 | * @see #padding |
| 11617 | */ |
| 11618 | |
| 11619 | /** |
| 11620 | * The treemap algorithm. The default value is "squarify". The "slice-and-dice" |
| 11621 | * algorithm may also be used, which alternates between horizontal and vertical |
| 11622 | * slices for different depths. In addition, the "slice" and "dice" algorithms |
| 11623 | * may be specified explicitly to control whether horizontal or vertical slices |
| 11624 | * are used, which may be useful for nested treemap layouts. |
| 11625 | * |
| 11626 | * @type string |
| 11627 | * @name pv.Layout.Treemap.prototype.mode |
| 11628 | * @see <a |
| 11629 | * href="ftp://ftp.cs.umd.edu/pub/hcil/Reports-Abstracts-Bibliography/2001-06html/2001-06.pdf" |
| 11630 | * >"Ordered Treemap Layouts"</a> by B. Shneiderman & M. Wattenberg, IEEE |
| 11631 | * InfoVis 2001. |
| 11632 | */ |
| 11633 | |
| 11634 | /** |
| 11635 | * The sibling node order. A <tt>null</tt> value means to use the sibling order |
| 11636 | * specified by the nodes property as-is; "reverse" will reverse the given |
| 11637 | * order. The default value "ascending" will sort siblings in ascending order of |
| 11638 | * size, while "descending" will do the reverse. For sorting based on data |
| 11639 | * attributes other than size, use the default <tt>null</tt> for the order |
| 11640 | * property, and sort the nodes beforehand using the {@link pv.Dom} operator. |
| 11641 | * |
| 11642 | * @type string |
| 11643 | * @name pv.Layout.Treemap.prototype.order |
| 11644 | */ |
| 11645 | |
| 11646 | /** |
| 11647 | * Alias for setting the left, right, top and bottom padding properties |
| 11648 | * simultaneously. |
| 11649 | * |
| 11650 | * @see #paddingLeft |
| 11651 | * @see #paddingRight |
| 11652 | * @see #paddingTop |
| 11653 | * @see #paddingBottom |
| 11654 | * @returns {pv.Layout.Treemap} this. |
| 11655 | */ |
| 11656 | pv.Layout.Treemap.prototype.padding = function(n) { |
| 11657 | return this.paddingLeft(n).paddingRight(n).paddingTop(n).paddingBottom(n); |
| 11658 | }; |
| 11659 | |
| 11660 | /** @private The default size function. */ |
| 11661 | pv.Layout.Treemap.prototype.$size = function(d) { |
| 11662 | return Number(d.nodeValue); |
| 11663 | }; |
| 11664 | |
| 11665 | /** |
| 11666 | * Specifies the sizing function. By default, the size function uses the |
| 11667 | * <tt>nodeValue</tt> attribute of nodes as a numeric value: <tt>function(d) |
| 11668 | * Number(d.nodeValue)</tt>. |
| 11669 | * |
| 11670 | * <p>The sizing function is invoked for each leaf node in the tree, per the |
| 11671 | * <tt>nodes</tt> property. For example, if the tree data structure represents a |
| 11672 | * file system, with files as leaf nodes, and each file has a <tt>bytes</tt> |
| 11673 | * attribute, you can specify a size function as: |
| 11674 | * |
| 11675 | * <pre> .size(function(d) d.bytes)</pre> |
| 11676 | * |
| 11677 | * @param {function} f the new sizing function. |
| 11678 | * @returns {pv.Layout.Treemap} this. |
| 11679 | */ |
| 11680 | pv.Layout.Treemap.prototype.size = function(f) { |
| 11681 | this.$size = pv.functor(f); |
| 11682 | return this; |
| 11683 | }; |
| 11684 | |
| 11685 | /** @private */ |
| 11686 | pv.Layout.Treemap.prototype.buildImplied = function(s) { |
| 11687 | if (pv.Layout.Hierarchy.prototype.buildImplied.call(this, s)) return; |
| 11688 | |
| 11689 | var that = this, |
| 11690 | nodes = s.nodes, |
| 11691 | root = nodes[0], |
| 11692 | stack = pv.Mark.stack, |
| 11693 | left = s.paddingLeft, |
| 11694 | right = s.paddingRight, |
| 11695 | top = s.paddingTop, |
| 11696 | bottom = s.paddingBottom, |
| 11697 | /** @ignore */ size = function(n) { return n.size; }, |
| 11698 | round = s.round ? Math.round : Number, |
| 11699 | mode = s.mode; |
| 11700 | |
| 11701 | /** @private */ |
| 11702 | function slice(row, sum, horizontal, x, y, w, h) { |
| 11703 | for (var i = 0, d = 0; i < row.length; i++) { |
| 11704 | var n = row[i]; |
| 11705 | if (horizontal) { |
| 11706 | n.x = x + d; |
| 11707 | n.y = y; |
| 11708 | d += n.dx = round(w * n.size / sum); |
| 11709 | n.dy = h; |
| 11710 | } else { |
| 11711 | n.x = x; |
| 11712 | n.y = y + d; |
| 11713 | n.dx = w; |
| 11714 | d += n.dy = round(h * n.size / sum); |
| 11715 | } |
| 11716 | } |
| 11717 | if (n) { // correct on-axis rounding error |
| 11718 | if (horizontal) { |
| 11719 | n.dx += w - d; |
| 11720 | } else { |
| 11721 | n.dy += h - d; |
| 11722 | } |
| 11723 | } |
| 11724 | } |
| 11725 | |
| 11726 | /** @private */ |
| 11727 | function ratio(row, l) { |
| 11728 | var rmax = -Infinity, rmin = Infinity, s = 0; |
| 11729 | for (var i = 0; i < row.length; i++) { |
| 11730 | var r = row[i].size; |
| 11731 | if (r < rmin) rmin = r; |
| 11732 | if (r > rmax) rmax = r; |
| 11733 | s += r; |
| 11734 | } |
| 11735 | s = s * s; |
| 11736 | l = l * l; |
| 11737 | return Math.max(l * rmax / s, s / (l * rmin)); |
| 11738 | } |
| 11739 | |
| 11740 | /** @private */ |
| 11741 | function layout(n, i) { |
| 11742 | var x = n.x + left, |
| 11743 | y = n.y + top, |
| 11744 | w = n.dx - left - right, |
| 11745 | h = n.dy - top - bottom; |
| 11746 | |
| 11747 | /* Assume squarify by default. */ |
| 11748 | if (mode != "squarify") { |
| 11749 | slice(n.childNodes, n.size, |
| 11750 | mode == "slice" ? true |
| 11751 | : mode == "dice" ? false |
| 11752 | : i & 1, x, y, w, h); |
| 11753 | return; |
| 11754 | } |
| 11755 | |
| 11756 | var row = [], |
| 11757 | mink = Infinity, |
| 11758 | l = Math.min(w, h), |
| 11759 | k = w * h / n.size; |
| 11760 | |
| 11761 | /* Abort if the size is nonpositive. */ |
| 11762 | if (n.size <= 0) return; |
| 11763 | |
| 11764 | /* Scale the sizes to fill the current subregion. */ |
| 11765 | n.visitBefore(function(n) { n.size *= k; }); |
| 11766 | |
| 11767 | /** @private Position the specified nodes along one dimension. */ |
| 11768 | function position(row) { |
| 11769 | var horizontal = w == l, |
| 11770 | sum = pv.sum(row, size), |
| 11771 | r = l ? round(sum / l) : 0; |
| 11772 | slice(row, sum, horizontal, x, y, horizontal ? w : r, horizontal ? r : h); |
| 11773 | if (horizontal) { |
| 11774 | y += r; |
| 11775 | h -= r; |
| 11776 | } else { |
| 11777 | x += r; |
| 11778 | w -= r; |
| 11779 | } |
| 11780 | l = Math.min(w, h); |
| 11781 | return horizontal; |
| 11782 | } |
| 11783 | |
| 11784 | var children = n.childNodes.slice(); // copy |
| 11785 | while (children.length) { |
| 11786 | var child = children[children.length - 1]; |
| 11787 | if (!child.size) { |
| 11788 | children.pop(); |
| 11789 | continue; |
| 11790 | } |
| 11791 | row.push(child); |
| 11792 | |
| 11793 | var k = ratio(row, l); |
| 11794 | if (k <= mink) { |
| 11795 | children.pop(); |
| 11796 | mink = k; |
| 11797 | } else { |
| 11798 | row.pop(); |
| 11799 | position(row); |
| 11800 | row.length = 0; |
| 11801 | mink = Infinity; |
| 11802 | } |
| 11803 | } |
| 11804 | |
| 11805 | /* correct off-axis rounding error */ |
| 11806 | if (position(row)) for (var i = 0; i < row.length; i++) { |
| 11807 | row[i].dy += h; |
| 11808 | } else for (var i = 0; i < row.length; i++) { |
| 11809 | row[i].dx += w; |
| 11810 | } |
| 11811 | } |
| 11812 | |
| 11813 | /* Recursively compute the node depth and size. */ |
| 11814 | stack.unshift(null); |
| 11815 | root.visitAfter(function(n, i) { |
| 11816 | n.depth = i; |
| 11817 | n.x = n.y = n.dx = n.dy = 0; |
| 11818 | n.size = n.firstChild |
| 11819 | ? pv.sum(n.childNodes, function(n) { return n.size; }) |
| 11820 | : that.$size.apply(that, (stack[0] = n, stack)); |
| 11821 | }); |
| 11822 | stack.shift(); |
| 11823 | |
| 11824 | /* Sort. */ |
| 11825 | switch (s.order) { |
| 11826 | case "ascending": { |
| 11827 | root.sort(function(a, b) { return a.size - b.size; }); |
| 11828 | break; |
| 11829 | } |
| 11830 | case "descending": { |
| 11831 | root.sort(function(a, b) { return b.size - a.size; }); |
| 11832 | break; |
| 11833 | } |
| 11834 | case "reverse": root.reverse(); break; |
| 11835 | } |
| 11836 | |
| 11837 | /* Recursively compute the layout. */ |
| 11838 | root.x = 0; |
| 11839 | root.y = 0; |
| 11840 | root.dx = s.width; |
| 11841 | root.dy = s.height; |
| 11842 | root.visitBefore(layout); |
| 11843 | }; |
| 11844 | /** |
| 11845 | * Constructs a new, empty tree layout. Layouts are not typically constructed |
| 11846 | * directly; instead, they are added to an existing panel via |
| 11847 | * {@link pv.Mark#add}. |
| 11848 | * |
| 11849 | * @class Implements a node-link tree diagram using the Reingold-Tilford "tidy" |
| 11850 | * tree layout algorithm. The specific algorithm used by this layout is based on |
| 11851 | * <a href="http://citeseer.ist.psu.edu/buchheim02improving.html">"Improving |
| 11852 | * Walker's Algorithm to Run in Linear Time"</A> by C. Buchheim, M. Jünger |
| 11853 | * & S. Leipert, Graph Drawing 2002. This layout supports both cartesian and |
| 11854 | * radial orientations orientations for node-link diagrams. |
| 11855 | * |
| 11856 | * <p>The tree layout supports a "group" property, which if true causes siblings |
| 11857 | * to be positioned closer together than unrelated nodes at the same depth. The |
| 11858 | * layout can be configured using the <tt>depth</tt> and <tt>breadth</tt> |
| 11859 | * properties, which control the increments in pixel space between nodes in both |
| 11860 | * dimensions, similar to the indent layout. |
| 11861 | * |
| 11862 | * <p>For more details on how to use this layout, see |
| 11863 | * {@link pv.Layout.Hierarchy}. |
| 11864 | * |
| 11865 | * @extends pv.Layout.Hierarchy |
| 11866 | */ |
| 11867 | pv.Layout.Tree = function() { |
| 11868 | pv.Layout.Hierarchy.call(this); |
| 11869 | }; |
| 11870 | |
| 11871 | pv.Layout.Tree.prototype = pv.extend(pv.Layout.Hierarchy) |
| 11872 | .property("group", Number) |
| 11873 | .property("breadth", Number) |
| 11874 | .property("depth", Number) |
| 11875 | .property("orient", String); |
| 11876 | |
| 11877 | /** |
| 11878 | * Default properties for tree layouts. The default orientation is "top", the |
| 11879 | * default group parameter is 1, and the default breadth and depth offsets are |
| 11880 | * 15 and 60 respectively. |
| 11881 | * |
| 11882 | * @type pv.Layout.Tree |
| 11883 | */ |
| 11884 | pv.Layout.Tree.prototype.defaults = new pv.Layout.Tree() |
| 11885 | .extend(pv.Layout.Hierarchy.prototype.defaults) |
| 11886 | .group(1) |
| 11887 | .breadth(15) |
| 11888 | .depth(60) |
| 11889 | .orient("top"); |
| 11890 | |
| 11891 | /** @private */ |
| 11892 | pv.Layout.Tree.prototype.buildImplied = function(s) { |
| 11893 | if (pv.Layout.Hierarchy.prototype.buildImplied.call(this, s)) return; |
| 11894 | |
| 11895 | var nodes = s.nodes, |
| 11896 | orient = s.orient, |
| 11897 | depth = s.depth, |
| 11898 | breadth = s.breadth, |
| 11899 | group = s.group, |
| 11900 | w = s.width, |
| 11901 | h = s.height; |
| 11902 | |
| 11903 | /** @private */ |
| 11904 | function firstWalk(v) { |
| 11905 | var l, r, a; |
| 11906 | if (!v.firstChild) { |
| 11907 | if (l = v.previousSibling) { |
| 11908 | v.prelim = l.prelim + distance(v.depth, true); |
| 11909 | } |
| 11910 | } else { |
| 11911 | l = v.firstChild; |
| 11912 | r = v.lastChild; |
| 11913 | a = l; // default ancestor |
| 11914 | for (var c = l; c; c = c.nextSibling) { |
| 11915 | firstWalk(c); |
| 11916 | a = apportion(c, a); |
| 11917 | } |
| 11918 | executeShifts(v); |
| 11919 | var midpoint = .5 * (l.prelim + r.prelim); |
| 11920 | if (l = v.previousSibling) { |
| 11921 | v.prelim = l.prelim + distance(v.depth, true); |
| 11922 | v.mod = v.prelim - midpoint; |
| 11923 | } else { |
| 11924 | v.prelim = midpoint; |
| 11925 | } |
| 11926 | } |
| 11927 | } |
| 11928 | |
| 11929 | /** @private */ |
| 11930 | function secondWalk(v, m, depth) { |
| 11931 | v.breadth = v.prelim + m; |
| 11932 | m += v.mod; |
| 11933 | for (var c = v.firstChild; c; c = c.nextSibling) { |
| 11934 | secondWalk(c, m, depth); |
| 11935 | } |
| 11936 | } |
| 11937 | |
| 11938 | /** @private */ |
| 11939 | function apportion(v, a) { |
| 11940 | var w = v.previousSibling; |
| 11941 | if (w) { |
| 11942 | var vip = v, |
| 11943 | vop = v, |
| 11944 | vim = w, |
| 11945 | vom = v.parentNode.firstChild, |
| 11946 | sip = vip.mod, |
| 11947 | sop = vop.mod, |
| 11948 | sim = vim.mod, |
| 11949 | som = vom.mod, |
| 11950 | nr = nextRight(vim), |
| 11951 | nl = nextLeft(vip); |
| 11952 | while (nr && nl) { |
| 11953 | vim = nr; |
| 11954 | vip = nl; |
| 11955 | vom = nextLeft(vom); |
| 11956 | vop = nextRight(vop); |
| 11957 | vop.ancestor = v; |
| 11958 | var shift = (vim.prelim + sim) - (vip.prelim + sip) + distance(vim.depth, false); |
| 11959 | if (shift > 0) { |
| 11960 | moveSubtree(ancestor(vim, v, a), v, shift); |
| 11961 | sip += shift; |
| 11962 | sop += shift; |
| 11963 | } |
| 11964 | sim += vim.mod; |
| 11965 | sip += vip.mod; |
| 11966 | som += vom.mod; |
| 11967 | sop += vop.mod; |
| 11968 | nr = nextRight(vim); |
| 11969 | nl = nextLeft(vip); |
| 11970 | } |
| 11971 | if (nr && !nextRight(vop)) { |
| 11972 | vop.thread = nr; |
| 11973 | vop.mod += sim - sop; |
| 11974 | } |
| 11975 | if (nl && !nextLeft(vom)) { |
| 11976 | vom.thread = nl; |
| 11977 | vom.mod += sip - som; |
| 11978 | a = v; |
| 11979 | } |
| 11980 | } |
| 11981 | return a; |
| 11982 | } |
| 11983 | |
| 11984 | /** @private */ |
| 11985 | function nextLeft(v) { |
| 11986 | return v.firstChild || v.thread; |
| 11987 | } |
| 11988 | |
| 11989 | /** @private */ |
| 11990 | function nextRight(v) { |
| 11991 | return v.lastChild || v.thread; |
| 11992 | } |
| 11993 | |
| 11994 | /** @private */ |
| 11995 | function moveSubtree(wm, wp, shift) { |
| 11996 | var subtrees = wp.number - wm.number; |
| 11997 | wp.change -= shift / subtrees; |
| 11998 | wp.shift += shift; |
| 11999 | wm.change += shift / subtrees; |
| 12000 | wp.prelim += shift; |
| 12001 | wp.mod += shift; |
| 12002 | } |
| 12003 | |
| 12004 | /** @private */ |
| 12005 | function executeShifts(v) { |
| 12006 | var shift = 0, change = 0; |
| 12007 | for (var c = v.lastChild; c; c = c.previousSibling) { |
| 12008 | c.prelim += shift; |
| 12009 | c.mod += shift; |
| 12010 | change += c.change; |
| 12011 | shift += c.shift + change; |
| 12012 | } |
| 12013 | } |
| 12014 | |
| 12015 | /** @private */ |
| 12016 | function ancestor(vim, v, a) { |
| 12017 | return (vim.ancestor.parentNode == v.parentNode) ? vim.ancestor : a; |
| 12018 | } |
| 12019 | |
| 12020 | /** @private */ |
| 12021 | function distance(depth, siblings) { |
| 12022 | return (siblings ? 1 : (group + 1)) / ((orient == "radial") ? depth : 1); |
| 12023 | } |
| 12024 | |
| 12025 | /* Initialize temporary layout variables. TODO: store separately. */ |
| 12026 | var root = nodes[0]; |
| 12027 | root.visitAfter(function(v, i) { |
| 12028 | v.ancestor = v; |
| 12029 | v.prelim = 0; |
| 12030 | v.mod = 0; |
| 12031 | v.change = 0; |
| 12032 | v.shift = 0; |
| 12033 | v.number = v.previousSibling ? (v.previousSibling.number + 1) : 0; |
| 12034 | v.depth = i; |
| 12035 | }); |
| 12036 | |
| 12037 | /* Compute the layout using Buchheim et al.'s algorithm. */ |
| 12038 | firstWalk(root); |
| 12039 | secondWalk(root, -root.prelim, 0); |
| 12040 | |
| 12041 | /** @private Returns the angle of the given node. */ |
| 12042 | function midAngle(n) { |
| 12043 | return (orient == "radial") ? n.breadth / depth : 0; |
| 12044 | } |
| 12045 | |
| 12046 | /** @private */ |
| 12047 | function x(n) { |
| 12048 | switch (orient) { |
| 12049 | case "left": return n.depth; |
| 12050 | case "right": return w - n.depth; |
| 12051 | case "top": |
| 12052 | case "bottom": return n.breadth + w / 2; |
| 12053 | case "radial": return w / 2 + n.depth * Math.cos(midAngle(n)); |
| 12054 | } |
| 12055 | } |
| 12056 | |
| 12057 | /** @private */ |
| 12058 | function y(n) { |
| 12059 | switch (orient) { |
| 12060 | case "left": |
| 12061 | case "right": return n.breadth + h / 2; |
| 12062 | case "top": return n.depth; |
| 12063 | case "bottom": return h - n.depth; |
| 12064 | case "radial": return h / 2 + n.depth * Math.sin(midAngle(n)); |
| 12065 | } |
| 12066 | } |
| 12067 | |
| 12068 | /* Clear temporary layout variables; transform depth and breadth. */ |
| 12069 | root.visitAfter(function(v) { |
| 12070 | v.breadth *= breadth; |
| 12071 | v.depth *= depth; |
| 12072 | v.midAngle = midAngle(v); |
| 12073 | v.x = x(v); |
| 12074 | v.y = y(v); |
| 12075 | if (v.firstChild) v.midAngle += Math.PI; |
| 12076 | delete v.breadth; |
| 12077 | delete v.depth; |
| 12078 | delete v.ancestor; |
| 12079 | delete v.prelim; |
| 12080 | delete v.mod; |
| 12081 | delete v.change; |
| 12082 | delete v.shift; |
| 12083 | delete v.number; |
| 12084 | delete v.thread; |
| 12085 | }); |
| 12086 | }; |
| 12087 | |
| 12088 | /** |
| 12089 | * The offset between siblings nodes; defaults to 15. |
| 12090 | * |
| 12091 | * @type number |
| 12092 | * @name pv.Layout.Tree.prototype.breadth |
| 12093 | */ |
| 12094 | |
| 12095 | /** |
| 12096 | * The offset between parent and child nodes; defaults to 60. |
| 12097 | * |
| 12098 | * @type number |
| 12099 | * @name pv.Layout.Tree.prototype.depth |
| 12100 | */ |
| 12101 | |
| 12102 | /** |
| 12103 | * The orientation. The default orientation is "top", which means that the root |
| 12104 | * node is placed on the top edge, leaf nodes appear at the bottom, and internal |
| 12105 | * nodes are in-between. The following orientations are supported:<ul> |
| 12106 | * |
| 12107 | * <li>left - left-to-right. |
| 12108 | * <li>right - right-to-left. |
| 12109 | * <li>top - top-to-bottom. |
| 12110 | * <li>bottom - bottom-to-top. |
| 12111 | * <li>radial - radially, with the root at the center.</ul> |
| 12112 | * |
| 12113 | * @type string |
| 12114 | * @name pv.Layout.Tree.prototype.orient |
| 12115 | */ |
| 12116 | |
| 12117 | /** |
| 12118 | * The sibling grouping, i.e., whether differentiating space is placed between |
| 12119 | * sibling groups. The default is 1 (or true), causing sibling leaves to be |
| 12120 | * separated by one breadth offset. Setting this to false (or 0) causes |
| 12121 | * non-siblings to be adjacent. |
| 12122 | * |
| 12123 | * @type number |
| 12124 | * @name pv.Layout.Tree.prototype.group |
| 12125 | */ |
| 12126 | /** |
| 12127 | * Constructs a new, empty indent layout. Layouts are not typically constructed |
| 12128 | * directly; instead, they are added to an existing panel via |
| 12129 | * {@link pv.Mark#add}. |
| 12130 | * |
| 12131 | * @class Implements a hierarchical layout using the indent algorithm. This |
| 12132 | * layout implements a node-link diagram where the nodes are presented in |
| 12133 | * preorder traversal, and nodes are indented based on their depth from the |
| 12134 | * root. This technique is used ubiquitously by operating systems to represent |
| 12135 | * file directories; although it requires much vertical space, indented trees |
| 12136 | * allow efficient <i>interactive</i> exploration of trees to find a specific |
| 12137 | * node. In addition they allow rapid scanning of node labels, and multivariate |
| 12138 | * data such as file sizes can be displayed adjacent to the hierarchy. |
| 12139 | * |
| 12140 | * <p>The indent layout can be configured using the <tt>depth</tt> and |
| 12141 | * <tt>breadth</tt> properties, which control the increments in pixel space for |
| 12142 | * each indent and row in the layout. This layout does not support multiple |
| 12143 | * orientations; the root node is rendered in the top-left, while |
| 12144 | * <tt>breadth</tt> is a vertical offset from the top, and <tt>depth</tt> is a |
| 12145 | * horizontal offset from the left. |
| 12146 | * |
| 12147 | * <p>For more details on how to use this layout, see |
| 12148 | * {@link pv.Layout.Hierarchy}. |
| 12149 | * |
| 12150 | * @extends pv.Layout.Hierarchy |
| 12151 | */ |
| 12152 | pv.Layout.Indent = function() { |
| 12153 | pv.Layout.Hierarchy.call(this); |
| 12154 | this.link.interpolate("step-after"); |
| 12155 | }; |
| 12156 | |
| 12157 | pv.Layout.Indent.prototype = pv.extend(pv.Layout.Hierarchy) |
| 12158 | .property("depth", Number) |
| 12159 | .property("breadth", Number); |
| 12160 | |
| 12161 | /** |
| 12162 | * The horizontal offset between different levels of the tree; defaults to 15. |
| 12163 | * |
| 12164 | * @type number |
| 12165 | * @name pv.Layout.Indent.prototype.depth |
| 12166 | */ |
| 12167 | |
| 12168 | /** |
| 12169 | * The vertical offset between nodes; defaults to 15. |
| 12170 | * |
| 12171 | * @type number |
| 12172 | * @name pv.Layout.Indent.prototype.breadth |
| 12173 | */ |
| 12174 | |
| 12175 | /** |
| 12176 | * Default properties for indent layouts. By default the depth and breadth |
| 12177 | * offsets are 15 pixels. |
| 12178 | * |
| 12179 | * @type pv.Layout.Indent |
| 12180 | */ |
| 12181 | pv.Layout.Indent.prototype.defaults = new pv.Layout.Indent() |
| 12182 | .extend(pv.Layout.Hierarchy.prototype.defaults) |
| 12183 | .depth(15) |
| 12184 | .breadth(15); |
| 12185 | |
| 12186 | /** @private */ |
| 12187 | pv.Layout.Indent.prototype.buildImplied = function(s) { |
| 12188 | if (pv.Layout.Hierarchy.prototype.buildImplied.call(this, s)) return; |
| 12189 | |
| 12190 | var nodes = s.nodes, |
| 12191 | bspace = s.breadth, |
| 12192 | dspace = s.depth, |
| 12193 | ax = 0, |
| 12194 | ay = 0; |
| 12195 | |
| 12196 | /** @private */ |
| 12197 | function position(n, breadth, depth) { |
| 12198 | n.x = ax + depth++ * dspace; |
| 12199 | n.y = ay + breadth++ * bspace; |
| 12200 | n.midAngle = 0; |
| 12201 | for (var c = n.firstChild; c; c = c.nextSibling) { |
| 12202 | breadth = position(c, breadth, depth); |
| 12203 | } |
| 12204 | return breadth; |
| 12205 | } |
| 12206 | |
| 12207 | position(nodes[0], 1, 1); |
| 12208 | }; |
| 12209 | /** |
| 12210 | * Constructs a new, empty circle-packing layout. Layouts are not typically |
| 12211 | * constructed directly; instead, they are added to an existing panel via |
| 12212 | * {@link pv.Mark#add}. |
| 12213 | * |
| 12214 | * @class Implements a hierarchical layout using circle-packing. The meaning of |
| 12215 | * the exported mark prototypes changes slightly in the space-filling |
| 12216 | * implementation:<ul> |
| 12217 | * |
| 12218 | * <li><tt>node</tt> - for rendering nodes; typically a {@link pv.Dot}. |
| 12219 | * |
| 12220 | * <p><li><tt>link</tt> - unsupported; undefined. Links are encoded implicitly |
| 12221 | * in the arrangement of the space-filling nodes. |
| 12222 | * |
| 12223 | * <p><li><tt>label</tt> - for rendering node labels; typically a |
| 12224 | * {@link pv.Label}. |
| 12225 | * |
| 12226 | * </ul>The pack layout support dynamic sizing for leaf nodes, if a |
| 12227 | * {@link #size} psuedo-property is specified. The default size function returns |
| 12228 | * 1, causing all leaf nodes to be sized equally, and all internal nodes to be |
| 12229 | * sized by the number of leaf nodes they have as descendants. |
| 12230 | * |
| 12231 | * <p>The size function can be used in conjunction with the order property, |
| 12232 | * which allows the nodes to the sorted by the computed size. Note: for sorting |
| 12233 | * based on other data attributes, simply use the default <tt>null</tt> for the |
| 12234 | * order property, and sort the nodes beforehand using the {@link pv.Dom} |
| 12235 | * operator. |
| 12236 | * |
| 12237 | * <p>For more details on how to use this layout, see |
| 12238 | * {@link pv.Layout.Hierarchy}. |
| 12239 | * |
| 12240 | * @extends pv.Layout.Hierarchy |
| 12241 | * @see <a href="http://portal.acm.org/citation.cfm?id=1124772.1124851" |
| 12242 | * >"Visualization of large hierarchical data by circle packing"</a> by W. Wang, |
| 12243 | * H. Wang, G. Dai, and H. Wang, ACM CHI 2006. |
| 12244 | */ |
| 12245 | pv.Layout.Pack = function() { |
| 12246 | pv.Layout.Hierarchy.call(this); |
| 12247 | |
| 12248 | this.node |
| 12249 | .radius(function(n) { return n.radius; }) |
| 12250 | .strokeStyle("rgb(31, 119, 180)") |
| 12251 | .fillStyle("rgba(31, 119, 180, .25)"); |
| 12252 | |
| 12253 | this.label |
| 12254 | .textAlign("center"); |
| 12255 | |
| 12256 | /* Hide unsupported link. */ |
| 12257 | delete this.link; |
| 12258 | }; |
| 12259 | |
| 12260 | pv.Layout.Pack.prototype = pv.extend(pv.Layout.Hierarchy) |
| 12261 | .property("spacing", Number) |
| 12262 | .property("order", String); // ascending, descending, reverse, null |
| 12263 | |
| 12264 | /** |
| 12265 | * Default properties for circle-packing layouts. The default spacing parameter |
| 12266 | * is 1 and the default order is "ascending". |
| 12267 | * |
| 12268 | * @type pv.Layout.Pack |
| 12269 | */ |
| 12270 | pv.Layout.Pack.prototype.defaults = new pv.Layout.Pack() |
| 12271 | .extend(pv.Layout.Hierarchy.prototype.defaults) |
| 12272 | .spacing(1) |
| 12273 | .order("ascending"); |
| 12274 | |
| 12275 | /** |
| 12276 | * The spacing parameter; defaults to 1, which provides a little bit of padding |
| 12277 | * between sibling nodes and the enclosing circle. Larger values increase the |
| 12278 | * spacing, by making the sibling nodes smaller; a value of zero makes the leaf |
| 12279 | * nodes as large as possible, with no padding on enclosing circles. |
| 12280 | * |
| 12281 | * @type number |
| 12282 | * @name pv.Layout.Pack.prototype.spacing |
| 12283 | */ |
| 12284 | |
| 12285 | /** |
| 12286 | * The sibling node order. The default order is <tt>null</tt>, which means to |
| 12287 | * use the sibling order specified by the nodes property as-is. A value of |
| 12288 | * "ascending" will sort siblings in ascending order of size, while "descending" |
| 12289 | * will do the reverse. For sorting based on data attributes other than size, |
| 12290 | * use the default <tt>null</tt> for the order property, and sort the nodes |
| 12291 | * beforehand using the {@link pv.Dom} operator. |
| 12292 | * |
| 12293 | * @see pv.Dom.Node#sort |
| 12294 | * @type string |
| 12295 | * @name pv.Layout.Pack.prototype.order |
| 12296 | */ |
| 12297 | |
| 12298 | /** @private The default size function. */ |
| 12299 | pv.Layout.Pack.prototype.$radius = function() { return 1; }; |
| 12300 | |
| 12301 | // TODO is it possible for spacing to operate in pixel space? |
| 12302 | // Right now it appears to be multiples of the smallest radius. |
| 12303 | |
| 12304 | /** |
| 12305 | * Specifies the sizing function. By default, a sizing function is disabled and |
| 12306 | * all nodes are given constant size. The sizing function is invoked for each |
| 12307 | * leaf node in the tree (passed to the constructor). |
| 12308 | * |
| 12309 | * <p>For example, if the tree data structure represents a file system, with |
| 12310 | * files as leaf nodes, and each file has a <tt>bytes</tt> attribute, you can |
| 12311 | * specify a size function as: |
| 12312 | * |
| 12313 | * <pre> .size(function(d) d.bytes)</pre> |
| 12314 | * |
| 12315 | * As with other properties, a size function may specify additional arguments to |
| 12316 | * access the data associated with the layout and any enclosing panels. |
| 12317 | * |
| 12318 | * @param {function} f the new sizing function. |
| 12319 | * @returns {pv.Layout.Pack} this. |
| 12320 | */ |
| 12321 | pv.Layout.Pack.prototype.size = function(f) { |
| 12322 | this.$radius = typeof f == "function" |
| 12323 | ? function() { return Math.sqrt(f.apply(this, arguments)); } |
| 12324 | : (f = Math.sqrt(f), function() { return f; }); |
| 12325 | return this; |
| 12326 | }; |
| 12327 | |
| 12328 | /** @private */ |
| 12329 | pv.Layout.Pack.prototype.buildImplied = function(s) { |
| 12330 | if (pv.Layout.Hierarchy.prototype.buildImplied.call(this, s)) return; |
| 12331 | |
| 12332 | var that = this, |
| 12333 | nodes = s.nodes, |
| 12334 | root = nodes[0]; |
| 12335 | |
| 12336 | /** @private Compute the radii of the leaf nodes. */ |
| 12337 | function radii(nodes) { |
| 12338 | var stack = pv.Mark.stack; |
| 12339 | stack.unshift(null); |
| 12340 | for (var i = 0, n = nodes.length; i < n; i++) { |
| 12341 | var c = nodes[i]; |
| 12342 | if (!c.firstChild) { |
| 12343 | c.radius = that.$radius.apply(that, (stack[0] = c, stack)); |
| 12344 | } |
| 12345 | } |
| 12346 | stack.shift(); |
| 12347 | } |
| 12348 | |
| 12349 | /** @private */ |
| 12350 | function packTree(n) { |
| 12351 | var nodes = []; |
| 12352 | for (var c = n.firstChild; c; c = c.nextSibling) { |
| 12353 | if (c.firstChild) c.radius = packTree(c); |
| 12354 | c.n = c.p = c; |
| 12355 | nodes.push(c); |
| 12356 | } |
| 12357 | |
| 12358 | /* Sort. */ |
| 12359 | switch (s.order) { |
| 12360 | case "ascending": { |
| 12361 | nodes.sort(function(a, b) { return a.radius - b.radius; }); |
| 12362 | break; |
| 12363 | } |
| 12364 | case "descending": { |
| 12365 | nodes.sort(function(a, b) { return b.radius - a.radius; }); |
| 12366 | break; |
| 12367 | } |
| 12368 | case "reverse": nodes.reverse(); break; |
| 12369 | } |
| 12370 | |
| 12371 | return packCircle(nodes); |
| 12372 | } |
| 12373 | |
| 12374 | /** @private */ |
| 12375 | function packCircle(nodes) { |
| 12376 | var xMin = Infinity, |
| 12377 | xMax = -Infinity, |
| 12378 | yMin = Infinity, |
| 12379 | yMax = -Infinity, |
| 12380 | a, b, c, j, k; |
| 12381 | |
| 12382 | /** @private */ |
| 12383 | function bound(n) { |
| 12384 | xMin = Math.min(n.x - n.radius, xMin); |
| 12385 | xMax = Math.max(n.x + n.radius, xMax); |
| 12386 | yMin = Math.min(n.y - n.radius, yMin); |
| 12387 | yMax = Math.max(n.y + n.radius, yMax); |
| 12388 | } |
| 12389 | |
| 12390 | /** @private */ |
| 12391 | function insert(a, b) { |
| 12392 | var c = a.n; |
| 12393 | a.n = b; |
| 12394 | b.p = a; |
| 12395 | b.n = c; |
| 12396 | c.p = b; |
| 12397 | } |
| 12398 | |
| 12399 | /** @private */ |
| 12400 | function splice(a, b) { |
| 12401 | a.n = b; |
| 12402 | b.p = a; |
| 12403 | } |
| 12404 | |
| 12405 | /** @private */ |
| 12406 | function intersects(a, b) { |
| 12407 | var dx = b.x - a.x, |
| 12408 | dy = b.y - a.y, |
| 12409 | dr = a.radius + b.radius; |
| 12410 | return (dr * dr - dx * dx - dy * dy) > .001; // within epsilon |
| 12411 | } |
| 12412 | |
| 12413 | /* Create first node. */ |
| 12414 | a = nodes[0]; |
| 12415 | a.x = -a.radius; |
| 12416 | a.y = 0; |
| 12417 | bound(a); |
| 12418 | |
| 12419 | /* Create second node. */ |
| 12420 | if (nodes.length > 1) { |
| 12421 | b = nodes[1]; |
| 12422 | b.x = b.radius; |
| 12423 | b.y = 0; |
| 12424 | bound(b); |
| 12425 | |
| 12426 | /* Create third node and build chain. */ |
| 12427 | if (nodes.length > 2) { |
| 12428 | c = nodes[2]; |
| 12429 | place(a, b, c); |
| 12430 | bound(c); |
| 12431 | insert(a, c); |
| 12432 | a.p = c; |
| 12433 | insert(c, b); |
| 12434 | b = a.n; |
| 12435 | |
| 12436 | /* Now iterate through the rest. */ |
| 12437 | for (var i = 3; i < nodes.length; i++) { |
| 12438 | place(a, b, c = nodes[i]); |
| 12439 | |
| 12440 | /* Search for the closest intersection. */ |
| 12441 | var isect = 0, s1 = 1, s2 = 1; |
| 12442 | for (j = b.n; j != b; j = j.n, s1++) { |
| 12443 | if (intersects(j, c)) { |
| 12444 | isect = 1; |
| 12445 | break; |
| 12446 | } |
| 12447 | } |
| 12448 | if (isect == 1) { |
| 12449 | for (k = a.p; k != j.p; k = k.p, s2++) { |
| 12450 | if (intersects(k, c)) { |
| 12451 | if (s2 < s1) { |
| 12452 | isect = -1; |
| 12453 | j = k; |
| 12454 | } |
| 12455 | break; |
| 12456 | } |
| 12457 | } |
| 12458 | } |
| 12459 | |
| 12460 | /* Update node chain. */ |
| 12461 | if (isect == 0) { |
| 12462 | insert(a, c); |
| 12463 | b = c; |
| 12464 | bound(c); |
| 12465 | } else if (isect > 0) { |
| 12466 | splice(a, j); |
| 12467 | b = j; |
| 12468 | i--; |
| 12469 | } else if (isect < 0) { |
| 12470 | splice(j, b); |
| 12471 | a = j; |
| 12472 | i--; |
| 12473 | } |
| 12474 | } |
| 12475 | } |
| 12476 | } |
| 12477 | |
| 12478 | /* Re-center the circles and return the encompassing radius. */ |
| 12479 | var cx = (xMin + xMax) / 2, |
| 12480 | cy = (yMin + yMax) / 2, |
| 12481 | cr = 0; |
| 12482 | for (var i = 0; i < nodes.length; i++) { |
| 12483 | var n = nodes[i]; |
| 12484 | n.x -= cx; |
| 12485 | n.y -= cy; |
| 12486 | cr = Math.max(cr, n.radius + Math.sqrt(n.x * n.x + n.y * n.y)); |
| 12487 | } |
| 12488 | return cr + s.spacing; |
| 12489 | } |
| 12490 | |
| 12491 | /** @private */ |
| 12492 | function place(a, b, c) { |
| 12493 | var da = b.radius + c.radius, |
| 12494 | db = a.radius + c.radius, |
| 12495 | dx = b.x - a.x, |
| 12496 | dy = b.y - a.y, |
| 12497 | dc = Math.sqrt(dx * dx + dy * dy), |
| 12498 | cos = (db * db + dc * dc - da * da) / (2 * db * dc), |
| 12499 | theta = Math.acos(cos), |
| 12500 | x = cos * db, |
| 12501 | h = Math.sin(theta) * db; |
| 12502 | dx /= dc; |
| 12503 | dy /= dc; |
| 12504 | c.x = a.x + x * dx + h * dy; |
| 12505 | c.y = a.y + x * dy - h * dx; |
| 12506 | } |
| 12507 | |
| 12508 | /** @private */ |
| 12509 | function transform(n, x, y, k) { |
| 12510 | for (var c = n.firstChild; c; c = c.nextSibling) { |
| 12511 | c.x += n.x; |
| 12512 | c.y += n.y; |
| 12513 | transform(c, x, y, k); |
| 12514 | } |
| 12515 | n.x = x + k * n.x; |
| 12516 | n.y = y + k * n.y; |
| 12517 | n.radius *= k; |
| 12518 | } |
| 12519 | |
| 12520 | radii(nodes); |
| 12521 | |
| 12522 | /* Recursively compute the layout. */ |
| 12523 | root.x = 0; |
| 12524 | root.y = 0; |
| 12525 | root.radius = packTree(root); |
| 12526 | |
| 12527 | var w = this.width(), |
| 12528 | h = this.height(), |
| 12529 | k = 1 / Math.max(2 * root.radius / w, 2 * root.radius / h); |
| 12530 | transform(root, w / 2, h / 2, k); |
| 12531 | }; |
| 12532 | /** |
| 12533 | * Constructs a new, empty force-directed layout. Layouts are not typically |
| 12534 | * constructed directly; instead, they are added to an existing panel via |
| 12535 | * {@link pv.Mark#add}. |
| 12536 | * |
| 12537 | * @class Implements force-directed network layout as a node-link diagram. This |
| 12538 | * layout uses the Fruchterman-Reingold algorithm, which applies an attractive |
| 12539 | * spring force between neighboring nodes, and a repulsive electrical charge |
| 12540 | * force between all nodes. An additional drag force improves stability of the |
| 12541 | * simulation. See {@link pv.Force.spring}, {@link pv.Force.drag} and {@link |
| 12542 | * pv.Force.charge} for more details; note that the n-body charge force is |
| 12543 | * approximated using the Barnes-Hut algorithm. |
| 12544 | * |
| 12545 | * <p>This layout is implemented on top of {@link pv.Simulation}, which can be |
| 12546 | * used directly for more control over simulation parameters. The simulation |
| 12547 | * uses Position Verlet integration, which does not compute velocities |
| 12548 | * explicitly, but allows for easy geometric constraints, such as bounding the |
| 12549 | * nodes within the layout panel. Many of the configuration properties supported |
| 12550 | * by this layout are simply passed through to the underlying forces and |
| 12551 | * constraints of the simulation. |
| 12552 | * |
| 12553 | * <p>Force layouts are typically interactive. The gradual movement of the nodes |
| 12554 | * as they stabilize to a local stress minimum can help reveal the structure of |
| 12555 | * the network, as can {@link pv.Behavior.drag}, which allows the user to pick |
| 12556 | * up nodes and reposition them while the physics simulation continues. This |
| 12557 | * layout can also be used with pan & zoom behaviors for interaction. |
| 12558 | * |
| 12559 | * <p>To facilitate interaction, this layout by default automatically re-renders |
| 12560 | * using a <tt>setInterval</tt> every 42 milliseconds. This can be disabled via |
| 12561 | * the <tt>iterations</tt> property, which if non-null specifies the number of |
| 12562 | * simulation iterations to run before the force-directed layout is finalized. |
| 12563 | * Be careful not to use too high an iteration count, as this can lead to an |
| 12564 | * annoying delay on page load. |
| 12565 | * |
| 12566 | * <p>As with other network layouts, the network data can be updated |
| 12567 | * dynamically, provided the property cache is reset. See |
| 12568 | * {@link pv.Layout.Network} for details. New nodes are initialized with random |
| 12569 | * positions near the center. Alternatively, positions can be specified manually |
| 12570 | * by setting the <tt>x</tt> and <tt>y</tt> attributes on nodes. |
| 12571 | * |
| 12572 | * @extends pv.Layout.Network |
| 12573 | * @see <a href="http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.13.8444&rep=rep1&type=pdf" |
| 12574 | * >"Graph Drawing by Force-directed Placement"</a> by T. Fruchterman & |
| 12575 | * E. Reingold, Software--Practice & Experience, November 1991. |
| 12576 | */ |
| 12577 | pv.Layout.Force = function() { |
| 12578 | pv.Layout.Network.call(this); |
| 12579 | |
| 12580 | /* Force-directed graphs can be messy, so reduce the link width. */ |
| 12581 | this.link.lineWidth(function(d, p) { return Math.sqrt(p.linkValue) * 1.5; }); |
| 12582 | this.label.textAlign("center"); |
| 12583 | }; |
| 12584 | |
| 12585 | pv.Layout.Force.prototype = pv.extend(pv.Layout.Network) |
| 12586 | .property("bound", Boolean) |
| 12587 | .property("iterations", Number) |
| 12588 | .property("dragConstant", Number) |
| 12589 | .property("chargeConstant", Number) |
| 12590 | .property("chargeMinDistance", Number) |
| 12591 | .property("chargeMaxDistance", Number) |
| 12592 | .property("chargeTheta", Number) |
| 12593 | .property("springConstant", Number) |
| 12594 | .property("springDamping", Number) |
| 12595 | .property("springLength", Number); |
| 12596 | |
| 12597 | /** |
| 12598 | * The bound parameter; true if nodes should be constrained within the layout |
| 12599 | * panel. Bounding is disabled by default. Currently the layout does not observe |
| 12600 | * the radius of the nodes; strictly speaking, only the center of the node is |
| 12601 | * constrained to be within the panel, with an additional 6-pixel offset for |
| 12602 | * padding. A future enhancement could extend the bound constraint to observe |
| 12603 | * the node's radius, which would also support bounding for variable-size nodes. |
| 12604 | * |
| 12605 | * <p>Note that if this layout is used in conjunction with pan & zoom |
| 12606 | * behaviors, those behaviors should have their bound parameter set to the same |
| 12607 | * value. |
| 12608 | * |
| 12609 | * @type boolean |
| 12610 | * @name pv.Layout.Force.prototype.bound |
| 12611 | */ |
| 12612 | |
| 12613 | /** |
| 12614 | * The number of simulation iterations to run, or null if this layout is |
| 12615 | * interactive. Force-directed layouts are interactive by default, using a |
| 12616 | * <tt>setInterval</tt> to advance the physics simulation and re-render |
| 12617 | * automatically. |
| 12618 | * |
| 12619 | * @type number |
| 12620 | * @name pv.Layout.Force.prototype.iterations |
| 12621 | */ |
| 12622 | |
| 12623 | /** |
| 12624 | * The drag constant, in the range [0,1]. A value of 0 means no drag (a |
| 12625 | * perfectly frictionless environment), while a value of 1 means friction |
| 12626 | * immediately cancels all momentum. The default value is 0.1, which provides a |
| 12627 | * minimum amount of drag that helps stabilize bouncy springs; lower values may |
| 12628 | * result in excessive bounciness, while higher values cause the simulation to |
| 12629 | * take longer to converge. |
| 12630 | * |
| 12631 | * @type number |
| 12632 | * @name pv.Layout.Force.prototype.dragConstant |
| 12633 | * @see pv.Force.drag#constant |
| 12634 | */ |
| 12635 | |
| 12636 | /** |
| 12637 | * The charge constant, which should be a negative number. The default value is |
| 12638 | * -40; more negative values will result in a stronger repulsive force, which |
| 12639 | * may lead to faster convergence at the risk of instability. Too strong |
| 12640 | * repulsive charge forces can cause comparatively weak springs to be stretched |
| 12641 | * well beyond their rest length, emphasizing global structure over local |
| 12642 | * structure. A nonnegative value will break the Fruchterman-Reingold algorithm, |
| 12643 | * and is for entertainment purposes only. |
| 12644 | * |
| 12645 | * @type number |
| 12646 | * @name pv.Layout.Force.prototype.chargeConstant |
| 12647 | * @see pv.Force.charge#constant |
| 12648 | */ |
| 12649 | |
| 12650 | /** |
| 12651 | * The minimum distance at which charge forces are applied. The default minimum |
| 12652 | * distance of 2 avoids applying forces that are two strong; because the physics |
| 12653 | * simulation is run at discrete time intervals, it is possible for two same- |
| 12654 | * charged particles to become very close or even a singularity! Since the |
| 12655 | * charge force is inversely proportional to the square of the distance, very |
| 12656 | * small distances can break the simulation. |
| 12657 | * |
| 12658 | * <p>In rare cases, two particles can become stuck on top of each other, as a |
| 12659 | * minimum distance threshold will prevent the charge force from repelling them. |
| 12660 | * However, this occurs very rarely because other forces and momentum typically |
| 12661 | * cause the particles to become separated again, at which point the repulsive |
| 12662 | * charge force kicks in. |
| 12663 | * |
| 12664 | * @type number |
| 12665 | * @name pv.Layout.Force.prototype.chargeMinDistance |
| 12666 | * @see pv.Force.charge#domain |
| 12667 | */ |
| 12668 | |
| 12669 | /** |
| 12670 | * The maximum distance at which charge forces are applied. This improves |
| 12671 | * performance by ignoring weak charge forces at great distances. Note that this |
| 12672 | * parameter is partly redundant, as the Barnes-Hut algorithm for n-body forces |
| 12673 | * already improves performance for far-away particles through approximation. |
| 12674 | * |
| 12675 | * @type number |
| 12676 | * @name pv.Layout.Force.prototype.chargeMaxDistance |
| 12677 | * @see pv.Force.charge#domain |
| 12678 | */ |
| 12679 | |
| 12680 | /** |
| 12681 | * The Barnes-Hut approximation factor. The Barnes-Hut approximation criterion |
| 12682 | * is the ratio of the size of the quadtree node to the distance from the point |
| 12683 | * to the node's center of mass is beneath some threshold. The default value is |
| 12684 | * 0.9. |
| 12685 | * |
| 12686 | * @type number |
| 12687 | * @name pv.Layout.Force.prototype.chargeTheta |
| 12688 | * @see pv.Force.charge#theta |
| 12689 | */ |
| 12690 | |
| 12691 | /** |
| 12692 | * The spring constant, which should be a positive number. The default value is |
| 12693 | * 0.1; greater values will result in a stronger attractive force, which may |
| 12694 | * lead to faster convergence at the risk of instability. Too strong spring |
| 12695 | * forces can cause comparatively weak charge forces to be ignored, emphasizing |
| 12696 | * local structure over global structure. A nonpositive value will break the |
| 12697 | * Fruchterman-Reingold algorithm, and is for entertainment purposes only. |
| 12698 | * |
| 12699 | * <p>The spring tension is automatically normalized using the inverse square |
| 12700 | * root of the maximum link degree of attached nodes. |
| 12701 | * |
| 12702 | * @type number |
| 12703 | * @name pv.Layout.Force.prototype.springConstant |
| 12704 | * @see pv.Force.spring#constant |
| 12705 | */ |
| 12706 | |
| 12707 | /** |
| 12708 | * The spring damping factor, in the range [0,1]. Damping functions identically |
| 12709 | * to drag forces, damping spring bounciness by applying a force in the opposite |
| 12710 | * direction of attached nodes' velocities. The default value is 0.3. |
| 12711 | * |
| 12712 | * <p>The spring damping is automatically normalized using the inverse square |
| 12713 | * root of the maximum link degree of attached nodes. |
| 12714 | * |
| 12715 | * @type number |
| 12716 | * @name pv.Layout.Force.prototype.springDamping |
| 12717 | * @see pv.Force.spring#damping |
| 12718 | */ |
| 12719 | |
| 12720 | /** |
| 12721 | * The spring rest length. The default value is 20 pixels. Larger values may be |
| 12722 | * appropriate if the layout panel is larger, or if the nodes are rendered |
| 12723 | * larger than the default dot size of 20. |
| 12724 | * |
| 12725 | * @type number |
| 12726 | * @name pv.Layout.Force.prototype.springLength |
| 12727 | * @see pv.Force.spring#length |
| 12728 | */ |
| 12729 | |
| 12730 | /** |
| 12731 | * Default properties for force-directed layouts. The default drag constant is |
| 12732 | * 0.1, the default charge constant is -40 (with a domain of [2, 500] and theta |
| 12733 | * of 0.9), and the default spring constant is 0.1 (with a damping of 0.3 and a |
| 12734 | * rest length of 20). |
| 12735 | * |
| 12736 | * @type pv.Layout.Force |
| 12737 | */ |
| 12738 | pv.Layout.Force.prototype.defaults = new pv.Layout.Force() |
| 12739 | .extend(pv.Layout.Network.prototype.defaults) |
| 12740 | .dragConstant(.1) |
| 12741 | .chargeConstant(-40) |
| 12742 | .chargeMinDistance(2) |
| 12743 | .chargeMaxDistance(500) |
| 12744 | .chargeTheta(.9) |
| 12745 | .springConstant(.1) |
| 12746 | .springDamping(.3) |
| 12747 | .springLength(20); |
| 12748 | |
| 12749 | /** @private Initialize the physics simulation. */ |
| 12750 | pv.Layout.Force.prototype.buildImplied = function(s) { |
| 12751 | |
| 12752 | /* Any cached interactive layouts need to be rebound for the timer. */ |
| 12753 | if (pv.Layout.Network.prototype.buildImplied.call(this, s)) { |
| 12754 | var f = s.$force; |
| 12755 | if (f) { |
| 12756 | f.next = this.binds.$force; |
| 12757 | this.binds.$force = f; |
| 12758 | } |
| 12759 | return; |
| 12760 | } |
| 12761 | |
| 12762 | var that = this, |
| 12763 | nodes = s.nodes, |
| 12764 | links = s.links, |
| 12765 | k = s.iterations, |
| 12766 | w = s.width, |
| 12767 | h = s.height; |
| 12768 | |
| 12769 | /* Initialize positions randomly near the center. */ |
| 12770 | for (var i = 0, n; i < nodes.length; i++) { |
| 12771 | n = nodes[i]; |
| 12772 | if (isNaN(n.x)) n.x = w / 2 + 40 * Math.random() - 20; |
| 12773 | if (isNaN(n.y)) n.y = h / 2 + 40 * Math.random() - 20; |
| 12774 | } |
| 12775 | |
| 12776 | /* Initialize the simulation. */ |
| 12777 | var sim = pv.simulation(nodes); |
| 12778 | |
| 12779 | /* Drag force. */ |
| 12780 | sim.force(pv.Force.drag(s.dragConstant)); |
| 12781 | |
| 12782 | /* Charge (repelling) force. */ |
| 12783 | sim.force(pv.Force.charge(s.chargeConstant) |
| 12784 | .domain(s.chargeMinDistance, s.chargeMaxDistance) |
| 12785 | .theta(s.chargeTheta)); |
| 12786 | |
| 12787 | /* Spring (attracting) force. */ |
| 12788 | sim.force(pv.Force.spring(s.springConstant) |
| 12789 | .damping(s.springDamping) |
| 12790 | .length(s.springLength) |
| 12791 | .links(links)); |
| 12792 | |
| 12793 | /* Position constraint (for interactive dragging). */ |
| 12794 | sim.constraint(pv.Constraint.position()); |
| 12795 | |
| 12796 | /* Optionally add bound constraint. TODO: better padding. */ |
| 12797 | if (s.bound) { |
| 12798 | sim.constraint(pv.Constraint.bound().x(6, w - 6).y(6, h - 6)); |
| 12799 | } |
| 12800 | |
| 12801 | /** @private Returns the speed of the given node, to determine cooling. */ |
| 12802 | function speed(n) { |
| 12803 | return n.fix ? 1 : n.vx * n.vx + n.vy * n.vy; |
| 12804 | } |
| 12805 | |
| 12806 | /* |
| 12807 | * If the iterations property is null (the default), the layout is |
| 12808 | * interactive. The simulation is run until the fastest particle drops below |
| 12809 | * an arbitrary minimum speed. Although the timer keeps firing, this speed |
| 12810 | * calculation is fast so there is minimal CPU overhead. Note: if a particle |
| 12811 | * is fixed for interactivity, treat this as a high speed and resume |
| 12812 | * simulation. |
| 12813 | */ |
| 12814 | if (k == null) { |
| 12815 | sim.step(); // compute initial previous velocities |
| 12816 | sim.step(); // compute initial velocities |
| 12817 | |
| 12818 | /* Add the simulation state to the bound list. */ |
| 12819 | var force = s.$force = this.binds.$force = { |
| 12820 | next: this.binds.$force, |
| 12821 | nodes: nodes, |
| 12822 | min: 1e-4 * (links.length + 1), |
| 12823 | sim: sim |
| 12824 | }; |
| 12825 | |
| 12826 | /* Start the timer, if not already started. */ |
| 12827 | if (!this.$timer) this.$timer = setInterval(function() { |
| 12828 | var render = false; |
| 12829 | for (var f = that.binds.$force; f; f = f.next) { |
| 12830 | if (pv.max(f.nodes, speed) > f.min) { |
| 12831 | f.sim.step(); |
| 12832 | render = true; |
| 12833 | } |
| 12834 | } |
| 12835 | if (render) that.render(); |
| 12836 | }, 42); |
| 12837 | } else for (var i = 0; i < k; i++) { |
| 12838 | sim.step(); |
| 12839 | } |
| 12840 | }; |
| 12841 | /** |
| 12842 | * Constructs a new, empty cluster layout. Layouts are not typically |
| 12843 | * constructed directly; instead, they are added to an existing panel via |
| 12844 | * {@link pv.Mark#add}. |
| 12845 | * |
| 12846 | * @class Implements a hierarchical layout using the cluster (or dendrogram) |
| 12847 | * algorithm. This layout provides both node-link and space-filling |
| 12848 | * implementations of cluster diagrams. In many ways it is similar to |
| 12849 | * {@link pv.Layout.Partition}, except that leaf nodes are positioned at maximum |
| 12850 | * depth, and the depth of internal nodes is based on their distance from their |
| 12851 | * deepest descendant, rather than their distance from the root. |
| 12852 | * |
| 12853 | * <p>The cluster layout supports a "group" property, which if true causes |
| 12854 | * siblings to be positioned closer together than unrelated nodes at the same |
| 12855 | * depth. Unlike the partition layout, this layout does not support dynamic |
| 12856 | * sizing for leaf nodes; all leaf nodes are the same size. |
| 12857 | * |
| 12858 | * <p>For more details on how to use this layout, see |
| 12859 | * {@link pv.Layout.Hierarchy}. |
| 12860 | * |
| 12861 | * @see pv.Layout.Cluster.Fill |
| 12862 | * @extends pv.Layout.Hierarchy |
| 12863 | */ |
| 12864 | pv.Layout.Cluster = function() { |
| 12865 | pv.Layout.Hierarchy.call(this); |
| 12866 | var interpolate, // cached interpolate |
| 12867 | buildImplied = this.buildImplied; |
| 12868 | |
| 12869 | /** @private Cache layout state to optimize properties. */ |
| 12870 | this.buildImplied = function(s) { |
| 12871 | buildImplied.call(this, s); |
| 12872 | interpolate |
| 12873 | = /^(top|bottom)$/.test(s.orient) ? "step-before" |
| 12874 | : /^(left|right)$/.test(s.orient) ? "step-after" |
| 12875 | : "linear"; |
| 12876 | }; |
| 12877 | |
| 12878 | this.link.interpolate(function() { return interpolate; }); |
| 12879 | }; |
| 12880 | |
| 12881 | pv.Layout.Cluster.prototype = pv.extend(pv.Layout.Hierarchy) |
| 12882 | .property("group", Number) |
| 12883 | .property("orient", String) |
| 12884 | .property("innerRadius", Number) |
| 12885 | .property("outerRadius", Number); |
| 12886 | |
| 12887 | /** |
| 12888 | * The group parameter; defaults to 0, disabling grouping of siblings. If this |
| 12889 | * parameter is set to a positive number (or true, which is equivalent to 1), |
| 12890 | * then additional space will be allotted between sibling groups. In other |
| 12891 | * words, siblings (nodes that share the same parent) will be positioned more |
| 12892 | * closely than nodes at the same depth that do not share a parent. |
| 12893 | * |
| 12894 | * @type number |
| 12895 | * @name pv.Layout.Cluster.prototype.group |
| 12896 | */ |
| 12897 | |
| 12898 | /** |
| 12899 | * The orientation. The default orientation is "top", which means that the root |
| 12900 | * node is placed on the top edge, leaf nodes appear on the bottom edge, and |
| 12901 | * internal nodes are in-between. The following orientations are supported:<ul> |
| 12902 | * |
| 12903 | * <li>left - left-to-right. |
| 12904 | * <li>right - right-to-left. |
| 12905 | * <li>top - top-to-bottom. |
| 12906 | * <li>bottom - bottom-to-top. |
| 12907 | * <li>radial - radially, with the root at the center.</ul> |
| 12908 | * |
| 12909 | * @type string |
| 12910 | * @name pv.Layout.Cluster.prototype.orient |
| 12911 | */ |
| 12912 | |
| 12913 | /** |
| 12914 | * The inner radius; defaults to 0. This property applies only to radial |
| 12915 | * orientations, and can be used to compress the layout radially. Note that for |
| 12916 | * the node-link implementation, the root node is always at the center, |
| 12917 | * regardless of the value of this property; this property only affects internal |
| 12918 | * and leaf nodes. For the space-filling implementation, a non-zero value of |
| 12919 | * this property will result in the root node represented as a ring rather than |
| 12920 | * a circle. |
| 12921 | * |
| 12922 | * @type number |
| 12923 | * @name pv.Layout.Cluster.prototype.innerRadius |
| 12924 | */ |
| 12925 | |
| 12926 | /** |
| 12927 | * The outer radius; defaults to fill the containing panel, based on the height |
| 12928 | * and width of the layout. If the layout has no height and width specified, it |
| 12929 | * will extend to fill the enclosing panel. |
| 12930 | * |
| 12931 | * @type number |
| 12932 | * @name pv.Layout.Cluster.prototype.outerRadius |
| 12933 | */ |
| 12934 | |
| 12935 | /** |
| 12936 | * Defaults for cluster layouts. The default group parameter is 0 and the |
| 12937 | * default orientation is "top". |
| 12938 | * |
| 12939 | * @type pv.Layout.Cluster |
| 12940 | */ |
| 12941 | pv.Layout.Cluster.prototype.defaults = new pv.Layout.Cluster() |
| 12942 | .extend(pv.Layout.Hierarchy.prototype.defaults) |
| 12943 | .group(0) |
| 12944 | .orient("top"); |
| 12945 | |
| 12946 | /** @private */ |
| 12947 | pv.Layout.Cluster.prototype.buildImplied = function(s) { |
| 12948 | if (pv.Layout.Hierarchy.prototype.buildImplied.call(this, s)) return; |
| 12949 | |
| 12950 | var root = s.nodes[0], |
| 12951 | group = s.group, |
| 12952 | breadth, |
| 12953 | depth, |
| 12954 | leafCount = 0, |
| 12955 | leafIndex = .5 - group / 2; |
| 12956 | |
| 12957 | /* Count the leaf nodes and compute the depth of descendants. */ |
| 12958 | var p = undefined; |
| 12959 | root.visitAfter(function(n) { |
| 12960 | if (n.firstChild) { |
| 12961 | n.depth = 1 + pv.max(n.childNodes, function(n) { return n.depth; }); |
| 12962 | } else { |
| 12963 | if (group && (p != n.parentNode)) { |
| 12964 | p = n.parentNode; |
| 12965 | leafCount += group; |
| 12966 | } |
| 12967 | leafCount++; |
| 12968 | n.depth = 0; |
| 12969 | } |
| 12970 | }); |
| 12971 | breadth = 1 / leafCount; |
| 12972 | depth = 1 / root.depth; |
| 12973 | |
| 12974 | /* Compute the unit breadth and depth of each node. */ |
| 12975 | var p = undefined; |
| 12976 | root.visitAfter(function(n) { |
| 12977 | if (n.firstChild) { |
| 12978 | n.breadth = pv.mean(n.childNodes, function(n) { return n.breadth; }); |
| 12979 | } else { |
| 12980 | if (group && (p != n.parentNode)) { |
| 12981 | p = n.parentNode; |
| 12982 | leafIndex += group; |
| 12983 | } |
| 12984 | n.breadth = breadth * leafIndex++; |
| 12985 | } |
| 12986 | n.depth = 1 - n.depth * depth; |
| 12987 | }); |
| 12988 | |
| 12989 | /* Compute breadth and depth ranges for space-filling layouts. */ |
| 12990 | root.visitAfter(function(n) { |
| 12991 | n.minBreadth = n.firstChild |
| 12992 | ? n.firstChild.minBreadth |
| 12993 | : (n.breadth - breadth / 2); |
| 12994 | n.maxBreadth = n.firstChild |
| 12995 | ? n.lastChild.maxBreadth |
| 12996 | : (n.breadth + breadth / 2); |
| 12997 | }); |
| 12998 | root.visitBefore(function(n) { |
| 12999 | n.minDepth = n.parentNode |
| 13000 | ? n.parentNode.maxDepth |
| 13001 | : 0; |
| 13002 | n.maxDepth = n.parentNode |
| 13003 | ? (n.depth + root.depth) |
| 13004 | : (n.minDepth + 2 * root.depth); |
| 13005 | }); |
| 13006 | root.minDepth = -depth; |
| 13007 | |
| 13008 | pv.Layout.Hierarchy.NodeLink.buildImplied.call(this, s); |
| 13009 | }; |
| 13010 | |
| 13011 | /** |
| 13012 | * Constructs a new, empty space-filling cluster layout. Layouts are not |
| 13013 | * typically constructed directly; instead, they are added to an existing panel |
| 13014 | * via {@link pv.Mark#add}. |
| 13015 | * |
| 13016 | * @class A variant of cluster layout that is space-filling. The meaning of the |
| 13017 | * exported mark prototypes changes slightly in the space-filling |
| 13018 | * implementation:<ul> |
| 13019 | * |
| 13020 | * <li><tt>node</tt> - for rendering nodes; typically a {@link pv.Bar} for |
| 13021 | * non-radial orientations, and a {@link pv.Wedge} for radial orientations. |
| 13022 | * |
| 13023 | * <p><li><tt>link</tt> - unsupported; undefined. Links are encoded implicitly |
| 13024 | * in the arrangement of the space-filling nodes. |
| 13025 | * |
| 13026 | * <p><li><tt>label</tt> - for rendering node labels; typically a |
| 13027 | * {@link pv.Label}. |
| 13028 | * |
| 13029 | * </ul>For more details on how to use this layout, see |
| 13030 | * {@link pv.Layout.Cluster}. |
| 13031 | * |
| 13032 | * @extends pv.Layout.Cluster |
| 13033 | */ |
| 13034 | pv.Layout.Cluster.Fill = function() { |
| 13035 | pv.Layout.Cluster.call(this); |
| 13036 | pv.Layout.Hierarchy.Fill.constructor.call(this); |
| 13037 | }; |
| 13038 | |
| 13039 | pv.Layout.Cluster.Fill.prototype = pv.extend(pv.Layout.Cluster); |
| 13040 | |
| 13041 | /** @private */ |
| 13042 | pv.Layout.Cluster.Fill.prototype.buildImplied = function(s) { |
| 13043 | if (pv.Layout.Cluster.prototype.buildImplied.call(this, s)) return; |
| 13044 | pv.Layout.Hierarchy.Fill.buildImplied.call(this, s); |
| 13045 | }; |
| 13046 | /** |
| 13047 | * Constructs a new, empty partition layout. Layouts are not typically |
| 13048 | * constructed directly; instead, they are added to an existing panel via |
| 13049 | * {@link pv.Mark#add}. |
| 13050 | * |
| 13051 | * @class Implemeents a hierarchical layout using the partition (or sunburst, |
| 13052 | * icicle) algorithm. This layout provides both node-link and space-filling |
| 13053 | * implementations of partition diagrams. In many ways it is similar to |
| 13054 | * {@link pv.Layout.Cluster}, except that leaf nodes are positioned based on |
| 13055 | * their distance from the root. |
| 13056 | * |
| 13057 | * <p>The partition layout support dynamic sizing for leaf nodes, if a |
| 13058 | * {@link #size} psuedo-property is specified. The default size function returns |
| 13059 | * 1, causing all leaf nodes to be sized equally, and all internal nodes to be |
| 13060 | * sized by the number of leaf nodes they have as descendants. |
| 13061 | * |
| 13062 | * <p>The size function can be used in conjunction with the order property, |
| 13063 | * which allows the nodes to the sorted by the computed size. Note: for sorting |
| 13064 | * based on other data attributes, simply use the default <tt>null</tt> for the |
| 13065 | * order property, and sort the nodes beforehand using the {@link pv.Dom} |
| 13066 | * operator. |
| 13067 | * |
| 13068 | * <p>For more details on how to use this layout, see |
| 13069 | * {@link pv.Layout.Hierarchy}. |
| 13070 | * |
| 13071 | * @see pv.Layout.Partition.Fill |
| 13072 | * @extends pv.Layout.Hierarchy |
| 13073 | */ |
| 13074 | pv.Layout.Partition = function() { |
| 13075 | pv.Layout.Hierarchy.call(this); |
| 13076 | }; |
| 13077 | |
| 13078 | pv.Layout.Partition.prototype = pv.extend(pv.Layout.Hierarchy) |
| 13079 | .property("order", String) // null, ascending, descending? |
| 13080 | .property("orient", String) // top, left, right, bottom, radial |
| 13081 | .property("innerRadius", Number) |
| 13082 | .property("outerRadius", Number); |
| 13083 | |
| 13084 | /** |
| 13085 | * The sibling node order. The default order is <tt>null</tt>, which means to |
| 13086 | * use the sibling order specified by the nodes property as-is. A value of |
| 13087 | * "ascending" will sort siblings in ascending order of size, while "descending" |
| 13088 | * will do the reverse. For sorting based on data attributes other than size, |
| 13089 | * use the default <tt>null</tt> for the order property, and sort the nodes |
| 13090 | * beforehand using the {@link pv.Dom} operator. |
| 13091 | * |
| 13092 | * @see pv.Dom.Node#sort |
| 13093 | * @type string |
| 13094 | * @name pv.Layout.Partition.prototype.order |
| 13095 | */ |
| 13096 | |
| 13097 | /** |
| 13098 | * The orientation. The default orientation is "top", which means that the root |
| 13099 | * node is placed on the top edge, leaf nodes appear at the bottom, and internal |
| 13100 | * nodes are in-between. The following orientations are supported:<ul> |
| 13101 | * |
| 13102 | * <li>left - left-to-right. |
| 13103 | * <li>right - right-to-left. |
| 13104 | * <li>top - top-to-bottom. |
| 13105 | * <li>bottom - bottom-to-top. |
| 13106 | * <li>radial - radially, with the root at the center.</ul> |
| 13107 | * |
| 13108 | * @type string |
| 13109 | * @name pv.Layout.Partition.prototype.orient |
| 13110 | */ |
| 13111 | |
| 13112 | /** |
| 13113 | * The inner radius; defaults to 0. This property applies only to radial |
| 13114 | * orientations, and can be used to compress the layout radially. Note that for |
| 13115 | * the node-link implementation, the root node is always at the center, |
| 13116 | * regardless of the value of this property; this property only affects internal |
| 13117 | * and leaf nodes. For the space-filling implementation, a non-zero value of |
| 13118 | * this property will result in the root node represented as a ring rather than |
| 13119 | * a circle. |
| 13120 | * |
| 13121 | * @type number |
| 13122 | * @name pv.Layout.Partition.prototype.innerRadius |
| 13123 | */ |
| 13124 | |
| 13125 | /** |
| 13126 | * The outer radius; defaults to fill the containing panel, based on the height |
| 13127 | * and width of the layout. If the layout has no height and width specified, it |
| 13128 | * will extend to fill the enclosing panel. |
| 13129 | * |
| 13130 | * @type number |
| 13131 | * @name pv.Layout.Partition.prototype.outerRadius |
| 13132 | */ |
| 13133 | |
| 13134 | /** |
| 13135 | * Default properties for partition layouts. The default orientation is "top". |
| 13136 | * |
| 13137 | * @type pv.Layout.Partition |
| 13138 | */ |
| 13139 | pv.Layout.Partition.prototype.defaults = new pv.Layout.Partition() |
| 13140 | .extend(pv.Layout.Hierarchy.prototype.defaults) |
| 13141 | .orient("top"); |
| 13142 | |
| 13143 | /** @private */ |
| 13144 | pv.Layout.Partition.prototype.$size = function() { return 1; }; |
| 13145 | |
| 13146 | /** |
| 13147 | * Specifies the sizing function. By default, a sizing function is disabled and |
| 13148 | * all nodes are given constant size. The sizing function is invoked for each |
| 13149 | * leaf node in the tree (passed to the constructor). |
| 13150 | * |
| 13151 | * <p>For example, if the tree data structure represents a file system, with |
| 13152 | * files as leaf nodes, and each file has a <tt>bytes</tt> attribute, you can |
| 13153 | * specify a size function as: |
| 13154 | * |
| 13155 | * <pre> .size(function(d) d.bytes)</pre> |
| 13156 | * |
| 13157 | * As with other properties, a size function may specify additional arguments to |
| 13158 | * access the data associated with the layout and any enclosing panels. |
| 13159 | * |
| 13160 | * @param {function} f the new sizing function. |
| 13161 | * @returns {pv.Layout.Partition} this. |
| 13162 | */ |
| 13163 | pv.Layout.Partition.prototype.size = function(f) { |
| 13164 | this.$size = f; |
| 13165 | return this; |
| 13166 | }; |
| 13167 | |
| 13168 | /** @private */ |
| 13169 | pv.Layout.Partition.prototype.buildImplied = function(s) { |
| 13170 | if (pv.Layout.Hierarchy.prototype.buildImplied.call(this, s)) return; |
| 13171 | |
| 13172 | var that = this, |
| 13173 | root = s.nodes[0], |
| 13174 | stack = pv.Mark.stack, |
| 13175 | maxDepth = 0; |
| 13176 | |
| 13177 | /* Recursively compute the tree depth and node size. */ |
| 13178 | stack.unshift(null); |
| 13179 | root.visitAfter(function(n, i) { |
| 13180 | if (i > maxDepth) maxDepth = i; |
| 13181 | n.size = n.firstChild |
| 13182 | ? pv.sum(n.childNodes, function(n) { return n.size; }) |
| 13183 | : that.$size.apply(that, (stack[0] = n, stack)); |
| 13184 | }); |
| 13185 | stack.shift(); |
| 13186 | |
| 13187 | /* Order */ |
| 13188 | switch (s.order) { |
| 13189 | case "ascending": root.sort(function(a, b) { return a.size - b.size; }); break; |
| 13190 | case "descending": root.sort(function(b, a) { return a.size - b.size; }); break; |
| 13191 | } |
| 13192 | |
| 13193 | /* Compute the unit breadth and depth of each node. */ |
| 13194 | var ds = 1 / maxDepth; |
| 13195 | root.minBreadth = 0; |
| 13196 | root.breadth = .5; |
| 13197 | root.maxBreadth = 1; |
| 13198 | root.visitBefore(function(n) { |
| 13199 | var b = n.minBreadth, s = n.maxBreadth - b; |
| 13200 | for (var c = n.firstChild; c; c = c.nextSibling) { |
| 13201 | c.minBreadth = b; |
| 13202 | c.maxBreadth = b += (c.size / n.size) * s; |
| 13203 | c.breadth = (b + c.minBreadth) / 2; |
| 13204 | } |
| 13205 | }); |
| 13206 | root.visitAfter(function(n, i) { |
| 13207 | n.minDepth = (i - 1) * ds; |
| 13208 | n.maxDepth = n.depth = i * ds; |
| 13209 | }); |
| 13210 | |
| 13211 | pv.Layout.Hierarchy.NodeLink.buildImplied.call(this, s); |
| 13212 | }; |
| 13213 | |
| 13214 | /** |
| 13215 | * Constructs a new, empty space-filling partition layout. Layouts are not |
| 13216 | * typically constructed directly; instead, they are added to an existing panel |
| 13217 | * via {@link pv.Mark#add}. |
| 13218 | * |
| 13219 | * @class A variant of partition layout that is space-filling. The meaning of |
| 13220 | * the exported mark prototypes changes slightly in the space-filling |
| 13221 | * implementation:<ul> |
| 13222 | * |
| 13223 | * <li><tt>node</tt> - for rendering nodes; typically a {@link pv.Bar} for |
| 13224 | * non-radial orientations, and a {@link pv.Wedge} for radial orientations. |
| 13225 | * |
| 13226 | * <p><li><tt>link</tt> - unsupported; undefined. Links are encoded implicitly |
| 13227 | * in the arrangement of the space-filling nodes. |
| 13228 | * |
| 13229 | * <p><li><tt>label</tt> - for rendering node labels; typically a |
| 13230 | * {@link pv.Label}. |
| 13231 | * |
| 13232 | * </ul>For more details on how to use this layout, see |
| 13233 | * {@link pv.Layout.Partition}. |
| 13234 | * |
| 13235 | * @extends pv.Layout.Partition |
| 13236 | */ |
| 13237 | pv.Layout.Partition.Fill = function() { |
| 13238 | pv.Layout.Partition.call(this); |
| 13239 | pv.Layout.Hierarchy.Fill.constructor.call(this); |
| 13240 | }; |
| 13241 | |
| 13242 | pv.Layout.Partition.Fill.prototype = pv.extend(pv.Layout.Partition); |
| 13243 | |
| 13244 | /** @private */ |
| 13245 | pv.Layout.Partition.Fill.prototype.buildImplied = function(s) { |
| 13246 | if (pv.Layout.Partition.prototype.buildImplied.call(this, s)) return; |
| 13247 | pv.Layout.Hierarchy.Fill.buildImplied.call(this, s); |
| 13248 | }; |
| 13249 | /** |
| 13250 | * Constructs a new, empty arc layout. Layouts are not typically constructed |
| 13251 | * directly; instead, they are added to an existing panel via |
| 13252 | * {@link pv.Mark#add}. |
| 13253 | * |
| 13254 | * @class Implements a layout for arc diagrams. An arc diagram is a network |
| 13255 | * visualization with a one-dimensional layout of nodes, using circular arcs to |
| 13256 | * render links between nodes. For undirected networks, arcs are rendering on a |
| 13257 | * single side; this makes arc diagrams useful as annotations to other |
| 13258 | * two-dimensional network layouts, such as rollup, matrix or table layouts. For |
| 13259 | * directed networks, links in opposite directions can be rendered on opposite |
| 13260 | * sides using <tt>directed(true)</tt>. |
| 13261 | * |
| 13262 | * <p>Arc layouts are particularly sensitive to node ordering; for best results, |
| 13263 | * order the nodes such that related nodes are close to each other. A poor |
| 13264 | * (e.g., random) order may result in large arcs with crossovers that impede |
| 13265 | * visual processing. A future improvement to this layout may include automatic |
| 13266 | * reordering using, e.g., spectral graph layout or simulated annealing. |
| 13267 | * |
| 13268 | * <p>This visualization technique is related to that developed by |
| 13269 | * M. Wattenberg, <a |
| 13270 | * href="http://www.research.ibm.com/visual/papers/arc-diagrams.pdf">"Arc |
| 13271 | * Diagrams: Visualizing Structure in Strings"</a> in <i>IEEE InfoVis</i>, 2002. |
| 13272 | * However, this implementation is limited to simple node-link networks, as |
| 13273 | * opposed to structures with hierarchical self-similarity (such as strings). |
| 13274 | * |
| 13275 | * <p>As with other network layouts, three mark prototypes are provided:<ul> |
| 13276 | * |
| 13277 | * <li><tt>node</tt> - for rendering nodes; typically a {@link pv.Dot}. |
| 13278 | * <li><tt>link</tt> - for rendering links; typically a {@link pv.Line}. |
| 13279 | * <li><tt>label</tt> - for rendering node labels; typically a {@link pv.Label}. |
| 13280 | * |
| 13281 | * </ul>For more details on how this layout is structured and can be customized, |
| 13282 | * see {@link pv.Layout.Network}. |
| 13283 | * |
| 13284 | * @extends pv.Layout.Network |
| 13285 | **/ |
| 13286 | pv.Layout.Arc = function() { |
| 13287 | pv.Layout.Network.call(this); |
| 13288 | var interpolate, // cached interpolate |
| 13289 | directed, // cached directed |
| 13290 | reverse, // cached reverse |
| 13291 | buildImplied = this.buildImplied; |
| 13292 | |
| 13293 | /** @private Cache layout state to optimize properties. */ |
| 13294 | this.buildImplied = function(s) { |
| 13295 | buildImplied.call(this, s); |
| 13296 | directed = s.directed; |
| 13297 | interpolate = s.orient == "radial" ? "linear" : "polar"; |
| 13298 | reverse = s.orient == "right" || s.orient == "top"; |
| 13299 | }; |
| 13300 | |
| 13301 | /* Override link properties to handle directedness and orientation. */ |
| 13302 | this.link |
| 13303 | .data(function(p) { |
| 13304 | var s = p.sourceNode, t = p.targetNode; |
| 13305 | return reverse != (directed || (s.breadth < t.breadth)) ? [s, t] : [t, s]; |
| 13306 | }) |
| 13307 | .interpolate(function() { return interpolate; }); |
| 13308 | }; |
| 13309 | |
| 13310 | pv.Layout.Arc.prototype = pv.extend(pv.Layout.Network) |
| 13311 | .property("orient", String) |
| 13312 | .property("directed", Boolean); |
| 13313 | |
| 13314 | /** |
| 13315 | * Default properties for arc layouts. By default, the orientation is "bottom". |
| 13316 | * |
| 13317 | * @type pv.Layout.Arc |
| 13318 | */ |
| 13319 | pv.Layout.Arc.prototype.defaults = new pv.Layout.Arc() |
| 13320 | .extend(pv.Layout.Network.prototype.defaults) |
| 13321 | .orient("bottom"); |
| 13322 | |
| 13323 | /** |
| 13324 | * Specifies an optional sort function. The sort function follows the same |
| 13325 | * comparator contract required by {@link pv.Dom.Node#sort}. Specifying a sort |
| 13326 | * function provides an alternative to sort the nodes as they are specified by |
| 13327 | * the <tt>nodes</tt> property; the main advantage of doing this is that the |
| 13328 | * comparator function can access implicit fields populated by the network |
| 13329 | * layout, such as the <tt>linkDegree</tt>. |
| 13330 | * |
| 13331 | * <p>Note that arc diagrams are particularly sensitive to order. This is |
| 13332 | * referred to as the seriation problem, and many different techniques exist to |
| 13333 | * find good node orders that emphasize clusters, such as spectral layout and |
| 13334 | * simulated annealing. |
| 13335 | * |
| 13336 | * @param {function} f comparator function for nodes. |
| 13337 | * @returns {pv.Layout.Arc} this. |
| 13338 | */ |
| 13339 | pv.Layout.Arc.prototype.sort = function(f) { |
| 13340 | this.$sort = f; |
| 13341 | return this; |
| 13342 | }; |
| 13343 | |
| 13344 | /** @private Populates the x, y and angle attributes on the nodes. */ |
| 13345 | pv.Layout.Arc.prototype.buildImplied = function(s) { |
| 13346 | if (pv.Layout.Network.prototype.buildImplied.call(this, s)) return; |
| 13347 | |
| 13348 | var nodes = s.nodes, |
| 13349 | orient = s.orient, |
| 13350 | sort = this.$sort, |
| 13351 | index = pv.range(nodes.length), |
| 13352 | w = s.width, |
| 13353 | h = s.height, |
| 13354 | r = Math.min(w, h) / 2; |
| 13355 | |
| 13356 | /* Sort the nodes. */ |
| 13357 | if (sort) index.sort(function(a, b) { return sort(nodes[a], nodes[b]); }); |
| 13358 | |
| 13359 | /** @private Returns the mid-angle, given the breadth. */ |
| 13360 | function midAngle(b) { |
| 13361 | switch (orient) { |
| 13362 | case "top": return -Math.PI / 2; |
| 13363 | case "bottom": return Math.PI / 2; |
| 13364 | case "left": return Math.PI; |
| 13365 | case "right": return 0; |
| 13366 | case "radial": return (b - .25) * 2 * Math.PI; |
| 13367 | } |
| 13368 | } |
| 13369 | |
| 13370 | /** @private Returns the x-position, given the breadth. */ |
| 13371 | function x(b) { |
| 13372 | switch (orient) { |
| 13373 | case "top": |
| 13374 | case "bottom": return b * w; |
| 13375 | case "left": return 0; |
| 13376 | case "right": return w; |
| 13377 | case "radial": return w / 2 + r * Math.cos(midAngle(b)); |
| 13378 | } |
| 13379 | } |
| 13380 | |
| 13381 | /** @private Returns the y-position, given the breadth. */ |
| 13382 | function y(b) { |
| 13383 | switch (orient) { |
| 13384 | case "top": return 0; |
| 13385 | case "bottom": return h; |
| 13386 | case "left": |
| 13387 | case "right": return b * h; |
| 13388 | case "radial": return h / 2 + r * Math.sin(midAngle(b)); |
| 13389 | } |
| 13390 | } |
| 13391 | |
| 13392 | /* Populate the x, y and mid-angle attributes. */ |
| 13393 | for (var i = 0; i < nodes.length; i++) { |
| 13394 | var n = nodes[index[i]], b = n.breadth = (i + .5) / nodes.length; |
| 13395 | n.x = x(b); |
| 13396 | n.y = y(b); |
| 13397 | n.midAngle = midAngle(b); |
| 13398 | } |
| 13399 | }; |
| 13400 | |
| 13401 | /** |
| 13402 | * The orientation. The default orientation is "left", which means that nodes |
| 13403 | * will be positioned from left-to-right in the order they are specified in the |
| 13404 | * <tt>nodes</tt> property. The following orientations are supported:<ul> |
| 13405 | * |
| 13406 | * <li>left - left-to-right. |
| 13407 | * <li>right - right-to-left. |
| 13408 | * <li>top - top-to-bottom. |
| 13409 | * <li>bottom - bottom-to-top. |
| 13410 | * <li>radial - radially, starting at 12 o'clock and proceeding clockwise.</ul> |
| 13411 | * |
| 13412 | * @type string |
| 13413 | * @name pv.Layout.Arc.prototype.orient |
| 13414 | */ |
| 13415 | |
| 13416 | /** |
| 13417 | * Whether this arc digram is directed (bidirectional); only applies to |
| 13418 | * non-radial orientations. By default, arc digrams are undirected, such that |
| 13419 | * all arcs appear on one side. If the arc digram is directed, then forward |
| 13420 | * links are drawn on the conventional side (the same as as undirected |
| 13421 | * links--right, left, bottom and top for left, right, top and bottom, |
| 13422 | * respectively), while reverse links are drawn on the opposite side. |
| 13423 | * |
| 13424 | * @type boolean |
| 13425 | * @name pv.Layout.Arc.prototype.directed |
| 13426 | */ |
| 13427 | /** |
| 13428 | * Constructs a new, empty horizon layout. Layouts are not typically constructed |
| 13429 | * directly; instead, they are added to an existing panel via |
| 13430 | * {@link pv.Mark#add}. |
| 13431 | * |
| 13432 | * @class Implements a horizon layout, which is a variation of a single-series |
| 13433 | * area chart where the area is folded into multiple bands. Color is used to |
| 13434 | * encode band, allowing the size of the chart to be reduced significantly |
| 13435 | * without impeding readability. This layout algorithm is based on the work of |
| 13436 | * J. Heer, N. Kong and M. Agrawala in <a |
| 13437 | * href="http://hci.stanford.edu/publications/2009/heer-horizon-chi09.pdf">"Sizing |
| 13438 | * the Horizon: The Effects of Chart Size and Layering on the Graphical |
| 13439 | * Perception of Time Series Visualizations"</a>, CHI 2009. |
| 13440 | * |
| 13441 | * <p>This layout exports a single <tt>band</tt> mark prototype, which is |
| 13442 | * intended to be used with an area mark. The band mark is contained in a panel |
| 13443 | * which is replicated per band (and for negative/positive bands). For example, |
| 13444 | * to create a simple horizon graph given an array of numbers: |
| 13445 | * |
| 13446 | * <pre>vis.add(pv.Layout.Horizon) |
| 13447 | * .bands(n) |
| 13448 | * .band.add(pv.Area) |
| 13449 | * .data(data) |
| 13450 | * .left(function() this.index * 35) |
| 13451 | * .height(function(d) d * 40);</pre> |
| 13452 | * |
| 13453 | * The layout can be further customized by changing the number of bands, and |
| 13454 | * toggling whether the negative bands are mirrored or offset. (See the |
| 13455 | * above-referenced paper for guidance.) |
| 13456 | * |
| 13457 | * <p>The <tt>fillStyle</tt> of the area can be overridden, though typically it |
| 13458 | * is easier to customize the layout's behavior through the custom |
| 13459 | * <tt>backgroundStyle</tt>, <tt>positiveStyle</tt> and <tt>negativeStyle</tt> |
| 13460 | * properties. By default, the background is white, positive bands are blue, and |
| 13461 | * negative bands are red. For the most accurate presentation, use fully-opaque |
| 13462 | * colors of equal intensity for the negative and positive bands. |
| 13463 | * |
| 13464 | * @extends pv.Layout |
| 13465 | */ |
| 13466 | pv.Layout.Horizon = function() { |
| 13467 | pv.Layout.call(this); |
| 13468 | var that = this, |
| 13469 | bands, // cached bands |
| 13470 | mode, // cached mode |
| 13471 | size, // cached height |
| 13472 | fill, // cached background style |
| 13473 | red, // cached negative color (ramp) |
| 13474 | blue, // cached positive color (ramp) |
| 13475 | buildImplied = this.buildImplied; |
| 13476 | |
| 13477 | /** @private Cache the layout state to optimize properties. */ |
| 13478 | this.buildImplied = function(s) { |
| 13479 | buildImplied.call(this, s); |
| 13480 | bands = s.bands; |
| 13481 | mode = s.mode; |
| 13482 | size = Math.round((mode == "color" ? .5 : 1) * s.height); |
| 13483 | fill = s.backgroundStyle; |
| 13484 | red = pv.ramp(fill, s.negativeStyle).domain(0, bands); |
| 13485 | blue = pv.ramp(fill, s.positiveStyle).domain(0, bands); |
| 13486 | }; |
| 13487 | |
| 13488 | var bands = new pv.Panel() |
| 13489 | .data(function() { return pv.range(bands * 2); }) |
| 13490 | .overflow("hidden") |
| 13491 | .height(function() { return size; }) |
| 13492 | .top(function(i) { return mode == "color" ? (i & 1) * size : 0; }) |
| 13493 | .fillStyle(function(i) { return i ? null : fill; }); |
| 13494 | |
| 13495 | /** |
| 13496 | * The band prototype. This prototype is intended to be used with an Area |
| 13497 | * mark to render the horizon bands. |
| 13498 | * |
| 13499 | * @type pv.Mark |
| 13500 | * @name pv.Layout.Horizon.prototype.band |
| 13501 | */ |
| 13502 | this.band = new pv.Mark() |
| 13503 | .top(function(d, i) { |
| 13504 | return mode == "mirror" && i & 1 |
| 13505 | ? (i + 1 >> 1) * size |
| 13506 | : null; |
| 13507 | }) |
| 13508 | .bottom(function(d, i) { |
| 13509 | return mode == "mirror" |
| 13510 | ? (i & 1 ? null : (i + 1 >> 1) * -size) |
| 13511 | : ((i & 1 || -1) * (i + 1 >> 1) * size); |
| 13512 | }) |
| 13513 | .fillStyle(function(d, i) { |
| 13514 | return (i & 1 ? red : blue)((i >> 1) + 1); |
| 13515 | }); |
| 13516 | |
| 13517 | this.band.add = function(type) { |
| 13518 | return that.add(pv.Panel).extend(bands).add(type).extend(this); |
| 13519 | }; |
| 13520 | }; |
| 13521 | |
| 13522 | pv.Layout.Horizon.prototype = pv.extend(pv.Layout) |
| 13523 | .property("bands", Number) |
| 13524 | .property("mode", String) |
| 13525 | .property("backgroundStyle", pv.color) |
| 13526 | .property("positiveStyle", pv.color) |
| 13527 | .property("negativeStyle", pv.color); |
| 13528 | |
| 13529 | /** |
| 13530 | * Default properties for horizon layouts. By default, there are two bands, the |
| 13531 | * mode is "offset", the background style is "white", the positive style is |
| 13532 | * blue, negative style is red. |
| 13533 | * |
| 13534 | * @type pv.Layout.Horizon |
| 13535 | */ |
| 13536 | pv.Layout.Horizon.prototype.defaults = new pv.Layout.Horizon() |
| 13537 | .extend(pv.Layout.prototype.defaults) |
| 13538 | .bands(2) |
| 13539 | .mode("offset") |
| 13540 | .backgroundStyle("white") |
| 13541 | .positiveStyle("#1f77b4") |
| 13542 | .negativeStyle("#d62728"); |
| 13543 | |
| 13544 | /** |
| 13545 | * The horizon mode: offset, mirror, or color. The default is "offset". |
| 13546 | * |
| 13547 | * @type string |
| 13548 | * @name pv.Layout.Horizon.prototype.mode |
| 13549 | */ |
| 13550 | |
| 13551 | /** |
| 13552 | * The number of bands. Must be at least one. The default value is two. |
| 13553 | * |
| 13554 | * @type number |
| 13555 | * @name pv.Layout.Horizon.prototype.bands |
| 13556 | */ |
| 13557 | |
| 13558 | /** |
| 13559 | * The positive band color; if non-null, the interior of positive bands are |
| 13560 | * filled with the specified color. The default value of this property is blue. |
| 13561 | * For accurate blending, this color should be fully opaque. |
| 13562 | * |
| 13563 | * @type pv.Color |
| 13564 | * @name pv.Layout.Horizon.prototype.positiveStyle |
| 13565 | */ |
| 13566 | |
| 13567 | /** |
| 13568 | * The negative band color; if non-null, the interior of negative bands are |
| 13569 | * filled with the specified color. The default value of this property is red. |
| 13570 | * For accurate blending, this color should be fully opaque. |
| 13571 | * |
| 13572 | * @type pv.Color |
| 13573 | * @name pv.Layout.Horizon.prototype.negativeStyle |
| 13574 | */ |
| 13575 | |
| 13576 | /** |
| 13577 | * The background color. The panel background is filled with the specified |
| 13578 | * color, and the negative and positive bands are filled with an interpolated |
| 13579 | * color between this color and the respective band color. The default value of |
| 13580 | * this property is white. For accurate blending, this color should be fully |
| 13581 | * opaque. |
| 13582 | * |
| 13583 | * @type pv.Color |
| 13584 | * @name pv.Layout.Horizon.prototype.backgroundStyle |
| 13585 | */ |
| 13586 | /** |
| 13587 | * Constructs a new, empty rollup network layout. Layouts are not typically |
| 13588 | * constructed directly; instead, they are added to an existing panel via |
| 13589 | * {@link pv.Mark#add}. |
| 13590 | * |
| 13591 | * @class Implements a network visualization using a node-link diagram where |
| 13592 | * nodes are rolled up along two dimensions. This implementation is based on the |
| 13593 | * "PivotGraph" designed by Martin Wattenberg: |
| 13594 | * |
| 13595 | * <blockquote>The method is designed for graphs that are "multivariate", i.e., |
| 13596 | * where each node is associated with several attributes. Unlike visualizations |
| 13597 | * which emphasize global graph topology, PivotGraph uses a simple grid-based |
| 13598 | * approach to focus on the relationship between node attributes & |
| 13599 | * connections.</blockquote> |
| 13600 | * |
| 13601 | * This layout requires two psuedo-properties to be specified, which assign node |
| 13602 | * positions along the two dimensions {@link #x} and {@link #y}, corresponding |
| 13603 | * to the left and top properties, respectively. Typically, these functions are |
| 13604 | * specified using an {@link pv.Scale.ordinal}. Nodes that share the same |
| 13605 | * position in <i>x</i> and <i>y</i> are "rolled up" into a meta-node, and |
| 13606 | * similarly links are aggregated between meta-nodes. For example, to construct |
| 13607 | * a rollup to analyze links by gender and affiliation, first define two ordinal |
| 13608 | * scales: |
| 13609 | * |
| 13610 | * <pre>var x = pv.Scale.ordinal(nodes, function(d) d.gender).split(0, w), |
| 13611 | * y = pv.Scale.ordinal(nodes, function(d) d.aff).split(0, h);</pre> |
| 13612 | * |
| 13613 | * Next, define the position psuedo-properties: |
| 13614 | * |
| 13615 | * <pre> .x(function(d) x(d.gender)) |
| 13616 | * .y(function(d) y(d.aff))</pre> |
| 13617 | * |
| 13618 | * Linear and other quantitative scales can alternatively be used to position |
| 13619 | * the nodes along either dimension. Note, however, that the rollup requires |
| 13620 | * that the positions match exactly, and thus ordinal scales are recommended to |
| 13621 | * avoid precision errors. |
| 13622 | * |
| 13623 | * <p>Note that because this layout provides a visualization of the rolled up |
| 13624 | * graph, the data properties for the mark prototypes (<tt>node</tt>, |
| 13625 | * <tt>link</tt> and <tt>label</tt>) are different from most other network |
| 13626 | * layouts: they reference the rolled-up nodes and links, rather than the nodes |
| 13627 | * and links of the full network. The underlying nodes and links for each |
| 13628 | * rolled-up node and link can be accessed via the <tt>nodes</tt> and |
| 13629 | * <tt>links</tt> attributes, respectively. The aggregated link values for |
| 13630 | * rolled-up links can similarly be accessed via the <tt>linkValue</tt> |
| 13631 | * attribute. |
| 13632 | * |
| 13633 | * <p>For undirected networks, links are duplicated in both directions. For |
| 13634 | * directed networks, use <tt>directed(true)</tt>. The graph is assumed to be |
| 13635 | * undirected by default. |
| 13636 | * |
| 13637 | * @extends pv.Layout.Network |
| 13638 | * @see <a href="http://www.research.ibm.com/visual/papers/pivotgraph.pdf" |
| 13639 | * >"Visual Exploration of Multivariate Graphs"</a> by M. Wattenberg, CHI 2006. |
| 13640 | */ |
| 13641 | pv.Layout.Rollup = function() { |
| 13642 | pv.Layout.Network.call(this); |
| 13643 | var that = this, |
| 13644 | nodes, // cached rollup nodes |
| 13645 | links, // cached rollup links |
| 13646 | buildImplied = that.buildImplied; |
| 13647 | |
| 13648 | /** @private Cache layout state to optimize properties. */ |
| 13649 | this.buildImplied = function(s) { |
| 13650 | buildImplied.call(this, s); |
| 13651 | nodes = s.$rollup.nodes; |
| 13652 | links = s.$rollup.links; |
| 13653 | }; |
| 13654 | |
| 13655 | /* Render rollup nodes. */ |
| 13656 | this.node |
| 13657 | .data(function() { return nodes; }) |
| 13658 | .size(function(d) { return d.nodes.length * 20; }); |
| 13659 | |
| 13660 | /* Render rollup links. */ |
| 13661 | this.link |
| 13662 | .interpolate("polar") |
| 13663 | .eccentricity(.8); |
| 13664 | |
| 13665 | this.link.add = function(type) { |
| 13666 | return that.add(pv.Panel) |
| 13667 | .data(function() { return links; }) |
| 13668 | .add(type) |
| 13669 | .extend(this); |
| 13670 | }; |
| 13671 | }; |
| 13672 | |
| 13673 | pv.Layout.Rollup.prototype = pv.extend(pv.Layout.Network) |
| 13674 | .property("directed", Boolean); |
| 13675 | |
| 13676 | /** |
| 13677 | * Whether the underlying network is directed. By default, the graph is assumed |
| 13678 | * to be undirected, and links are rendered in both directions. If the network |
| 13679 | * is directed, then forward links are drawn above the diagonal, while reverse |
| 13680 | * links are drawn below. |
| 13681 | * |
| 13682 | * @type boolean |
| 13683 | * @name pv.Layout.Rollup.prototype.directed |
| 13684 | */ |
| 13685 | |
| 13686 | /** |
| 13687 | * Specifies the <i>x</i>-position function used to rollup nodes. The rolled up |
| 13688 | * nodes are positioned horizontally using the return values from the given |
| 13689 | * function. Typically the function is specified as an ordinal scale. For |
| 13690 | * single-dimension rollups, a constant value can be specified. |
| 13691 | * |
| 13692 | * @param {function} f the <i>x</i>-position function. |
| 13693 | * @returns {pv.Layout.Rollup} this. |
| 13694 | * @see pv.Scale.ordinal |
| 13695 | */ |
| 13696 | pv.Layout.Rollup.prototype.x = function(f) { |
| 13697 | this.$x = pv.functor(f); |
| 13698 | return this; |
| 13699 | }; |
| 13700 | |
| 13701 | /** |
| 13702 | * Specifies the <i>y</i>-position function used to rollup nodes. The rolled up |
| 13703 | * nodes are positioned vertically using the return values from the given |
| 13704 | * function. Typically the function is specified as an ordinal scale. For |
| 13705 | * single-dimension rollups, a constant value can be specified. |
| 13706 | * |
| 13707 | * @param {function} f the <i>y</i>-position function. |
| 13708 | * @returns {pv.Layout.Rollup} this. |
| 13709 | * @see pv.Scale.ordinal |
| 13710 | */ |
| 13711 | pv.Layout.Rollup.prototype.y = function(f) { |
| 13712 | this.$y = pv.functor(f); |
| 13713 | return this; |
| 13714 | }; |
| 13715 | |
| 13716 | /** @private */ |
| 13717 | pv.Layout.Rollup.prototype.buildImplied = function(s) { |
| 13718 | if (pv.Layout.Network.prototype.buildImplied.call(this, s)) return; |
| 13719 | |
| 13720 | var nodes = s.nodes, |
| 13721 | links = s.links, |
| 13722 | directed = s.directed, |
| 13723 | n = nodes.length, |
| 13724 | x = [], |
| 13725 | y = [], |
| 13726 | rnindex = 0, |
| 13727 | rnodes = {}, |
| 13728 | rlinks = {}; |
| 13729 | |
| 13730 | /** @private */ |
| 13731 | function id(i) { |
| 13732 | return x[i] + "," + y[i]; |
| 13733 | } |
| 13734 | |
| 13735 | /* Iterate over the data, evaluating the x and y functions. */ |
| 13736 | var stack = pv.Mark.stack, o = {parent: this}; |
| 13737 | stack.unshift(null); |
| 13738 | for (var i = 0; i < n; i++) { |
| 13739 | o.index = i; |
| 13740 | stack[0] = nodes[i]; |
| 13741 | x[i] = this.$x.apply(o, stack); |
| 13742 | y[i] = this.$y.apply(o, stack); |
| 13743 | } |
| 13744 | stack.shift(); |
| 13745 | |
| 13746 | /* Compute rollup nodes. */ |
| 13747 | for (var i = 0; i < nodes.length; i++) { |
| 13748 | var nodeId = id(i), |
| 13749 | rn = rnodes[nodeId]; |
| 13750 | if (!rn) { |
| 13751 | rn = rnodes[nodeId] = pv.extend(nodes[i]); |
| 13752 | rn.index = rnindex++; |
| 13753 | rn.x = x[i]; |
| 13754 | rn.y = y[i]; |
| 13755 | rn.nodes = []; |
| 13756 | } |
| 13757 | rn.nodes.push(nodes[i]); |
| 13758 | } |
| 13759 | |
| 13760 | /* Compute rollup links. */ |
| 13761 | for (var i = 0; i < links.length; i++) { |
| 13762 | var source = links[i].sourceNode, |
| 13763 | target = links[i].targetNode, |
| 13764 | rsource = rnodes[id(source.index)], |
| 13765 | rtarget = rnodes[id(target.index)], |
| 13766 | reverse = !directed && rsource.index > rtarget.index, |
| 13767 | linkId = reverse |
| 13768 | ? rtarget.index + "," + rsource.index |
| 13769 | : rsource.index + "," + rtarget.index, |
| 13770 | rl = rlinks[linkId]; |
| 13771 | if (!rl) { |
| 13772 | rl = rlinks[linkId] = { |
| 13773 | sourceNode: rsource, |
| 13774 | targetNode: rtarget, |
| 13775 | linkValue: 0, |
| 13776 | links: [] |
| 13777 | }; |
| 13778 | } |
| 13779 | rl.links.push(links[i]); |
| 13780 | rl.linkValue += links[i].linkValue; |
| 13781 | } |
| 13782 | |
| 13783 | /* Export the rolled up nodes and links to the scene. */ |
| 13784 | s.$rollup = { |
| 13785 | nodes: pv.values(rnodes), |
| 13786 | links: pv.values(rlinks) |
| 13787 | }; |
| 13788 | }; |
| 13789 | /** |
| 13790 | * Constructs a new, empty matrix network layout. Layouts are not typically |
| 13791 | * constructed directly; instead, they are added to an existing panel via |
| 13792 | * {@link pv.Mark#add}. |
| 13793 | * |
| 13794 | * @class Implements a network visualization using a matrix view. This is, in |
| 13795 | * effect, a visualization of the graph's <i>adjacency matrix</i>: the cell at |
| 13796 | * row <i>i</i>, column <i>j</i>, corresponds to the link from node <i>i</i> to |
| 13797 | * node <i>j</i>. The fill color of each cell is binary by default, and |
| 13798 | * corresponds to whether a link exists between the two nodes. If the underlying |
| 13799 | * graph has links with variable values, the <tt>fillStyle</tt> property can be |
| 13800 | * substited to use an appropriate color function, such as {@link pv.ramp}. |
| 13801 | * |
| 13802 | * <p>For undirected networks, the matrix is symmetric around the diagonal. For |
| 13803 | * directed networks, links in opposite directions can be rendered on opposite |
| 13804 | * sides of the diagonal using <tt>directed(true)</tt>. The graph is assumed to |
| 13805 | * be undirected by default. |
| 13806 | * |
| 13807 | * <p>The mark prototypes for this network layout are slightly different than |
| 13808 | * other implementations:<ul> |
| 13809 | * |
| 13810 | * <li><tt>node</tt> - unsupported; undefined. No mark is needed to visualize |
| 13811 | * nodes directly, as the nodes are implicit in the location (rows and columns) |
| 13812 | * of the links. |
| 13813 | * |
| 13814 | * <p><li><tt>link</tt> - for rendering links; typically a {@link pv.Bar}. The |
| 13815 | * link mark is added directly to the layout, with the data property defined as |
| 13816 | * all possible pairs of nodes. Each pair is represented as a |
| 13817 | * {@link pv.Network.Layout.Link}, though the <tt>linkValue</tt> attribute may |
| 13818 | * be 0 if no link exists in the graph. |
| 13819 | * |
| 13820 | * <p><li><tt>label</tt> - for rendering node labels; typically a |
| 13821 | * {@link pv.Label}. The label mark is added directly to the layout, with the |
| 13822 | * data property defined via the layout's <tt>nodes</tt> property; note, |
| 13823 | * however, that the nodes are duplicated so as to provide a label across the |
| 13824 | * top and down the side. Properties such as <tt>strokeStyle</tt> and |
| 13825 | * <tt>fillStyle</tt> can be overridden to compute properties from node data |
| 13826 | * dynamically. |
| 13827 | * |
| 13828 | * </ul>For more details on how to use this layout, see |
| 13829 | * {@link pv.Layout.Network}. |
| 13830 | * |
| 13831 | * @extends pv.Layout.Network |
| 13832 | */ |
| 13833 | pv.Layout.Matrix = function() { |
| 13834 | pv.Layout.Network.call(this); |
| 13835 | var that = this, |
| 13836 | n, // cached matrix size |
| 13837 | dx, // cached cell width |
| 13838 | dy, // cached cell height |
| 13839 | labels, // cached labels (array of strings) |
| 13840 | pairs, // cached pairs (array of links) |
| 13841 | buildImplied = that.buildImplied; |
| 13842 | |
| 13843 | /** @private Cache layout state to optimize properties. */ |
| 13844 | this.buildImplied = function(s) { |
| 13845 | buildImplied.call(this, s); |
| 13846 | n = s.nodes.length; |
| 13847 | dx = s.width / n; |
| 13848 | dy = s.height / n; |
| 13849 | labels = s.$matrix.labels; |
| 13850 | pairs = s.$matrix.pairs; |
| 13851 | }; |
| 13852 | |
| 13853 | /* Links are all pairs of nodes. */ |
| 13854 | this.link |
| 13855 | .data(function() { return pairs; }) |
| 13856 | .left(function() { return dx * (this.index % n); }) |
| 13857 | .top(function() { return dy * Math.floor(this.index / n); }) |
| 13858 | .width(function() { return dx; }) |
| 13859 | .height(function() { return dy; }) |
| 13860 | .lineWidth(1.5) |
| 13861 | .strokeStyle("#fff") |
| 13862 | .fillStyle(function(l) { return l.linkValue ? "#555" : "#eee"; }) |
| 13863 | .parent = this; |
| 13864 | |
| 13865 | /* No special add for links! */ |
| 13866 | delete this.link.add; |
| 13867 | |
| 13868 | /* Labels are duplicated for top & left. */ |
| 13869 | this.label |
| 13870 | .data(function() { return labels; }) |
| 13871 | .left(function() { return this.index & 1 ? dx * ((this.index >> 1) + .5) : null; }) |
| 13872 | .top(function() { return this.index & 1 ? null : dy * ((this.index >> 1) + .5); }) |
| 13873 | .textMargin(4) |
| 13874 | .textAlign(function() { return this.index & 1 ? "left" : "right"; }) |
| 13875 | .textAngle(function() { return this.index & 1 ? -Math.PI / 2 : 0; }); |
| 13876 | |
| 13877 | /* The node mark is unused. */ |
| 13878 | delete this.node; |
| 13879 | }; |
| 13880 | |
| 13881 | pv.Layout.Matrix.prototype = pv.extend(pv.Layout.Network) |
| 13882 | .property("directed", Boolean); |
| 13883 | |
| 13884 | /** |
| 13885 | * Whether this matrix visualization is directed (bidirectional). By default, |
| 13886 | * the graph is assumed to be undirected, such that the visualization is |
| 13887 | * symmetric across the matrix diagonal. If the network is directed, then |
| 13888 | * forward links are drawn above the diagonal, while reverse links are drawn |
| 13889 | * below. |
| 13890 | * |
| 13891 | * @type boolean |
| 13892 | * @name pv.Layout.Matrix.prototype.directed |
| 13893 | */ |
| 13894 | |
| 13895 | /** |
| 13896 | * Specifies an optional sort function. The sort function follows the same |
| 13897 | * comparator contract required by {@link pv.Dom.Node#sort}. Specifying a sort |
| 13898 | * function provides an alternative to sort the nodes as they are specified by |
| 13899 | * the <tt>nodes</tt> property; the main advantage of doing this is that the |
| 13900 | * comparator function can access implicit fields populated by the network |
| 13901 | * layout, such as the <tt>linkDegree</tt>. |
| 13902 | * |
| 13903 | * <p>Note that matrix visualizations are particularly sensitive to order. This |
| 13904 | * is referred to as the seriation problem, and many different techniques exist |
| 13905 | * to find good node orders that emphasize clusters, such as spectral layout and |
| 13906 | * simulated annealing. |
| 13907 | * |
| 13908 | * @param {function} f comparator function for nodes. |
| 13909 | * @returns {pv.Layout.Matrix} this. |
| 13910 | */ |
| 13911 | pv.Layout.Matrix.prototype.sort = function(f) { |
| 13912 | this.$sort = f; |
| 13913 | return this; |
| 13914 | }; |
| 13915 | |
| 13916 | /** @private */ |
| 13917 | pv.Layout.Matrix.prototype.buildImplied = function(s) { |
| 13918 | if (pv.Layout.Network.prototype.buildImplied.call(this, s)) return; |
| 13919 | |
| 13920 | var nodes = s.nodes, |
| 13921 | links = s.links, |
| 13922 | sort = this.$sort, |
| 13923 | n = nodes.length, |
| 13924 | index = pv.range(n), |
| 13925 | labels = [], |
| 13926 | pairs = [], |
| 13927 | map = {}; |
| 13928 | |
| 13929 | s.$matrix = {labels: labels, pairs: pairs}; |
| 13930 | |
| 13931 | /* Sort the nodes. */ |
| 13932 | if (sort) index.sort(function(a, b) { return sort(nodes[a], nodes[b]); }); |
| 13933 | |
| 13934 | /* Create pairs. */ |
| 13935 | for (var i = 0; i < n; i++) { |
| 13936 | for (var j = 0; j < n; j++) { |
| 13937 | var a = index[i], |
| 13938 | b = index[j], |
| 13939 | p = { |
| 13940 | row: i, |
| 13941 | col: j, |
| 13942 | sourceNode: nodes[a], |
| 13943 | targetNode: nodes[b], |
| 13944 | linkValue: 0 |
| 13945 | }; |
| 13946 | pairs.push(map[a + "." + b] = p); |
| 13947 | } |
| 13948 | } |
| 13949 | |
| 13950 | /* Create labels. */ |
| 13951 | for (var i = 0; i < n; i++) { |
| 13952 | var a = index[i]; |
| 13953 | labels.push(nodes[a], nodes[a]); |
| 13954 | } |
| 13955 | |
| 13956 | /* Accumulate link values. */ |
| 13957 | for (var i = 0; i < links.length; i++) { |
| 13958 | var l = links[i], |
| 13959 | source = l.sourceNode.index, |
| 13960 | target = l.targetNode.index, |
| 13961 | value = l.linkValue; |
| 13962 | map[source + "." + target].linkValue += value; |
| 13963 | if (!s.directed) map[target + "." + source].linkValue += value; |
| 13964 | } |
| 13965 | }; |
| 13966 | // ranges (bad, satisfactory, good) |
| 13967 | // measures (actual, forecast) |
| 13968 | // markers (previous, goal) |
| 13969 | |
| 13970 | /* |
| 13971 | * Chart design based on the recommendations of Stephen Few. Implementation |
| 13972 | * based on the work of Clint Ivy, Jamie Love, and Jason Davies. |
| 13973 | * http://projects.instantcognition.com/protovis/bulletchart/ |
| 13974 | */ |
| 13975 | |
| 13976 | /** |
| 13977 | * Constructs a new, empty bullet layout. Layouts are not typically constructed |
| 13978 | * directly; instead, they are added to an existing panel via |
| 13979 | * {@link pv.Mark#add}. |
| 13980 | * |
| 13981 | * @class |
| 13982 | * @extends pv.Layout |
| 13983 | */ |
| 13984 | pv.Layout.Bullet = function() { |
| 13985 | pv.Layout.call(this); |
| 13986 | var that = this, |
| 13987 | buildImplied = that.buildImplied, |
| 13988 | scale = that.x = pv.Scale.linear(), |
| 13989 | orient, |
| 13990 | horizontal, |
| 13991 | rangeColor, |
| 13992 | measureColor, |
| 13993 | x; |
| 13994 | |
| 13995 | /** @private Cache layout state to optimize properties. */ |
| 13996 | this.buildImplied = function(s) { |
| 13997 | buildImplied.call(this, x = s); |
| 13998 | orient = s.orient; |
| 13999 | horizontal = /^left|right$/.test(orient); |
| 14000 | rangeColor = pv.ramp("#bbb", "#eee") |
| 14001 | .domain(0, Math.max(1, x.ranges.length - 1)); |
| 14002 | measureColor = pv.ramp("steelblue", "lightsteelblue") |
| 14003 | .domain(0, Math.max(1, x.measures.length - 1)); |
| 14004 | }; |
| 14005 | |
| 14006 | /** |
| 14007 | * The range prototype. |
| 14008 | * |
| 14009 | * @type pv.Mark |
| 14010 | * @name pv.Layout.Bullet.prototype.range |
| 14011 | */ |
| 14012 | (this.range = new pv.Mark()) |
| 14013 | .data(function() { return x.ranges; }) |
| 14014 | .reverse(true) |
| 14015 | .left(function() { return orient == "left" ? 0 : null; }) |
| 14016 | .top(function() { return orient == "top" ? 0 : null; }) |
| 14017 | .right(function() { return orient == "right" ? 0 : null; }) |
| 14018 | .bottom(function() { return orient == "bottom" ? 0 : null; }) |
| 14019 | .width(function(d) { return horizontal ? scale(d) : null; }) |
| 14020 | .height(function(d) { return horizontal ? null : scale(d); }) |
| 14021 | .fillStyle(function() { return rangeColor(this.index); }) |
| 14022 | .antialias(false) |
| 14023 | .parent = that; |
| 14024 | |
| 14025 | /** |
| 14026 | * The measure prototype. |
| 14027 | * |
| 14028 | * @type pv.Mark |
| 14029 | * @name pv.Layout.Bullet.prototype.measure |
| 14030 | */ |
| 14031 | (this.measure = new pv.Mark()) |
| 14032 | .extend(this.range) |
| 14033 | .data(function() { return x.measures; }) |
| 14034 | .left(function() { return orient == "left" ? 0 : horizontal ? null : this.parent.width() / 3.25; }) |
| 14035 | .top(function() { return orient == "top" ? 0 : horizontal ? this.parent.height() / 3.25 : null; }) |
| 14036 | .right(function() { return orient == "right" ? 0 : horizontal ? null : this.parent.width() / 3.25; }) |
| 14037 | .bottom(function() { return orient == "bottom" ? 0 : horizontal ? this.parent.height() / 3.25 : null; }) |
| 14038 | .fillStyle(function() { return measureColor(this.index); }) |
| 14039 | .parent = that; |
| 14040 | |
| 14041 | /** |
| 14042 | * The marker prototype. |
| 14043 | * |
| 14044 | * @type pv.Mark |
| 14045 | * @name pv.Layout.Bullet.prototype.marker |
| 14046 | */ |
| 14047 | (this.marker = new pv.Mark()) |
| 14048 | .data(function() { return x.markers; }) |
| 14049 | .left(function(d) { return orient == "left" ? scale(d) : horizontal ? null : this.parent.width() / 2; }) |
| 14050 | .top(function(d) { return orient == "top" ? scale(d) : horizontal ? this.parent.height() / 2 : null; }) |
| 14051 | .right(function(d) { return orient == "right" ? scale(d) : null; }) |
| 14052 | .bottom(function(d) { return orient == "bottom" ? scale(d) : null; }) |
| 14053 | .strokeStyle("black") |
| 14054 | .shape("bar") |
| 14055 | .angle(function() { return horizontal ? 0 : Math.PI / 2; }) |
| 14056 | .parent = that; |
| 14057 | |
| 14058 | (this.tick = new pv.Mark()) |
| 14059 | .data(function() { return scale.ticks(7); }) |
| 14060 | .left(function(d) { return orient == "left" ? scale(d) : null; }) |
| 14061 | .top(function(d) { return orient == "top" ? scale(d) : null; }) |
| 14062 | .right(function(d) { return orient == "right" ? scale(d) : horizontal ? null : -6; }) |
| 14063 | .bottom(function(d) { return orient == "bottom" ? scale(d) : horizontal ? -8 : null; }) |
| 14064 | .height(function() { return horizontal ? 6 : null; }) |
| 14065 | .width(function() { return horizontal ? null : 6; }) |
| 14066 | .parent = that; |
| 14067 | }; |
| 14068 | |
| 14069 | pv.Layout.Bullet.prototype = pv.extend(pv.Layout) |
| 14070 | .property("orient", String) // left, right, top, bottom |
| 14071 | .property("ranges") |
| 14072 | .property("markers") |
| 14073 | .property("measures") |
| 14074 | .property("maximum", Number); |
| 14075 | |
| 14076 | /** |
| 14077 | * Default properties for bullet layouts. |
| 14078 | * |
| 14079 | * @type pv.Layout.Bullet |
| 14080 | */ |
| 14081 | pv.Layout.Bullet.prototype.defaults = new pv.Layout.Bullet() |
| 14082 | .extend(pv.Layout.prototype.defaults) |
| 14083 | .orient("left") |
| 14084 | .ranges([]) |
| 14085 | .markers([]) |
| 14086 | .measures([]); |
| 14087 | |
| 14088 | /** |
| 14089 | * The orientation. |
| 14090 | * |
| 14091 | * @type string |
| 14092 | * @name pv.Layout.Bullet.prototype.orient |
| 14093 | */ |
| 14094 | |
| 14095 | /** |
| 14096 | * The array of range values. |
| 14097 | * |
| 14098 | * @type array |
| 14099 | * @name pv.Layout.Bullet.prototype.ranges |
| 14100 | */ |
| 14101 | |
| 14102 | /** |
| 14103 | * The array of marker values. |
| 14104 | * |
| 14105 | * @type array |
| 14106 | * @name pv.Layout.Bullet.prototype.markers |
| 14107 | */ |
| 14108 | |
| 14109 | /** |
| 14110 | * The array of measure values. |
| 14111 | * |
| 14112 | * @type array |
| 14113 | * @name pv.Layout.Bullet.prototype.measures |
| 14114 | */ |
| 14115 | |
| 14116 | /** |
| 14117 | * Optional; the maximum range value. |
| 14118 | * |
| 14119 | * @type number |
| 14120 | * @name pv.Layout.Bullet.prototype.maximum |
| 14121 | */ |
| 14122 | |
| 14123 | /** @private */ |
| 14124 | pv.Layout.Bullet.prototype.buildImplied = function(s) { |
| 14125 | pv.Layout.prototype.buildImplied.call(this, s); |
| 14126 | var size = this.parent[/^left|right$/.test(s.orient) ? "width" : "height"](); |
| 14127 | s.maximum = s.maximum || pv.max([].concat(s.ranges, s.markers, s.measures)); |
| 14128 | this.x.domain(0, s.maximum).range(0, size); |
| 14129 | }; |
| 14130 | /** |
| 14131 | * Abstract; see an implementing class for details. |
| 14132 | * |
| 14133 | * @class Represents a reusable interaction; applies an interactive behavior to |
| 14134 | * a given mark. Behaviors are themselves functions designed to be used as event |
| 14135 | * handlers. For example, to add pan and zoom support to any panel, say: |
| 14136 | * |
| 14137 | * <pre> .event("mousedown", pv.Behavior.pan()) |
| 14138 | * .event("mousewheel", pv.Behavior.zoom())</pre> |
| 14139 | * |
| 14140 | * The behavior should be registered on the event that triggers the start of the |
| 14141 | * behavior. Typically, the behavior will take care of registering for any |
| 14142 | * additional events that are necessary. For example, dragging starts on |
| 14143 | * mousedown, while the drag behavior automatically listens for mousemove and |
| 14144 | * mouseup events on the window. By listening to the window, the behavior can |
| 14145 | * continue to receive mouse events even if the mouse briefly leaves the mark |
| 14146 | * being dragged, or even the root panel. |
| 14147 | * |
| 14148 | * <p>Each behavior implementation has specific requirements as to which events |
| 14149 | * it supports, and how it should be used. For example, the drag behavior |
| 14150 | * requires that the data associated with the mark be an object with <tt>x</tt> |
| 14151 | * and <tt>y</tt> attributes, such as a {@link pv.Vector}, storing the mark's |
| 14152 | * position. See an implementing class for details. |
| 14153 | * |
| 14154 | * @see pv.Behavior.drag |
| 14155 | * @see pv.Behavior.pan |
| 14156 | * @see pv.Behavior.point |
| 14157 | * @see pv.Behavior.select |
| 14158 | * @see pv.Behavior.zoom |
| 14159 | * @extends function |
| 14160 | */ |
| 14161 | pv.Behavior = {}; |
| 14162 | /** |
| 14163 | * Returns a new drag behavior to be registered on mousedown events. |
| 14164 | * |
| 14165 | * @class Implements interactive dragging starting with mousedown events. |
| 14166 | * Register this behavior on marks that should be draggable by the user, such as |
| 14167 | * the selected region for brushing and linking. This behavior can be used in |
| 14168 | * tandom with {@link pv.Behavior.select} to allow the selected region to be |
| 14169 | * dragged interactively. |
| 14170 | * |
| 14171 | * <p>After the initial mousedown event is triggered, this behavior listens for |
| 14172 | * mousemove and mouseup events on the window. This allows dragging to continue |
| 14173 | * even if the mouse temporarily leaves the mark that is being dragged, or even |
| 14174 | * the root panel. |
| 14175 | * |
| 14176 | * <p>This behavior requires that the data associated with the mark being |
| 14177 | * dragged have <tt>x</tt> and <tt>y</tt> attributes that correspond to the |
| 14178 | * mark's location in pixels. The mark's positional properties are not set |
| 14179 | * directly by this behavior; instead, the positional properties should be |
| 14180 | * defined as: |
| 14181 | * |
| 14182 | * <pre> .left(function(d) d.x) |
| 14183 | * .top(function(d) d.y)</pre> |
| 14184 | * |
| 14185 | * Thus, the behavior does not move the mark directly, but instead updates the |
| 14186 | * mark position by updating the underlying data. Note that if the positional |
| 14187 | * properties are defined with bottom and right (rather than top and left), the |
| 14188 | * drag behavior will be inverted, which will confuse users! |
| 14189 | * |
| 14190 | * <p>The drag behavior is bounded by the parent panel; the <tt>x</tt> and |
| 14191 | * <tt>y</tt> attributes are clamped such that the mark being dragged does not |
| 14192 | * extend outside the enclosing panel's bounds. To facilitate this, the drag |
| 14193 | * behavior also queries for <tt>dx</tt> and <tt>dy</tt> attributes on the |
| 14194 | * underlying data, to determine the dimensions of the bar being dragged. For |
| 14195 | * non-rectangular marks, the drag behavior simply treats the mark as a point, |
| 14196 | * which means that only the mark's center is bounded. |
| 14197 | * |
| 14198 | * <p>The mark being dragged is automatically re-rendered for each mouse event |
| 14199 | * as part of the drag operation. In addition, a <tt>fix</tt> attribute is |
| 14200 | * populated on the mark, which allows visual feedback for dragging. For |
| 14201 | * example, to change the mark fill color while dragging: |
| 14202 | * |
| 14203 | * <pre> .fillStyle(function(d) d.fix ? "#ff7f0e" : "#aec7e8")</pre> |
| 14204 | * |
| 14205 | * In some cases, such as with network layouts, dragging the mark may cause |
| 14206 | * related marks to change, in which case additional marks may also need to be |
| 14207 | * rendered. This can be accomplished by listening for the drag |
| 14208 | * psuedo-events:<ul> |
| 14209 | * |
| 14210 | * <li>dragstart (on mousedown) |
| 14211 | * <li>drag (on mousemove) |
| 14212 | * <li>dragend (on mouseup) |
| 14213 | * |
| 14214 | * </ul>For example, to render the parent panel while dragging, thus |
| 14215 | * re-rendering all sibling marks: |
| 14216 | * |
| 14217 | * <pre> .event("mousedown", pv.Behavior.drag()) |
| 14218 | * .event("drag", function() this.parent)</pre> |
| 14219 | * |
| 14220 | * This behavior may be enhanced in the future to allow more flexible |
| 14221 | * configuration of drag behavior. |
| 14222 | * |
| 14223 | * @extends pv.Behavior |
| 14224 | * @see pv.Behavior |
| 14225 | * @see pv.Behavior.select |
| 14226 | * @see pv.Layout.force |
| 14227 | */ |
| 14228 | pv.Behavior.drag = function() { |
| 14229 | var scene, // scene context |
| 14230 | index, // scene context |
| 14231 | p, // particle being dragged |
| 14232 | v1, // initial mouse-particle offset |
| 14233 | max; |
| 14234 | |
| 14235 | /** @private */ |
| 14236 | function mousedown(d) { |
| 14237 | index = this.index; |
| 14238 | scene = this.scene; |
| 14239 | var m = this.mouse(); |
| 14240 | v1 = ((p = d).fix = pv.vector(d.x, d.y)).minus(m); |
| 14241 | max = { |
| 14242 | x: this.parent.width() - (d.dx || 0), |
| 14243 | y: this.parent.height() - (d.dy || 0) |
| 14244 | }; |
| 14245 | scene.mark.context(scene, index, function() { this.render(); }); |
| 14246 | pv.Mark.dispatch("dragstart", scene, index); |
| 14247 | } |
| 14248 | |
| 14249 | /** @private */ |
| 14250 | function mousemove() { |
| 14251 | if (!scene) return; |
| 14252 | scene.mark.context(scene, index, function() { |
| 14253 | var m = this.mouse(); |
| 14254 | p.x = p.fix.x = Math.max(0, Math.min(v1.x + m.x, max.x)); |
| 14255 | p.y = p.fix.y = Math.max(0, Math.min(v1.y + m.y, max.y)); |
| 14256 | this.render(); |
| 14257 | }); |
| 14258 | pv.Mark.dispatch("drag", scene, index); |
| 14259 | } |
| 14260 | |
| 14261 | /** @private */ |
| 14262 | function mouseup() { |
| 14263 | if (!scene) return; |
| 14264 | p.fix = null; |
| 14265 | scene.mark.context(scene, index, function() { this.render(); }); |
| 14266 | pv.Mark.dispatch("dragend", scene, index); |
| 14267 | scene = null; |
| 14268 | } |
| 14269 | |
| 14270 | pv.listen(window, "mousemove", mousemove); |
| 14271 | pv.listen(window, "mouseup", mouseup); |
| 14272 | return mousedown; |
| 14273 | }; |
| 14274 | /** |
| 14275 | * Returns a new point behavior to be registered on mousemove events. |
| 14276 | * |
| 14277 | * @class Implements interactive fuzzy pointing, identifying marks that are in |
| 14278 | * close proximity to the mouse cursor. This behavior is an alternative to the |
| 14279 | * native mouseover and mouseout events, improving usability. Rather than |
| 14280 | * requiring the user to mouseover a mark exactly, the mouse simply needs to |
| 14281 | * move near the given mark and a "point" event is triggered. In addition, if |
| 14282 | * multiple marks overlap, the point behavior can be used to identify the mark |
| 14283 | * instance closest to the cursor, as opposed to the one that is rendered on |
| 14284 | * top. |
| 14285 | * |
| 14286 | * <p>The point behavior can also identify the closest mark instance for marks |
| 14287 | * that produce a continuous graphic primitive. The point behavior can thus be |
| 14288 | * used to provide details-on-demand for both discrete marks (such as dots and |
| 14289 | * bars), as well as continuous marks (such as lines and areas). |
| 14290 | * |
| 14291 | * <p>This behavior is implemented by finding the closest mark instance to the |
| 14292 | * mouse cursor on every mousemove event. If this closest mark is within the |
| 14293 | * given radius threshold, which defaults to 30 pixels, a "point" psuedo-event |
| 14294 | * is dispatched to the given mark instance. If any mark were previously |
| 14295 | * pointed, it would receive a corresponding "unpoint" event. These two |
| 14296 | * psuedo-event types correspond to the native "mouseover" and "mouseout" |
| 14297 | * events, respectively. To increase the radius at which the point behavior can |
| 14298 | * be applied, specify an appropriate threshold to the constructor, up to |
| 14299 | * <tt>Infinity</tt>. |
| 14300 | * |
| 14301 | * <p>By default, the standard Cartesian distance is computed. However, with |
| 14302 | * some visualizations it is desirable to consider only a single dimension, such |
| 14303 | * as the <i>x</i>-dimension for an independent variable. In this case, the |
| 14304 | * collapse parameter can be set to collapse the <i>y</i> dimension: |
| 14305 | * |
| 14306 | * <pre> .event("mousemove", pv.Behavior.point(Infinity).collapse("y"))</pre> |
| 14307 | * |
| 14308 | * <p>This behavior only listens to mousemove events on the assigned panel, |
| 14309 | * which is typically the root panel. The behavior will search recursively for |
| 14310 | * descendant marks to point. If the mouse leaves the assigned panel, the |
| 14311 | * behavior no longer receives mousemove events; an unpoint psuedo-event is |
| 14312 | * automatically dispatched to unpoint any pointed mark. Marks may be re-pointed |
| 14313 | * when the mouse reenters the panel. |
| 14314 | * |
| 14315 | * <p>Panels have transparent fill styles by default; this means that panels may |
| 14316 | * not receive the initial mousemove event to start pointing. To fix this |
| 14317 | * problem, either given the panel a visible fill style (such as "white"), or |
| 14318 | * set the <tt>events</tt> property to "all" such that the panel receives events |
| 14319 | * despite its transparent fill. |
| 14320 | * |
| 14321 | * <p>Note: this behavior does not currently wedge marks. |
| 14322 | * |
| 14323 | * @extends pv.Behavior |
| 14324 | * |
| 14325 | * @param {number} [r] the fuzzy radius threshold in pixels |
| 14326 | * @see <a href="http://www.tovigrossman.com/papers/chi2005bubblecursor.pdf" |
| 14327 | * >"The Bubble Cursor: Enhancing Target Acquisition by Dynamic Resizing of the |
| 14328 | * Cursor's Activation Area"</a> by T. Grossman & R. Balakrishnan, CHI 2005. |
| 14329 | */ |
| 14330 | pv.Behavior.point = function(r) { |
| 14331 | var unpoint, // the current pointer target |
| 14332 | collapse = null, // dimensions to collapse |
| 14333 | kx = 1, // x-dimension cost scale |
| 14334 | ky = 1, // y-dimension cost scale |
| 14335 | r2 = arguments.length ? r * r : 900; // fuzzy radius |
| 14336 | |
| 14337 | /** @private Search for the mark closest to the mouse. */ |
| 14338 | function search(scene, index) { |
| 14339 | var s = scene[index], |
| 14340 | point = {cost: Infinity}; |
| 14341 | for (var i = 0, n = s.visible && s.children.length; i < n; i++) { |
| 14342 | var child = s.children[i], mark = child.mark, p; |
| 14343 | if (mark.type == "panel") { |
| 14344 | mark.scene = child; |
| 14345 | for (var j = 0, m = child.length; j < m; j++) { |
| 14346 | mark.index = j; |
| 14347 | p = search(child, j); |
| 14348 | if (p.cost < point.cost) point = p; |
| 14349 | } |
| 14350 | delete mark.scene; |
| 14351 | delete mark.index; |
| 14352 | } else if (mark.$handlers.point) { |
| 14353 | var v = mark.mouse(); |
| 14354 | for (var j = 0, m = child.length; j < m; j++) { |
| 14355 | var c = child[j], |
| 14356 | dx = v.x - c.left - (c.width || 0) / 2, |
| 14357 | dy = v.y - c.top - (c.height || 0) / 2, |
| 14358 | dd = kx * dx * dx + ky * dy * dy; |
| 14359 | if (dd < point.cost) { |
| 14360 | point.distance = dx * dx + dy * dy; |
| 14361 | point.cost = dd; |
| 14362 | point.scene = child; |
| 14363 | point.index = j; |
| 14364 | } |
| 14365 | } |
| 14366 | } |
| 14367 | } |
| 14368 | return point; |
| 14369 | } |
| 14370 | |
| 14371 | /** @private */ |
| 14372 | function mousemove() { |
| 14373 | /* If the closest mark is far away, clear the current target. */ |
| 14374 | var point = search(this.scene, this.index); |
| 14375 | if ((point.cost == Infinity) || (point.distance > r2)) point = null; |
| 14376 | |
| 14377 | /* Unpoint the old target, if it's not the new target. */ |
| 14378 | if (unpoint) { |
| 14379 | if (point |
| 14380 | && (unpoint.scene == point.scene) |
| 14381 | && (unpoint.index == point.index)) return; |
| 14382 | pv.Mark.dispatch("unpoint", unpoint.scene, unpoint.index); |
| 14383 | } |
| 14384 | |
| 14385 | /* Point the new target, if there is one. */ |
| 14386 | if (unpoint = point) { |
| 14387 | pv.Mark.dispatch("point", point.scene, point.index); |
| 14388 | |
| 14389 | /* Unpoint when the mouse leaves the root panel. */ |
| 14390 | pv.listen(this.root.canvas(), "mouseout", mouseout); |
| 14391 | } |
| 14392 | } |
| 14393 | |
| 14394 | /** @private */ |
| 14395 | function mouseout(e) { |
| 14396 | if (unpoint && !pv.ancestor(this, e.relatedTarget)) { |
| 14397 | pv.Mark.dispatch("unpoint", unpoint.scene, unpoint.index); |
| 14398 | unpoint = null; |
| 14399 | } |
| 14400 | } |
| 14401 | |
| 14402 | /** |
| 14403 | * Sets or gets the collapse parameter. By default, the standard Cartesian |
| 14404 | * distance is computed. However, with some visualizations it is desirable to |
| 14405 | * consider only a single dimension, such as the <i>x</i>-dimension for an |
| 14406 | * independent variable. In this case, the collapse parameter can be set to |
| 14407 | * collapse the <i>y</i> dimension: |
| 14408 | * |
| 14409 | * <pre> .event("mousemove", pv.Behavior.point(Infinity).collapse("y"))</pre> |
| 14410 | * |
| 14411 | * @function |
| 14412 | * @returns {pv.Behavior.point} this, or the current collapse parameter. |
| 14413 | * @name pv.Behavior.point.prototype.collapse |
| 14414 | * @param {string} [x] the new collapse parameter |
| 14415 | */ |
| 14416 | mousemove.collapse = function(x) { |
| 14417 | if (arguments.length) { |
| 14418 | collapse = String(x); |
| 14419 | switch (collapse) { |
| 14420 | case "y": kx = 1; ky = 0; break; |
| 14421 | case "x": kx = 0; ky = 1; break; |
| 14422 | default: kx = 1; ky = 1; break; |
| 14423 | } |
| 14424 | return mousemove; |
| 14425 | } |
| 14426 | return collapse; |
| 14427 | }; |
| 14428 | |
| 14429 | return mousemove; |
| 14430 | }; |
| 14431 | /** |
| 14432 | * Returns a new select behavior to be registered on mousedown events. |
| 14433 | * |
| 14434 | * @class Implements interactive selecting starting with mousedown events. |
| 14435 | * Register this behavior on panels that should be selectable by the user, such |
| 14436 | * for brushing and linking. This behavior can be used in tandom with |
| 14437 | * {@link pv.Behavior.drag} to allow the selected region to be dragged |
| 14438 | * interactively. |
| 14439 | * |
| 14440 | * <p>After the initial mousedown event is triggered, this behavior listens for |
| 14441 | * mousemove and mouseup events on the window. This allows selecting to continue |
| 14442 | * even if the mouse temporarily leaves the assigned panel, or even the root |
| 14443 | * panel. |
| 14444 | * |
| 14445 | * <p>This behavior requires that the data associated with the mark being |
| 14446 | * dragged have <tt>x</tt>, <tt>y</tt>, <tt>dx</tt> and <tt>dy</tt> attributes |
| 14447 | * that correspond to the mark's location and dimensions in pixels. The mark's |
| 14448 | * positional properties are not set directly by this behavior; instead, the |
| 14449 | * positional properties should be defined as: |
| 14450 | * |
| 14451 | * <pre> .left(function(d) d.x) |
| 14452 | * .top(function(d) d.y) |
| 14453 | * .width(function(d) d.dx) |
| 14454 | * .height(function(d) d.dy)</pre> |
| 14455 | * |
| 14456 | * Thus, the behavior does not resize the mark directly, but instead updates the |
| 14457 | * selection by updating the assigned panel's underlying data. Note that if the |
| 14458 | * positional properties are defined with bottom and right (rather than top and |
| 14459 | * left), the drag behavior will be inverted, which will confuse users! |
| 14460 | * |
| 14461 | * <p>The select behavior is bounded by the assigned panel; the positional |
| 14462 | * attributes are clamped such that the selection does not extend outside the |
| 14463 | * panel's bounds. |
| 14464 | * |
| 14465 | * <p>The panel being selected is automatically re-rendered for each mouse event |
| 14466 | * as part of the drag operation. This behavior may be enhanced in the future to |
| 14467 | * allow more flexible configuration of select behavior. In some cases, such as |
| 14468 | * with parallel coordinates, making a selection may cause related marks to |
| 14469 | * change, in which case additional marks may also need to be rendered. This can |
| 14470 | * be accomplished by listening for the select psuedo-events:<ul> |
| 14471 | * |
| 14472 | * <li>selectstart (on mousedown) |
| 14473 | * <li>select (on mousemove) |
| 14474 | * <li>selectend (on mouseup) |
| 14475 | * |
| 14476 | * </ul>For example, to render the parent panel while selecting, thus |
| 14477 | * re-rendering all sibling marks: |
| 14478 | * |
| 14479 | * <pre> .event("mousedown", pv.Behavior.drag()) |
| 14480 | * .event("select", function() this.parent)</pre> |
| 14481 | * |
| 14482 | * This behavior may be enhanced in the future to allow more flexible |
| 14483 | * configuration of the selection behavior. |
| 14484 | * |
| 14485 | * @extends pv.Behavior |
| 14486 | * @see pv.Behavior.drag |
| 14487 | */ |
| 14488 | pv.Behavior.select = function() { |
| 14489 | var scene, // scene context |
| 14490 | index, // scene context |
| 14491 | r, // region being selected |
| 14492 | m1; // initial mouse position |
| 14493 | |
| 14494 | /** @private */ |
| 14495 | function mousedown(d) { |
| 14496 | index = this.index; |
| 14497 | scene = this.scene; |
| 14498 | m1 = this.mouse(); |
| 14499 | r = d; |
| 14500 | r.x = m1.x; |
| 14501 | r.y = m1.y; |
| 14502 | r.dx = r.dy = 0; |
| 14503 | pv.Mark.dispatch("selectstart", scene, index); |
| 14504 | } |
| 14505 | |
| 14506 | /** @private */ |
| 14507 | function mousemove() { |
| 14508 | if (!scene) return; |
| 14509 | scene.mark.context(scene, index, function() { |
| 14510 | var m2 = this.mouse(); |
| 14511 | r.x = Math.max(0, Math.min(m1.x, m2.x)); |
| 14512 | r.y = Math.max(0, Math.min(m1.y, m2.y)); |
| 14513 | r.dx = Math.min(this.width(), Math.max(m2.x, m1.x)) - r.x; |
| 14514 | r.dy = Math.min(this.height(), Math.max(m2.y, m1.y)) - r.y; |
| 14515 | this.render(); |
| 14516 | }); |
| 14517 | pv.Mark.dispatch("select", scene, index); |
| 14518 | } |
| 14519 | |
| 14520 | /** @private */ |
| 14521 | function mouseup() { |
| 14522 | if (!scene) return; |
| 14523 | pv.Mark.dispatch("selectend", scene, index); |
| 14524 | scene = null; |
| 14525 | } |
| 14526 | |
| 14527 | pv.listen(window, "mousemove", mousemove); |
| 14528 | pv.listen(window, "mouseup", mouseup); |
| 14529 | return mousedown; |
| 14530 | }; |
| 14531 | /** |
| 14532 | * Returns a new resize behavior to be registered on mousedown events. |
| 14533 | * |
| 14534 | * @class Implements interactive resizing of a selection starting with mousedown |
| 14535 | * events. Register this behavior on selection handles that should be resizeable |
| 14536 | * by the user, such for brushing and linking. This behavior can be used in |
| 14537 | * tandom with {@link pv.Behavior.select} and {@link pv.Behavior.drag} to allow |
| 14538 | * the selected region to be selected and dragged interactively. |
| 14539 | * |
| 14540 | * <p>After the initial mousedown event is triggered, this behavior listens for |
| 14541 | * mousemove and mouseup events on the window. This allows resizing to continue |
| 14542 | * even if the mouse temporarily leaves the assigned panel, or even the root |
| 14543 | * panel. |
| 14544 | * |
| 14545 | * <p>This behavior requires that the data associated with the mark being |
| 14546 | * resized have <tt>x</tt>, <tt>y</tt>, <tt>dx</tt> and <tt>dy</tt> attributes |
| 14547 | * that correspond to the mark's location and dimensions in pixels. The mark's |
| 14548 | * positional properties are not set directly by this behavior; instead, the |
| 14549 | * positional properties should be defined as: |
| 14550 | * |
| 14551 | * <pre> .left(function(d) d.x) |
| 14552 | * .top(function(d) d.y) |
| 14553 | * .width(function(d) d.dx) |
| 14554 | * .height(function(d) d.dy)</pre> |
| 14555 | * |
| 14556 | * Thus, the behavior does not resize the mark directly, but instead updates the |
| 14557 | * size by updating the assigned panel's underlying data. Note that if the |
| 14558 | * positional properties are defined with bottom and right (rather than top and |
| 14559 | * left), the resize behavior will be inverted, which will confuse users! |
| 14560 | * |
| 14561 | * <p>The resize behavior is bounded by the assigned mark's enclosing panel; the |
| 14562 | * positional attributes are clamped such that the selection does not extend |
| 14563 | * outside the panel's bounds. |
| 14564 | * |
| 14565 | * <p>The mark being resized is automatically re-rendered for each mouse event |
| 14566 | * as part of the resize operation. This behavior may be enhanced in the future |
| 14567 | * to allow more flexible configuration. In some cases, such as with parallel |
| 14568 | * coordinates, resizing the selection may cause related marks to change, in |
| 14569 | * which case additional marks may also need to be rendered. This can be |
| 14570 | * accomplished by listening for the select psuedo-events:<ul> |
| 14571 | * |
| 14572 | * <li>resizestart (on mousedown) |
| 14573 | * <li>resize (on mousemove) |
| 14574 | * <li>resizeend (on mouseup) |
| 14575 | * |
| 14576 | * </ul>For example, to render the parent panel while resizing, thus |
| 14577 | * re-rendering all sibling marks: |
| 14578 | * |
| 14579 | * <pre> .event("mousedown", pv.Behavior.resize("left")) |
| 14580 | * .event("resize", function() this.parent)</pre> |
| 14581 | * |
| 14582 | * This behavior may be enhanced in the future to allow more flexible |
| 14583 | * configuration of the selection behavior. |
| 14584 | * |
| 14585 | * @extends pv.Behavior |
| 14586 | * @see pv.Behavior.select |
| 14587 | * @see pv.Behavior.drag |
| 14588 | */ |
| 14589 | pv.Behavior.resize = function(side) { |
| 14590 | var scene, // scene context |
| 14591 | index, // scene context |
| 14592 | r, // region being selected |
| 14593 | m1; // initial mouse position |
| 14594 | |
| 14595 | /** @private */ |
| 14596 | function mousedown(d) { |
| 14597 | index = this.index; |
| 14598 | scene = this.scene; |
| 14599 | m1 = this.mouse(); |
| 14600 | r = d; |
| 14601 | switch (side) { |
| 14602 | case "left": m1.x = r.x + r.dx; break; |
| 14603 | case "right": m1.x = r.x; break; |
| 14604 | case "top": m1.y = r.y + r.dy; break; |
| 14605 | case "bottom": m1.y = r.y; break; |
| 14606 | } |
| 14607 | pv.Mark.dispatch("resizestart", scene, index); |
| 14608 | } |
| 14609 | |
| 14610 | /** @private */ |
| 14611 | function mousemove() { |
| 14612 | if (!scene) return; |
| 14613 | scene.mark.context(scene, index, function() { |
| 14614 | var m2 = this.mouse(); |
| 14615 | r.x = Math.max(0, Math.min(m1.x, m2.x)); |
| 14616 | r.y = Math.max(0, Math.min(m1.y, m2.y)); |
| 14617 | r.dx = Math.min(this.parent.width(), Math.max(m2.x, m1.x)) - r.x; |
| 14618 | r.dy = Math.min(this.parent.height(), Math.max(m2.y, m1.y)) - r.y; |
| 14619 | this.render(); |
| 14620 | }); |
| 14621 | pv.Mark.dispatch("resize", scene, index); |
| 14622 | } |
| 14623 | |
| 14624 | /** @private */ |
| 14625 | function mouseup() { |
| 14626 | if (!scene) return; |
| 14627 | pv.Mark.dispatch("resizeend", scene, index); |
| 14628 | scene = null; |
| 14629 | } |
| 14630 | |
| 14631 | pv.listen(window, "mousemove", mousemove); |
| 14632 | pv.listen(window, "mouseup", mouseup); |
| 14633 | return mousedown; |
| 14634 | }; |
| 14635 | /** |
| 14636 | * Returns a new pan behavior to be registered on mousedown events. |
| 14637 | * |
| 14638 | * @class Implements interactive panning starting with mousedown events. |
| 14639 | * Register this behavior on panels to allow panning. This behavior can be used |
| 14640 | * in tandem with {@link pv.Behavior.zoom} to allow both panning and zooming: |
| 14641 | * |
| 14642 | * <pre> .event("mousedown", pv.Behavior.pan()) |
| 14643 | * .event("mousewheel", pv.Behavior.zoom())</pre> |
| 14644 | * |
| 14645 | * The pan behavior currently supports only mouse events; support for keyboard |
| 14646 | * shortcuts to improve accessibility may be added in the future. |
| 14647 | * |
| 14648 | * <p>After the initial mousedown event is triggered, this behavior listens for |
| 14649 | * mousemove and mouseup events on the window. This allows panning to continue |
| 14650 | * even if the mouse temporarily leaves the panel that is being panned, or even |
| 14651 | * the root panel. |
| 14652 | * |
| 14653 | * <p>The implementation of this behavior relies on the panel's |
| 14654 | * <tt>transform</tt> property, which specifies a matrix transformation that is |
| 14655 | * applied to child marks. Note that the transform property only affects the |
| 14656 | * panel's children, but not the panel itself; therefore the panel's fill and |
| 14657 | * stroke will not change when the contents are panned. |
| 14658 | * |
| 14659 | * <p>Panels have transparent fill styles by default; this means that panels may |
| 14660 | * not receive the initial mousedown event to start panning. To fix this |
| 14661 | * problem, either given the panel a visible fill style (such as "white"), or |
| 14662 | * set the <tt>events</tt> property to "all" such that the panel receives events |
| 14663 | * despite its transparent fill. |
| 14664 | * |
| 14665 | * <p>The pan behavior has optional support for bounding. If enabled, the user |
| 14666 | * will not be able to pan the panel outside of the initial bounds. This feature |
| 14667 | * is designed to work in conjunction with the zoom behavior; otherwise, |
| 14668 | * bounding the panel effectively disables all panning. |
| 14669 | * |
| 14670 | * @extends pv.Behavior |
| 14671 | * @see pv.Behavior.zoom |
| 14672 | * @see pv.Panel#transform |
| 14673 | */ |
| 14674 | pv.Behavior.pan = function() { |
| 14675 | var scene, // scene context |
| 14676 | index, // scene context |
| 14677 | m1, // transformation matrix at the start of panning |
| 14678 | v1, // mouse location at the start of panning |
| 14679 | k, // inverse scale |
| 14680 | bound; // whether to bound to the panel |
| 14681 | |
| 14682 | /** @private */ |
| 14683 | function mousedown() { |
| 14684 | index = this.index; |
| 14685 | scene = this.scene; |
| 14686 | v1 = pv.vector(pv.event.pageX, pv.event.pageY); |
| 14687 | m1 = this.transform(); |
| 14688 | k = 1 / (m1.k * this.scale); |
| 14689 | if (bound) { |
| 14690 | bound = { |
| 14691 | x: (1 - m1.k) * this.width(), |
| 14692 | y: (1 - m1.k) * this.height() |
| 14693 | }; |
| 14694 | } |
| 14695 | } |
| 14696 | |
| 14697 | /** @private */ |
| 14698 | function mousemove() { |
| 14699 | if (!scene) return; |
| 14700 | scene.mark.context(scene, index, function() { |
| 14701 | var x = (pv.event.pageX - v1.x) * k, |
| 14702 | y = (pv.event.pageY - v1.y) * k, |
| 14703 | m = m1.translate(x, y); |
| 14704 | if (bound) { |
| 14705 | m.x = Math.max(bound.x, Math.min(0, m.x)); |
| 14706 | m.y = Math.max(bound.y, Math.min(0, m.y)); |
| 14707 | } |
| 14708 | this.transform(m).render(); |
| 14709 | }); |
| 14710 | pv.Mark.dispatch("pan", scene, index); |
| 14711 | } |
| 14712 | |
| 14713 | /** @private */ |
| 14714 | function mouseup() { |
| 14715 | scene = null; |
| 14716 | } |
| 14717 | |
| 14718 | /** |
| 14719 | * Sets or gets the bound parameter. If bounding is enabled, the user will not |
| 14720 | * be able to pan outside the initial panel bounds; this typically applies |
| 14721 | * only when the pan behavior is used in tandem with the zoom behavior. |
| 14722 | * Bounding is not enabled by default. |
| 14723 | * |
| 14724 | * <p>Note: enabling bounding after panning has already occurred will not |
| 14725 | * immediately reset the transform. Bounding should be enabled before the |
| 14726 | * panning behavior is applied. |
| 14727 | * |
| 14728 | * @function |
| 14729 | * @returns {pv.Behavior.pan} this, or the current bound parameter. |
| 14730 | * @name pv.Behavior.pan.prototype.bound |
| 14731 | * @param {boolean} [x] the new bound parameter. |
| 14732 | */ |
| 14733 | mousedown.bound = function(x) { |
| 14734 | if (arguments.length) { |
| 14735 | bound = Boolean(x); |
| 14736 | return this; |
| 14737 | } |
| 14738 | return Boolean(bound); |
| 14739 | }; |
| 14740 | |
| 14741 | pv.listen(window, "mousemove", mousemove); |
| 14742 | pv.listen(window, "mouseup", mouseup); |
| 14743 | return mousedown; |
| 14744 | }; |
| 14745 | /** |
| 14746 | * Returns a new zoom behavior to be registered on mousewheel events. |
| 14747 | * |
| 14748 | * @class Implements interactive zooming using mousewheel events. Register this |
| 14749 | * behavior on panels to allow zooming. This behavior can be used in tandem with |
| 14750 | * {@link pv.Behavior.pan} to allow both panning and zooming: |
| 14751 | * |
| 14752 | * <pre> .event("mousedown", pv.Behavior.pan()) |
| 14753 | * .event("mousewheel", pv.Behavior.zoom())</pre> |
| 14754 | * |
| 14755 | * The zoom behavior currently supports only mousewheel events; support for |
| 14756 | * keyboard shortcuts and gesture events to improve accessibility may be added |
| 14757 | * in the future. |
| 14758 | * |
| 14759 | * <p>The implementation of this behavior relies on the panel's |
| 14760 | * <tt>transform</tt> property, which specifies a matrix transformation that is |
| 14761 | * applied to child marks. Note that the transform property only affects the |
| 14762 | * panel's children, but not the panel itself; therefore the panel's fill and |
| 14763 | * stroke will not change when the contents are zoomed. The built-in support for |
| 14764 | * transforms only supports uniform scaling and translates, which is sufficient |
| 14765 | * for panning and zooming. Note that this is not a strict geometric |
| 14766 | * transformation, as the <tt>lineWidth</tt> property is scale-aware: strokes |
| 14767 | * are drawn at constant size independent of scale. |
| 14768 | * |
| 14769 | * <p>Panels have transparent fill styles by default; this means that panels may |
| 14770 | * not receive mousewheel events to zoom. To fix this problem, either given the |
| 14771 | * panel a visible fill style (such as "white"), or set the <tt>events</tt> |
| 14772 | * property to "all" such that the panel receives events despite its transparent |
| 14773 | * fill. |
| 14774 | * |
| 14775 | * <p>The zoom behavior has optional support for bounding. If enabled, the user |
| 14776 | * will not be able to zoom out farther than the initial bounds. This feature is |
| 14777 | * designed to work in conjunction with the pan behavior. |
| 14778 | * |
| 14779 | * @extends pv.Behavior |
| 14780 | * @see pv.Panel#transform |
| 14781 | * @see pv.Mark#scale |
| 14782 | * @param {number} speed |
| 14783 | */ |
| 14784 | pv.Behavior.zoom = function(speed) { |
| 14785 | var bound; // whether to bound to the panel |
| 14786 | |
| 14787 | if (!arguments.length) speed = 1 / 48; |
| 14788 | |
| 14789 | /** @private */ |
| 14790 | function mousewheel() { |
| 14791 | var v = this.mouse(), |
| 14792 | k = pv.event.wheel * speed, |
| 14793 | m = this.transform().translate(v.x, v.y) |
| 14794 | .scale((k < 0) ? (1e3 / (1e3 - k)) : ((1e3 + k) / 1e3)) |
| 14795 | .translate(-v.x, -v.y); |
| 14796 | if (bound) { |
| 14797 | m.k = Math.max(1, m.k); |
| 14798 | m.x = Math.max((1 - m.k) * this.width(), Math.min(0, m.x)); |
| 14799 | m.y = Math.max((1 - m.k) * this.height(), Math.min(0, m.y)); |
| 14800 | } |
| 14801 | this.transform(m).render(); |
| 14802 | pv.Mark.dispatch("zoom", this.scene, this.index); |
| 14803 | } |
| 14804 | |
| 14805 | /** |
| 14806 | * Sets or gets the bound parameter. If bounding is enabled, the user will not |
| 14807 | * be able to zoom out farther than the initial panel bounds. Bounding is not |
| 14808 | * enabled by default. If this behavior is used in tandem with the pan |
| 14809 | * behavior, both should use the same bound parameter. |
| 14810 | * |
| 14811 | * <p>Note: enabling bounding after zooming has already occurred will not |
| 14812 | * immediately reset the transform. Bounding should be enabled before the zoom |
| 14813 | * behavior is applied. |
| 14814 | * |
| 14815 | * @function |
| 14816 | * @returns {pv.Behavior.zoom} this, or the current bound parameter. |
| 14817 | * @name pv.Behavior.zoom.prototype.bound |
| 14818 | * @param {boolean} [x] the new bound parameter. |
| 14819 | */ |
| 14820 | mousewheel.bound = function(x) { |
| 14821 | if (arguments.length) { |
| 14822 | bound = Boolean(x); |
| 14823 | return this; |
| 14824 | } |
| 14825 | return Boolean(bound); |
| 14826 | }; |
| 14827 | |
| 14828 | return mousewheel; |
| 14829 | }; |
| 14830 | /** |
| 14831 | * @ignore |
| 14832 | * @namespace |
| 14833 | */ |
| 14834 | pv.Geo = function() {}; |
| 14835 | /** |
| 14836 | * Abstract; not implemented. There is no explicit constructor; this class |
| 14837 | * merely serves to document the representation used by {@link pv.Geo.scale}. |
| 14838 | * |
| 14839 | * @class Represents a pair of geographic coordinates. |
| 14840 | * |
| 14841 | * @name pv.Geo.LatLng |
| 14842 | * @see pv.Geo.scale |
| 14843 | */ |
| 14844 | |
| 14845 | /** |
| 14846 | * The <i>latitude</i> coordinate in degrees; positive is North. |
| 14847 | * |
| 14848 | * @type number |
| 14849 | * @name pv.Geo.LatLng.prototype.lat |
| 14850 | */ |
| 14851 | |
| 14852 | /** |
| 14853 | * The <i>longitude</i> coordinate in degrees; positive is East. |
| 14854 | * |
| 14855 | * @type number |
| 14856 | * @name pv.Geo.LatLng.prototype.lng |
| 14857 | */ |
| 14858 | /** |
| 14859 | * Abstract; not implemented. There is no explicit constructor; this class |
| 14860 | * merely serves to document the representation used by {@link pv.Geo.scale}. |
| 14861 | * |
| 14862 | * @class Represents a geographic projection. This class provides the core |
| 14863 | * implementation for {@link pv.Geo.scale}s, mapping between geographic |
| 14864 | * coordinates (latitude and longitude) and normalized screen space in the range |
| 14865 | * [-1,1]. The remaining mapping between normalized screen space and actual |
| 14866 | * pixels is performed by <tt>pv.Geo.scale</tt>. |
| 14867 | * |
| 14868 | * <p>Many geographic projections have a point around which the projection is |
| 14869 | * centered. Rather than have each implementation add support for a |
| 14870 | * user-specified center point, the <tt>pv.Geo.scale</tt> translates the |
| 14871 | * geographic coordinates relative to the center point for both the forward and |
| 14872 | * inverse projection. |
| 14873 | * |
| 14874 | * <p>In general, this class should not be used directly, unless the desire is |
| 14875 | * to implement a new geographic projection. Instead, use <tt>pv.Geo.scale</tt>. |
| 14876 | * Implementations are not required to implement inverse projections, but are |
| 14877 | * needed for some forms of interactivity. Also note that some inverse |
| 14878 | * projections are ambiguous, such as the connecting points in Dymaxian maps. |
| 14879 | * |
| 14880 | * @name pv.Geo.Projection |
| 14881 | * @see pv.Geo.scale |
| 14882 | */ |
| 14883 | |
| 14884 | /** |
| 14885 | * The <i>forward</i> projection. |
| 14886 | * |
| 14887 | * @function |
| 14888 | * @name pv.Geo.Projection.prototype.project |
| 14889 | * @param {pv.Geo.LatLng} latlng the latitude and longitude to project. |
| 14890 | * @returns {pv.Vector} the xy-coordinates of the given point. |
| 14891 | */ |
| 14892 | |
| 14893 | /** |
| 14894 | * The <i>inverse</i> projection; optional. |
| 14895 | * |
| 14896 | * @function |
| 14897 | * @name pv.Geo.Projection.prototype.invert |
| 14898 | * @param {pv.Vector} xy the x- and y-coordinates to invert. |
| 14899 | * @returns {pv.Geo.LatLng} the latitude and longitude of the given point. |
| 14900 | */ |
| 14901 | /** |
| 14902 | * The built-in projections. |
| 14903 | * |
| 14904 | * @see pv.Geo.Projection |
| 14905 | * @namespace |
| 14906 | */ |
| 14907 | pv.Geo.projections = { |
| 14908 | |
| 14909 | /** @see http://en.wikipedia.org/wiki/Mercator_projection */ |
| 14910 | mercator: { |
| 14911 | project: function(latlng) { |
| 14912 | return { |
| 14913 | x: latlng.lng / 180, |
| 14914 | y: latlng.lat > 85 ? 1 : latlng.lat < -85 ? -1 |
| 14915 | : Math.log(Math.tan(Math.PI / 4 |
| 14916 | + pv.radians(latlng.lat) / 2)) / Math.PI |
| 14917 | }; |
| 14918 | }, |
| 14919 | invert: function(xy) { |
| 14920 | return { |
| 14921 | lng: xy.x * 180, |
| 14922 | lat: pv.degrees(2 * Math.atan(Math.exp(xy.y * Math.PI)) - Math.PI / 2) |
| 14923 | }; |
| 14924 | } |
| 14925 | }, |
| 14926 | |
| 14927 | /** @see http://en.wikipedia.org/wiki/Gall-Peters_projection */ |
| 14928 | "gall-peters": { |
| 14929 | project: function(latlng) { |
| 14930 | return { |
| 14931 | x: latlng.lng / 180, |
| 14932 | y: Math.sin(pv.radians(latlng.lat)) |
| 14933 | }; |
| 14934 | }, |
| 14935 | invert: function(xy) { |
| 14936 | return { |
| 14937 | lng: xy.x * 180, |
| 14938 | lat: pv.degrees(Math.asin(xy.y)) |
| 14939 | }; |
| 14940 | } |
| 14941 | }, |
| 14942 | |
| 14943 | /** @see http://en.wikipedia.org/wiki/Sinusoidal_projection */ |
| 14944 | sinusoidal: { |
| 14945 | project: function(latlng) { |
| 14946 | return { |
| 14947 | x: pv.radians(latlng.lng) * Math.cos(pv.radians(latlng.lat)) / Math.PI, |
| 14948 | y: latlng.lat / 90 |
| 14949 | }; |
| 14950 | }, |
| 14951 | invert: function(xy) { |
| 14952 | return { |
| 14953 | lng: pv.degrees((xy.x * Math.PI) / Math.cos(xy.y * Math.PI / 2)), |
| 14954 | lat: xy.y * 90 |
| 14955 | }; |
| 14956 | } |
| 14957 | }, |
| 14958 | |
| 14959 | /** @see http://en.wikipedia.org/wiki/Aitoff_projection */ |
| 14960 | aitoff: { |
| 14961 | project: function(latlng) { |
| 14962 | var l = pv.radians(latlng.lng), |
| 14963 | f = pv.radians(latlng.lat), |
| 14964 | a = Math.acos(Math.cos(f) * Math.cos(l / 2)); |
| 14965 | return { |
| 14966 | x: 2 * (a ? (Math.cos(f) * Math.sin(l / 2) * a / Math.sin(a)) : 0) / Math.PI, |
| 14967 | y: 2 * (a ? (Math.sin(f) * a / Math.sin(a)) : 0) / Math.PI |
| 14968 | }; |
| 14969 | }, |
| 14970 | invert: function(xy) { |
| 14971 | var x = xy.x * Math.PI / 2, |
| 14972 | y = xy.y * Math.PI / 2; |
| 14973 | return { |
| 14974 | lng: pv.degrees(x / Math.cos(y)), |
| 14975 | lat: pv.degrees(y) |
| 14976 | }; |
| 14977 | } |
| 14978 | }, |
| 14979 | |
| 14980 | /** @see http://en.wikipedia.org/wiki/Hammer_projection */ |
| 14981 | hammer: { |
| 14982 | project: function(latlng) { |
| 14983 | var l = pv.radians(latlng.lng), |
| 14984 | f = pv.radians(latlng.lat), |
| 14985 | c = Math.sqrt(1 + Math.cos(f) * Math.cos(l / 2)); |
| 14986 | return { |
| 14987 | x: 2 * Math.SQRT2 * Math.cos(f) * Math.sin(l / 2) / c / 3, |
| 14988 | y: Math.SQRT2 * Math.sin(f) / c / 1.5 |
| 14989 | }; |
| 14990 | }, |
| 14991 | invert: function(xy) { |
| 14992 | var x = xy.x * 3, |
| 14993 | y = xy.y * 1.5, |
| 14994 | z = Math.sqrt(1 - x * x / 16 - y * y / 4); |
| 14995 | return { |
| 14996 | lng: pv.degrees(2 * Math.atan2(z * x, 2 * (2 * z * z - 1))), |
| 14997 | lat: pv.degrees(Math.asin(z * y)) |
| 14998 | }; |
| 14999 | } |
| 15000 | }, |
| 15001 | |
| 15002 | /** The identity or "none" projection. */ |
| 15003 | identity: { |
| 15004 | project: function(latlng) { |
| 15005 | return { |
| 15006 | x: latlng.lng / 180, |
| 15007 | y: latlng.lat / 90 |
| 15008 | }; |
| 15009 | }, |
| 15010 | invert: function(xy) { |
| 15011 | return { |
| 15012 | lng: xy.x * 180, |
| 15013 | lat: xy.y * 90 |
| 15014 | }; |
| 15015 | } |
| 15016 | } |
| 15017 | }; |
| 15018 | /** |
| 15019 | * Returns a geographic scale. The arguments to this constructor are optional, |
| 15020 | * and equivalent to calling {@link #projection}. |
| 15021 | * |
| 15022 | * @class Represents a geographic scale; a mapping between latitude-longitude |
| 15023 | * coordinates and screen pixel coordinates. By default, the domain is inferred |
| 15024 | * from the geographic coordinates, so that the domain fills the output range. |
| 15025 | * |
| 15026 | * <p>Note that geographic scales are two-dimensional transformations, rather |
| 15027 | * than the one-dimensional bidrectional mapping typical of other scales. |
| 15028 | * Rather than mapping (for example) between a numeric domain and a numeric |
| 15029 | * range, geographic scales map between two coordinate objects: {@link |
| 15030 | * pv.Geo.LatLng} and {@link pv.Vector}. |
| 15031 | * |
| 15032 | * @param {pv.Geo.Projection} [p] optional projection. |
| 15033 | * @see pv.Geo.scale#ticks |
| 15034 | */ |
| 15035 | pv.Geo.scale = function(p) { |
| 15036 | var rmin = {x: 0, y: 0}, // default range minimum |
| 15037 | rmax = {x: 1, y: 1}, // default range maximum |
| 15038 | d = [], // default domain |
| 15039 | j = pv.Geo.projections.identity, // domain <-> normalized range |
| 15040 | x = pv.Scale.linear(-1, 1).range(0, 1), // normalized <-> range |
| 15041 | y = pv.Scale.linear(-1, 1).range(1, 0), // normalized <-> range |
| 15042 | c = {lng: 0, lat: 0}, // Center Point |
| 15043 | lastLatLng, // cached latlng |
| 15044 | lastPoint; // cached point |
| 15045 | |
| 15046 | /** @private */ |
| 15047 | function scale(latlng) { |
| 15048 | if (!lastLatLng |
| 15049 | || (latlng.lng != lastLatLng.lng) |
| 15050 | || (latlng.lat != lastLatLng.lat)) { |
| 15051 | lastLatLng = latlng; |
| 15052 | var p = project(latlng); |
| 15053 | lastPoint = {x: x(p.x), y: y(p.y)}; |
| 15054 | } |
| 15055 | return lastPoint; |
| 15056 | } |
| 15057 | |
| 15058 | /** @private */ |
| 15059 | function project(latlng) { |
| 15060 | var offset = {lng: latlng.lng - c.lng, lat: latlng.lat}; |
| 15061 | return j.project(offset); |
| 15062 | } |
| 15063 | |
| 15064 | /** @private */ |
| 15065 | function invert(xy) { |
| 15066 | var latlng = j.invert(xy); |
| 15067 | latlng.lng += c.lng; |
| 15068 | return latlng; |
| 15069 | } |
| 15070 | |
| 15071 | /** Returns the projected x-coordinate. */ |
| 15072 | scale.x = function(latlng) { |
| 15073 | return scale(latlng).x; |
| 15074 | }; |
| 15075 | |
| 15076 | /** Returns the projected y-coordinate. */ |
| 15077 | scale.y = function(latlng) { |
| 15078 | return scale(latlng).y; |
| 15079 | }; |
| 15080 | |
| 15081 | /** |
| 15082 | * Abstract; this is a local namespace on a given geographic scale. |
| 15083 | * |
| 15084 | * @namespace Tick functions for geographic scales. Because geographic scales |
| 15085 | * represent two-dimensional transformations (as opposed to one-dimensional |
| 15086 | * transformations typical of other scales), the tick values are similarly |
| 15087 | * represented as two-dimensional coordinates in the input domain, i.e., |
| 15088 | * {@link pv.Geo.LatLng} objects. |
| 15089 | * |
| 15090 | * <p>Also, note that non-rectilinear projections, such as sinsuoidal and |
| 15091 | * aitoff, may not produce straight lines for constant longitude or constant |
| 15092 | * latitude. Therefore the returned array of ticks is a two-dimensional array, |
| 15093 | * sampling various latitudes as constant longitude, and vice versa. |
| 15094 | * |
| 15095 | * <p>The tick lines can therefore be approximated as polylines, either with |
| 15096 | * "linear" or "cardinal" interpolation. This is not as accurate as drawing |
| 15097 | * the true curve through the projection space, but is usually sufficient. |
| 15098 | * |
| 15099 | * @name pv.Geo.scale.prototype.ticks |
| 15100 | * @see pv.Geo.scale |
| 15101 | * @see pv.Geo.LatLng |
| 15102 | * @see pv.Line#interpolate |
| 15103 | */ |
| 15104 | scale.ticks = { |
| 15105 | |
| 15106 | /** |
| 15107 | * Returns longitude ticks. |
| 15108 | * |
| 15109 | * @function |
| 15110 | * @param {number} [m] the desired number of ticks. |
| 15111 | * @returns {array} a nested array of <tt>pv.Geo.LatLng</tt> ticks. |
| 15112 | * @name pv.Geo.scale.prototype.ticks.prototype.lng |
| 15113 | */ |
| 15114 | lng: function(m) { |
| 15115 | var lat, lng; |
| 15116 | if (d.length > 1) { |
| 15117 | var s = pv.Scale.linear(); |
| 15118 | if (m == undefined) m = 10; |
| 15119 | lat = s.domain(d, function(d) { return d.lat; }).ticks(m); |
| 15120 | lng = s.domain(d, function(d) { return d.lng; }).ticks(m); |
| 15121 | } else { |
| 15122 | lat = pv.range(-80, 81, 10); |
| 15123 | lng = pv.range(-180, 181, 10); |
| 15124 | } |
| 15125 | return lng.map(function(lng) { |
| 15126 | return lat.map(function(lat) { |
| 15127 | return {lat: lat, lng: lng}; |
| 15128 | }); |
| 15129 | }); |
| 15130 | }, |
| 15131 | |
| 15132 | /** |
| 15133 | * Returns latitude ticks. |
| 15134 | * |
| 15135 | * @function |
| 15136 | * @param {number} [m] the desired number of ticks. |
| 15137 | * @returns {array} a nested array of <tt>pv.Geo.LatLng</tt> ticks. |
| 15138 | * @name pv.Geo.scale.prototype.ticks.prototype.lat |
| 15139 | */ |
| 15140 | lat: function(m) { |
| 15141 | return pv.transpose(scale.ticks.lng(m)); |
| 15142 | } |
| 15143 | }; |
| 15144 | |
| 15145 | /** |
| 15146 | * Inverts the specified value in the output range, returning the |
| 15147 | * corresponding value in the input domain. This is frequently used to convert |
| 15148 | * the mouse location (see {@link pv.Mark#mouse}) to a value in the input |
| 15149 | * domain. Inversion is only supported for numeric ranges, and not colors. |
| 15150 | * |
| 15151 | * <p>Note that this method does not do any rounding or bounds checking. If |
| 15152 | * the input domain is discrete (e.g., an array index), the returned value |
| 15153 | * should be rounded. If the specified <tt>y</tt> value is outside the range, |
| 15154 | * the returned value may be equivalently outside the input domain. |
| 15155 | * |
| 15156 | * @function |
| 15157 | * @name pv.Geo.scale.prototype.invert |
| 15158 | * @param {number} y a value in the output range (a pixel location). |
| 15159 | * @returns {number} a value in the input domain. |
| 15160 | */ |
| 15161 | scale.invert = function(p) { |
| 15162 | return invert({x: x.invert(p.x), y: y.invert(p.y)}); |
| 15163 | }; |
| 15164 | |
| 15165 | /** |
| 15166 | * Sets or gets the input domain. Note that unlike quantitative scales, the |
| 15167 | * domain cannot be reduced to a simple rectangle (i.e., minimum and maximum |
| 15168 | * values for latitude and longitude). Instead, the domain values must be |
| 15169 | * projected to normalized space, effectively finding the domain in normalized |
| 15170 | * space rather than in terms of latitude and longitude. Thus, changing the |
| 15171 | * projection requires recomputing the normalized domain. |
| 15172 | * |
| 15173 | * <p>This method can be invoked several ways: |
| 15174 | * |
| 15175 | * <p>1. <tt>domain(values...)</tt> |
| 15176 | * |
| 15177 | * <p>Specifying the domain as a series of {@link pv.Geo.LatLng}s is the most |
| 15178 | * explicit and recommended approach. However, if the domain values are |
| 15179 | * derived from data, you may find the second method more appropriate. |
| 15180 | * |
| 15181 | * <p>2. <tt>domain(array, f)</tt> |
| 15182 | * |
| 15183 | * <p>Rather than enumerating the domain explicitly, you can specify a single |
| 15184 | * argument of an array. In addition, you can specify an optional accessor |
| 15185 | * function to extract the domain values (as {@link pv.Geo.LatLng}s) from the |
| 15186 | * array. If the specified array has fewer than two elements, this scale will |
| 15187 | * default to the full normalized domain. |
| 15188 | * |
| 15189 | * <p>2. <tt>domain()</tt> |
| 15190 | * |
| 15191 | * <p>Invoking the <tt>domain</tt> method with no arguments returns the |
| 15192 | * current domain as an array. |
| 15193 | * |
| 15194 | * @function |
| 15195 | * @name pv.Geo.scale.prototype.domain |
| 15196 | * @param {...} domain... domain values. |
| 15197 | * @returns {pv.Geo.scale} <tt>this</tt>, or the current domain. |
| 15198 | */ |
| 15199 | scale.domain = function(array, f) { |
| 15200 | if (arguments.length) { |
| 15201 | d = (array instanceof Array) |
| 15202 | ? ((arguments.length > 1) ? pv.map(array, f) : array) |
| 15203 | : Array.prototype.slice.call(arguments); |
| 15204 | if (d.length > 1) { |
| 15205 | var lngs = d.map(function(c) { return c.lng; }); |
| 15206 | var lats = d.map(function(c) { return c.lat; }); |
| 15207 | c = { |
| 15208 | lng: (pv.max(lngs) + pv.min(lngs)) / 2, |
| 15209 | lat: (pv.max(lats) + pv.min(lats)) / 2 |
| 15210 | }; |
| 15211 | var n = d.map(project); // normalized domain |
| 15212 | x.domain(n, function(p) { return p.x; }); |
| 15213 | y.domain(n, function(p) { return p.y; }); |
| 15214 | } else { |
| 15215 | c = {lng: 0, lat: 0}; |
| 15216 | x.domain(-1, 1); |
| 15217 | y.domain(-1, 1); |
| 15218 | } |
| 15219 | lastLatLng = null; // invalidate the cache |
| 15220 | return this; |
| 15221 | } |
| 15222 | return d; |
| 15223 | }; |
| 15224 | |
| 15225 | /** |
| 15226 | * Sets or gets the output range. This method can be invoked several ways: |
| 15227 | * |
| 15228 | * <p>1. <tt>range(min, max)</tt> |
| 15229 | * |
| 15230 | * <p>If two objects are specified, the arguments should be {@link pv.Vector}s |
| 15231 | * which specify the minimum and maximum values of the x- and y-coordinates |
| 15232 | * explicitly. |
| 15233 | * |
| 15234 | * <p>2. <tt>range(width, height)</tt> |
| 15235 | * |
| 15236 | * <p>If two numbers are specified, the arguments specify the maximum values |
| 15237 | * of the x- and y-coordinates explicitly; the minimum values are implicitly |
| 15238 | * zero. |
| 15239 | * |
| 15240 | * <p>3. <tt>range()</tt> |
| 15241 | * |
| 15242 | * <p>Invoking the <tt>range</tt> method with no arguments returns the current |
| 15243 | * range as an array of two {@link pv.Vector}s: the minimum (top-left) and |
| 15244 | * maximum (bottom-right) values. |
| 15245 | * |
| 15246 | * @function |
| 15247 | * @name pv.Geo.scale.prototype.range |
| 15248 | * @param {...} range... range values. |
| 15249 | * @returns {pv.Geo.scale} <tt>this</tt>, or the current range. |
| 15250 | */ |
| 15251 | scale.range = function(min, max) { |
| 15252 | if (arguments.length) { |
| 15253 | if (typeof min == "object") { |
| 15254 | rmin = {x: Number(min.x), y: Number(min.y)}; |
| 15255 | rmax = {x: Number(max.x), y: Number(max.y)}; |
| 15256 | } else { |
| 15257 | rmin = {x: 0, y: 0}; |
| 15258 | rmax = {x: Number(min), y: Number(max)}; |
| 15259 | } |
| 15260 | x.range(rmin.x, rmax.x); |
| 15261 | y.range(rmax.y, rmin.y); // XXX flipped? |
| 15262 | lastLatLng = null; // invalidate the cache |
| 15263 | return this; |
| 15264 | } |
| 15265 | return [rmin, rmax]; |
| 15266 | }; |
| 15267 | |
| 15268 | /** |
| 15269 | * Sets or gets the projection. This method can be invoked several ways: |
| 15270 | * |
| 15271 | * <p>1. <tt>projection(string)</tt> |
| 15272 | * |
| 15273 | * <p>Specifying a string sets the projection to the given named projection in |
| 15274 | * {@link pv.Geo.projections}. If no such projection is found, the identity |
| 15275 | * projection is used. |
| 15276 | * |
| 15277 | * <p>2. <tt>projection(object)</tt> |
| 15278 | * |
| 15279 | * <p>Specifying an object sets the projection to the given custom projection, |
| 15280 | * which must implement the <i>forward</i> and <i>inverse</i> methods per the |
| 15281 | * {@link pv.Geo.Projection} interface. |
| 15282 | * |
| 15283 | * <p>3. <tt>projection()</tt> |
| 15284 | * |
| 15285 | * <p>Invoking the <tt>projection</tt> method with no arguments returns the |
| 15286 | * current object that defined the projection. |
| 15287 | * |
| 15288 | * @function |
| 15289 | * @name pv.Scale.geo.prototype.projection |
| 15290 | * @param {...} range... range values. |
| 15291 | * @returns {pv.Scale.geo} <tt>this</tt>, or the current range. |
| 15292 | */ |
| 15293 | scale.projection = function(p) { |
| 15294 | if (arguments.length) { |
| 15295 | j = typeof p == "string" |
| 15296 | ? pv.Geo.projections[p] || pv.Geo.projections.identity |
| 15297 | : p; |
| 15298 | return this.domain(d); // recompute normalized domain |
| 15299 | } |
| 15300 | return p; |
| 15301 | }; |
| 15302 | |
| 15303 | /** |
| 15304 | * Returns a view of this scale by the specified accessor function <tt>f</tt>. |
| 15305 | * Given a scale <tt>g</tt>, <tt>g.by(function(d) d.foo)</tt> is equivalent to |
| 15306 | * <tt>function(d) g(d.foo)</tt>. This method should be used judiciously; it |
| 15307 | * is typically more clear to invoke the scale directly, passing in the value |
| 15308 | * to be scaled. |
| 15309 | * |
| 15310 | * @function |
| 15311 | * @name pv.Geo.scale.prototype.by |
| 15312 | * @param {function} f an accessor function. |
| 15313 | * @returns {pv.Geo.scale} a view of this scale by the specified accessor |
| 15314 | * function. |
| 15315 | */ |
| 15316 | scale.by = function(f) { |
| 15317 | function by() { return scale(f.apply(this, arguments)); } |
| 15318 | for (var method in scale) by[method] = scale[method]; |
| 15319 | return by; |
| 15320 | }; |
| 15321 | |
| 15322 | if (arguments.length) scale.projection(p); |
| 15323 | return scale; |
| 15324 | }; |