github.com/ghodss/etcd@v0.3.1-0.20140417172404-cc329bfa55cb/mod/dashboard/app/scripts/vega.js (about) 1 vg = (function(d3, topojson) { // take d3 & topojson as imports 2 var vg = { 3 version: "1.3.2", // semantic versioning 4 d3: d3, // stash d3 for use in property functions 5 topojson: topojson // stash topojson similarly 6 }; 7 // type checking functions 8 var toString = Object.prototype.toString; 9 10 vg.isObject = function(obj) { 11 return obj === Object(obj); 12 }; 13 14 vg.isFunction = function(obj) { 15 return toString.call(obj) == '[object Function]'; 16 }; 17 18 vg.isString = function(obj) { 19 return toString.call(obj) == '[object String]'; 20 }; 21 22 vg.isArray = Array.isArray || function(obj) { 23 return toString.call(obj) == '[object Array]'; 24 }; 25 26 vg.isNumber = function(obj) { 27 return toString.call(obj) == '[object Number]'; 28 }; 29 30 vg.isBoolean = function(obj) { 31 return toString.call(obj) == '[object Boolean]'; 32 }; 33 34 vg.isTree = function(obj) { 35 return vg.isArray(obj) && obj.__vgtree__; 36 }; 37 38 vg.number = function(s) { return +s; }; 39 40 vg.boolean = function(s) { return !!s; }; 41 42 // utility functions 43 44 vg.identity = function(x) { return x; }; 45 46 vg.extend = function(obj) { 47 for (var x, name, i=1, len=arguments.length; i<len; ++i) { 48 x = arguments[i]; 49 for (name in x) { obj[name] = x[name]; } 50 } 51 return obj; 52 }; 53 54 vg.duplicate = function(obj) { 55 return JSON.parse(JSON.stringify(obj)); 56 }; 57 58 vg.field = function(f) { 59 return f.split("\\.") 60 .map(function(d) { return d.split("."); }) 61 .reduce(function(a, b) { 62 if (a.length) { a[a.length-1] += "." + b.shift(); } 63 a.push.apply(a, b); 64 return a; 65 }, []); 66 }; 67 68 vg.accessor = function(f) { 69 var s; 70 return (vg.isFunction(f) || f==null) 71 ? f : vg.isString(f) && (s=vg.field(f)).length > 1 72 ? function(x) { return s.reduce(function(x,f) { return x[f]; }, x); } 73 : function(x) { return x[f]; }; 74 }; 75 76 vg.comparator = function(sort) { 77 var sign = []; 78 if (sort === undefined) sort = []; 79 sort = vg.array(sort).map(function(f) { 80 var s = 1; 81 if (f[0] === "-") { s = -1; f = f.slice(1); } 82 else if (f[0] === "+") { s = +1; f = f.slice(1); } 83 sign.push(s); 84 return vg.accessor(f); 85 }); 86 return function(a,b) { 87 var i, n, f, x, y; 88 for (i=0, n=sort.length; i<n; ++i) { 89 f = sort[i]; x = f(a); y = f(b); 90 if (x < y) return -1 * sign[i]; 91 if (x > y) return sign[i]; 92 } 93 return 0; 94 }; 95 }; 96 97 vg.cmp = function(a, b) { return a<b ? -1 : a>b ? 1 : 0; }; 98 99 vg.numcmp = function(a, b) { return a - b; }; 100 101 vg.array = function(x) { 102 return x != null ? (vg.isArray(x) ? x : [x]) : []; 103 }; 104 105 vg.values = function(x) { 106 return (vg.isObject(x) && !vg.isArray(x) && x.values) ? x.values : x; 107 }; 108 109 vg.str = function(x) { 110 return vg.isArray(x) ? "[" + x.map(vg.str) + "]" 111 : vg.isObject(x) ? JSON.stringify(x) 112 : vg.isString(x) ? ("'"+vg_escape_str(x)+"'") : x; 113 }; 114 115 var escape_str_re = /(^|[^\\])'/g; 116 117 function vg_escape_str(x) { 118 return x.replace(escape_str_re, "$1\\'"); 119 } 120 121 vg.keys = function(x) { 122 var keys = []; 123 for (var key in x) keys.push(key); 124 return keys; 125 }; 126 127 vg.unique = function(data, f, results) { 128 if (!vg.isArray(data) || data.length==0) return []; 129 f = f || vg.identity; 130 results = results || []; 131 for (var v, i=0, n=data.length; i<n; ++i) { 132 v = f(data[i]); 133 if (results.indexOf(v) < 0) results.push(v); 134 } 135 return results; 136 }; 137 138 vg.minIndex = function(data, f) { 139 if (!vg.isArray(data) || data.length==0) return -1; 140 f = f || vg.identity; 141 var idx = 0, min = f(data[0]), v = min; 142 for (var i=1, n=data.length; i<n; ++i) { 143 v = f(data[i]); 144 if (v < min) { min = v; idx = i; } 145 } 146 return idx; 147 }; 148 149 vg.maxIndex = function(data, f) { 150 if (!vg.isArray(data) || data.length==0) return -1; 151 f = f || vg.identity; 152 var idx = 0, max = f(data[0]), v = max; 153 for (var i=1, n=data.length; i<n; ++i) { 154 v = f(data[i]); 155 if (v > max) { max = v; idx = i; } 156 } 157 return idx; 158 }; 159 160 vg.truncate = function(s, length, pos, word, ellipsis) { 161 var len = s.length; 162 if (len <= length) return s; 163 ellipsis = ellipsis || "..."; 164 var l = Math.max(0, length - ellipsis.length); 165 166 switch (pos) { 167 case "left": 168 return ellipsis + (word ? vg_truncateOnWord(s,l,1) : s.slice(len-l)); 169 case "middle": 170 case "center": 171 var l1 = Math.ceil(l/2), l2 = Math.floor(l/2); 172 return (word ? vg_truncateOnWord(s,l1) : s.slice(0,l1)) + ellipsis 173 + (word ? vg_truncateOnWord(s,l2,1) : s.slice(len-l2)); 174 default: 175 return (word ? vg_truncateOnWord(s,l) : s.slice(0,l)) + ellipsis; 176 } 177 } 178 179 function vg_truncateOnWord(s, len, rev) { 180 var cnt = 0, tok = s.split(vg_truncate_word_re); 181 if (rev) { 182 s = (tok = tok.reverse()) 183 .filter(function(w) { cnt += w.length; return cnt <= len; }) 184 .reverse(); 185 } else { 186 s = tok.filter(function(w) { cnt += w.length; return cnt <= len; }); 187 } 188 return s.length ? s.join("").trim() : tok[0].slice(0, len); 189 } 190 191 var vg_truncate_word_re = /([\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u2028\u2029\u3000\uFEFF])/; 192 193 // Logging 194 195 function vg_write(msg) { 196 vg.config.isNode 197 ? process.stderr.write(msg + "\n") 198 : console.log(msg); 199 } 200 201 vg.log = function(msg) { 202 vg_write("[Vega Log] " + msg); 203 }; 204 205 vg.error = function(msg) { 206 msg = "[Vega Err] " + msg; 207 vg_write(msg); 208 if (typeof alert !== "undefined") alert(msg); 209 };vg.config = {}; 210 211 // are we running in node.js? 212 // via timetler.com/2012/10/13/environment-detection-in-javascript/ 213 vg.config.isNode = typeof exports !== 'undefined' && this.exports !== exports; 214 215 // base url for loading external data files 216 // used only for server-side operation 217 vg.config.baseURL = ""; 218 219 // version and namepsaces for exported svg 220 vg.config.svgNamespace = 221 'version="1.1" xmlns="http://www.w3.org/2000/svg" ' + 222 'xmlns:xlink="http://www.w3.org/1999/xlink"'; 223 224 // inset padding for automatic padding calculation 225 vg.config.autopadInset = 5; 226 227 // extensible scale lookup table 228 // all d3.scale.* instances also supported 229 vg.config.scale = { 230 time: d3.time.scale, 231 utc: d3.time.scale.utc 232 }; 233 234 // default rendering settings 235 vg.config.render = { 236 lineWidth: 1, 237 lineCap: "butt", 238 font: "sans-serif", 239 fontSize: 11 240 }; 241 242 // default axis properties 243 vg.config.axis = { 244 orient: "bottom", 245 ticks: 10, 246 padding: 3, 247 axisColor: "#000", 248 gridColor: "#d8d8d8", 249 tickColor: "#000", 250 tickLabelColor: "#000", 251 axisWidth: 1, 252 tickWidth: 1, 253 tickSize: 6, 254 tickLabelFontSize: 11, 255 tickLabelFont: "sans-serif", 256 titleColor: "#000", 257 titleFont: "sans-serif", 258 titleFontSize: 11, 259 titleFontWeight: "bold", 260 titleOffset: 35 261 }; 262 263 // default legend properties 264 vg.config.legend = { 265 orient: "right", 266 offset: 10, 267 padding: 3, 268 gradientStrokeColor: "#888", 269 gradientStrokeWidth: 1, 270 gradientHeight: 16, 271 gradientWidth: 100, 272 labelColor: "#000", 273 labelFontSize: 10, 274 labelFont: "sans-serif", 275 labelAlign: "left", 276 labelBaseline: "middle", 277 labelOffset: 8, 278 symbolShape: "circle", 279 symbolSize: 50, 280 symbolColor: "#888", 281 symbolStrokeWidth: 1, 282 titleColor: "#000", 283 titleFont: "sans-serif", 284 titleFontSize: 11, 285 titleFontWeight: "bold" 286 }; 287 288 // default color values 289 vg.config.color = { 290 rgb: [128, 128, 128], 291 lab: [50, 0, 0], 292 hcl: [0, 0, 50], 293 hsl: [0, 0, 0.5] 294 }; 295 296 // default scale ranges 297 vg.config.range = { 298 category10: [ 299 "#1f77b4", 300 "#ff7f0e", 301 "#2ca02c", 302 "#d62728", 303 "#9467bd", 304 "#8c564b", 305 "#e377c2", 306 "#7f7f7f", 307 "#bcbd22", 308 "#17becf" 309 ], 310 category20: [ 311 "#1f77b4", 312 "#aec7e8", 313 "#ff7f0e", 314 "#ffbb78", 315 "#2ca02c", 316 "#98df8a", 317 "#d62728", 318 "#ff9896", 319 "#9467bd", 320 "#c5b0d5", 321 "#8c564b", 322 "#c49c94", 323 "#e377c2", 324 "#f7b6d2", 325 "#7f7f7f", 326 "#c7c7c7", 327 "#bcbd22", 328 "#dbdb8d", 329 "#17becf", 330 "#9edae5" 331 ], 332 shapes: [ 333 "circle", 334 "cross", 335 "diamond", 336 "square", 337 "triangle-down", 338 "triangle-up" 339 ] 340 };vg.Bounds = (function() { 341 var bounds = function(b) { 342 this.clear(); 343 if (b) this.union(b); 344 }; 345 346 var prototype = bounds.prototype; 347 348 prototype.clear = function() { 349 this.x1 = +Number.MAX_VALUE; 350 this.y1 = +Number.MAX_VALUE; 351 this.x2 = -Number.MAX_VALUE; 352 this.y2 = -Number.MAX_VALUE; 353 return this; 354 }; 355 356 prototype.set = function(x1, y1, x2, y2) { 357 this.x1 = x1; 358 this.y1 = y1; 359 this.x2 = x2; 360 this.y2 = y2; 361 return this; 362 }; 363 364 prototype.add = function(x, y) { 365 if (x < this.x1) this.x1 = x; 366 if (y < this.y1) this.y1 = y; 367 if (x > this.x2) this.x2 = x; 368 if (y > this.y2) this.y2 = y; 369 return this; 370 }; 371 372 prototype.expand = function(d) { 373 this.x1 -= d; 374 this.y1 -= d; 375 this.x2 += d; 376 this.y2 += d; 377 return this; 378 }; 379 380 prototype.round = function() { 381 this.x1 = Math.floor(this.x1); 382 this.y1 = Math.floor(this.y1); 383 this.x2 = Math.ceil(this.x2); 384 this.y2 = Math.ceil(this.y2); 385 return this; 386 }; 387 388 prototype.translate = function(dx, dy) { 389 this.x1 += dx; 390 this.x2 += dx; 391 this.y1 += dy; 392 this.y2 += dy; 393 return this; 394 }; 395 396 prototype.rotate = function(angle, x, y) { 397 var cos = Math.cos(angle), 398 sin = Math.sin(angle), 399 cx = x - x*cos + y*sin, 400 cy = y - x*sin - y*cos, 401 x1 = this.x1, x2 = this.x2, 402 y1 = this.y1, y2 = this.y2; 403 404 return this.clear() 405 .add(cos*x1 - sin*y1 + cx, sin*x1 + cos*y1 + cy) 406 .add(cos*x1 - sin*y2 + cx, sin*x1 + cos*y2 + cy) 407 .add(cos*x2 - sin*y1 + cx, sin*x2 + cos*y1 + cy) 408 .add(cos*x2 - sin*y2 + cx, sin*x2 + cos*y2 + cy); 409 } 410 411 prototype.union = function(b) { 412 if (b.x1 < this.x1) this.x1 = b.x1; 413 if (b.y1 < this.y1) this.y1 = b.y1; 414 if (b.x2 > this.x2) this.x2 = b.x2; 415 if (b.y2 > this.y2) this.y2 = b.y2; 416 return this; 417 }; 418 419 prototype.encloses = function(b) { 420 return b && ( 421 this.x1 <= b.x1 && 422 this.x2 >= b.x2 && 423 this.y1 <= b.y1 && 424 this.y2 >= b.y2 425 ); 426 }; 427 428 prototype.intersects = function(b) { 429 return b && !( 430 this.x2 < b.x1 || 431 this.x1 > b.x2 || 432 this.y2 < b.y1 || 433 this.y1 > b.y2 434 ); 435 }; 436 437 prototype.contains = function(x, y) { 438 return !( 439 x < this.x1 || 440 x > this.x2 || 441 y < this.y1 || 442 y > this.y2 443 ); 444 }; 445 446 prototype.width = function() { 447 return this.x2 - this.x1; 448 }; 449 450 prototype.height = function() { 451 return this.y2 - this.y1; 452 }; 453 454 return bounds; 455 })();vg.Gradient = (function() { 456 457 function gradient(type) { 458 this.id = "grad_" + (vg_gradient_id++); 459 this.type = type || "linear"; 460 this.stops = []; 461 this.x1 = 0; 462 this.x2 = 1; 463 this.y1 = 0; 464 this.y2 = 0; 465 }; 466 467 var prototype = gradient.prototype; 468 469 prototype.stop = function(offset, color) { 470 this.stops.push({ 471 offset: offset, 472 color: color 473 }); 474 return this; 475 }; 476 477 return gradient; 478 })(); 479 480 var vg_gradient_id = 0;vg.canvas = {};vg.canvas.path = (function() { 481 482 // Path parsing and rendering code taken from fabric.js -- Thanks! 483 var cmdLength = { m:2, l:2, h:1, v:1, c:6, s:4, q:4, t:2, a:7 }, 484 re = [/([MLHVCSQTAZmlhvcsqtaz])/g, /###/, /(\d)-/g, /\s|,|###/]; 485 486 function parse(path) { 487 var result = [], 488 currentPath, 489 chunks, 490 parsed; 491 492 // First, break path into command sequence 493 path = path.slice().replace(re[0], '###$1').split(re[1]).slice(1); 494 495 // Next, parse each command in turn 496 for (var i=0, j, chunksParsed, len=path.length; i<len; i++) { 497 currentPath = path[i]; 498 chunks = currentPath.slice(1).trim().replace(re[2],'$1###-').split(re[3]); 499 chunksParsed = [currentPath.charAt(0)]; 500 501 for (var j = 0, jlen = chunks.length; j < jlen; j++) { 502 parsed = parseFloat(chunks[j]); 503 if (!isNaN(parsed)) { 504 chunksParsed.push(parsed); 505 } 506 } 507 508 var command = chunksParsed[0].toLowerCase(), 509 commandLength = cmdLength[command]; 510 511 if (chunksParsed.length - 1 > commandLength) { 512 for (var k = 1, klen = chunksParsed.length; k < klen; k += commandLength) { 513 result.push([ chunksParsed[0] ].concat(chunksParsed.slice(k, k + commandLength))); 514 } 515 } 516 else { 517 result.push(chunksParsed); 518 } 519 } 520 521 return result; 522 } 523 524 function drawArc(g, x, y, coords, bounds, l, t) { 525 var rx = coords[0]; 526 var ry = coords[1]; 527 var rot = coords[2]; 528 var large = coords[3]; 529 var sweep = coords[4]; 530 var ex = coords[5]; 531 var ey = coords[6]; 532 var segs = arcToSegments(ex, ey, rx, ry, large, sweep, rot, x, y); 533 for (var i=0; i<segs.length; i++) { 534 var bez = segmentToBezier.apply(null, segs[i]); 535 g.bezierCurveTo.apply(g, bez); 536 bounds.add(bez[0]-l, bez[1]-t); 537 bounds.add(bez[2]-l, bez[3]-t); 538 bounds.add(bez[4]-l, bez[5]-t); 539 } 540 } 541 542 function boundArc(x, y, coords, bounds) { 543 var rx = coords[0]; 544 var ry = coords[1]; 545 var rot = coords[2]; 546 var large = coords[3]; 547 var sweep = coords[4]; 548 var ex = coords[5]; 549 var ey = coords[6]; 550 var segs = arcToSegments(ex, ey, rx, ry, large, sweep, rot, x, y); 551 for (var i=0; i<segs.length; i++) { 552 var bez = segmentToBezier.apply(null, segs[i]); 553 bounds.add(bez[0]-l, bez[1]-t); 554 bounds.add(bez[2]-l, bez[3]-t); 555 bounds.add(bez[4]-l, bez[5]-t); 556 } 557 } 558 559 var arcToSegmentsCache = { }, 560 segmentToBezierCache = { }, 561 join = Array.prototype.join, 562 argsStr; 563 564 // Copied from Inkscape svgtopdf, thanks! 565 function arcToSegments(x, y, rx, ry, large, sweep, rotateX, ox, oy) { 566 argsStr = join.call(arguments); 567 if (arcToSegmentsCache[argsStr]) { 568 return arcToSegmentsCache[argsStr]; 569 } 570 571 var th = rotateX * (Math.PI/180); 572 var sin_th = Math.sin(th); 573 var cos_th = Math.cos(th); 574 rx = Math.abs(rx); 575 ry = Math.abs(ry); 576 var px = cos_th * (ox - x) * 0.5 + sin_th * (oy - y) * 0.5; 577 var py = cos_th * (oy - y) * 0.5 - sin_th * (ox - x) * 0.5; 578 var pl = (px*px) / (rx*rx) + (py*py) / (ry*ry); 579 if (pl > 1) { 580 pl = Math.sqrt(pl); 581 rx *= pl; 582 ry *= pl; 583 } 584 585 var a00 = cos_th / rx; 586 var a01 = sin_th / rx; 587 var a10 = (-sin_th) / ry; 588 var a11 = (cos_th) / ry; 589 var x0 = a00 * ox + a01 * oy; 590 var y0 = a10 * ox + a11 * oy; 591 var x1 = a00 * x + a01 * y; 592 var y1 = a10 * x + a11 * y; 593 594 var d = (x1-x0) * (x1-x0) + (y1-y0) * (y1-y0); 595 var sfactor_sq = 1 / d - 0.25; 596 if (sfactor_sq < 0) sfactor_sq = 0; 597 var sfactor = Math.sqrt(sfactor_sq); 598 if (sweep == large) sfactor = -sfactor; 599 var xc = 0.5 * (x0 + x1) - sfactor * (y1-y0); 600 var yc = 0.5 * (y0 + y1) + sfactor * (x1-x0); 601 602 var th0 = Math.atan2(y0-yc, x0-xc); 603 var th1 = Math.atan2(y1-yc, x1-xc); 604 605 var th_arc = th1-th0; 606 if (th_arc < 0 && sweep == 1){ 607 th_arc += 2*Math.PI; 608 } else if (th_arc > 0 && sweep == 0) { 609 th_arc -= 2 * Math.PI; 610 } 611 612 var segments = Math.ceil(Math.abs(th_arc / (Math.PI * 0.5 + 0.001))); 613 var result = []; 614 for (var i=0; i<segments; i++) { 615 var th2 = th0 + i * th_arc / segments; 616 var th3 = th0 + (i+1) * th_arc / segments; 617 result[i] = [xc, yc, th2, th3, rx, ry, sin_th, cos_th]; 618 } 619 620 return (arcToSegmentsCache[argsStr] = result); 621 } 622 623 function segmentToBezier(cx, cy, th0, th1, rx, ry, sin_th, cos_th) { 624 argsStr = join.call(arguments); 625 if (segmentToBezierCache[argsStr]) { 626 return segmentToBezierCache[argsStr]; 627 } 628 629 var a00 = cos_th * rx; 630 var a01 = -sin_th * ry; 631 var a10 = sin_th * rx; 632 var a11 = cos_th * ry; 633 634 var cos_th0 = Math.cos(th0); 635 var sin_th0 = Math.sin(th0); 636 var cos_th1 = Math.cos(th1); 637 var sin_th1 = Math.sin(th1); 638 639 var th_half = 0.5 * (th1 - th0); 640 var sin_th_h2 = Math.sin(th_half * 0.5); 641 var t = (8/3) * sin_th_h2 * sin_th_h2 / Math.sin(th_half); 642 var x1 = cx + cos_th0 - t * sin_th0; 643 var y1 = cy + sin_th0 + t * cos_th0; 644 var x3 = cx + cos_th1; 645 var y3 = cy + sin_th1; 646 var x2 = x3 + t * sin_th1; 647 var y2 = y3 - t * cos_th1; 648 649 return (segmentToBezierCache[argsStr] = [ 650 a00 * x1 + a01 * y1, a10 * x1 + a11 * y1, 651 a00 * x2 + a01 * y2, a10 * x2 + a11 * y2, 652 a00 * x3 + a01 * y3, a10 * x3 + a11 * y3 653 ]); 654 } 655 656 function render(g, path, l, t) { 657 var current, // current instruction 658 previous = null, 659 x = 0, // current x 660 y = 0, // current y 661 controlX = 0, // current control point x 662 controlY = 0, // current control point y 663 tempX, 664 tempY, 665 tempControlX, 666 tempControlY, 667 bounds = new vg.Bounds(); 668 if (l == undefined) l = 0; 669 if (t == undefined) t = 0; 670 671 g.beginPath(); 672 673 for (var i=0, len=path.length; i<len; ++i) { 674 current = path[i]; 675 676 switch (current[0]) { // first letter 677 678 case 'l': // lineto, relative 679 x += current[1]; 680 y += current[2]; 681 g.lineTo(x + l, y + t); 682 bounds.add(x, y); 683 break; 684 685 case 'L': // lineto, absolute 686 x = current[1]; 687 y = current[2]; 688 g.lineTo(x + l, y + t); 689 bounds.add(x, y); 690 break; 691 692 case 'h': // horizontal lineto, relative 693 x += current[1]; 694 g.lineTo(x + l, y + t); 695 bounds.add(x, y); 696 break; 697 698 case 'H': // horizontal lineto, absolute 699 x = current[1]; 700 g.lineTo(x + l, y + t); 701 bounds.add(x, y); 702 break; 703 704 case 'v': // vertical lineto, relative 705 y += current[1]; 706 g.lineTo(x + l, y + t); 707 bounds.add(x, y); 708 break; 709 710 case 'V': // verical lineto, absolute 711 y = current[1]; 712 g.lineTo(x + l, y + t); 713 bounds.add(x, y); 714 break; 715 716 case 'm': // moveTo, relative 717 x += current[1]; 718 y += current[2]; 719 g.moveTo(x + l, y + t); 720 bounds.add(x, y); 721 break; 722 723 case 'M': // moveTo, absolute 724 x = current[1]; 725 y = current[2]; 726 g.moveTo(x + l, y + t); 727 bounds.add(x, y); 728 break; 729 730 case 'c': // bezierCurveTo, relative 731 tempX = x + current[5]; 732 tempY = y + current[6]; 733 controlX = x + current[3]; 734 controlY = y + current[4]; 735 g.bezierCurveTo( 736 x + current[1] + l, // x1 737 y + current[2] + t, // y1 738 controlX + l, // x2 739 controlY + t, // y2 740 tempX + l, 741 tempY + t 742 ); 743 bounds.add(x + current[1], y + current[2]); 744 bounds.add(controlX, controlY); 745 bounds.add(tempX, tempY); 746 x = tempX; 747 y = tempY; 748 break; 749 750 case 'C': // bezierCurveTo, absolute 751 x = current[5]; 752 y = current[6]; 753 controlX = current[3]; 754 controlY = current[4]; 755 g.bezierCurveTo( 756 current[1] + l, 757 current[2] + t, 758 controlX + l, 759 controlY + t, 760 x + l, 761 y + t 762 ); 763 bounds.add(current[1], current[2]); 764 bounds.add(controlX, controlY); 765 bounds.add(x, y); 766 break; 767 768 case 's': // shorthand cubic bezierCurveTo, relative 769 // transform to absolute x,y 770 tempX = x + current[3]; 771 tempY = y + current[4]; 772 // calculate reflection of previous control points 773 controlX = 2 * x - controlX; 774 controlY = 2 * y - controlY; 775 g.bezierCurveTo( 776 controlX + l, 777 controlY + t, 778 x + current[1] + l, 779 y + current[2] + t, 780 tempX + l, 781 tempY + t 782 ); 783 bounds.add(controlX, controlY); 784 bounds.add(x + current[1], y + current[2]); 785 bounds.add(tempX, tempY); 786 787 // set control point to 2nd one of this command 788 // "... the first control point is assumed to be the reflection of the second control point on the previous command relative to the current point." 789 controlX = x + current[1]; 790 controlY = y + current[2]; 791 792 x = tempX; 793 y = tempY; 794 break; 795 796 case 'S': // shorthand cubic bezierCurveTo, absolute 797 tempX = current[3]; 798 tempY = current[4]; 799 // calculate reflection of previous control points 800 controlX = 2*x - controlX; 801 controlY = 2*y - controlY; 802 g.bezierCurveTo( 803 controlX + l, 804 controlY + t, 805 current[1] + l, 806 current[2] + t, 807 tempX + l, 808 tempY + t 809 ); 810 x = tempX; 811 y = tempY; 812 bounds.add(current[1], current[2]); 813 bounds.add(controlX, controlY); 814 bounds.add(tempX, tempY); 815 // set control point to 2nd one of this command 816 // "... the first control point is assumed to be the reflection of the second control point on the previous command relative to the current point." 817 controlX = current[1]; 818 controlY = current[2]; 819 820 break; 821 822 case 'q': // quadraticCurveTo, relative 823 // transform to absolute x,y 824 tempX = x + current[3]; 825 tempY = y + current[4]; 826 827 controlX = x + current[1]; 828 controlY = y + current[2]; 829 830 g.quadraticCurveTo( 831 controlX + l, 832 controlY + t, 833 tempX + l, 834 tempY + t 835 ); 836 x = tempX; 837 y = tempY; 838 bounds.add(controlX, controlY); 839 bounds.add(tempX, tempY); 840 break; 841 842 case 'Q': // quadraticCurveTo, absolute 843 tempX = current[3]; 844 tempY = current[4]; 845 846 g.quadraticCurveTo( 847 current[1] + l, 848 current[2] + t, 849 tempX + l, 850 tempY + t 851 ); 852 x = tempX; 853 y = tempY; 854 controlX = current[1]; 855 controlY = current[2]; 856 bounds.add(controlX, controlY); 857 bounds.add(tempX, tempY); 858 break; 859 860 case 't': // shorthand quadraticCurveTo, relative 861 862 // transform to absolute x,y 863 tempX = x + current[1]; 864 tempY = y + current[2]; 865 866 if (previous[0].match(/[QqTt]/) === null) { 867 // If there is no previous command or if the previous command was not a Q, q, T or t, 868 // assume the control point is coincident with the current point 869 controlX = x; 870 controlY = y; 871 } 872 else if (previous[0] === 't') { 873 // calculate reflection of previous control points for t 874 controlX = 2 * x - tempControlX; 875 controlY = 2 * y - tempControlY; 876 } 877 else if (previous[0] === 'q') { 878 // calculate reflection of previous control points for q 879 controlX = 2 * x - controlX; 880 controlY = 2 * y - controlY; 881 } 882 883 tempControlX = controlX; 884 tempControlY = controlY; 885 886 g.quadraticCurveTo( 887 controlX + l, 888 controlY + t, 889 tempX + l, 890 tempY + t 891 ); 892 x = tempX; 893 y = tempY; 894 controlX = x + current[1]; 895 controlY = y + current[2]; 896 bounds.add(controlX, controlY); 897 bounds.add(tempX, tempY); 898 break; 899 900 case 'T': 901 tempX = current[1]; 902 tempY = current[2]; 903 904 // calculate reflection of previous control points 905 controlX = 2 * x - controlX; 906 controlY = 2 * y - controlY; 907 g.quadraticCurveTo( 908 controlX + l, 909 controlY + t, 910 tempX + l, 911 tempY + t 912 ); 913 x = tempX; 914 y = tempY; 915 bounds.add(controlX, controlY); 916 bounds.add(tempX, tempY); 917 break; 918 919 case 'a': 920 drawArc(g, x + l, y + t, [ 921 current[1], 922 current[2], 923 current[3], 924 current[4], 925 current[5], 926 current[6] + x + l, 927 current[7] + y + t 928 ], bounds, l, t); 929 x += current[6]; 930 y += current[7]; 931 break; 932 933 case 'A': 934 drawArc(g, x + l, y + t, [ 935 current[1], 936 current[2], 937 current[3], 938 current[4], 939 current[5], 940 current[6] + l, 941 current[7] + t 942 ], bounds, l, t); 943 x = current[6]; 944 y = current[7]; 945 break; 946 947 case 'z': 948 case 'Z': 949 g.closePath(); 950 break; 951 } 952 previous = current; 953 } 954 return bounds.translate(l, t); 955 } 956 957 function bounds(path, bounds) { 958 var current, // current instruction 959 previous = null, 960 x = 0, // current x 961 y = 0, // current y 962 controlX = 0, // current control point x 963 controlY = 0, // current control point y 964 tempX, 965 tempY, 966 tempControlX, 967 tempControlY; 968 969 for (var i=0, len=path.length; i<len; ++i) { 970 current = path[i]; 971 972 switch (current[0]) { // first letter 973 974 case 'l': // lineto, relative 975 x += current[1]; 976 y += current[2]; 977 bounds.add(x, y); 978 break; 979 980 case 'L': // lineto, absolute 981 x = current[1]; 982 y = current[2]; 983 bounds.add(x, y); 984 break; 985 986 case 'h': // horizontal lineto, relative 987 x += current[1]; 988 bounds.add(x, y); 989 break; 990 991 case 'H': // horizontal lineto, absolute 992 x = current[1]; 993 bounds.add(x, y); 994 break; 995 996 case 'v': // vertical lineto, relative 997 y += current[1]; 998 bounds.add(x, y); 999 break; 1000 1001 case 'V': // verical lineto, absolute 1002 y = current[1]; 1003 bounds.add(x, y); 1004 break; 1005 1006 case 'm': // moveTo, relative 1007 x += current[1]; 1008 y += current[2]; 1009 bounds.add(x, y); 1010 break; 1011 1012 case 'M': // moveTo, absolute 1013 x = current[1]; 1014 y = current[2]; 1015 bounds.add(x, y); 1016 break; 1017 1018 case 'c': // bezierCurveTo, relative 1019 tempX = x + current[5]; 1020 tempY = y + current[6]; 1021 controlX = x + current[3]; 1022 controlY = y + current[4]; 1023 bounds.add(x + current[1], y + current[2]); 1024 bounds.add(controlX, controlY); 1025 bounds.add(tempX, tempY); 1026 x = tempX; 1027 y = tempY; 1028 break; 1029 1030 case 'C': // bezierCurveTo, absolute 1031 x = current[5]; 1032 y = current[6]; 1033 controlX = current[3]; 1034 controlY = current[4]; 1035 bounds.add(current[1], current[2]); 1036 bounds.add(controlX, controlY); 1037 bounds.add(x, y); 1038 break; 1039 1040 case 's': // shorthand cubic bezierCurveTo, relative 1041 // transform to absolute x,y 1042 tempX = x + current[3]; 1043 tempY = y + current[4]; 1044 // calculate reflection of previous control points 1045 controlX = 2 * x - controlX; 1046 controlY = 2 * y - controlY; 1047 bounds.add(controlX, controlY); 1048 bounds.add(x + current[1], y + current[2]); 1049 bounds.add(tempX, tempY); 1050 1051 // set control point to 2nd one of this command 1052 // "... the first control point is assumed to be the reflection of the second control point on the previous command relative to the current point." 1053 controlX = x + current[1]; 1054 controlY = y + current[2]; 1055 1056 x = tempX; 1057 y = tempY; 1058 break; 1059 1060 case 'S': // shorthand cubic bezierCurveTo, absolute 1061 tempX = current[3]; 1062 tempY = current[4]; 1063 // calculate reflection of previous control points 1064 controlX = 2*x - controlX; 1065 controlY = 2*y - controlY; 1066 x = tempX; 1067 y = tempY; 1068 bounds.add(current[1], current[2]); 1069 bounds.add(controlX, controlY); 1070 bounds.add(tempX, tempY); 1071 // set control point to 2nd one of this command 1072 // "... the first control point is assumed to be the reflection of the second control point on the previous command relative to the current point." 1073 controlX = current[1]; 1074 controlY = current[2]; 1075 1076 break; 1077 1078 case 'q': // quadraticCurveTo, relative 1079 // transform to absolute x,y 1080 tempX = x + current[3]; 1081 tempY = y + current[4]; 1082 1083 controlX = x + current[1]; 1084 controlY = y + current[2]; 1085 1086 x = tempX; 1087 y = tempY; 1088 bounds.add(controlX, controlY); 1089 bounds.add(tempX, tempY); 1090 break; 1091 1092 case 'Q': // quadraticCurveTo, absolute 1093 tempX = current[3]; 1094 tempY = current[4]; 1095 1096 x = tempX; 1097 y = tempY; 1098 controlX = current[1]; 1099 controlY = current[2]; 1100 bounds.add(controlX, controlY); 1101 bounds.add(tempX, tempY); 1102 break; 1103 1104 case 't': // shorthand quadraticCurveTo, relative 1105 1106 // transform to absolute x,y 1107 tempX = x + current[1]; 1108 tempY = y + current[2]; 1109 1110 if (previous[0].match(/[QqTt]/) === null) { 1111 // If there is no previous command or if the previous command was not a Q, q, T or t, 1112 // assume the control point is coincident with the current point 1113 controlX = x; 1114 controlY = y; 1115 } 1116 else if (previous[0] === 't') { 1117 // calculate reflection of previous control points for t 1118 controlX = 2 * x - tempControlX; 1119 controlY = 2 * y - tempControlY; 1120 } 1121 else if (previous[0] === 'q') { 1122 // calculate reflection of previous control points for q 1123 controlX = 2 * x - controlX; 1124 controlY = 2 * y - controlY; 1125 } 1126 1127 tempControlX = controlX; 1128 tempControlY = controlY; 1129 1130 x = tempX; 1131 y = tempY; 1132 controlX = x + current[1]; 1133 controlY = y + current[2]; 1134 bounds.add(controlX, controlY); 1135 bounds.add(tempX, tempY); 1136 break; 1137 1138 case 'T': 1139 tempX = current[1]; 1140 tempY = current[2]; 1141 1142 // calculate reflection of previous control points 1143 controlX = 2 * x - controlX; 1144 controlY = 2 * y - controlY; 1145 1146 x = tempX; 1147 y = tempY; 1148 bounds.add(controlX, controlY); 1149 bounds.add(tempX, tempY); 1150 break; 1151 1152 case 'a': 1153 boundArc(x, y, [ 1154 current[1], 1155 current[2], 1156 current[3], 1157 current[4], 1158 current[5], 1159 current[6] + x, 1160 current[7] + y 1161 ], bounds); 1162 x += current[6]; 1163 y += current[7]; 1164 break; 1165 1166 case 'A': 1167 boundArc(x, y, [ 1168 current[1], 1169 current[2], 1170 current[3], 1171 current[4], 1172 current[5], 1173 current[6], 1174 current[7] 1175 ], bounds); 1176 x = current[6]; 1177 y = current[7]; 1178 break; 1179 1180 case 'z': 1181 case 'Z': 1182 break; 1183 } 1184 previous = current; 1185 } 1186 return bounds; 1187 } 1188 1189 function area(items) { 1190 var o = items[0]; 1191 var area = d3.svg.area() 1192 .x(function(d) { return d.x; }) 1193 .y1(function(d) { return d.y; }) 1194 .y0(function(d) { return d.y + d.height; }); 1195 if (o.interpolate) area.interpolate(o.interpolate); 1196 if (o.tension != null) area.tension(o.tension); 1197 return area(items); 1198 } 1199 1200 function line(items) { 1201 var o = items[0]; 1202 var line = d3.svg.line() 1203 .x(function(d) { return d.x; }) 1204 .y(function(d) { return d.y; }); 1205 if (o.interpolate) line.interpolate(o.interpolate); 1206 if (o.tension != null) line.tension(o.tension); 1207 return line(items); 1208 } 1209 1210 return { 1211 parse: parse, 1212 render: render, 1213 bounds: bounds, 1214 area: area, 1215 line: line 1216 }; 1217 1218 })();vg.canvas.marks = (function() { 1219 1220 var parsePath = vg.canvas.path.parse, 1221 renderPath = vg.canvas.path.render, 1222 halfpi = Math.PI / 2, 1223 sqrt3 = Math.sqrt(3), 1224 tan30 = Math.tan(30 * Math.PI / 180), 1225 tmpBounds = new vg.Bounds(); 1226 1227 // path generators 1228 1229 function arcPath(g, o) { 1230 var x = o.x || 0, 1231 y = o.y || 0, 1232 ir = o.innerRadius || 0, 1233 or = o.outerRadius || 0, 1234 sa = (o.startAngle || 0) - Math.PI/2, 1235 ea = (o.endAngle || 0) - Math.PI/2; 1236 g.beginPath(); 1237 if (ir === 0) g.moveTo(x, y); 1238 else g.arc(x, y, ir, sa, ea, 0); 1239 g.arc(x, y, or, ea, sa, 1); 1240 g.closePath(); 1241 } 1242 1243 function pathPath(g, o) { 1244 if (o.path == null) return; 1245 if (!o["path:parsed"]) { 1246 o["path:parsed"] = parsePath(o.path); 1247 } 1248 return renderPath(g, o["path:parsed"], o.x, o.y); 1249 } 1250 1251 function symbolPath(g, o) { 1252 g.beginPath(); 1253 var size = o.size != null ? o.size : 100, 1254 x = o.x, y = o.y, r, t, rx, ry; 1255 1256 if (o.shape == null || o.shape === "circle") { 1257 r = Math.sqrt(size/Math.PI); 1258 g.arc(x, y, r, 0, 2*Math.PI, 0); 1259 g.closePath(); 1260 return; 1261 } 1262 1263 switch (o.shape) { 1264 case "cross": 1265 r = Math.sqrt(size / 5) / 2; 1266 t = 3*r; 1267 g.moveTo(x-t, y-r); 1268 g.lineTo(x-r, y-r); 1269 g.lineTo(x-r, y-t); 1270 g.lineTo(x+r, y-t); 1271 g.lineTo(x+r, y-r); 1272 g.lineTo(x+t, y-r); 1273 g.lineTo(x+t, y+r); 1274 g.lineTo(x+r, y+r); 1275 g.lineTo(x+r, y+t); 1276 g.lineTo(x-r, y+t); 1277 g.lineTo(x-r, y+r); 1278 g.lineTo(x-t, y+r); 1279 break; 1280 1281 case "diamond": 1282 ry = Math.sqrt(size / (2 * tan30)); 1283 rx = ry * tan30; 1284 g.moveTo(x, y-ry); 1285 g.lineTo(x+rx, y); 1286 g.lineTo(x, y+ry); 1287 g.lineTo(x-rx, y); 1288 break; 1289 1290 case "square": 1291 t = Math.sqrt(size); 1292 r = t / 2; 1293 g.rect(x-r, y-r, t, t); 1294 break; 1295 1296 case "triangle-down": 1297 rx = Math.sqrt(size / sqrt3); 1298 ry = rx * sqrt3 / 2; 1299 g.moveTo(x, y+ry); 1300 g.lineTo(x+rx, y-ry); 1301 g.lineTo(x-rx, y-ry); 1302 break; 1303 1304 case "triangle-up": 1305 rx = Math.sqrt(size / sqrt3); 1306 ry = rx * sqrt3 / 2; 1307 g.moveTo(x, y-ry); 1308 g.lineTo(x+rx, y+ry); 1309 g.lineTo(x-rx, y+ry); 1310 } 1311 g.closePath(); 1312 } 1313 1314 function areaPath(g, items) { 1315 var o = items[0], 1316 p = o["path:parsed"] || 1317 (o["path:parsed"] = parsePath(vg.canvas.path.area(items))); 1318 renderPath(g, p); 1319 } 1320 1321 function linePath(g, items) { 1322 var o = items[0], 1323 p = o["path:parsed"] || 1324 (o["path:parsed"] = parsePath(vg.canvas.path.line(items))); 1325 renderPath(g, p); 1326 } 1327 1328 function lineStroke(g, items) { 1329 var o = items[0], 1330 lw = o.strokeWidth, 1331 lc = o.strokeCap; 1332 g.lineWidth = lw != null ? lw : vg.config.render.lineWidth; 1333 g.lineCap = lc != null ? lc : vg.config.render.lineCap; 1334 linePath(g, items); 1335 } 1336 1337 function ruleStroke(g, o) { 1338 var x1 = o.x || 0, 1339 y1 = o.y || 0, 1340 x2 = o.x2 != null ? o.x2 : x1, 1341 y2 = o.y2 != null ? o.y2 : y1, 1342 lw = o.strokeWidth, 1343 lc = o.strokeCap; 1344 1345 g.lineWidth = lw != null ? lw : vg.config.render.lineWidth; 1346 g.lineCap = lc != null ? lc : vg.config.render.lineCap; 1347 g.beginPath(); 1348 g.moveTo(x1, y1); 1349 g.lineTo(x2, y2); 1350 } 1351 1352 // drawing functions 1353 1354 function drawPathOne(path, g, o, items) { 1355 var fill = o.fill, stroke = o.stroke, opac, lc, lw; 1356 1357 path(g, items); 1358 1359 opac = o.opacity == null ? 1 : o.opacity; 1360 if (opac == 0 || !fill && !stroke) return; 1361 1362 if (fill) { 1363 g.globalAlpha = opac * (o.fillOpacity==null ? 1 : o.fillOpacity); 1364 g.fillStyle = color(g, o, fill); 1365 g.fill(); 1366 } 1367 1368 if (stroke) { 1369 lw = (lw = o.strokeWidth) != null ? lw : vg.config.render.lineWidth; 1370 if (lw > 0) { 1371 g.globalAlpha = opac * (o.strokeOpacity==null ? 1 : o.strokeOpacity); 1372 g.strokeStyle = color(g, o, stroke); 1373 g.lineWidth = lw; 1374 g.lineCap = (lc = o.strokeCap) != null ? lc : vg.config.render.lineCap; 1375 g.vgLineDash(o.strokeDash || null); 1376 g.vgLineDashOffset(o.strokeDashOffset || 0); 1377 g.stroke(); 1378 } 1379 } 1380 } 1381 1382 function drawPathAll(path, g, scene, bounds) { 1383 var i, len, item; 1384 for (i=0, len=scene.items.length; i<len; ++i) { 1385 item = scene.items[i]; 1386 if (bounds && !bounds.intersects(item.bounds)) 1387 continue; // bounds check 1388 drawPathOne(path, g, item, item); 1389 } 1390 } 1391 1392 function drawRect(g, scene, bounds) { 1393 if (!scene.items.length) return; 1394 var items = scene.items, 1395 o, fill, stroke, opac, lc, lw, x, y, w, h; 1396 1397 for (var i=0, len=items.length; i<len; ++i) { 1398 o = items[i]; 1399 if (bounds && !bounds.intersects(o.bounds)) 1400 continue; // bounds check 1401 1402 x = o.x || 0; 1403 y = o.y || 0; 1404 w = o.width || 0; 1405 h = o.height || 0; 1406 1407 opac = o.opacity == null ? 1 : o.opacity; 1408 if (opac == 0) return; 1409 1410 if (fill = o.fill) { 1411 g.globalAlpha = opac * (o.fillOpacity==null ? 1 : o.fillOpacity); 1412 g.fillStyle = color(g, o, fill); 1413 g.fillRect(x, y, w, h); 1414 } 1415 1416 if (stroke = o.stroke) { 1417 lw = (lw = o.strokeWidth) != null ? lw : vg.config.render.lineWidth; 1418 if (lw > 0) { 1419 g.globalAlpha = opac * (o.strokeOpacity==null ? 1 : o.strokeOpacity); 1420 g.strokeStyle = color(g, o, stroke); 1421 g.lineWidth = lw; 1422 g.lineCap = (lc = o.strokeCap) != null ? lc : vg.config.render.lineCap; 1423 g.vgLineDash(o.strokeDash || null); 1424 g.vgLineDashOffset(o.strokeDashOffset || 0); 1425 g.strokeRect(x, y, w, h); 1426 } 1427 } 1428 } 1429 } 1430 1431 function drawRule(g, scene, bounds) { 1432 if (!scene.items.length) return; 1433 var items = scene.items, 1434 o, stroke, opac, lc, lw, x1, y1, x2, y2; 1435 1436 for (var i=0, len=items.length; i<len; ++i) { 1437 o = items[i]; 1438 if (bounds && !bounds.intersects(o.bounds)) 1439 continue; // bounds check 1440 1441 x1 = o.x || 0; 1442 y1 = o.y || 0; 1443 x2 = o.x2 != null ? o.x2 : x1; 1444 y2 = o.y2 != null ? o.y2 : y1; 1445 1446 opac = o.opacity == null ? 1 : o.opacity; 1447 if (opac == 0) return; 1448 1449 if (stroke = o.stroke) { 1450 lw = (lw = o.strokeWidth) != null ? lw : vg.config.render.lineWidth; 1451 if (lw > 0) { 1452 g.globalAlpha = opac * (o.strokeOpacity==null ? 1 : o.strokeOpacity); 1453 g.strokeStyle = color(g, o, stroke); 1454 g.lineWidth = lw; 1455 g.lineCap = (lc = o.strokeCap) != null ? lc : vg.config.render.lineCap; 1456 g.vgLineDash(o.strokeDash || null); 1457 g.vgLineDashOffset(o.strokeDashOffset || 0); 1458 g.beginPath(); 1459 g.moveTo(x1, y1); 1460 g.lineTo(x2, y2); 1461 g.stroke(); 1462 } 1463 } 1464 } 1465 } 1466 1467 function drawImage(g, scene, bounds) { 1468 if (!scene.items.length) return; 1469 var renderer = this, 1470 items = scene.items, o; 1471 1472 for (var i=0, len=items.length; i<len; ++i) { 1473 o = items[i]; 1474 if (bounds && !bounds.intersects(o.bounds)) 1475 continue; // bounds check 1476 1477 if (!(o.image && o.image.url === o.url)) { 1478 o.image = renderer.loadImage(o.url); 1479 o.image.url = o.url; 1480 } 1481 1482 var x, y, w, h, opac; 1483 w = o.width || (o.image && o.image.width) || 0; 1484 h = o.height || (o.image && o.image.height) || 0; 1485 x = (o.x||0) - (o.align === "center" 1486 ? w/2 : (o.align === "right" ? w : 0)); 1487 y = (o.y||0) - (o.baseline === "middle" 1488 ? h/2 : (o.baseline === "bottom" ? h : 0)); 1489 1490 if (o.image.loaded) { 1491 g.globalAlpha = (opac = o.opacity) != null ? opac : 1; 1492 g.drawImage(o.image, x, y, w, h); 1493 } 1494 } 1495 } 1496 1497 function drawText(g, scene, bounds) { 1498 if (!scene.items.length) return; 1499 var items = scene.items, 1500 o, fill, stroke, opac, lw, text, ta, tb; 1501 1502 for (var i=0, len=items.length; i<len; ++i) { 1503 o = items[i]; 1504 if (bounds && !bounds.intersects(o.bounds)) 1505 continue; // bounds check 1506 1507 g.font = vg.scene.fontString(o); 1508 g.textAlign = o.align || "left"; 1509 g.textBaseline = o.baseline || "alphabetic"; 1510 1511 opac = o.opacity == null ? 1 : o.opacity; 1512 if (opac == 0) return; 1513 1514 if (o.angle) { 1515 g.save(); 1516 g.translate(o.x || 0, o.y || 0); 1517 g.rotate(o.angle * Math.PI/180); 1518 x = o.dx || 0; 1519 y = o.dy || 0; 1520 } else { 1521 x = (o.x || 0) + (o.dx || 0); 1522 y = (o.y || 0) + (o.dy || 0); 1523 } 1524 1525 if (fill = o.fill) { 1526 g.globalAlpha = opac * (o.fillOpacity==null ? 1 : o.fillOpacity); 1527 g.fillStyle = color(g, o, fill); 1528 g.fillText(o.text, x, y); 1529 } 1530 1531 if (stroke = o.stroke) { 1532 lw = (lw = o.strokeWidth) != null ? lw : 1; 1533 if (lw > 0) { 1534 g.globalAlpha = opac * (o.strokeOpacity==null ? 1 : o.strokeOpacity); 1535 g.strokeStyle = color(o, stroke); 1536 g.lineWidth = lw; 1537 g.strokeText(o.text, x, y); 1538 } 1539 } 1540 1541 if (o.angle) g.restore(); 1542 } 1543 } 1544 1545 function drawAll(pathFunc) { 1546 return function(g, scene, bounds) { 1547 drawPathAll(pathFunc, g, scene, bounds); 1548 } 1549 } 1550 1551 function drawOne(pathFunc) { 1552 return function(g, scene, bounds) { 1553 if (!scene.items.length) return; 1554 if (bounds && !bounds.intersects(scene.items[0].bounds)) 1555 return; // bounds check 1556 drawPathOne(pathFunc, g, scene.items[0], scene.items); 1557 } 1558 } 1559 1560 function drawGroup(g, scene, bounds) { 1561 if (!scene.items.length) return; 1562 var items = scene.items, group, axes, legends, 1563 renderer = this, gx, gy, gb, i, n, j, m; 1564 1565 drawRect(g, scene, bounds); 1566 1567 for (i=0, n=items.length; i<n; ++i) { 1568 group = items[i]; 1569 axes = group.axisItems || []; 1570 legends = group.legendItems || []; 1571 gx = group.x || 0; 1572 gy = group.y || 0; 1573 1574 // render group contents 1575 g.save(); 1576 g.translate(gx, gy); 1577 if (bounds) bounds.translate(-gx, -gy); 1578 for (j=0, m=axes.length; j<m; ++j) { 1579 if (axes[j].def.layer === "back") { 1580 renderer.draw(g, axes[j], bounds); 1581 } 1582 } 1583 for (j=0, m=group.items.length; j<m; ++j) { 1584 renderer.draw(g, group.items[j], bounds); 1585 } 1586 for (j=0, m=axes.length; j<m; ++j) { 1587 if (axes[j].def.layer !== "back") { 1588 renderer.draw(g, axes[j], bounds); 1589 } 1590 } 1591 for (j=0, m=legends.length; j<m; ++j) { 1592 renderer.draw(g, legends[j], bounds); 1593 } 1594 if (bounds) bounds.translate(gx, gy); 1595 g.restore(); 1596 } 1597 } 1598 1599 function color(g, o, value) { 1600 return (value.id) 1601 ? gradient(g, value, o.bounds) 1602 : value; 1603 } 1604 1605 function gradient(g, p, b) { 1606 var w = b.width(), 1607 h = b.height(), 1608 x1 = b.x1 + p.x1 * w, 1609 y1 = b.y1 + p.y1 * h, 1610 x2 = b.x1 + p.x2 * w, 1611 y2 = b.y1 + p.y2 * h, 1612 grad = g.createLinearGradient(x1, y1, x2, y2), 1613 stop = p.stops, 1614 i, n; 1615 1616 for (i=0, n=stop.length; i<n; ++i) { 1617 grad.addColorStop(stop[i].offset, stop[i].color); 1618 } 1619 return grad; 1620 } 1621 1622 // hit testing 1623 1624 function pickGroup(g, scene, x, y, gx, gy) { 1625 if (scene.items.length === 0 || 1626 scene.bounds && !scene.bounds.contains(gx, gy)) { 1627 return false; 1628 } 1629 var items = scene.items, subscene, group, hit, dx, dy, 1630 handler = this, i, j; 1631 1632 for (i=items.length; --i>=0;) { 1633 group = items[i]; 1634 dx = group.x || 0; 1635 dy = group.y || 0; 1636 1637 g.save(); 1638 g.translate(dx, dy); 1639 for (j=group.items.length; --j >= 0;) { 1640 subscene = group.items[j]; 1641 if (subscene.interactive === false) continue; 1642 hit = handler.pick(subscene, x, y, gx-dx, gy-dy); 1643 if (hit) { 1644 g.restore(); 1645 return hit; 1646 } 1647 } 1648 g.restore(); 1649 } 1650 1651 return scene.interactive 1652 ? pickAll(hitTests.rect, g, scene, x, y, gx, gy) 1653 : false; 1654 } 1655 1656 function pickAll(test, g, scene, x, y, gx, gy) { 1657 if (!scene.items.length) return false; 1658 var o, b, i; 1659 1660 if (g._ratio !== 1) { 1661 x *= g._ratio; 1662 y *= g._ratio; 1663 } 1664 1665 for (i=scene.items.length; --i >= 0;) { 1666 o = scene.items[i]; b = o.bounds; 1667 // first hit test against bounding box 1668 if ((b && !b.contains(gx, gy)) || !b) continue; 1669 // if in bounding box, perform more careful test 1670 if (test(g, o, x, y, gx, gy)) return o; 1671 } 1672 return false; 1673 } 1674 1675 function pickArea(g, scene, x, y, gx, gy) { 1676 if (!scene.items.length) return false; 1677 var items = scene.items, 1678 o, b, i, di, dd, od, dx, dy; 1679 1680 b = items[0].bounds; 1681 if (b && !b.contains(gx, gy)) return false; 1682 if (g._ratio !== 1) { 1683 x *= g._ratio; 1684 y *= g._ratio; 1685 } 1686 if (!hitTests.area(g, items, x, y)) return false; 1687 return items[0]; 1688 } 1689 1690 function pickLine(g, scene, x, y, gx, gy) { 1691 if (!scene.items.length) return false; 1692 var items = scene.items, 1693 o, b, i, di, dd, od, dx, dy; 1694 1695 b = items[0].bounds; 1696 if (b && !b.contains(gx, gy)) return false; 1697 if (g._ratio !== 1) { 1698 x *= g._ratio; 1699 y *= g._ratio; 1700 } 1701 if (!hitTests.line(g, items, x, y)) return false; 1702 return items[0]; 1703 } 1704 1705 function pick(test) { 1706 return function (g, scene, x, y, gx, gy) { 1707 return pickAll(test, g, scene, x, y, gx, gy); 1708 }; 1709 } 1710 1711 function textHit(g, o, x, y, gx, gy) { 1712 if (!o.fontSize) return false; 1713 if (!o.angle) return true; // bounds sufficient if no rotation 1714 1715 var b = vg.scene.bounds.text(o, tmpBounds, true), 1716 a = -o.angle * Math.PI / 180, 1717 cos = Math.cos(a), 1718 sin = Math.sin(a), 1719 x = o.x, 1720 y = o.y, 1721 px = cos*gx - sin*gy + (x - x*cos + y*sin), 1722 py = sin*gx + cos*gy + (y - x*sin - y*cos); 1723 1724 return b.contains(px, py); 1725 } 1726 1727 var hitTests = { 1728 text: textHit, 1729 rect: function(g,o,x,y) { return true; }, // bounds test is sufficient 1730 image: function(g,o,x,y) { return true; }, // bounds test is sufficient 1731 rule: function(g,o,x,y) { 1732 if (!g.isPointInStroke) return false; 1733 ruleStroke(g,o); return g.isPointInStroke(x,y); 1734 }, 1735 line: function(g,s,x,y) { 1736 if (!g.isPointInStroke) return false; 1737 lineStroke(g,s); return g.isPointInStroke(x,y); 1738 }, 1739 arc: function(g,o,x,y) { arcPath(g,o); return g.isPointInPath(x,y); }, 1740 area: function(g,s,x,y) { areaPath(g,s); return g.isPointInPath(x,y); }, 1741 path: function(g,o,x,y) { pathPath(g,o); return g.isPointInPath(x,y); }, 1742 symbol: function(g,o,x,y) { symbolPath(g,o); return g.isPointInPath(x,y); } 1743 }; 1744 1745 return { 1746 draw: { 1747 group: drawGroup, 1748 area: drawOne(areaPath), 1749 line: drawOne(linePath), 1750 arc: drawAll(arcPath), 1751 path: drawAll(pathPath), 1752 symbol: drawAll(symbolPath), 1753 rect: drawRect, 1754 rule: drawRule, 1755 text: drawText, 1756 image: drawImage, 1757 drawOne: drawOne, // expose for extensibility 1758 drawAll: drawAll // expose for extensibility 1759 }, 1760 pick: { 1761 group: pickGroup, 1762 area: pickArea, 1763 line: pickLine, 1764 arc: pick(hitTests.arc), 1765 path: pick(hitTests.path), 1766 symbol: pick(hitTests.symbol), 1767 rect: pick(hitTests.rect), 1768 rule: pick(hitTests.rule), 1769 text: pick(hitTests.text), 1770 image: pick(hitTests.image), 1771 pickAll: pickAll // expose for extensibility 1772 } 1773 }; 1774 1775 })();vg.canvas.Renderer = (function() { 1776 var renderer = function() { 1777 this._ctx = null; 1778 this._el = null; 1779 this._imgload = 0; 1780 }; 1781 1782 var prototype = renderer.prototype; 1783 1784 prototype.initialize = function(el, width, height, pad) { 1785 this._el = el; 1786 1787 if (!el) return this; // early exit if no DOM element 1788 1789 // select canvas element 1790 var canvas = d3.select(el) 1791 .selectAll("canvas.marks") 1792 .data([1]); 1793 1794 // create new canvas element if needed 1795 canvas.enter() 1796 .append("canvas") 1797 .attr("class", "marks"); 1798 1799 // remove extraneous canvas if needed 1800 canvas.exit().remove(); 1801 1802 return this.resize(width, height, pad); 1803 }; 1804 1805 prototype.resize = function(width, height, pad) { 1806 this._width = width; 1807 this._height = height; 1808 this._padding = pad; 1809 1810 if (this._el) { 1811 var canvas = d3.select(this._el).select("canvas.marks"); 1812 1813 // initialize canvas attributes 1814 canvas 1815 .attr("width", width + pad.left + pad.right) 1816 .attr("height", height + pad.top + pad.bottom); 1817 1818 // get the canvas graphics context 1819 var s; 1820 this._ctx = canvas.node().getContext("2d"); 1821 this._ctx._ratio = (s = scaleCanvas(canvas.node(), this._ctx) || 1); 1822 this._ctx.setTransform(s, 0, 0, s, s*pad.left, s*pad.top); 1823 } 1824 1825 initializeLineDash(this._ctx); 1826 return this; 1827 }; 1828 1829 function scaleCanvas(canvas, ctx) { 1830 // get canvas pixel data 1831 var devicePixelRatio = window.devicePixelRatio || 1, 1832 backingStoreRatio = ( 1833 ctx.webkitBackingStorePixelRatio || 1834 ctx.mozBackingStorePixelRatio || 1835 ctx.msBackingStorePixelRatio || 1836 ctx.oBackingStorePixelRatio || 1837 ctx.backingStorePixelRatio) || 1, 1838 ratio = devicePixelRatio / backingStoreRatio; 1839 1840 if (devicePixelRatio !== backingStoreRatio) { 1841 var w = canvas.width, h = canvas.height; 1842 // set actual and visible canvas size 1843 canvas.setAttribute("width", w * ratio); 1844 canvas.setAttribute("height", h * ratio); 1845 canvas.style.width = w + 'px'; 1846 canvas.style.height = h + 'px'; 1847 } 1848 return ratio; 1849 } 1850 1851 function initializeLineDash(ctx) { 1852 if (ctx.vgLineDash) return; // already set 1853 1854 var NODASH = []; 1855 if (ctx.setLineDash) { 1856 ctx.vgLineDash = function(dash) { this.setLineDash(dash || NODASH); }; 1857 ctx.vgLineDashOffset = function(off) { this.lineDashOffset = off; }; 1858 } else if (ctx.webkitLineDash !== undefined) { 1859 ctx.vgLineDash = function(dash) { this.webkitLineDash = dash || NODASH; }; 1860 ctx.vgLineDashOffset = function(off) { this.webkitLineDashOffset = off; }; 1861 } else if (ctx.mozDash !== undefined) { 1862 ctx.vgLineDash = function(dash) { this.mozDash = dash; }; 1863 ctx.vgLineDashOffset = function(off) { /* unsupported */ }; 1864 } else { 1865 ctx.vgLineDash = function(dash) { /* unsupported */ }; 1866 ctx.vgLineDashOffset = function(off) { /* unsupported */ }; 1867 } 1868 } 1869 1870 prototype.context = function(ctx) { 1871 if (ctx) { this._ctx = ctx; return this; } 1872 else return this._ctx; 1873 }; 1874 1875 prototype.element = function() { 1876 return this._el; 1877 }; 1878 1879 prototype.pendingImages = function() { 1880 return this._imgload; 1881 }; 1882 1883 function translatedBounds(item, bounds) { 1884 var b = new vg.Bounds(bounds); 1885 while ((item = item.mark.group) != null) { 1886 b.translate(item.x || 0, item.y || 0); 1887 } 1888 return b; 1889 } 1890 1891 function getBounds(items) { 1892 return !items ? null : 1893 vg.array(items).reduce(function(b, item) { 1894 return b.union(translatedBounds(item, item.bounds)) 1895 .union(translatedBounds(item, item['bounds:prev'])); 1896 }, new vg.Bounds()); 1897 } 1898 1899 function setBounds(g, bounds) { 1900 var bbox = null; 1901 if (bounds) { 1902 bbox = (new vg.Bounds(bounds)).round(); 1903 g.beginPath(); 1904 g.rect(bbox.x1, bbox.y1, bbox.width(), bbox.height()); 1905 g.clip(); 1906 } 1907 return bbox; 1908 } 1909 1910 prototype.render = function(scene, items) { 1911 var g = this._ctx, 1912 pad = this._padding, 1913 w = this._width + pad.left + pad.right, 1914 h = this._height + pad.top + pad.bottom, 1915 bb = null, bb2; 1916 1917 // setup 1918 this._scene = scene; 1919 g.save(); 1920 bb = setBounds(g, getBounds(items)); 1921 g.clearRect(-pad.left, -pad.top, w, h); 1922 1923 // render 1924 this.draw(g, scene, bb); 1925 1926 // render again to handle possible bounds change 1927 if (items) { 1928 g.restore(); 1929 g.save(); 1930 bb2 = setBounds(g, getBounds(items)); 1931 if (!bb.encloses(bb2)) { 1932 g.clearRect(-pad.left, -pad.top, w, h); 1933 this.draw(g, scene, bb2); 1934 } 1935 } 1936 1937 // takedown 1938 g.restore(); 1939 this._scene = null; 1940 }; 1941 1942 prototype.draw = function(ctx, scene, bounds) { 1943 var marktype = scene.marktype, 1944 renderer = vg.canvas.marks.draw[marktype]; 1945 renderer.call(this, ctx, scene, bounds); 1946 1947 // compute mark-level bounds 1948 scene.bounds = scene.items.reduce(function(b, item) { 1949 return item.bounds ? b.union(item.bounds) : b; 1950 }, scene.bounds || new vg.Bounds()); 1951 }; 1952 1953 prototype.renderAsync = function(scene) { 1954 // TODO make safe for multiple scene rendering? 1955 var renderer = this; 1956 if (renderer._async_id) { 1957 clearTimeout(renderer._async_id); 1958 } 1959 renderer._async_id = setTimeout(function() { 1960 renderer.render(scene); 1961 delete renderer._async_id; 1962 }, 50); 1963 }; 1964 1965 prototype.loadImage = function(uri) { 1966 var renderer = this, 1967 scene = renderer._scene, 1968 image = null, url; 1969 1970 renderer._imgload += 1; 1971 if (vg.config.isNode) { 1972 image = new (require("canvas").Image)(); 1973 vg.data.load(uri, function(err, data) { 1974 if (err) { vg.error(err); return; } 1975 image.src = data; 1976 image.loaded = true; 1977 renderer._imgload -= 1; 1978 }); 1979 } else { 1980 image = new Image(); 1981 url = vg.config.baseURL + uri; 1982 image.onload = function() { 1983 vg.log("LOAD IMAGE: "+url); 1984 image.loaded = true; 1985 renderer._imgload -= 1; 1986 renderer.renderAsync(scene); 1987 }; 1988 image.src = url; 1989 } 1990 1991 return image; 1992 }; 1993 1994 return renderer; 1995 })();vg.canvas.Handler = (function() { 1996 var handler = function(el, model) { 1997 this._active = null; 1998 this._handlers = {}; 1999 if (el) this.initialize(el); 2000 if (model) this.model(model); 2001 }; 2002 2003 var prototype = handler.prototype; 2004 2005 prototype.initialize = function(el, pad, obj) { 2006 this._el = d3.select(el).node(); 2007 this._canvas = d3.select(el).select("canvas.marks").node(); 2008 this._padding = pad; 2009 this._obj = obj || null; 2010 2011 // add event listeners 2012 var canvas = this._canvas, that = this; 2013 events.forEach(function(type) { 2014 canvas.addEventListener(type, function(evt) { 2015 prototype[type].call(that, evt); 2016 }); 2017 }); 2018 2019 return this; 2020 }; 2021 2022 prototype.padding = function(pad) { 2023 this._padding = pad; 2024 return this; 2025 }; 2026 2027 prototype.model = function(model) { 2028 if (!arguments.length) return this._model; 2029 this._model = model; 2030 return this; 2031 }; 2032 2033 prototype.handlers = function() { 2034 var h = this._handlers; 2035 return vg.keys(h).reduce(function(a, k) { 2036 return h[k].reduce(function(a, x) { return (a.push(x), a); }, a); 2037 }, []); 2038 }; 2039 2040 // setup events 2041 var events = [ 2042 "mousedown", 2043 "mouseup", 2044 "click", 2045 "dblclick", 2046 "wheel", 2047 "keydown", 2048 "keypress", 2049 "keyup", 2050 "mousewheel" 2051 ]; 2052 events.forEach(function(type) { 2053 prototype[type] = function(evt) { 2054 this.fire(type, evt); 2055 }; 2056 }); 2057 events.push("mousemove"); 2058 events.push("mouseout"); 2059 2060 function eventName(name) { 2061 var i = name.indexOf("."); 2062 return i < 0 ? name : name.slice(0,i); 2063 } 2064 2065 prototype.mousemove = function(evt) { 2066 var pad = this._padding, 2067 b = evt.target.getBoundingClientRect(), 2068 x = evt.clientX - b.left, 2069 y = evt.clientY - b.top, 2070 a = this._active, 2071 p = this.pick(this._model.scene(), x, y, x-pad.left, y-pad.top); 2072 2073 if (p === a) { 2074 this.fire("mousemove", evt); 2075 return; 2076 } else if (a) { 2077 this.fire("mouseout", evt); 2078 } 2079 this._active = p; 2080 if (p) { 2081 this.fire("mouseover", evt); 2082 } 2083 }; 2084 2085 prototype.mouseout = function(evt) { 2086 if (this._active) { 2087 this.fire("mouseout", evt); 2088 } 2089 this._active = null; 2090 }; 2091 2092 // to keep firefox happy 2093 prototype.DOMMouseScroll = function(evt) { 2094 this.fire("mousewheel", evt); 2095 }; 2096 2097 // fire an event 2098 prototype.fire = function(type, evt) { 2099 var a = this._active, 2100 h = this._handlers[type]; 2101 if (a && h) { 2102 for (var i=0, len=h.length; i<len; ++i) { 2103 h[i].handler.call(this._obj, evt, a); 2104 } 2105 } 2106 }; 2107 2108 // add an event handler 2109 prototype.on = function(type, handler) { 2110 var name = eventName(type), 2111 h = this._handlers; 2112 h = h[name] || (h[name] = []); 2113 h.push({ 2114 type: type, 2115 handler: handler 2116 }); 2117 return this; 2118 }; 2119 2120 // remove an event handler 2121 prototype.off = function(type, handler) { 2122 var name = eventName(type), 2123 h = this._handlers[name]; 2124 if (!h) return; 2125 for (var i=h.length; --i>=0;) { 2126 if (h[i].type !== type) continue; 2127 if (!handler || h[i].handler === handler) h.splice(i, 1); 2128 } 2129 return this; 2130 }; 2131 2132 // retrieve the current canvas context 2133 prototype.context = function() { 2134 return this._canvas.getContext("2d"); 2135 }; 2136 2137 // find the scenegraph item at the current mouse position 2138 // returns an array of scenegraph items, from leaf node up to the root 2139 // x, y -- the absolute x, y mouse coordinates on the canvas element 2140 // gx, gy -- the relative coordinates within the current group 2141 prototype.pick = function(scene, x, y, gx, gy) { 2142 var g = this.context(), 2143 marktype = scene.marktype, 2144 picker = vg.canvas.marks.pick[marktype]; 2145 return picker.call(this, g, scene, x, y, gx, gy); 2146 }; 2147 2148 return handler; 2149 })();vg.svg = {};vg.svg.marks = (function() { 2150 2151 function x(o) { return o.x || 0; } 2152 function y(o) { return o.y || 0; } 2153 function yh(o) { return o.y + o.height || 0; } 2154 function key(o) { return o.key; } 2155 function size(o) { return o.size==null ? 100 : o.size; } 2156 function shape(o) { return o.shape || "circle"; } 2157 2158 var arc_path = d3.svg.arc(), 2159 area_path = d3.svg.area().x(x).y1(y).y0(yh), 2160 line_path = d3.svg.line().x(x).y(y), 2161 symbol_path = d3.svg.symbol().type(shape).size(size); 2162 2163 var mark_id = 0; 2164 2165 var textAlign = { 2166 "left": "start", 2167 "center": "middle", 2168 "right": "end" 2169 }; 2170 2171 var styles = { 2172 "fill": "fill", 2173 "fillOpacity": "fill-opacity", 2174 "stroke": "stroke", 2175 "strokeWidth": "stroke-width", 2176 "strokeOpacity": "stroke-opacity", 2177 "strokeCap": "stroke-linecap", 2178 "strokeDash": "stroke-dasharray", 2179 "strokeDashOffset": "stroke-dashoffset", 2180 "opacity": "opacity" 2181 }; 2182 var styleProps = vg.keys(styles); 2183 2184 function style(d) { 2185 var i, n, prop, name, value, 2186 o = d.mark ? d : d.length ? d[0] : null; 2187 if (o === null) return; 2188 2189 for (i=0, n=styleProps.length; i<n; ++i) { 2190 prop = styleProps[i]; 2191 name = styles[prop]; 2192 value = o[prop]; 2193 2194 if (value == null) { 2195 if (name === "fill") this.style.setProperty(name, "none", null); 2196 else this.style.removeProperty(name); 2197 } else { 2198 if (value.id) { 2199 // ensure definition is included 2200 vg.svg._cur._defs[value.id] = value; 2201 value = "url(#" + value.id + ")"; 2202 } 2203 this.style.setProperty(name, value+"", null); 2204 } 2205 } 2206 } 2207 2208 function arc(o) { 2209 var x = o.x || 0, 2210 y = o.y || 0; 2211 this.setAttribute("transform", "translate("+x+","+y+")"); 2212 this.setAttribute("d", arc_path(o)); 2213 } 2214 2215 function area(items) { 2216 if (!items.length) return; 2217 var o = items[0]; 2218 area_path 2219 .interpolate(o.interpolate || "linear") 2220 .tension(o.tension == null ? 0.7 : o.tension); 2221 this.setAttribute("d", area_path(items)); 2222 } 2223 2224 function line(items) { 2225 if (!items.length) return; 2226 var o = items[0]; 2227 line_path 2228 .interpolate(o.interpolate || "linear") 2229 .tension(o.tension == null ? 0.7 : o.tension); 2230 this.setAttribute("d", line_path(items)); 2231 } 2232 2233 function path(o) { 2234 var x = o.x || 0, 2235 y = o.y || 0; 2236 this.setAttribute("transform", "translate("+x+","+y+")"); 2237 if (o.path != null) this.setAttribute("d", o.path); 2238 } 2239 2240 function rect(o) { 2241 this.setAttribute("x", o.x || 0); 2242 this.setAttribute("y", o.y || 0); 2243 this.setAttribute("width", o.width || 0); 2244 this.setAttribute("height", o.height || 0); 2245 } 2246 2247 function rule(o) { 2248 var x1 = o.x || 0, 2249 y1 = o.y || 0; 2250 this.setAttribute("x1", x1); 2251 this.setAttribute("y1", y1); 2252 this.setAttribute("x2", o.x2 != null ? o.x2 : x1); 2253 this.setAttribute("y2", o.y2 != null ? o.y2 : y1); 2254 } 2255 2256 function symbol(o) { 2257 var x = o.x || 0, 2258 y = o.y || 0; 2259 this.setAttribute("transform", "translate("+x+","+y+")"); 2260 this.setAttribute("d", symbol_path(o)); 2261 } 2262 2263 function image(o) { 2264 var w = o.width || (o.image && o.image.width) || 0, 2265 h = o.height || (o.image && o.image.height) || 0, 2266 x = o.x - (o.align === "center" 2267 ? w/2 : (o.align === "right" ? w : 0)), 2268 y = o.y - (o.baseline === "middle" 2269 ? h/2 : (o.baseline === "bottom" ? h : 0)), 2270 url = vg.config.baseURL + o.url; 2271 2272 this.setAttributeNS("http://www.w3.org/1999/xlink", "href", url); 2273 this.setAttribute("x", x); 2274 this.setAttribute("y", y); 2275 this.setAttribute("width", w); 2276 this.setAttribute("height", h); 2277 } 2278 2279 function fontString(o) { 2280 return (o.fontStyle ? o.fontStyle + " " : "") 2281 + (o.fontVariant ? o.fontVariant + " " : "") 2282 + (o.fontWeight ? o.fontWeight + " " : "") 2283 + (o.fontSize != null ? o.fontSize : vg.config.render.fontSize) + "px " 2284 + (o.font || vg.config.render.font); 2285 } 2286 2287 function text(o) { 2288 var x = o.x || 0, 2289 y = o.y || 0, 2290 dx = o.dx || 0, 2291 dy = o.dy || 0, 2292 a = o.angle || 0, 2293 align = textAlign[o.align || "left"], 2294 base = o.baseline==="top" ? ".9em" 2295 : o.baseline==="middle" ? ".35em" : 0; 2296 2297 this.setAttribute("x", x + dx); 2298 this.setAttribute("y", y + dy); 2299 this.setAttribute("dy", dy); 2300 this.setAttribute("text-anchor", align); 2301 2302 if (a) this.setAttribute("transform", "rotate("+a+" "+x+","+y+")"); 2303 else this.removeAttribute("transform"); 2304 2305 if (base) this.setAttribute("dy", base); 2306 else this.removeAttribute("dy"); 2307 2308 this.textContent = o.text; 2309 this.style.setProperty("font", fontString(o), null); 2310 } 2311 2312 function group(o) { 2313 var x = o.x || 0, 2314 y = o.y || 0; 2315 this.setAttribute("transform", "translate("+x+","+y+")"); 2316 } 2317 2318 function group_bg(o) { 2319 var w = o.width || 0, 2320 h = o.height || 0; 2321 this.setAttribute("width", w); 2322 this.setAttribute("height", h); 2323 } 2324 2325 function draw(tag, attr, nest) { 2326 return function(g, scene, index) { 2327 drawMark(g, scene, index, "mark_", tag, attr, nest); 2328 }; 2329 } 2330 2331 function drawMark(g, scene, index, prefix, tag, attr, nest) { 2332 var data = nest ? [scene.items] : scene.items, 2333 evts = scene.interactive===false ? "none" : null, 2334 grps = g.node().childNodes, 2335 notG = (tag !== "g"), 2336 p = (p = grps[index+1]) // +1 to skip group background rect 2337 ? d3.select(p) 2338 : g.append("g").attr("id", "g"+(++mark_id)); 2339 2340 var id = "#" + p.attr("id"), 2341 s = id + " > " + tag, 2342 m = p.selectAll(s).data(data), 2343 e = m.enter().append(tag); 2344 2345 if (notG) { 2346 p.style("pointer-events", evts); 2347 e.each(function(d) { 2348 if (d.mark) d._svg = this; 2349 else if (d.length) d[0]._svg = this; 2350 }); 2351 } else { 2352 e.append("rect").attr("class","background").style("pointer-events",evts); 2353 } 2354 2355 m.exit().remove(); 2356 m.each(attr); 2357 if (notG) m.each(style); 2358 else p.selectAll(s+" > rect.background").each(group_bg).each(style); 2359 2360 return p; 2361 } 2362 2363 function drawGroup(g, scene, index, prefix) { 2364 var p = drawMark(g, scene, index, prefix || "group_", "g", group), 2365 c = p.node().childNodes, n = c.length, i, j, m; 2366 2367 for (i=0; i<n; ++i) { 2368 var items = c[i].__data__.items, 2369 legends = c[i].__data__.legendItems || [], 2370 axes = c[i].__data__.axisItems || [], 2371 sel = d3.select(c[i]), 2372 idx = 0; 2373 2374 for (j=0, m=axes.length; j<m; ++j) { 2375 if (axes[j].def.layer === "back") { 2376 drawGroup.call(this, sel, axes[j], idx++, "axis_"); 2377 } 2378 } 2379 for (j=0, m=items.length; j<m; ++j) { 2380 this.draw(sel, items[j], idx++); 2381 } 2382 for (j=0, m=axes.length; j<m; ++j) { 2383 if (axes[j].def.layer !== "back") { 2384 drawGroup.call(this, sel, axes[j], idx++, "axis_"); 2385 } 2386 } 2387 for (j=0, m=legends.length; j<m; ++j) { 2388 drawGroup.call(this, sel, legends[j], idx++, "legend_"); 2389 } 2390 } 2391 } 2392 2393 return { 2394 update: { 2395 group: rect, 2396 area: area, 2397 line: line, 2398 arc: arc, 2399 path: path, 2400 symbol: symbol, 2401 rect: rect, 2402 rule: rule, 2403 text: text, 2404 image: image 2405 }, 2406 nested: { 2407 "area": true, 2408 "line": true 2409 }, 2410 style: style, 2411 draw: { 2412 group: drawGroup, 2413 area: draw("path", area, true), 2414 line: draw("path", line, true), 2415 arc: draw("path", arc), 2416 path: draw("path", path), 2417 symbol: draw("path", symbol), 2418 rect: draw("rect", rect), 2419 rule: draw("line", rule), 2420 text: draw("text", text), 2421 image: draw("image", image), 2422 draw: draw // expose for extensibility 2423 } 2424 }; 2425 2426 })();vg.svg.Renderer = (function() { 2427 var renderer = function() { 2428 this._svg = null; 2429 this._ctx = null; 2430 this._el = null; 2431 this._defs = {}; 2432 }; 2433 2434 var prototype = renderer.prototype; 2435 2436 prototype.initialize = function(el, width, height, pad) { 2437 this._el = el; 2438 2439 // remove any existing svg element 2440 d3.select(el).select("svg.marks").remove(); 2441 2442 // create svg element and initialize attributes 2443 this._svg = d3.select(el) 2444 .append("svg") 2445 .attr("class", "marks"); 2446 2447 // set the svg root group 2448 this._ctx = this._svg.append("g"); 2449 2450 return this.resize(width, height, pad); 2451 }; 2452 2453 prototype.resize = function(width, height, pad) { 2454 this._width = width; 2455 this._height = height; 2456 this._padding = pad; 2457 2458 this._svg 2459 .attr("width", width + pad.left + pad.right) 2460 .attr("height", height + pad.top + pad.bottom); 2461 2462 this._ctx 2463 .attr("transform", "translate("+pad.left+","+pad.top+")"); 2464 2465 return this; 2466 }; 2467 2468 prototype.context = function() { 2469 return this._ctx; 2470 }; 2471 2472 prototype.element = function() { 2473 return this._el; 2474 }; 2475 2476 prototype.updateDefs = function() { 2477 var svg = this._svg, 2478 all = this._defs, 2479 ids = vg.keys(all), 2480 defs = svg.select("defs"), grds; 2481 2482 // get or create svg defs block 2483 if (ids.length===0) { defs.remove(); return; } 2484 if (defs.empty()) defs = svg.insert("defs", ":first-child"); 2485 2486 grds = defs.selectAll("linearGradient").data(ids, vg.identity); 2487 grds.enter().append("linearGradient").attr("id", vg.identity); 2488 grds.exit().remove(); 2489 grds.each(function(id) { 2490 var def = all[id], 2491 grd = d3.select(this); 2492 2493 // set gradient coordinates 2494 grd.attr({x1: def.x1, x2: def.x2, y1: def.y1, y2: def.y2}); 2495 2496 // set gradient stops 2497 stop = grd.selectAll("stop").data(def.stops); 2498 stop.enter().append("stop"); 2499 stop.exit().remove(); 2500 stop.attr("offset", function(d) { return d.offset; }) 2501 .attr("stop-color", function(d) { return d.color; }); 2502 }); 2503 }; 2504 2505 prototype.render = function(scene, items) { 2506 vg.svg._cur = this; 2507 2508 if (items) this.renderItems(vg.array(items)); 2509 else this.draw(this._ctx, scene, -1); 2510 this.updateDefs(); 2511 2512 delete vg.svg._cur; 2513 }; 2514 2515 prototype.renderItems = function(items) { 2516 var item, node, type, nest, i, n, 2517 marks = vg.svg.marks; 2518 2519 for (i=0, n=items.length; i<n; ++i) { 2520 item = items[i]; 2521 node = item._svg; 2522 type = item.mark.marktype; 2523 2524 item = marks.nested[type] ? item.mark.items : item; 2525 marks.update[type].call(node, item); 2526 marks.style.call(node, item); 2527 } 2528 } 2529 2530 prototype.draw = function(ctx, scene, index) { 2531 var marktype = scene.marktype, 2532 renderer = vg.svg.marks.draw[marktype]; 2533 renderer.call(this, ctx, scene, index); 2534 }; 2535 2536 return renderer; 2537 })();vg.svg.Handler = (function() { 2538 var handler = function(el, model) { 2539 this._active = null; 2540 this._handlers = {}; 2541 if (el) this.initialize(el); 2542 if (model) this.model(model); 2543 }; 2544 2545 function svgHandler(handler) { 2546 var that = this; 2547 return function(evt) { 2548 var target = evt.target, 2549 item = target.__data__; 2550 if (item) { 2551 item = item.mark ? item : item[0]; 2552 handler.call(that._obj, evt, item); 2553 } 2554 }; 2555 } 2556 2557 function eventName(name) { 2558 var i = name.indexOf("."); 2559 return i < 0 ? name : name.slice(0,i); 2560 } 2561 2562 var prototype = handler.prototype; 2563 2564 prototype.initialize = function(el, pad, obj) { 2565 this._el = d3.select(el).node(); 2566 this._svg = d3.select(el).select("svg.marks").node(); 2567 this._padding = pad; 2568 this._obj = obj || null; 2569 return this; 2570 }; 2571 2572 prototype.padding = function(pad) { 2573 this._padding = pad; 2574 return this; 2575 }; 2576 2577 prototype.model = function(model) { 2578 if (!arguments.length) return this._model; 2579 this._model = model; 2580 return this; 2581 }; 2582 2583 prototype.handlers = function() { 2584 var h = this._handlers; 2585 return vg.keys(h).reduce(function(a, k) { 2586 return h[k].reduce(function(a, x) { return (a.push(x), a); }, a); 2587 }, []); 2588 }; 2589 2590 // add an event handler 2591 prototype.on = function(type, handler) { 2592 var name = eventName(type), 2593 h = this._handlers, 2594 dom = d3.select(this._svg).node(); 2595 2596 var x = { 2597 type: type, 2598 handler: handler, 2599 svg: svgHandler.call(this, handler) 2600 }; 2601 h = h[name] || (h[name] = []); 2602 h.push(x); 2603 2604 dom.addEventListener(name, x.svg); 2605 return this; 2606 }; 2607 2608 // remove an event handler 2609 prototype.off = function(type, handler) { 2610 var name = eventName(type), 2611 h = this._handlers[name], 2612 dom = d3.select(this._svg).node(); 2613 if (!h) return; 2614 for (var i=h.length; --i>=0;) { 2615 if (h[i].type !== type) continue; 2616 if (!handler || h[i].handler === handler) { 2617 dom.removeEventListener(name, h[i].svg); 2618 h.splice(i, 1); 2619 } 2620 } 2621 return this; 2622 }; 2623 2624 return handler; 2625 })();vg.data = {}; 2626 2627 vg.data.ingestAll = function(data) { 2628 return vg.isTree(data) 2629 ? vg_make_tree(vg.data.ingestTree(data[0], data.children)) 2630 : data.map(vg.data.ingest); 2631 }; 2632 2633 vg.data.ingest = function(datum, index) { 2634 return { 2635 data: datum, 2636 index: index 2637 }; 2638 }; 2639 2640 vg.data.ingestTree = function(node, children) { 2641 var d = vg.data.ingest(node), 2642 c = node[children], n, i; 2643 if (c && (n = c.length)) { 2644 d.values = Array(n); 2645 for (i=0; i<n; ++i) { 2646 d.values[i] = vg.data.ingestTree(c[i], children); 2647 } 2648 } 2649 return d; 2650 }; 2651 2652 2653 function vg_make_tree(d) { 2654 d.__vgtree__ = true; 2655 d.nodes = function() { return vg_tree_nodes(this, []); }; 2656 return d; 2657 } 2658 2659 function vg_tree_nodes(root, nodes) { 2660 var c = root.values, 2661 n = c ? c.length : 0, i; 2662 nodes.push(root); 2663 for (i=0; i<n; ++i) { vg_tree_nodes(c[i], nodes); } 2664 return nodes; 2665 } 2666 2667 function vg_data_duplicate(d) { 2668 var x=d, i, n; 2669 if (vg.isArray(d)) { 2670 x = []; 2671 for (i=0, n=d.length; i<n; ++i) { 2672 x.push(vg_data_duplicate(d[i])); 2673 } 2674 } else if (vg.isObject(d)) { 2675 x = {}; 2676 for (i in d) { 2677 x[i] = vg_data_duplicate(d[i]); 2678 } 2679 } 2680 return x; 2681 } 2682 2683 vg.data.mapper = function(func) { 2684 return function(data) { 2685 data.forEach(func); 2686 return data; 2687 } 2688 }; 2689 2690 vg.data.size = function(size, group) { 2691 size = vg.isArray(size) ? size : [0, size]; 2692 size = size.map(function(d) { 2693 return (typeof d === 'string') ? group[d] : d; 2694 }); 2695 return size; 2696 };vg.data.load = function(uri, callback) { 2697 var url = vg_load_hasProtocol(uri) ? uri : vg.config.baseURL + uri; 2698 if (vg.config.isNode) { 2699 // in node.js, consult url and select file or http 2700 var get = vg_load_isFile(url) ? vg_load_file : vg_load_http; 2701 get(url, callback); 2702 } else { 2703 // in browser, use xhr 2704 vg_load_xhr(url, callback); 2705 } 2706 }; 2707 2708 var vg_load_protocolRE = /^[A-Za-z]+\:\/\//; 2709 var vg_load_fileProtocol = "file://"; 2710 2711 function vg_load_hasProtocol(url) { 2712 return vg_load_protocolRE.test(url); 2713 } 2714 2715 function vg_load_isFile(url) { 2716 return url.indexOf(vg_load_fileProtocol) === 0; 2717 } 2718 2719 function vg_load_xhr(url, callback) { 2720 vg.log("LOAD: " + url); 2721 d3.xhr(url, function(err, resp) { 2722 if (resp) resp = resp.responseText; 2723 callback(err, resp); 2724 }); 2725 } 2726 2727 function vg_load_file(file, callback) { 2728 vg.log("LOAD FILE: " + file); 2729 var idx = file.indexOf(vg_load_fileProtocol); 2730 if (idx >= 0) file = file.slice(vg_load_fileProtocol.length); 2731 require("fs").readFile(file, callback); 2732 } 2733 2734 function vg_load_http(url, callback) { 2735 vg.log("LOAD HTTP: " + url); 2736 var req = require("http").request(url, function(res) { 2737 var pos=0, data = new Buffer(parseInt(res.headers['content-length'],10)); 2738 res.on("error", function(err) { callback(err, null); }); 2739 res.on("data", function(x) { x.copy(data, pos); pos += x.length; }); 2740 res.on("end", function() { callback(null, data); }); 2741 }); 2742 req.on("error", function(err) { callback(err); }); 2743 req.end(); 2744 }vg.data.read = (function() { 2745 var formats = {}, 2746 parsers = { 2747 "number": vg.number, 2748 "boolean": vg.boolean, 2749 "date": Date.parse 2750 }; 2751 2752 function read(data, format) { 2753 var type = (format && format.type) || "json"; 2754 data = formats[type](data, format); 2755 if (format && format.parse) parseValues(data, format.parse); 2756 return data; 2757 } 2758 2759 formats.json = function(data, format) { 2760 var d = JSON.parse(data); 2761 if (format && format.property) { 2762 d = vg.accessor(format.property)(d); 2763 } 2764 return d; 2765 }; 2766 2767 formats.csv = function(data, format) { 2768 var d = d3.csv.parse(data); 2769 return d; 2770 }; 2771 2772 formats.tsv = function(data, format) { 2773 var d = d3.tsv.parse(data); 2774 return d; 2775 }; 2776 2777 formats.topojson = function(data, format) { 2778 if (topojson == null) { 2779 vg.error("TopoJSON library not loaded."); 2780 return []; 2781 } 2782 var t = JSON.parse(data), obj = []; 2783 2784 if (format && format.feature) { 2785 obj = (obj = t.objects[format.feature]) 2786 ? topojson.feature(t, obj).features 2787 : (vg.error("Invalid TopoJSON object: "+format.feature), []); 2788 } else if (format && format.mesh) { 2789 obj = (obj = t.objects[format.mesh]) 2790 ? [topojson.mesh(t, t.objects[format.mesh])] 2791 : (vg.error("Invalid TopoJSON object: " + format.mesh), []); 2792 } 2793 else { vg.error("Missing TopoJSON feature or mesh parameter."); } 2794 2795 return obj; 2796 }; 2797 2798 formats.treejson = function(data, format) { 2799 var d = [JSON.parse(data)]; 2800 d.__vgtree__ = true; 2801 d.children = format.children || "children"; 2802 return d; 2803 }; 2804 2805 function parseValues(data, types) { 2806 var cols = vg.keys(types), 2807 p = cols.map(function(col) { return parsers[types[col]]; }), 2808 tree = vg.isTree(data); 2809 vg_parseArray(tree ? [data] : data, cols, p, tree); 2810 } 2811 2812 function vg_parseArray(data, cols, p, tree) { 2813 var d, i, j, len, clen; 2814 for (i=0, len=data.length; i<len; ++i) { 2815 d = data[i]; 2816 for (j=0, clen=cols.length; j<clen; ++j) { 2817 d[cols[j]] = p[j](d[cols[j]]); 2818 } 2819 if (tree && d.values) parseCollection(d, cols, p, true); 2820 } 2821 } 2822 2823 read.formats = formats; 2824 read.parse = parseValues; 2825 return read; 2826 })();vg.data.array = function() { 2827 var fields = []; 2828 2829 function array(data) { 2830 return data.map(function(d) { 2831 var list = []; 2832 for (var i=0, len=fields.length; i<len; ++i) { 2833 list.push(fields[i](d)); 2834 } 2835 return list; 2836 }); 2837 } 2838 2839 array.fields = function(fieldList) { 2840 fields = vg.array(fieldList).map(vg.accessor); 2841 return array; 2842 }; 2843 2844 return array; 2845 };vg.data.copy = function() { 2846 var from = vg.accessor("data"), 2847 fields = [], 2848 as = null; 2849 2850 var copy = vg.data.mapper(function(d) { 2851 var src = from(d), i, len, 2852 source = fields, 2853 target = as || fields; 2854 for (i=0, len=fields.length; i<len; ++i) { 2855 d[target[i]] = src[fields[i]]; 2856 } 2857 return d; 2858 }); 2859 2860 copy.from = function(field) { 2861 from = vg.accessor(field); 2862 return copy; 2863 }; 2864 2865 copy.fields = function(fieldList) { 2866 fields = vg.array(fieldList); 2867 return copy; 2868 }; 2869 2870 copy.as = function(fieldList) { 2871 as = vg.array(fieldList); 2872 return copy; 2873 }; 2874 2875 return copy; 2876 };vg.data.cross = function() { 2877 var other = null, 2878 nodiag = false, 2879 output = {left:"a", right:"b"}; 2880 2881 function cross(data) { 2882 var result = [], 2883 data2 = other || data, 2884 o, i, j, n = data.length; 2885 2886 for (i=0; i<n; ++i) { 2887 for (j=0; j<n; ++j) { 2888 if (nodiag && i===j) continue; 2889 o = {}; 2890 o[output.left] = data[i]; 2891 o[output.right] = data2[j]; 2892 result.push(o); 2893 } 2894 } 2895 return result; 2896 } 2897 2898 cross["with"] = function(d) { 2899 other = d; 2900 return cross; 2901 }; 2902 2903 cross.diagonal = function(x) { 2904 nodiag = !x; 2905 return cross; 2906 }; 2907 2908 cross.output = function(map) { 2909 vg.keys(output).forEach(function(k) { 2910 if (map[k] !== undefined) { output[k] = map[k]; } 2911 }); 2912 return cross; 2913 }; 2914 2915 return cross; 2916 }; 2917 vg.data.facet = function() { 2918 2919 var keys = [], 2920 sort = null; 2921 2922 function facet(data) { 2923 var result = { 2924 key: "", 2925 keys: [], 2926 values: [] 2927 }, 2928 map = {}, 2929 vals = result.values, 2930 obj, klist, kstr, len, i, j, k, kv, cmp; 2931 2932 if (keys.length === 0) { 2933 // if no keys, skip collation step 2934 vals.push(obj = { 2935 key: "", keys: [], index: 0, 2936 values: sort ? data.slice() : data 2937 }); 2938 if (sort) sort(obj.values); 2939 return result; 2940 } 2941 2942 for (i=0, len=data.length; i<len; ++i) { 2943 for (k=0, klist=[], kstr=""; k<keys.length; ++k) { 2944 kv = keys[k](data[i]); 2945 klist.push(kv); 2946 kstr += (k>0 ? "|" : "") + String(kv); 2947 } 2948 obj = map[kstr]; 2949 if (obj === undefined) { 2950 vals.push(obj = map[kstr] = { 2951 key: kstr, 2952 keys: klist, 2953 index: vals.length, 2954 values: [] 2955 }); 2956 } 2957 obj.values.push(data[i]); 2958 } 2959 2960 if (sort) { 2961 for (i=0, len=vals.length; i<len; ++i) { 2962 sort(vals[i].values); 2963 } 2964 } 2965 2966 return result; 2967 } 2968 2969 facet.keys = function(k) { 2970 keys = vg.array(k).map(vg.accessor); 2971 return facet; 2972 }; 2973 2974 facet.sort = function(s) { 2975 sort = vg.data.sort().by(s); 2976 return facet; 2977 }; 2978 2979 return facet; 2980 };vg.data.filter = function() { 2981 2982 var test = null; 2983 2984 function filter(data) { 2985 return test ? data.filter(test) : data; 2986 } 2987 2988 filter.test = function(func) { 2989 test = vg.isFunction(func) ? func : vg.parse.expr(func); 2990 return filter; 2991 }; 2992 2993 return filter; 2994 };vg.data.flatten = function() { 2995 2996 function flatten(data) { 2997 return flat(data, []); 2998 } 2999 3000 function flat(data, list) { 3001 if (data.values) { 3002 for (var i=0, n=data.values.length; i<n; ++i) { 3003 flat(data.values[i], list); 3004 } 3005 } else { 3006 list.push(data); 3007 } 3008 return list; 3009 } 3010 3011 return flatten; 3012 };vg.data.fold = function() { 3013 var fields = [], 3014 accessors = [], 3015 output = { 3016 key: "key", 3017 value: "value" 3018 }; 3019 3020 function fold(data) { 3021 var values = [], 3022 item, i, j, n, m = fields.length; 3023 3024 for (i=0, n=data.length; i<n; ++i) { 3025 item = data[i]; 3026 for (j=0; j<m; ++j) { 3027 var o = { 3028 index: values.length, 3029 data: item.data 3030 }; 3031 o[output.key] = fields[j]; 3032 o[output.value] = accessors[j](item); 3033 values.push(o); 3034 } 3035 } 3036 3037 return values; 3038 } 3039 3040 fold.fields = function(f) { 3041 fields = vg.array(f); 3042 accessors = fields.map(vg.accessor); 3043 return fold; 3044 }; 3045 3046 fold.output = function(map) { 3047 vg.keys(output).forEach(function(k) { 3048 if (map[k] !== undefined) { 3049 output[k] = map[k]; 3050 } 3051 }); 3052 return fold; 3053 }; 3054 3055 return fold; 3056 };vg.data.force = function() { 3057 var layout = d3.layout.force(), 3058 links = null, 3059 linkDistance = 20, 3060 linkStrength = 1, 3061 charge = -30, 3062 iterations = 500, 3063 size = ["width", "height"], 3064 params = [ 3065 "friction", 3066 "theta", 3067 "gravity", 3068 "alpha" 3069 ]; 3070 3071 function force(data, db, group) { 3072 layout 3073 .size(vg.data.size(size, group)) 3074 .nodes(data); 3075 3076 if (links && db[links]) { 3077 layout.links(db[links]); 3078 } 3079 3080 layout.start(); 3081 for (var i=0; i<iterations; ++i) { 3082 layout.tick(); 3083 } 3084 layout.stop(); 3085 3086 return data; 3087 } 3088 3089 force.links = function(dataSetName) { 3090 links = dataSetName; 3091 return force; 3092 }; 3093 3094 force.size = function(sz) { 3095 size = sz; 3096 return force; 3097 }; 3098 3099 force.linkDistance = function(field) { 3100 linkDistance = typeof field === 'number' 3101 ? field 3102 : vg.accessor(field); 3103 layout.linkDistance(linkDistance); 3104 return force; 3105 }; 3106 3107 force.linkStrength = function(field) { 3108 linkStrength = typeof field === 'number' 3109 ? field 3110 : vg.accessor(field); 3111 layout.linkStrength(linkStrength); 3112 return force; 3113 }; 3114 3115 force.charge = function(field) { 3116 charge = typeof field === 'number' 3117 ? field 3118 : vg.accessor(field); 3119 layout.charge(charge); 3120 return force; 3121 }; 3122 3123 force.iterations = function(iter) { 3124 iterations = iter; 3125 return force; 3126 }; 3127 3128 params.forEach(function(name) { 3129 force[name] = function(x) { 3130 layout[name](x); 3131 return force; 3132 } 3133 }); 3134 3135 return force; 3136 };vg.data.formula = (function() { 3137 3138 return function() { 3139 var field = null, 3140 expr = vg.identity; 3141 3142 var formula = vg.data.mapper(function(d, i, list) { 3143 if (field) d[field] = expr.call(null, d, i, list); 3144 return d; 3145 }); 3146 3147 formula.field = function(name) { 3148 field = name; 3149 return formula; 3150 }; 3151 3152 formula.expr = function(func) { 3153 expr = vg.isFunction(func) ? func : vg.parse.expr(func); 3154 return formula; 3155 }; 3156 3157 return formula; 3158 }; 3159 })();vg.data.geo = (function() { 3160 var params = [ 3161 "center", 3162 "scale", 3163 "translate", 3164 "rotate", 3165 "precision", 3166 "clipAngle" 3167 ]; 3168 3169 function geo() { 3170 var opt = {}, 3171 projection = "mercator", 3172 func = d3.geo[projection](), 3173 lat = vg.identity, 3174 lon = vg.identity, 3175 output = { 3176 "x": "x", 3177 "y": "y" 3178 }; 3179 3180 var map = vg.data.mapper(function(d) { 3181 var ll = [lon(d), lat(d)], 3182 xy = func(ll); 3183 d[output.x] = xy[0]; 3184 d[output.y] = xy[1]; 3185 return d; 3186 }); 3187 3188 map.func = function() { 3189 return func; 3190 }; 3191 3192 map.projection = function(p) { 3193 if (projection !== p) { 3194 projection = p; 3195 func = d3.geo[projection](); 3196 for (var name in opt) { 3197 func[name](opt[name]); 3198 } 3199 } 3200 return map; 3201 }; 3202 3203 params.forEach(function(name) { 3204 map[name] = function(x) { 3205 opt[name] = x; 3206 func[name](x); 3207 return map; 3208 } 3209 }); 3210 3211 map.lon = function(field) { 3212 lon = vg.accessor(field); 3213 return map; 3214 }; 3215 3216 map.lat = function(field) { 3217 lat = vg.accessor(field); 3218 return map; 3219 }; 3220 3221 map.output = function(map) { 3222 vg.keys(output).forEach(function(k) { 3223 if (map[k] !== undefined) { 3224 output[k] = map[k]; 3225 } 3226 }); 3227 return map; 3228 }; 3229 3230 3231 return map; 3232 }; 3233 3234 geo.params = params; 3235 return geo; 3236 })();vg.data.geopath = function() { 3237 var geopath = d3.geo.path().projection(d3.geo.mercator()), 3238 projection = "mercator", 3239 geojson = vg.identity, 3240 opt = {}, 3241 output = {"path": "path"}; 3242 3243 var map = vg.data.mapper(function(d) { 3244 d[output.path] = geopath(geojson(d)); 3245 return d; 3246 }); 3247 3248 map.projection = function(proj) { 3249 if (projection !== proj) { 3250 projection = proj; 3251 var p = d3.geo[projection](); 3252 for (var name in opt) { 3253 p[name](opt[name]); 3254 } 3255 geopath.projection(p); 3256 } 3257 return map; 3258 }; 3259 3260 vg.data.geo.params.forEach(function(name) { 3261 map[name] = function(x) { 3262 opt[name] = x; 3263 (geopath.projection())[name](x); 3264 return map; 3265 } 3266 }); 3267 3268 map.value = function(field) { 3269 geojson = vg.accessor(field); 3270 return map; 3271 }; 3272 3273 map.output = function(map) { 3274 vg.keys(output).forEach(function(k) { 3275 if (map[k] !== undefined) { 3276 output[k] = map[k]; 3277 } 3278 }); 3279 return map; 3280 }; 3281 3282 return map; 3283 };vg.data.link = function() { 3284 var shape = "line", 3285 source = vg.accessor("source"), 3286 target = vg.accessor("target"), 3287 tension = 0.2, 3288 output = {"path": "path"}; 3289 3290 function line(d) { 3291 var s = source(d), 3292 t = target(d); 3293 return "M" + s.x + "," + s.y 3294 + "L" + t.x + "," + t.y; 3295 } 3296 3297 function curve(d) { 3298 var s = source(d), 3299 t = target(d), 3300 dx = t.x - s.x, 3301 dy = t.y - s.y, 3302 ix = tension * (dx + dy), 3303 iy = tension * (dy - dx); 3304 return "M" + s.x + "," + s.y 3305 + "C" + (s.x+ix) + "," + (s.y+iy) 3306 + " " + (t.x+iy) + "," + (t.y-ix) 3307 + " " + t.x + "," + t.y; 3308 } 3309 3310 function diagonalX(d) { 3311 var s = source(d), 3312 t = target(d), 3313 m = (s.x + t.x) / 2; 3314 return "M" + s.x + "," + s.y 3315 + "C" + m + "," + s.y 3316 + " " + m + "," + t.y 3317 + " " + t.x + "," + t.y; 3318 } 3319 3320 function diagonalY(d) { 3321 var s = source(d), 3322 t = target(d), 3323 m = (s.y + t.y) / 2; 3324 return "M" + s.x + "," + s.y 3325 + "C" + s.x + "," + m 3326 + " " + t.x + "," + m 3327 + " " + t.x + "," + t.y; 3328 } 3329 3330 var shapes = { 3331 line: line, 3332 curve: curve, 3333 diagonal: diagonalX, 3334 diagonalX: diagonalX, 3335 diagonalY: diagonalY 3336 }; 3337 3338 function link(data) { 3339 var path = shapes[shape]; 3340 3341 data.forEach(function(d) { 3342 d[output.path] = path(d); 3343 }); 3344 3345 return data; 3346 } 3347 3348 link.shape = function(val) { 3349 shape = val; 3350 return link; 3351 }; 3352 3353 link.tension = function(val) { 3354 tension = val; 3355 return link; 3356 }; 3357 3358 link.source = function(field) { 3359 source = vg.accessor(field); 3360 return link; 3361 }; 3362 3363 link.target = function(field) { 3364 target = vg.accessor(field); 3365 return link; 3366 }; 3367 3368 link.output = function(map) { 3369 vg.keys(output).forEach(function(k) { 3370 if (map[k] !== undefined) { 3371 output[k] = map[k]; 3372 } 3373 }); 3374 return link; 3375 }; 3376 3377 return link; 3378 };vg.data.pie = function() { 3379 var one = function() { return 1; }, 3380 value = one, 3381 start = 0, 3382 end = 2 * Math.PI, 3383 sort = false, 3384 output = { 3385 "startAngle": "startAngle", 3386 "endAngle": "endAngle" 3387 }; 3388 3389 function pie(data) { 3390 var values = data.map(function(d, i) { return +value(d); }), 3391 a = start, 3392 k = (end - start) / d3.sum(values), 3393 index = d3.range(data.length); 3394 3395 if (sort) { 3396 index.sort(function(a, b) { 3397 return values[a] - values[b]; 3398 }); 3399 } 3400 3401 index.forEach(function(i) { 3402 var d; 3403 data[i].value = (d = values[i]); 3404 data[i][output.startAngle] = a; 3405 data[i][output.endAngle] = (a += d * k); 3406 }); 3407 3408 return data; 3409 } 3410 3411 pie.sort = function(b) { 3412 sort = b; 3413 return pie; 3414 }; 3415 3416 pie.value = function(field) { 3417 value = field ? vg.accessor(field) : one; 3418 return pie; 3419 }; 3420 3421 pie.startAngle = function(startAngle) { 3422 start = Math.PI * startAngle / 180; 3423 return pie; 3424 }; 3425 3426 pie.endAngle = function(endAngle) { 3427 end = Math.PI * endAngle / 180; 3428 return pie; 3429 }; 3430 3431 pie.output = function(map) { 3432 vg.keys(output).forEach(function(k) { 3433 if (map[k] !== undefined) { 3434 output[k] = map[k]; 3435 } 3436 }); 3437 return pie; 3438 }; 3439 3440 return pie; 3441 };vg.data.slice = function() { 3442 var by = null, 3443 field = vg.accessor("data"); 3444 3445 function slice(data) { 3446 data = vg.values(data); 3447 3448 if (by === "min") { 3449 data = [data[vg.minIndex(data, field)]]; 3450 } else if (by === "max") { 3451 data = [data[vg.maxIndex(data, field)]]; 3452 } else if (by === "median") { 3453 var list = data.slice().sort(function(a,b) { 3454 a = field(a); b = field(b); 3455 return a < b ? -1 : a > b ? 1 : 0; 3456 }); 3457 data = [data[~~(list.length/2)]]; 3458 } else { 3459 var idx = vg.array(by); 3460 data = data.slice(idx[0], idx[1]); 3461 } 3462 return data; 3463 } 3464 3465 slice.by = function(x) { 3466 by = x; 3467 return slice; 3468 }; 3469 3470 slice.field = function(f) { 3471 field = vg.accessor(f); 3472 return slice; 3473 }; 3474 3475 return slice; 3476 };vg.data.sort = function() { 3477 var by = null; 3478 3479 function sort(data) { 3480 data = (vg.isArray(data) ? data : data.values || []); 3481 data.sort(by); 3482 for (var i=0, n=data.length; i<n; ++i) data[i].index = i; // re-index 3483 return data; 3484 } 3485 3486 sort.by = function(s) { 3487 by = vg.comparator(s); 3488 return sort; 3489 }; 3490 3491 return sort; 3492 };vg.data.stack = function() { 3493 var layout = d3.layout.stack(), 3494 point = vg.accessor("index"), 3495 height = vg.accessor("data"), 3496 params = ["offset", "order"], 3497 output = { 3498 "y0": "y2", 3499 "y1": "y", 3500 "cy": "cy" 3501 }; 3502 3503 function stack(data) { 3504 var out_y0 = output["y0"], 3505 out_y1 = output["y1"], 3506 out_cy = output["cy"]; 3507 3508 var series = stacks(data); 3509 if (series.length === 0) return data; 3510 3511 layout.out(function(d, y0, y) { 3512 if (d.datum) { 3513 d.datum[out_y0] = y0; 3514 d.datum[out_y1] = y + y0; 3515 d.datum[out_cy] = y0 + y/2; 3516 } 3517 })(series); 3518 3519 return data; 3520 } 3521 3522 function stacks(data) { 3523 var values = vg.values(data), 3524 points = [], series = [], 3525 a, i, n, j, m, k, p, v, x; 3526 3527 // exit early if no data 3528 if (values.length === 0) return series; 3529 3530 // collect and sort data points 3531 for (i=0, n=values.length; i<n; ++i) { 3532 a = vg.values(values[i]); 3533 for (j=0, m=a.length; j<m; ++j) { 3534 points.push({x:point(a[j]), y:height(a[j]), z:i, datum:a[j]}); 3535 } 3536 series.push([]); 3537 } 3538 points.sort(function(a,b) { 3539 return a.x<b.x ? -1 : a.x>b.x ? 1 : (a.z<b.z ? -1 : a.z>b.z ? 1 : 0); 3540 }); 3541 3542 // emit data series for stack layout 3543 for (x=points[0].x, i=0, j=0, k=0, n=points.length; k<n; ++k) { 3544 p = points[k]; 3545 if (p.x !== x) { 3546 while (i < series.length) series[i++].push({x:j, y:0}); 3547 x = p.x; i = 0; j += 1; 3548 } 3549 while (p.z > i) series[i++].push({x:j, y:0}); 3550 p.x = j; 3551 series[i++].push(p); 3552 } 3553 while (i < series.length) series[i++].push({x:j, y:0}); 3554 3555 return series; 3556 } 3557 3558 stack.point = function(field) { 3559 point = vg.accessor(field); 3560 return stack; 3561 }; 3562 3563 stack.height = function(field) { 3564 height = vg.accessor(field); 3565 return stack; 3566 }; 3567 3568 params.forEach(function(name) { 3569 stack[name] = function(x) { 3570 layout[name](x); 3571 return stack; 3572 } 3573 }); 3574 3575 stack.output = function(map) { 3576 d3.keys(output).forEach(function(k) { 3577 if (map[k] !== undefined) { 3578 output[k] = map[k]; 3579 } 3580 }); 3581 return stack; 3582 }; 3583 3584 return stack; 3585 };vg.data.stats = function() { 3586 var value = vg.accessor("data"), 3587 assign = false, 3588 median = false, 3589 output = { 3590 "count": "count", 3591 "min": "min", 3592 "max": "max", 3593 "sum": "sum", 3594 "mean": "mean", 3595 "variance": "variance", 3596 "stdev": "stdev", 3597 "median": "median" 3598 }; 3599 3600 function reduce(data) { 3601 var min = +Infinity, 3602 max = -Infinity, 3603 sum = 0, 3604 mean = 0, 3605 M2 = 0, 3606 i, len, v, delta; 3607 3608 var list = (vg.isArray(data) ? data : data.values || []).map(value); 3609 3610 // compute aggregates 3611 for (i=0, len=list.length; i<len; ++i) { 3612 v = list[i]; 3613 if (v < min) min = v; 3614 if (v > max) max = v; 3615 sum += v; 3616 delta = v - mean; 3617 mean = mean + delta / (i+1); 3618 M2 = M2 + delta * (v - mean); 3619 } 3620 M2 = M2 / (len - 1); 3621 3622 var o = vg.isArray(data) ? {} : data; 3623 if (median) { 3624 list.sort(vg.numcmp); 3625 i = list.length >> 1; 3626 o[output.median] = list.length % 2 3627 ? list[i] 3628 : (list[i-1] + list[i])/2; 3629 } 3630 o[output.count] = len; 3631 o[output.min] = min; 3632 o[output.max] = max; 3633 o[output.sum] = sum; 3634 o[output.mean] = mean; 3635 o[output.variance] = M2; 3636 o[output.stdev] = Math.sqrt(M2); 3637 3638 if (assign) { 3639 list = (vg.isArray(data) ? data : data.values); 3640 v = {}; 3641 v[output.count] = len; 3642 v[output.min] = min; 3643 v[output.max] = max; 3644 v[output.sum] = sum; 3645 v[output.mean] = mean; 3646 v[output.variance] = M2; 3647 v[output.stdev] = Math.sqrt(M2); 3648 if (median) v[output.median] = o[output.median]; 3649 for (i=0, len=list.length; i<len; ++i) { 3650 list[i].stats = v; 3651 } 3652 } 3653 3654 return o; 3655 } 3656 3657 function stats(data) { 3658 return (vg.isArray(data) ? [data] : data.values || []) 3659 .map(reduce); // no pun intended 3660 } 3661 3662 stats.median = function(bool) { 3663 median = bool || false; 3664 return stats; 3665 }; 3666 3667 stats.value = function(field) { 3668 value = vg.accessor(field); 3669 return stats; 3670 }; 3671 3672 stats.assign = function(b) { 3673 assign = b; 3674 return stats; 3675 }; 3676 3677 stats.output = function(map) { 3678 vg.keys(output).forEach(function(k) { 3679 if (map[k] !== undefined) { 3680 output[k] = map[k]; 3681 } 3682 }); 3683 return stats; 3684 }; 3685 3686 return stats; 3687 };vg.data.treemap = function() { 3688 var layout = d3.layout.treemap() 3689 .children(function(d) { return d.values; }), 3690 value = vg.accessor("data"), 3691 size = ["width", "height"], 3692 params = ["round", "sticky", "ratio", "padding"], 3693 output = { 3694 "x": "x", 3695 "y": "y", 3696 "dx": "width", 3697 "dy": "height" 3698 }; 3699 3700 function treemap(data, db, group) { 3701 data = layout 3702 .size(vg.data.size(size, group)) 3703 .value(value) 3704 .nodes(vg.isTree(data) ? data.nodes() : data); 3705 3706 var keys = vg.keys(output), 3707 len = keys.length; 3708 data.forEach(function(d) { 3709 var key, val; 3710 for (var i=0; i<len; ++i) { 3711 key = keys[i]; 3712 if (key !== output[key]) { 3713 val = d[key]; 3714 delete d[key]; 3715 d[output[key]] = val; 3716 } 3717 } 3718 }); 3719 3720 return data; 3721 } 3722 3723 treemap.size = function(sz) { 3724 size = sz; 3725 return treemap; 3726 }; 3727 3728 treemap.value = function(field) { 3729 value = vg.accessor(field); 3730 return treemap; 3731 }; 3732 3733 params.forEach(function(name) { 3734 treemap[name] = function(x) { 3735 layout[name](x); 3736 return treemap; 3737 } 3738 }); 3739 3740 treemap.output = function(map) { 3741 vg.keys(output).forEach(function(k) { 3742 if (map[k] !== undefined) { 3743 output[k] = map[k]; 3744 } 3745 }); 3746 return treemap; 3747 }; 3748 3749 return treemap; 3750 };vg.data.truncate = function() { 3751 var value = vg.accessor("data"), 3752 as = "truncate", 3753 position = "right", 3754 ellipsis = "...", 3755 wordBreak = true, 3756 limit = 100; 3757 3758 var truncate = vg.data.mapper(function(d) { 3759 var text = vg.truncate(value(d), limit, position, wordBreak, ellipsis); 3760 return (d[as] = text, d); 3761 }); 3762 3763 truncate.value = function(field) { 3764 value = vg.accessor(field); 3765 return truncate; 3766 }; 3767 3768 truncate.output = function(field) { 3769 as = field; 3770 return truncate; 3771 }; 3772 3773 truncate.limit = function(len) { 3774 limit = +len; 3775 return truncate; 3776 }; 3777 3778 truncate.position = function(pos) { 3779 position = pos; 3780 return truncate; 3781 }; 3782 3783 truncate.ellipsis = function(str) { 3784 ellipsis = str+""; 3785 return truncate; 3786 }; 3787 3788 truncate.wordbreak = function(b) { 3789 wordBreak = !!b; 3790 return truncate; 3791 }; 3792 3793 return truncate; 3794 };vg.data.unique = function() { 3795 3796 var field = null, 3797 as = "field"; 3798 3799 function unique(data) { 3800 return vg.unique(data, field) 3801 .map(function(x) { 3802 var o = {}; 3803 o[as] = x; 3804 return o; 3805 }); 3806 } 3807 3808 unique.field = function(f) { 3809 field = vg.accessor(f); 3810 return unique; 3811 }; 3812 3813 unique.as = function(x) { 3814 as = x; 3815 return unique; 3816 }; 3817 3818 return unique; 3819 };vg.data.window = function() { 3820 3821 var size = 2, 3822 step = 1; 3823 3824 function win(data) { 3825 data = vg.isArray(data) ? data : data.values || []; 3826 var runs = [], i, j, n=data.length-size, curr; 3827 for (i=0; i<=n; i+=step) { 3828 for (j=0, curr=[]; j<size; ++j) curr.push(data[i+j]); 3829 runs.push({key: i, values: curr}); 3830 } 3831 return {values: runs}; 3832 } 3833 3834 win.size = function(n) { 3835 size = n; 3836 return win; 3837 }; 3838 3839 win.step = function(n) { 3840 step = n; 3841 return win; 3842 }; 3843 3844 return win; 3845 };vg.data.wordcloud = function() { 3846 var layout = d3.layout.cloud().size([900, 500]), 3847 text = vg.accessor("data"), 3848 size = ["width", "height"], 3849 fontSize = function() { return 14; }, 3850 rotate = function() { return 0; }, 3851 params = ["font", "fontStyle", "fontWeight", "padding"]; 3852 3853 var output = { 3854 "x": "x", 3855 "y": "y", 3856 "size": "fontSize", 3857 "font": "font", 3858 "rotate": "angle" 3859 }; 3860 3861 function cloud(data, db, group) { 3862 function finish(tags, bounds) { 3863 var size = layout.size(), 3864 dx = size[0] / 2, 3865 dy = size[1] / 2, 3866 keys = vg.keys(output), 3867 key, d, i, n, k, m = keys.length; 3868 3869 // sort data to match wordcloud order 3870 data.sort(function(a,b) { 3871 return fontSize(b) - fontSize(a); 3872 }); 3873 3874 for (i=0, n=tags.length; i<n; ++i) { 3875 d = data[i]; 3876 for (k=0; k<m; ++k) { 3877 key = keys[k]; 3878 d[output[key]] = tags[i][key]; 3879 if (key === "x") d[output.x] += dx; 3880 if (key === "y") d[output.y] += dy; 3881 } 3882 } 3883 } 3884 3885 layout 3886 .size(vg.data.size(size, group)) 3887 .text(text) 3888 .fontSize(fontSize) 3889 .rotate(rotate) 3890 .words(data) 3891 .on("end", finish) 3892 .start(); 3893 return data; 3894 } 3895 3896 cloud.text = function(field) { 3897 text = vg.accessor(field); 3898 return cloud; 3899 }; 3900 3901 cloud.size = function(sz) { 3902 size = sz; 3903 return cloud; 3904 }; 3905 3906 cloud.fontSize = function(field) { 3907 fontSize = vg.accessor(field); 3908 return cloud; 3909 }; 3910 3911 cloud.rotate = function(x) { 3912 var v; 3913 if (vg.isObject(x) && !Array.isArray(x)) { 3914 if (x.random !== undefined) { 3915 v = (v = x.random) ? vg.array(v) : [0]; 3916 rotate = function() { 3917 return v[~~(Math.random()*v.length-0.00001)]; 3918 }; 3919 } else if (x.alternate !== undefined) { 3920 v = (v = x.alternate) ? vg.array(v) : [0]; 3921 rotate = function(d, i) { 3922 return v[i % v.length]; 3923 }; 3924 } 3925 } else { 3926 rotate = vg.accessor(field); 3927 } 3928 return cloud; 3929 }; 3930 3931 params.forEach(function(name) { 3932 cloud[name] = function(x) { 3933 layout[name](x); 3934 return cloud; 3935 } 3936 }); 3937 3938 cloud.output = function(map) { 3939 vg.keys(output).forEach(function(k) { 3940 if (map[k] !== undefined) { 3941 output[k] = map[k]; 3942 } 3943 }); 3944 return cloud; 3945 }; 3946 3947 return cloud; 3948 };vg.data.zip = function() { 3949 var z = null, 3950 as = "zip", 3951 key = vg.accessor("data"), 3952 defaultValue = undefined, 3953 withKey = null; 3954 3955 function zip(data, db) { 3956 var zdata = db[z], zlen = zdata.length, v, d, i, len, map; 3957 3958 if (withKey) { 3959 map = {}; 3960 zdata.forEach(function(s) { map[withKey(s)] = s; }); 3961 } 3962 3963 for (i=0, len=data.length; i<len; ++i) { 3964 d = data[i]; 3965 d[as] = map 3966 ? ((v=map[key(d)]) != null ? v : defaultValue) 3967 : zdata[i % zlen]; 3968 } 3969 3970 return data; 3971 } 3972 3973 zip["with"] = function(d) { 3974 z = d; 3975 return zip; 3976 }; 3977 3978 zip["default"] = function(d) { 3979 defaultValue = d; 3980 return zip; 3981 }; 3982 3983 zip.as = function(name) { 3984 as = name; 3985 return zip; 3986 }; 3987 3988 zip.key = function(k) { 3989 key = vg.accessor(k); 3990 return zip; 3991 }; 3992 3993 zip.withKey = function(k) { 3994 withKey = vg.accessor(k); 3995 return zip; 3996 }; 3997 3998 return zip; 3999 };vg.parse = {};vg.parse.axes = (function() { 4000 var ORIENT = { 4001 "x": "bottom", 4002 "y": "left", 4003 "top": "top", 4004 "bottom": "bottom", 4005 "left": "left", 4006 "right": "right" 4007 }; 4008 4009 function axes(spec, axes, scales) { 4010 (spec || []).forEach(function(def, index) { 4011 axes[index] = axes[index] || vg.scene.axis(); 4012 axis(def, index, axes[index], scales); 4013 }); 4014 }; 4015 4016 function axis(def, index, axis, scales) { 4017 // axis scale 4018 if (def.scale !== undefined) { 4019 axis.scale(scales[def.scale]); 4020 } 4021 4022 // axis orientation 4023 axis.orient(def.orient || ORIENT[def.type]); 4024 // axis offset 4025 axis.offset(def.offset || 0); 4026 // axis layer 4027 axis.layer(def.layer || "front"); 4028 // axis grid lines 4029 axis.grid(def.grid || false); 4030 // axis title 4031 axis.title(def.title || null); 4032 // axis title offset 4033 axis.titleOffset(def.titleOffset != null 4034 ? def.titleOffset : vg.config.axis.titleOffset); 4035 // axis values 4036 axis.tickValues(def.values || null); 4037 // axis label formatting 4038 axis.tickFormat(def.format ? d3.format(def.format) : null); 4039 // axis tick subdivision 4040 axis.tickSubdivide(def.subdivide || 0); 4041 // axis tick padding 4042 axis.tickPadding(def.tickPadding || vg.config.axis.padding); 4043 4044 // axis tick size(s) 4045 var size = []; 4046 if (def.tickSize !== undefined) { 4047 for (var i=0; i<3; ++i) size.push(def.tickSize); 4048 } else { 4049 var ts = vg.config.axis.tickSize; 4050 size = [ts, ts, ts]; 4051 } 4052 if (def.tickSizeMajor != null) size[0] = def.tickSizeMajor; 4053 if (def.tickSizeMinor != null) size[1] = def.tickSizeMinor; 4054 if (def.tickSizeEnd != null) size[2] = def.tickSizeEnd; 4055 if (size.length) { 4056 axis.tickSize.apply(axis, size); 4057 } 4058 4059 // tick arguments 4060 if (def.ticks != null) { 4061 var ticks = vg.isArray(def.ticks) ? def.ticks : [def.ticks]; 4062 axis.ticks.apply(axis, ticks); 4063 } else { 4064 axis.ticks(vg.config.axis.ticks); 4065 } 4066 4067 // style properties 4068 var p = def.properties; 4069 if (p && p.ticks) { 4070 axis.majorTickProperties(p.majorTicks 4071 ? vg.extend({}, p.ticks, p.majorTicks) : p.ticks); 4072 axis.minorTickProperties(p.minorTicks 4073 ? vg.extend({}, p.ticks, p.minorTicks) : p.ticks); 4074 } else { 4075 axis.majorTickProperties(p && p.majorTicks || {}); 4076 axis.minorTickProperties(p && p.minorTicks || {}); 4077 } 4078 axis.tickLabelProperties(p && p.labels || {}); 4079 axis.titleProperties(p && p.title || {}); 4080 axis.gridLineProperties(p && p.grid || {}); 4081 axis.domainProperties(p && p.axis || {}); 4082 } 4083 4084 return axes; 4085 })();vg.parse.data = function(spec, callback) { 4086 var model = { 4087 defs: spec, 4088 load: {}, 4089 flow: {}, 4090 source: {} 4091 }; 4092 4093 var count = 0; 4094 4095 function load(d) { 4096 return function(error, data) { 4097 if (error) { 4098 vg.error("LOADING FAILED: " + d.url); 4099 } else { 4100 model.load[d.name] = vg.data.read(data.toString(), d.format); 4101 } 4102 if (--count === 0) callback(); 4103 } 4104 } 4105 4106 (spec || []).forEach(function(d) { 4107 if (d.url) { 4108 count += 1; 4109 vg.data.load(d.url, load(d)); 4110 } 4111 4112 if (d.values) { 4113 if (d.format && d.format.parse) { 4114 // run specified value parsers 4115 vg.data.read.parse(d.values, d.format.parse); 4116 } 4117 model.load[d.name] = d.values; 4118 } 4119 4120 if (d.source) { 4121 var list = model.source[d.source] || (model.source[d.source] = []); 4122 list.push(d.name); 4123 } 4124 4125 if (d.transform) { 4126 model.flow[d.name] = vg.parse.dataflow(d); 4127 } 4128 }); 4129 4130 if (count === 0) setTimeout(callback, 1); 4131 return model; 4132 };vg.parse.dataflow = function(def) { 4133 var tx = (def.transform || []).map(vg.parse.transform); 4134 return !tx.length ? vg.identity : 4135 function(data, db, group) { 4136 return tx.reduce(function(d,t) { return t(d, db, group); }, data); 4137 }; 4138 };vg.parse.expr = (function() { 4139 4140 var CONSTANT = { 4141 "E": "Math.E", 4142 "LN2": "Math.LN2", 4143 "LN10": "Math.LN10", 4144 "LOG2E": "Math.LOG2E", 4145 "LOG10E": "Math.LOG10E", 4146 "PI": "Math.PI", 4147 "SQRT1_2": "Math.SQRT1_2", 4148 "SQRT2": "Math.SQRT2" 4149 }; 4150 4151 var FUNCTION = { 4152 "abs": "Math.abs", 4153 "acos": "Math.acos", 4154 "asin": "Math.asin", 4155 "atan": "Math.atan", 4156 "atan2": "Math.atan2", 4157 "ceil": "Math.ceil", 4158 "cos": "Math.cos", 4159 "exp": "Math.exp", 4160 "floor": "Math.floor", 4161 "log": "Math.log", 4162 "max": "Math.max", 4163 "min": "Math.min", 4164 "pow": "Math.pow", 4165 "random": "Math.random", 4166 "round": "Math.round", 4167 "sin": "Math.sin", 4168 "sqrt": "Math.sqrt", 4169 "tan": "Math.tan" 4170 }; 4171 4172 var lexer = /([\"\']|[\=\<\>\~\&\|\?\:\+\-\/\*\%\!\^\,\;\[\]\{\}\(\) ]+)/; 4173 4174 return function(x) { 4175 var tokens = x.split(lexer), 4176 t, v, i, n, sq, dq; 4177 4178 for (sq=0, dq=0, i=0, n=tokens.length; i<n; ++i) { 4179 var t = tokens[i]; 4180 if (t==="'") { if (!dq) sq = !sq; continue; } 4181 if (t==='"') { if (!sq) dq = !dq; continue; } 4182 if (dq || sq) continue; 4183 if (CONSTANT[t]) { 4184 tokens[i] = CONSTANT[t]; 4185 } 4186 if (FUNCTION[t] && (v=tokens[i+1]) && v[0]==="(") { 4187 tokens[i] = FUNCTION[t]; 4188 } 4189 } 4190 4191 return Function("d", "index", "data", "return ("+tokens.join("")+");"); 4192 }; 4193 4194 })();vg.parse.legends = (function() { 4195 4196 function legends(spec, legends, scales) { 4197 (spec || []).forEach(function(def, index) { 4198 legends[index] = legends[index] || vg.scene.legend(); 4199 legend(def, index, legends[index], scales); 4200 }); 4201 }; 4202 4203 function legend(def, index, legend, scales) { 4204 // legend scales 4205 legend.size (def.size ? scales[def.size] : null); 4206 legend.shape (def.shape ? scales[def.shape] : null); 4207 legend.fill (def.fill ? scales[def.fill] : null); 4208 legend.stroke(def.stroke ? scales[def.stroke] : null); 4209 4210 // legend orientation 4211 if (def.orient) legend.orient(def.orient); 4212 4213 // legend offset 4214 if (def.offset != null) legend.offset(def.offset); 4215 4216 // legend title 4217 legend.title(def.title || null); 4218 4219 // legend values 4220 legend.values(def.values || null); 4221 4222 // legend label formatting 4223 legend.format(def.format !== undefined ? d3.format(def.format) : null); 4224 4225 // style properties 4226 var p = def.properties; 4227 legend.titleProperties(p && p.title || {}); 4228 legend.labelProperties(p && p.labels || {}); 4229 legend.legendProperties(p && p.legend || {}); 4230 legend.symbolProperties(p && p.symbols || {}); 4231 legend.gradientProperties(p && p.gradient || {}); 4232 } 4233 4234 return legends; 4235 })();vg.parse.mark = function(mark) { 4236 var props = mark.properties, 4237 group = mark.marks; 4238 4239 // parse mark property definitions 4240 vg.keys(props).forEach(function(k) { 4241 props[k] = vg.parse.properties(mark.type, props[k]); 4242 }); 4243 // parse delay function 4244 if (mark.delay) { 4245 mark.delay = vg.parse.properties(mark.type, {delay: mark.delay}); 4246 } 4247 4248 // parse mark data definition 4249 if (mark.from) { 4250 var name = mark.from.data, 4251 tx = vg.parse.dataflow(mark.from); 4252 mark.from = function(db, group, parentData) { 4253 var data = vg.scene.data(name ? db[name] : null, parentData); 4254 return tx(data, db, group); 4255 }; 4256 } 4257 4258 // recurse if group type 4259 if (group) { 4260 mark.marks = group.map(vg.parse.mark); 4261 } 4262 4263 return mark; 4264 };vg.parse.marks = function(spec, width, height) { 4265 return { 4266 type: "group", 4267 width: width, 4268 height: height, 4269 scales: spec.scales || [], 4270 axes: spec.axes || [], 4271 legends: spec.legends || [], 4272 marks: (spec.marks || []).map(vg.parse.mark) 4273 }; 4274 };vg.parse.padding = function(pad) { 4275 if (pad == null) return "auto"; 4276 else if (vg.isString(pad)) return pad==="strict" ? "strict" : "auto"; 4277 else if (vg.isObject(pad)) return pad; 4278 var p = vg.isNumber(pad) ? pad : 20; 4279 return {top:p, left:p, right:p, bottom:p}; 4280 }; 4281 vg.parse.properties = (function() { 4282 function compile(mark, spec) { 4283 var code = "", 4284 names = vg.keys(spec), 4285 i, len, name, ref, vars = {}; 4286 4287 code += "var o = trans ? {} : item;\n" 4288 4289 for (i=0, len=names.length; i<len; ++i) { 4290 ref = spec[name = names[i]]; 4291 code += (i > 0) ? "\n " : " "; 4292 code += "o."+name+" = "+valueRef(name, ref)+";"; 4293 vars[name] = true; 4294 } 4295 4296 if (vars.x2) { 4297 if (vars.x) { 4298 code += "\n if (o.x > o.x2) { " 4299 + "var t = o.x; o.x = o.x2; o.x2 = t; };"; 4300 code += "\n o.width = (o.x2 - o.x);"; 4301 } else if (vars.width && !vars.x1) { 4302 code += "\n o.x = (o.x2 - o.width);"; 4303 } 4304 } 4305 4306 if (vars.y2) { 4307 if (vars.y) { 4308 code += "\n if (o.y > o.y2) { " 4309 + "var t = o.y; o.y = o.y2; o.y2 = t; };"; 4310 code += "\n o.height = (o.y2 - o.y);"; 4311 } else if (vars.height && !vars.y1) { 4312 code += "\n o.y = (o.y2 - o.height);"; 4313 } 4314 } 4315 4316 if (hasPath(mark, vars)) { 4317 code += "\n if (o['path:parsed']) o['path:parsed'] = null;" 4318 } 4319 code += "\n if (trans) trans.interpolate(item, o);"; 4320 4321 try { 4322 return Function("item", "group", "trans", code); 4323 } catch (e) { 4324 vg.error(e); 4325 vg.log(code); 4326 } 4327 } 4328 4329 function hasPath(mark, vars) { 4330 return vars.path || 4331 ((mark==="area" || mark==="line") && 4332 (vars.x || vars.x2 || vars.width || 4333 vars.y || vars.y2 || vars.height || 4334 vars.tension || vars.interpolate)); 4335 } 4336 4337 var GROUP_VARS = { 4338 "width": 1, 4339 "height": 1, 4340 "mark.group.width": 1, 4341 "mark.group.height": 1 4342 }; 4343 4344 function valueRef(name, ref) { 4345 if (ref == null) return null; 4346 var isColor = name==="fill" || name==="stroke"; 4347 4348 if (isColor) { 4349 if (ref.c) { 4350 return colorRef("hcl", ref.h, ref.c, ref.l); 4351 } else if (ref.h || ref.s) { 4352 return colorRef("hsl", ref.h, ref.s, ref.l); 4353 } else if (ref.l || ref.a) { 4354 return colorRef("lab", ref.l, ref.a, ref.b); 4355 } else if (ref.r || ref.g || ref.b) { 4356 return colorRef("rgb", ref.r, ref.g, ref.b); 4357 } 4358 } 4359 4360 // initialize value 4361 var val = "item.datum.data"; 4362 if (ref.value !== undefined) { 4363 val = vg.str(ref.value); 4364 } 4365 4366 // get field reference for enclosing group 4367 if (ref.group != null) { 4368 var grp = ""; 4369 if (vg.isString(ref.group)) { 4370 grp = GROUP_VARS[ref.group] 4371 ? "group." + ref.group 4372 : "group.datum["+vg.field(ref.group).map(vg.str).join("][")+"]"; 4373 } 4374 } 4375 4376 // get data field value 4377 if (ref.field != null) { 4378 if (vg.isString(ref.field)) { 4379 val = "item.datum["+vg.field(ref.field).map(vg.str).join("][")+"]"; 4380 if (ref.group != null) { val = grp+"["+val+"]"; } 4381 } else { 4382 val = "this.accessor(group.datum[" 4383 + vg.field(ref.field.group).map(vg.str).join("][") 4384 + "])(item.datum.data)"; 4385 } 4386 } else if (ref.group != null) { 4387 val = grp; 4388 } 4389 4390 // run through scale function 4391 if (ref.scale != null) { 4392 var scale = vg.isString(ref.scale) 4393 ? vg.str(ref.scale) 4394 : (ref.scale.group ? "group" : "item") 4395 + ".datum[" + vg.str(ref.scale.group || ref.scale.field) + "]"; 4396 scale = "group.scales[" + scale + "]"; 4397 val = scale + (ref.band ? ".rangeBand()" : "("+val+")"); 4398 } 4399 4400 // multiply, offset, return value 4401 val = "(" + (ref.mult?(vg.number(ref.mult)+" * "):"") + val + ")" 4402 + (ref.offset ? " + " + vg.number(ref.offset) : ""); 4403 if (isColor) val = '('+val+')+""'; 4404 return val; 4405 } 4406 4407 function colorRef(type, x, y, z) { 4408 var xx = x ? valueRef("", x) : vg.config.color[type][0], 4409 yy = y ? valueRef("", y) : vg.config.color[type][1], 4410 zz = z ? valueRef("", z) : vg.config.color[type][2]; 4411 return "(this.d3." + type + "(" + [xx,yy,zz].join(",") + ') + "")'; 4412 } 4413 4414 return compile; 4415 })();vg.parse.scales = (function() { 4416 var LINEAR = "linear", 4417 ORDINAL = "ordinal", 4418 LOG = "log", 4419 POWER = "pow", 4420 TIME = "time", 4421 GROUP_PROPERTY = {width: 1, height: 1}; 4422 4423 function scales(spec, scales, db, group) { 4424 return (spec || []).reduce(function(o, def) { 4425 var name = def.name, prev = name + ":prev"; 4426 o[name] = scale(def, o[name], db, group); 4427 o[prev] = o[prev] || o[name]; 4428 return o; 4429 }, scales || {}); 4430 } 4431 4432 function scale(def, scale, db, group) { 4433 var s = instance(def, scale), 4434 m = s.type===ORDINAL ? ordinal : quantitative, 4435 rng = range(def, group), 4436 data = vg.values(group.datum); 4437 4438 m(def, s, rng, db, data); 4439 return s; 4440 } 4441 4442 function instance(def, scale) { 4443 var type = def.type || LINEAR; 4444 if (!scale || type !== scale.type) { 4445 var ctor = vg.config.scale[type] || d3.scale[type]; 4446 if (!ctor) vg.error("Unrecognized scale type: " + type); 4447 (scale = ctor()).type = scale.type || type; 4448 scale.scaleName = def.name; 4449 } 4450 return scale; 4451 } 4452 4453 function ordinal(def, scale, rng, db, data) { 4454 var domain, refs, values, str; 4455 4456 // domain 4457 domain = def.domain; 4458 if (vg.isArray(domain)) { 4459 scale.domain(domain); 4460 } else if (vg.isObject(domain)) { 4461 refs = def.domain.fields || vg.array(def.domain); 4462 values = refs.reduce(function(values, r) { 4463 var dat = vg.values(db[r.data] || data), 4464 get = vg.accessor(vg.isString(r.field) 4465 ? r.field : "data." + vg.accessor(r.field.group)(data)); 4466 return vg.unique(dat, get, values); 4467 }, []); 4468 if (def.sort) values.sort(vg.cmp); 4469 scale.domain(values); 4470 } 4471 4472 // range 4473 str = typeof rng[0] === 'string'; 4474 if (str || rng.length > 2) { 4475 scale.range(rng); // color or shape values 4476 } else if (def.points) { 4477 scale.rangePoints(rng, def.padding||0); 4478 } else if (def.round || def.round===undefined) { 4479 scale.rangeRoundBands(rng, def.padding||0); 4480 } else { 4481 scale.rangeBands(rng, def.padding||0); 4482 } 4483 } 4484 4485 function quantitative(def, scale, rng, db, data) { 4486 var domain, refs, interval, z; 4487 4488 // domain 4489 domain = [null, null]; 4490 function extract(ref, min, max, z) { 4491 var dat = vg.values(db[ref.data] || data); 4492 var fields = vg.array(ref.field).map(function(f) { 4493 return vg.isString(f) ? f 4494 : "data." + vg.accessor(f.group)(data); 4495 }); 4496 4497 fields.forEach(function(f,i) { 4498 f = vg.accessor(f); 4499 if (min) domain[0] = d3.min([domain[0], d3.min(dat, f)]); 4500 if (max) domain[z] = d3.max([domain[z], d3.max(dat, f)]); 4501 }); 4502 } 4503 if (def.domain !== undefined) { 4504 if (vg.isArray(def.domain)) { 4505 domain = def.domain.slice(); 4506 } else if (vg.isObject(def.domain)) { 4507 refs = def.domain.fields || vg.array(def.domain); 4508 refs.forEach(function(r) { extract(r,1,1,1); }); 4509 } else { 4510 domain = def.domain; 4511 } 4512 } 4513 z = domain.length - 1; 4514 if (def.domainMin !== undefined) { 4515 if (vg.isObject(def.domainMin)) { 4516 domain[0] = null; 4517 refs = def.domainMin.fields || vg.array(def.domainMin); 4518 refs.forEach(function(r) { extract(r,1,0,z); }); 4519 } else { 4520 domain[0] = def.domainMin; 4521 } 4522 } 4523 if (def.domainMax !== undefined) { 4524 if (vg.isObject(def.domainMax)) { 4525 domain[z] = null; 4526 refs = def.domainMax.fields || vg.array(def.domainMax); 4527 refs.forEach(function(r) { extract(r,0,1,z); }); 4528 } else { 4529 domain[z] = def.domainMax; 4530 } 4531 } 4532 if (def.type !== LOG && def.type !== TIME && (def.zero || def.zero===undefined)) { 4533 domain[0] = Math.min(0, domain[0]); 4534 domain[z] = Math.max(0, domain[z]); 4535 } 4536 scale.domain(domain); 4537 4538 // range 4539 // vertical scales should flip by default, so use XOR here 4540 if (def.range === "height") rng = rng.reverse(); 4541 scale[def.round && scale.rangeRound ? "rangeRound" : "range"](rng); 4542 4543 if (def.exponent && def.type===POWER) scale.exponent(def.exponent); 4544 if (def.clamp) scale.clamp(true); 4545 if (def.nice) { 4546 if (def.type === TIME) { 4547 interval = d3.time[def.nice]; 4548 if (!interval) vg.error("Unrecognized interval: " + interval); 4549 scale.nice(interval); 4550 } else { 4551 scale.nice(); 4552 } 4553 } 4554 } 4555 4556 function range(def, group) { 4557 var rng = [null, null]; 4558 4559 if (def.range !== undefined) { 4560 if (typeof def.range === 'string') { 4561 if (GROUP_PROPERTY[def.range]) { 4562 rng = [0, group[def.range]]; 4563 } else if (vg.config.range[def.range]) { 4564 rng = vg.config.range[def.range]; 4565 } else { 4566 vg.error("Unrecogized range: "+def.range); 4567 return rng; 4568 } 4569 } else if (vg.isArray(def.range)) { 4570 rng = def.range; 4571 } else { 4572 rng = [0, def.range]; 4573 } 4574 } 4575 if (def.rangeMin !== undefined) { 4576 rng[0] = def.rangeMin; 4577 } 4578 if (def.rangeMax !== undefined) { 4579 rng[rng.length-1] = def.rangeMax; 4580 } 4581 4582 if (def.reverse !== undefined) { 4583 var rev = def.reverse; 4584 if (vg.isObject(rev)) { 4585 rev = vg.accessor(rev.field)(group.datum); 4586 } 4587 if (rev) rng = rng.reverse(); 4588 } 4589 4590 return rng; 4591 } 4592 4593 return scales; 4594 })(); 4595 vg.parse.spec = function(spec, callback, viewFactory) { 4596 4597 viewFactory = viewFactory || vg.ViewFactory; 4598 4599 function parse(spec) { 4600 // protect against subsequent spec modification 4601 spec = vg.duplicate(spec); 4602 4603 var width = spec.width || 500, 4604 height = spec.height || 500, 4605 viewport = spec.viewport || null; 4606 4607 var defs = { 4608 width: width, 4609 height: height, 4610 viewport: viewport, 4611 padding: vg.parse.padding(spec.padding), 4612 marks: vg.parse.marks(spec, width, height), 4613 data: vg.parse.data(spec.data, function() { callback(viewConstructor); }) 4614 }; 4615 4616 var viewConstructor = viewFactory(defs); 4617 } 4618 4619 vg.isObject(spec) ? parse(spec) : 4620 d3.json(spec, function(error, json) { 4621 error ? vg.error(error) : parse(json); 4622 }); 4623 };vg.parse.transform = function(def) { 4624 var tx = vg.data[def.type](); 4625 4626 vg.keys(def).forEach(function(k) { 4627 if (k === 'type') return; 4628 (tx[k])(def[k]); 4629 }); 4630 4631 return tx; 4632 };vg.scene = {}; 4633 4634 vg.scene.GROUP = "group", 4635 vg.scene.ENTER = 0, 4636 vg.scene.UPDATE = 1, 4637 vg.scene.EXIT = 2; 4638 4639 vg.scene.DEFAULT_DATA = {"sentinel":1} 4640 4641 vg.scene.data = function(data, parentData) { 4642 var DEFAULT = vg.scene.DEFAULT_DATA; 4643 4644 // if data is undefined, inherit or use default 4645 data = vg.values(data || parentData || [DEFAULT]); 4646 4647 // if inheriting default data, ensure its in an array 4648 if (data === DEFAULT) data = [DEFAULT]; 4649 4650 return data; 4651 }; 4652 4653 vg.scene.fontString = function(o) { 4654 return (o.fontStyle ? o.fontStyle + " " : "") 4655 + (o.fontVariant ? o.fontVariant + " " : "") 4656 + (o.fontWeight ? o.fontWeight + " " : "") 4657 + (o.fontSize != null ? o.fontSize : vg.config.render.fontSize) + "px " 4658 + (o.font || vg.config.render.font); 4659 };vg.scene.Item = (function() { 4660 function item(mark) { 4661 this.mark = mark; 4662 } 4663 4664 var prototype = item.prototype; 4665 4666 prototype.hasPropertySet = function(name) { 4667 var props = this.mark.def.properties; 4668 return props && props[name] != null; 4669 }; 4670 4671 prototype.cousin = function(offset, index) { 4672 if (offset === 0) return this; 4673 offset = offset || -1; 4674 var mark = this.mark, 4675 group = mark.group, 4676 iidx = index==null ? mark.items.indexOf(this) : index, 4677 midx = group.items.indexOf(mark) + offset; 4678 return group.items[midx].items[iidx]; 4679 }; 4680 4681 prototype.sibling = function(offset) { 4682 if (offset === 0) return this; 4683 offset = offset || -1; 4684 var mark = this.mark, 4685 iidx = mark.items.indexOf(this) + offset; 4686 return mark.items[iidx]; 4687 }; 4688 4689 prototype.remove = function() { 4690 var item = this, 4691 list = item.mark.items, 4692 i = list.indexOf(item); 4693 if (i >= 0) (i===list.length-1) ? list.pop() : list.splice(i, 1); 4694 return item; 4695 }; 4696 4697 return item; 4698 })(); 4699 4700 vg.scene.item = function(mark) { 4701 return new vg.scene.Item(mark); 4702 };vg.scene.visit = function(node, func) { 4703 var i, n, items; 4704 if (func(node)) return true; 4705 if (items = node.items) { 4706 for (i=0, n=items.length; i<n; ++i) { 4707 if (vg.scene.visit(items[i], func)) return true; 4708 } 4709 } 4710 };vg.scene.build = (function() { 4711 var GROUP = vg.scene.GROUP, 4712 ENTER = vg.scene.ENTER, 4713 UPDATE = vg.scene.UPDATE, 4714 EXIT = vg.scene.EXIT, 4715 DEFAULT= {"sentinel":1}; 4716 4717 function build(def, db, node, parentData) { 4718 var data = vg.scene.data( 4719 def.from ? def.from(db, node, parentData) : null, 4720 parentData); 4721 4722 // build node and items 4723 node = buildNode(def, node); 4724 node.items = buildItems(def, data, node); 4725 buildTrans(def, node); 4726 4727 // recurse if group 4728 if (def.type === GROUP) { 4729 buildGroup(def, db, node); 4730 } 4731 4732 return node; 4733 }; 4734 4735 function buildNode(def, node) { 4736 node = node || {}; 4737 node.def = def; 4738 node.marktype = def.type; 4739 node.interactive = !(def.interactive === false); 4740 return node; 4741 } 4742 4743 function buildItems(def, data, node) { 4744 var keyf = keyFunction(def.key), 4745 prev = node.items || [], 4746 next = [], 4747 map = {}, 4748 i, key, len, item, datum, enter; 4749 4750 for (i=0, len=prev.length; i<len; ++i) { 4751 item = prev[i]; 4752 item.status = EXIT; 4753 if (keyf) map[item.key] = item; 4754 } 4755 4756 for (i=0, len=data.length; i<len; ++i) { 4757 datum = data[i]; 4758 key = i; 4759 item = keyf ? map[key = keyf(datum)] : prev[i]; 4760 enter = item ? false : (item = vg.scene.item(node), true); 4761 item.status = enter ? ENTER : UPDATE; 4762 item.datum = datum; 4763 item.key = key; 4764 next.push(item); 4765 } 4766 4767 for (i=0, len=prev.length; i<len; ++i) { 4768 item = prev[i]; 4769 if (item.status === EXIT) { 4770 item.key = keyf ? item.key : next.length; 4771 next.push(item); 4772 } 4773 } 4774 4775 return next; 4776 } 4777 4778 function buildGroup(def, db, node) { 4779 var groups = node.items, 4780 marks = def.marks, 4781 i, len, m, mlen, name, group; 4782 4783 for (i=0, len=groups.length; i<len; ++i) { 4784 group = groups[i]; 4785 4786 // update scales 4787 if (group.scales) for (name in group.scales) { 4788 if (name.indexOf(":prev") < 0) { 4789 group.scales[name+":prev"] = group.scales[name].copy(); 4790 } 4791 } 4792 4793 // build items 4794 group.items = group.items || []; 4795 for (m=0, mlen=marks.length; m<mlen; ++m) { 4796 group.items[m] = build(marks[m], db, group.items[m], group.datum); 4797 group.items[m].group = group; 4798 } 4799 } 4800 } 4801 4802 function buildTrans(def, node) { 4803 if (def.duration) node.duration = def.duration; 4804 if (def.ease) node.ease = d3.ease(def.ease) 4805 if (def.delay) { 4806 var items = node.items, group = node.group, n = items.length, i; 4807 for (i=0; i<n; ++i) def.delay.call(this, items[i], group); 4808 } 4809 } 4810 4811 function keyFunction(key) { 4812 if (key == null) return null; 4813 var f = vg.array(key).map(vg.accessor); 4814 return function(d) { 4815 for (var s="", i=0, n=f.length; i<n; ++i) { 4816 if (i>0) s += "|"; 4817 s += String(f[i](d)); 4818 } 4819 return s; 4820 } 4821 } 4822 4823 return build; 4824 })();vg.scene.bounds = (function() { 4825 4826 var parse = vg.canvas.path.parse, 4827 boundPath = vg.canvas.path.bounds, 4828 areaPath = vg.canvas.path.area, 4829 linePath = vg.canvas.path.line, 4830 halfpi = Math.PI / 2, 4831 sqrt3 = Math.sqrt(3), 4832 tan30 = Math.tan(30 * Math.PI / 180), 4833 gfx = null; 4834 4835 function context() { 4836 return gfx || (gfx = (vg.config.isNode 4837 ? new (require("canvas"))(1,1) 4838 : d3.select("body").append("canvas") 4839 .attr("class", "vega_hidden") 4840 .attr("width", 1) 4841 .attr("height", 1) 4842 .style("display", "none") 4843 .node()) 4844 .getContext("2d")); 4845 } 4846 4847 function pathBounds(o, path, bounds) { 4848 if (path == null) { 4849 bounds.set(0, 0, 0, 0); 4850 } else { 4851 boundPath(path, bounds); 4852 if (o.stroke && o.opacity !== 0 && o.strokeWidth > 0) { 4853 bounds.expand(o.strokeWidth); 4854 } 4855 } 4856 return bounds; 4857 } 4858 4859 function path(o, bounds) { 4860 var p = o.path 4861 ? o["path:parsed"] || (o["path:parsed"] = parse(o.path)) 4862 : null; 4863 return pathBounds(o, p, bounds); 4864 } 4865 4866 function area(o, bounds) { 4867 var items = o.mark.items, o = items[0]; 4868 var p = o["path:parsed"] || (o["path:parsed"]=parse(areaPath(items))); 4869 return pathBounds(items[0], p, bounds); 4870 } 4871 4872 function line(o, bounds) { 4873 var items = o.mark.items, o = items[0]; 4874 var p = o["path:parsed"] || (o["path:parsed"]=parse(linePath(items))); 4875 return pathBounds(items[0], p, bounds); 4876 } 4877 4878 function rect(o, bounds) { 4879 var x = o.x || 0, 4880 y = o.y || 0, 4881 w = (x + o.width) || 0, 4882 h = (y + o.height) || 0; 4883 bounds.set(x, y, w, h); 4884 if (o.stroke && o.opacity !== 0 && o.strokeWidth > 0) { 4885 bounds.expand(o.strokeWidth); 4886 } 4887 return bounds; 4888 } 4889 4890 function image(o, bounds) { 4891 var w = o.width || 0, 4892 h = o.height || 0, 4893 x = (o.x||0) - (o.align === "center" 4894 ? w/2 : (o.align === "right" ? w : 0)), 4895 y = (o.y||0) - (o.baseline === "middle" 4896 ? h/2 : (o.baseline === "bottom" ? h : 0)); 4897 return bounds.set(x, y, x+w, y+h); 4898 } 4899 4900 function rule(o, bounds) { 4901 var x1, y1; 4902 bounds.set( 4903 x1 = o.x || 0, 4904 y1 = o.y || 0, 4905 o.x2 != null ? o.x2 : x1, 4906 o.y2 != null ? o.y2 : y1 4907 ); 4908 if (o.stroke && o.opacity !== 0 && o.strokeWidth > 0) { 4909 bounds.expand(o.strokeWidth); 4910 } 4911 return bounds; 4912 } 4913 4914 function arc(o, bounds) { 4915 var cx = o.x || 0, 4916 cy = o.y || 0, 4917 ir = o.innerRadius || 0, 4918 or = o.outerRadius || 0, 4919 sa = (o.startAngle || 0) - halfpi, 4920 ea = (o.endAngle || 0) - halfpi, 4921 xmin = Infinity, xmax = -Infinity, 4922 ymin = Infinity, ymax = -Infinity, 4923 a, i, n, x, y, ix, iy, ox, oy; 4924 4925 var angles = [sa, ea], 4926 s = sa - (sa%halfpi); 4927 for (i=0; i<4 && s<ea; ++i, s+=halfpi) { 4928 angles.push(s); 4929 } 4930 4931 for (i=0, n=angles.length; i<n; ++i) { 4932 a = angles[i]; 4933 x = Math.cos(a); ix = ir*x; ox = or*x; 4934 y = Math.sin(a); iy = ir*y; oy = or*y; 4935 xmin = Math.min(xmin, ix, ox); 4936 xmax = Math.max(xmax, ix, ox); 4937 ymin = Math.min(ymin, iy, oy); 4938 ymax = Math.max(ymax, iy, oy); 4939 } 4940 4941 bounds.set(cx+xmin, cy+ymin, cx+xmax, cy+ymax); 4942 if (o.stroke && o.opacity !== 0 && o.strokeWidth > 0) { 4943 bounds.expand(o.strokeWidth); 4944 } 4945 return bounds; 4946 } 4947 4948 function symbol(o, bounds) { 4949 var size = o.size != null ? o.size : 100, 4950 x = o.x || 0, 4951 y = o.y || 0, 4952 r, t, rx, ry; 4953 4954 switch (o.shape) { 4955 case "cross": 4956 r = Math.sqrt(size / 5) / 2; 4957 t = 3*r; 4958 bounds.set(x-t, y-t, x+y, y+t); 4959 break; 4960 4961 case "diamond": 4962 ry = Math.sqrt(size / (2 * tan30)); 4963 rx = ry * tan30; 4964 bounds.set(x-rx, y-ry, x+rx, y+ry); 4965 break; 4966 4967 case "square": 4968 t = Math.sqrt(size); 4969 r = t / 2; 4970 bounds.set(x-r, y-r, x+r, y+r); 4971 break; 4972 4973 case "triangle-down": 4974 rx = Math.sqrt(size / sqrt3); 4975 ry = rx * sqrt3 / 2; 4976 bounds.set(x-rx, y-ry, x+rx, y+ry); 4977 break; 4978 4979 case "triangle-up": 4980 rx = Math.sqrt(size / sqrt3); 4981 ry = rx * sqrt3 / 2; 4982 bounds.set(x-rx, y-ry, x+rx, y+ry); 4983 break; 4984 4985 default: 4986 r = Math.sqrt(size/Math.PI); 4987 bounds.set(x-r, y-r, x+r, y+r); 4988 } 4989 if (o.stroke && o.opacity !== 0 && o.strokeWidth > 0) { 4990 bounds.expand(o.strokeWidth); 4991 } 4992 return bounds; 4993 } 4994 4995 function text(o, bounds, noRotate) { 4996 var x = (o.x || 0) + (o.dx || 0), 4997 y = (o.y || 0) + (o.dy || 0), 4998 h = o.fontSize || vg.config.render.fontSize, 4999 a = o.align, 5000 b = o.baseline, 5001 g = context(), w; 5002 5003 g.font = vg.scene.fontString(o); 5004 g.textAlign = a || "left"; 5005 g.textBaseline = b || "alphabetic"; 5006 w = g.measureText(o.text || "").width; 5007 5008 // horizontal 5009 if (a === "center") { 5010 x = x - (w / 2); 5011 } else if (a === "right") { 5012 x = x - w; 5013 } else { 5014 // left by default, do nothing 5015 } 5016 5017 /// TODO find a robust solution for heights. 5018 /// These offsets work for some but not all fonts. 5019 5020 // vertical 5021 if (b === "top") { 5022 y = y + (h/5); 5023 } else if (b === "bottom") { 5024 y = y - h; 5025 } else if (b === "middle") { 5026 y = y - (h/2) + (h/10); 5027 } else { 5028 y = y - 4*h/5; // alphabetic by default 5029 } 5030 5031 bounds.set(x, y, x+w, y+h); 5032 if (o.angle && !noRotate) { 5033 bounds.rotate(o.angle*Math.PI/180, o.x||0, o.y||0); 5034 } 5035 return bounds.expand(noRotate ? 0 : 1); 5036 } 5037 5038 function group(g, bounds, includeLegends) { 5039 var axes = g.axisItems || [], 5040 legends = g.legendItems || [], j, m; 5041 5042 for (j=0, m=axes.length; j<m; ++j) { 5043 bounds.union(axes[j].bounds); 5044 } 5045 for (j=0, m=g.items.length; j<m; ++j) { 5046 bounds.union(g.items[j].bounds); 5047 } 5048 if (includeLegends) { 5049 for (j=0, m=legends.length; j<m; ++j) { 5050 bounds.union(legends[j].bounds); 5051 } 5052 if (g.width != null && g.height != null) { 5053 bounds.add(g.width, g.height); 5054 } 5055 if (g.x != null && g.y != null) { 5056 bounds.add(0, 0); 5057 } 5058 } 5059 bounds.translate(g.x||0, g.y||0); 5060 return bounds; 5061 } 5062 5063 var methods = { 5064 group: group, 5065 symbol: symbol, 5066 image: image, 5067 rect: rect, 5068 rule: rule, 5069 arc: arc, 5070 text: text, 5071 path: path, 5072 area: area, 5073 line: line 5074 }; 5075 5076 function itemBounds(item, func, opt) { 5077 func = func || methods[item.mark.marktype]; 5078 if (!item.bounds_prev) item['bounds:prev'] = new vg.Bounds(); 5079 var b = item.bounds, pb = item['bounds:prev']; 5080 if (b) pb.clear().union(b); 5081 item.bounds = func(item, b ? b.clear() : new vg.Bounds(), opt); 5082 if (!b) pb.clear().union(item.bounds); 5083 return item.bounds; 5084 } 5085 5086 function markBounds(mark, bounds, opt) { 5087 bounds = bounds || mark.bounds && mark.bounds.clear() || new vg.Bounds(); 5088 var type = mark.marktype, 5089 func = methods[type], 5090 items = mark.items, 5091 item, i, len; 5092 5093 if (type==="area" || type==="line") { 5094 items[0].bounds = func(items[0], bounds); 5095 } else { 5096 for (i=0, len=items.length; i<len; ++i) { 5097 bounds.union(itemBounds(items[i], func, opt)); 5098 } 5099 } 5100 mark.bounds = bounds; 5101 } 5102 5103 return { 5104 mark: markBounds, 5105 item: itemBounds, 5106 text: text, 5107 group: group 5108 }; 5109 5110 })();vg.scene.encode = (function() { 5111 var GROUP = vg.scene.GROUP, 5112 ENTER = vg.scene.ENTER, 5113 UPDATE = vg.scene.UPDATE, 5114 EXIT = vg.scene.EXIT, 5115 EMPTY = {}; 5116 5117 function main(scene, def, trans, request, items) { 5118 (request && items) 5119 ? update.call(this, scene, def, trans, request, items) 5120 : encode.call(this, scene, scene, def, trans, request); 5121 return scene; 5122 } 5123 5124 function update(scene, def, trans, request, items) { 5125 items = vg.array(items); 5126 var i, len, item, group, props, prop; 5127 for (i=0, len=items.length; i<len; ++i) { 5128 item = items[i]; 5129 group = item.mark.group || null; 5130 props = item.mark.def.properties; 5131 prop = props && props[request]; 5132 if (prop) { 5133 prop.call(vg, item, group, trans); 5134 vg.scene.bounds.item(item); 5135 } 5136 } 5137 } 5138 5139 function encode(group, scene, def, trans, request) { 5140 encodeItems.call(this, group, scene.items, def, trans, request); 5141 if (scene.marktype === GROUP) { 5142 encodeGroup.call(this, scene, def, group, trans, request); 5143 } else { 5144 vg.scene.bounds.mark(scene); 5145 } 5146 } 5147 5148 function encodeLegend(group, scene, def, trans, request) { 5149 encodeGroup.call(this, scene, def, group, trans, request); 5150 encodeItems.call(this, group, scene.items, def, trans, request); 5151 vg.scene.bounds.mark(scene, null, true); 5152 } 5153 5154 function encodeGroup(scene, def, parent, trans, request) { 5155 var i, len, m, mlen, group, scales, 5156 axes, axisItems, axisDef, leg, legItems, legDef; 5157 5158 for (i=0, len=scene.items.length; i<len; ++i) { 5159 group = scene.items[i]; 5160 5161 // cascade scales recursively 5162 // use parent scales if there are no group-level scale defs 5163 scales = group.scales || (group.scales = 5164 def.scales ? vg.extend({}, parent.scales) : parent.scales); 5165 5166 // update group-level scales 5167 if (def.scales) { 5168 vg.parse.scales(def.scales, scales, this._data, group); 5169 } 5170 5171 // update group-level axes 5172 if (def.axes) { 5173 axes = group.axes || (group.axes = []); 5174 axisItems = group.axisItems || (group.axisItems = []); 5175 vg.parse.axes(def.axes, axes, group.scales); 5176 axes.forEach(function(a, i) { 5177 axisDef = a.def(); 5178 axisItems[i] = vg.scene.build(axisDef, this._data, axisItems[i]); 5179 axisItems[i].group = group; 5180 encode.call(this, group, group.axisItems[i], axisDef, trans); 5181 }); 5182 } 5183 5184 // encode children marks 5185 for (m=0, mlen=group.items.length; m<mlen; ++m) { 5186 encode.call(this, group, group.items[m], def.marks[m], trans, request); 5187 } 5188 } 5189 5190 // compute bounds (without legend) 5191 vg.scene.bounds.mark(scene, null, !def.legends); 5192 5193 // update legends 5194 if (def.legends) { 5195 for (i=0, len=scene.items.length; i<len; ++i) { 5196 group = scene.items[i]; 5197 leg = group.legends || (group.legends = []); 5198 legItems = group.legendItems || (group.legendItems = []); 5199 vg.parse.legends(def.legends, leg, group.scales); 5200 leg.forEach(function(l, i) { 5201 legDef = l.def(); 5202 legItems[i] = vg.scene.build(legDef, this._data, legItems[i]); 5203 legItems[i].group = group; 5204 encodeLegend.call(this, group, group.legendItems[i], legDef, trans); 5205 }); 5206 } 5207 vg.scene.bounds.mark(scene, null, true); 5208 } 5209 } 5210 5211 function encodeItems(group, items, def, trans, request) { 5212 var props = def.properties || EMPTY, 5213 enter = props.enter, 5214 update = props.update, 5215 exit = props.exit, 5216 i, len, item, prop; 5217 5218 if (request) { 5219 if (prop = props[request]) { 5220 for (i=0, len=items.length; i<len; ++i) { 5221 prop.call(vg, items[i], group, trans); 5222 } 5223 } 5224 return; // exit early if given request 5225 } 5226 5227 for (i=0; i<items.length; ++i) { 5228 item = items[i]; 5229 5230 // enter set 5231 if (item.status === ENTER) { 5232 if (enter) enter.call(vg, item, group); 5233 item.status = UPDATE; 5234 } 5235 5236 // update set 5237 if (item.status !== EXIT && update) { 5238 update.call(vg, item, group, trans); 5239 } 5240 5241 // exit set 5242 if (item.status === EXIT) { 5243 if (exit) exit.call(vg, item, group, trans); 5244 if (trans && !exit) trans.interpolate(item, EMPTY); 5245 else if (!trans) items[i--].remove(); 5246 } 5247 } 5248 } 5249 5250 return main; 5251 })();vg.scene.Transition = (function() { 5252 function trans(duration, ease) { 5253 this.duration = duration || 500; 5254 this.ease = ease && d3.ease(ease) || d3.ease("cubic-in-out"); 5255 this.updates = {next: null}; 5256 } 5257 5258 var prototype = trans.prototype; 5259 5260 prototype.interpolate = function(item, values) { 5261 var key, curr, next, interp, list = null; 5262 5263 for (key in values) { 5264 curr = item[key]; 5265 next = values[key]; 5266 if (curr !== next) { 5267 if (key === "text") { 5268 // skip interpolation for text labels 5269 item[key] = next; 5270 } else { 5271 // otherwise lookup interpolator 5272 interp = d3.interpolate(curr, next); 5273 interp.property = key; 5274 (list || (list=[])).push(interp); 5275 } 5276 } 5277 } 5278 5279 if (list === null && item.status === vg.scene.EXIT) { 5280 list = []; // ensure exiting items are included 5281 } 5282 5283 if (list != null) { 5284 list.item = item; 5285 list.ease = item.mark.ease || this.ease; 5286 list.next = this.updates.next; 5287 this.updates.next = list; 5288 } 5289 return this; 5290 }; 5291 5292 prototype.start = function(callback) { 5293 var t = this, prev = t.updates, curr = prev.next; 5294 for (; curr!=null; prev=curr, curr=prev.next) { 5295 if (curr.item.status === vg.scene.EXIT) curr.remove = true; 5296 } 5297 t.callback = callback; 5298 d3.timer(function(elapsed) { return step.call(t, elapsed); }); 5299 }; 5300 5301 function step(elapsed) { 5302 var list = this.updates, prev = list, curr = prev.next, 5303 duration = this.duration, 5304 item, delay, f, e, i, n, stop = true; 5305 5306 for (; curr!=null; prev=curr, curr=prev.next) { 5307 item = curr.item; 5308 delay = item.delay || 0; 5309 5310 f = (elapsed - delay) / duration; 5311 if (f < 0) { stop = false; continue; } 5312 if (f > 1) f = 1; 5313 e = curr.ease(f); 5314 5315 for (i=0, n=curr.length; i<n; ++i) { 5316 item[curr[i].property] = curr[i](e); 5317 vg.scene.bounds.item(item); 5318 } 5319 5320 if (f === 1) { 5321 if (curr.remove) item.remove(); 5322 prev.next = curr.next; 5323 curr = prev; 5324 } else { 5325 stop = false; 5326 } 5327 } 5328 5329 this.callback(); 5330 return stop; 5331 }; 5332 5333 return trans; 5334 5335 })(); 5336 5337 vg.scene.transition = function(dur, ease) { 5338 return new vg.scene.Transition(dur, ease); 5339 };vg.scene.axis = function() { 5340 var scale, 5341 orient = vg.config.axis.orient, 5342 offset = 0, 5343 titleOffset = vg.config.axis.titleOffset, 5344 axisDef = null, 5345 layer = "front", 5346 grid = false, 5347 title = null, 5348 tickMajorSize = vg.config.axis.tickSize, 5349 tickMinorSize = vg.config.axis.tickSize, 5350 tickEndSize = vg.config.axis.tickSize, 5351 tickPadding = vg.config.axis.padding, 5352 tickValues = null, 5353 tickFormat = null, 5354 tickSubdivide = 0, 5355 tickArguments = [vg.config.axis.ticks], 5356 gridLineStyle = {}, 5357 tickLabelStyle = {}, 5358 majorTickStyle = {}, 5359 minorTickStyle = {}, 5360 titleStyle = {}, 5361 domainStyle = {}; 5362 5363 var axis = {}; 5364 5365 function reset() { axisDef = null; } 5366 5367 axis.def = function() { 5368 var def = axisDef ? axisDef : (axisDef = axis_def(scale)); 5369 5370 // generate data 5371 var major = tickValues == null 5372 ? (scale.ticks ? scale.ticks.apply(scale, tickArguments) : scale.domain()) 5373 : tickValues; 5374 var minor = vg_axisSubdivide(scale, major, tickSubdivide).map(vg.data.ingest); 5375 major = major.map(vg.data.ingest); 5376 var fmt = tickFormat==null ? (scale.tickFormat ? scale.tickFormat.apply(scale, tickArguments) : String) : tickFormat; 5377 major.forEach(function(d) { d.label = fmt(d.data); }); 5378 var tdata = title ? [title].map(vg.data.ingest) : []; 5379 5380 // update axis def 5381 def.marks[0].from = function() { return grid ? major : []; }; 5382 def.marks[1].from = function() { return major; }; 5383 def.marks[2].from = function() { return minor; }; 5384 def.marks[3].from = def.marks[1].from; 5385 def.marks[4].from = function() { return [1]; }; 5386 def.marks[5].from = function() { return tdata; }; 5387 def.offset = offset; 5388 def.orient = orient; 5389 def.layer = layer; 5390 return def; 5391 }; 5392 5393 function axis_def(scale) { 5394 // setup scale mapping 5395 var newScale, oldScale, range; 5396 if (scale.type === "ordinal") { 5397 newScale = {scale: scale.scaleName, offset: 0.5 + scale.rangeBand()/2}; 5398 oldScale = newScale; 5399 } else { 5400 newScale = {scale: scale.scaleName, offset: 0.5}; 5401 oldScale = {scale: scale.scaleName+":prev", offset: 0.5}; 5402 } 5403 range = vg_axisScaleRange(scale); 5404 5405 // setup axis marks 5406 var gridLines = vg_axisTicks(); 5407 var majorTicks = vg_axisTicks(); 5408 var minorTicks = vg_axisTicks(); 5409 var tickLabels = vg_axisTickLabels(); 5410 var domain = vg_axisDomain(); 5411 var title = vg_axisTitle(); 5412 gridLines.properties.enter.stroke = {value: vg.config.axis.gridColor}; 5413 5414 // extend axis marks based on axis orientation 5415 vg_axisTicksExtend(orient, gridLines, oldScale, newScale, Infinity); 5416 vg_axisTicksExtend(orient, majorTicks, oldScale, newScale, tickMajorSize); 5417 vg_axisTicksExtend(orient, minorTicks, oldScale, newScale, tickMinorSize); 5418 vg_axisLabelExtend(orient, tickLabels, oldScale, newScale, tickMajorSize, tickPadding); 5419 5420 vg_axisDomainExtend(orient, domain, range, tickEndSize); 5421 vg_axisTitleExtend(orient, title, range, titleOffset); // TODO get offset 5422 5423 // add / override custom style properties 5424 vg.extend(gridLines.properties.update, gridLineStyle); 5425 vg.extend(majorTicks.properties.update, majorTickStyle); 5426 vg.extend(minorTicks.properties.update, minorTickStyle); 5427 vg.extend(tickLabels.properties.update, tickLabelStyle); 5428 vg.extend(domain.properties.update, domainStyle); 5429 vg.extend(title.properties.update, titleStyle); 5430 5431 var marks = [gridLines, majorTicks, minorTicks, tickLabels, domain, title]; 5432 return { 5433 type: "group", 5434 interactive: false, 5435 properties: { enter: vg_axisUpdate, update: vg_axisUpdate }, 5436 marks: marks.map(vg.parse.mark) 5437 }; 5438 } 5439 5440 axis.scale = function(x) { 5441 if (!arguments.length) return scale; 5442 if (scale !== x) { scale = x; reset(); } 5443 return axis; 5444 }; 5445 5446 axis.orient = function(x) { 5447 if (!arguments.length) return orient; 5448 if (orient !== x) { 5449 orient = x in vg_axisOrients ? x + "" : vg.config.axis.orient; 5450 reset(); 5451 } 5452 return axis; 5453 }; 5454 5455 axis.title = function(x) { 5456 if (!arguments.length) return title; 5457 if (title !== x) { title = x; reset(); } 5458 return axis; 5459 }; 5460 5461 axis.ticks = function() { 5462 if (!arguments.length) return tickArguments; 5463 tickArguments = arguments; 5464 return axis; 5465 }; 5466 5467 axis.tickValues = function(x) { 5468 if (!arguments.length) return tickValues; 5469 tickValues = x; 5470 return axis; 5471 }; 5472 5473 axis.tickFormat = function(x) { 5474 if (!arguments.length) return tickFormat; 5475 tickFormat = x; 5476 return axis; 5477 }; 5478 5479 axis.tickSize = function(x, y) { 5480 if (!arguments.length) return tickMajorSize; 5481 var n = arguments.length - 1, 5482 major = +x, 5483 minor = n > 1 ? +y : tickMajorSize, 5484 end = n > 0 ? +arguments[n] : tickMajorSize; 5485 5486 if (tickMajorSize !== major || 5487 tickMinorSize !== minor || 5488 tickEndSize !== end) { 5489 reset(); 5490 } 5491 5492 tickMajorSize = major; 5493 tickMinorSize = minor; 5494 tickEndSize = end; 5495 return axis; 5496 }; 5497 5498 axis.tickSubdivide = function(x) { 5499 if (!arguments.length) return tickSubdivide; 5500 tickSubdivide = +x; 5501 return axis; 5502 }; 5503 5504 axis.offset = function(x) { 5505 if (!arguments.length) return offset; 5506 offset = vg.isObject(x) ? x : +x; 5507 return axis; 5508 }; 5509 5510 axis.tickPadding = function(x) { 5511 if (!arguments.length) return tickPadding; 5512 if (tickPadding !== +x) { tickPadding = +x; reset(); } 5513 return axis; 5514 }; 5515 5516 axis.titleOffset = function(x) { 5517 if (!arguments.length) return titleOffset; 5518 if (titleOffset !== +x) { titleOffset = +x; reset(); } 5519 return axis; 5520 }; 5521 5522 axis.layer = function(x) { 5523 if (!arguments.length) return layer; 5524 if (layer !== x) { layer = x; reset(); } 5525 return axis; 5526 }; 5527 5528 axis.grid = function(x) { 5529 if (!arguments.length) return grid; 5530 if (grid !== x) { grid = x; reset(); } 5531 return axis; 5532 }; 5533 5534 axis.gridLineProperties = function(x) { 5535 if (!arguments.length) return gridLineStyle; 5536 if (gridLineStyle !== x) { gridLineStyle = x; } 5537 return axis; 5538 }; 5539 5540 axis.majorTickProperties = function(x) { 5541 if (!arguments.length) return majorTickStyle; 5542 if (majorTickStyle !== x) { majorTickStyle = x; } 5543 return axis; 5544 }; 5545 5546 axis.minorTickProperties = function(x) { 5547 if (!arguments.length) return minorTickStyle; 5548 if (minorTickStyle !== x) { minorTickStyle = x; } 5549 return axis; 5550 }; 5551 5552 axis.tickLabelProperties = function(x) { 5553 if (!arguments.length) return tickLabelStyle; 5554 if (tickLabelStyle !== x) { tickLabelStyle = x; } 5555 return axis; 5556 }; 5557 5558 axis.titleProperties = function(x) { 5559 if (!arguments.length) return titleStyle; 5560 if (titleStyle !== x) { titleStyle = x; } 5561 return axis; 5562 }; 5563 5564 axis.domainProperties = function(x) { 5565 if (!arguments.length) return domainStyle; 5566 if (domainStyle !== x) { domainStyle = x; } 5567 return axis; 5568 }; 5569 5570 axis.reset = function() { reset(); }; 5571 5572 return axis; 5573 }; 5574 5575 var vg_axisOrients = {top: 1, right: 1, bottom: 1, left: 1}; 5576 5577 function vg_axisSubdivide(scale, ticks, m) { 5578 subticks = []; 5579 if (m && ticks.length > 1) { 5580 var extent = vg_axisScaleExtent(scale.domain()), 5581 subticks, 5582 i = -1, 5583 n = ticks.length, 5584 d = (ticks[1] - ticks[0]) / ++m, 5585 j, 5586 v; 5587 while (++i < n) { 5588 for (j = m; --j > 0;) { 5589 if ((v = +ticks[i] - j * d) >= extent[0]) { 5590 subticks.push(v); 5591 } 5592 } 5593 } 5594 for (--i, j = 0; ++j < m && (v = +ticks[i] + j * d) < extent[1];) { 5595 subticks.push(v); 5596 } 5597 } 5598 return subticks; 5599 } 5600 5601 function vg_axisScaleExtent(domain) { 5602 var start = domain[0], stop = domain[domain.length - 1]; 5603 return start < stop ? [start, stop] : [stop, start]; 5604 } 5605 5606 function vg_axisScaleRange(scale) { 5607 return scale.rangeExtent 5608 ? scale.rangeExtent() 5609 : vg_axisScaleExtent(scale.range()); 5610 } 5611 5612 var vg_axisAlign = { 5613 bottom: "center", 5614 top: "center", 5615 left: "right", 5616 right: "left" 5617 }; 5618 5619 var vg_axisBaseline = { 5620 bottom: "top", 5621 top: "bottom", 5622 left: "middle", 5623 right: "middle" 5624 }; 5625 5626 function vg_axisLabelExtend(orient, labels, oldScale, newScale, size, pad) { 5627 size = Math.max(size, 0) + pad; 5628 if (orient === "left" || orient === "top") { 5629 size *= -1; 5630 } 5631 if (orient === "top" || orient === "bottom") { 5632 vg.extend(labels.properties.enter, { 5633 x: oldScale, 5634 y: {value: size}, 5635 }); 5636 vg.extend(labels.properties.update, { 5637 x: newScale, 5638 y: {value: size}, 5639 align: {value: "center"}, 5640 baseline: {value: vg_axisBaseline[orient]} 5641 }); 5642 } else { 5643 vg.extend(labels.properties.enter, { 5644 x: {value: size}, 5645 y: oldScale, 5646 }); 5647 vg.extend(labels.properties.update, { 5648 x: {value: size}, 5649 y: newScale, 5650 align: {value: vg_axisAlign[orient]}, 5651 baseline: {value: "middle"} 5652 }); 5653 } 5654 } 5655 5656 function vg_axisTicksExtend(orient, ticks, oldScale, newScale, size) { 5657 var sign = (orient === "left" || orient === "top") ? -1 : 1; 5658 if (size === Infinity) { 5659 size = (orient === "top" || orient === "bottom") 5660 ? {group: "mark.group.height", mult: -sign} 5661 : {group: "mark.group.width", mult: -sign}; 5662 } else { 5663 size = {value: sign * size}; 5664 } 5665 if (orient === "top" || orient === "bottom") { 5666 vg.extend(ticks.properties.enter, { 5667 x: oldScale, 5668 y: {value: 0}, 5669 y2: size 5670 }); 5671 vg.extend(ticks.properties.update, { 5672 x: newScale, 5673 y: {value: 0}, 5674 y2: size 5675 }); 5676 vg.extend(ticks.properties.exit, { 5677 x: newScale, 5678 }); 5679 } else { 5680 vg.extend(ticks.properties.enter, { 5681 x: {value: 0}, 5682 x2: size, 5683 y: oldScale 5684 }); 5685 vg.extend(ticks.properties.update, { 5686 x: {value: 0}, 5687 x2: size, 5688 y: newScale 5689 }); 5690 vg.extend(ticks.properties.exit, { 5691 y: newScale, 5692 }); 5693 } 5694 } 5695 5696 function vg_axisTitleExtend(orient, title, range, offset) { 5697 var mid = ~~((range[1] - range[0]) / 2), 5698 sign = (orient === "top" || orient === "left") ? -1 : 1; 5699 5700 if (orient === "bottom" || orient === "top") { 5701 vg.extend(title.properties.update, { 5702 x: {value: mid}, 5703 y: {value: sign*offset}, 5704 angle: {value: 0} 5705 }); 5706 } else { 5707 vg.extend(title.properties.update, { 5708 x: {value: sign*offset}, 5709 y: {value: mid}, 5710 angle: {value: -90} 5711 }); 5712 } 5713 } 5714 5715 function vg_axisDomainExtend(orient, domain, range, size) { 5716 var path; 5717 if (orient === "top" || orient === "left") { 5718 size = -1 * size; 5719 } 5720 if (orient === "bottom" || orient === "top") { 5721 path = "M" + range[0] + "," + size + "V0H" + range[1] + "V" + size; 5722 } else { 5723 path = "M" + size + "," + range[0] + "H0V" + range[1] + "H" + size; 5724 } 5725 domain.properties.update.path = {value: path}; 5726 } 5727 5728 function vg_axisUpdate(item, group, trans) { 5729 var o = trans ? {} : item, 5730 offset = item.mark.def.offset, 5731 orient = item.mark.def.orient, 5732 width = group.width, 5733 height = group.height; // TODO fallback to global w,h? 5734 5735 if (vg.isObject(offset)) { 5736 offset = -group.scales[offset.scale](offset.value); 5737 } 5738 5739 switch (orient) { 5740 case "left": { o.x = -offset; o.y = 0; break; } 5741 case "right": { o.x = width + offset; o.y = 0; break; } 5742 case "bottom": { o.x = 0; o.y = height + offset; break; } 5743 case "top": { o.x = 0; o.y = -offset; break; } 5744 default: { o.x = 0; o.y = 0; } 5745 } 5746 5747 if (trans) trans.interpolate(item, o); 5748 } 5749 5750 function vg_axisTicks() { 5751 return { 5752 type: "rule", 5753 interactive: false, 5754 key: "data", 5755 properties: { 5756 enter: { 5757 stroke: {value: vg.config.axis.tickColor}, 5758 strokeWidth: {value: vg.config.axis.tickWidth}, 5759 opacity: {value: 1e-6} 5760 }, 5761 exit: { opacity: {value: 1e-6} }, 5762 update: { opacity: {value: 1} } 5763 } 5764 }; 5765 } 5766 5767 function vg_axisTickLabels() { 5768 return { 5769 type: "text", 5770 interactive: true, 5771 key: "data", 5772 properties: { 5773 enter: { 5774 fill: {value: vg.config.axis.tickLabelColor}, 5775 font: {value: vg.config.axis.tickLabelFont}, 5776 fontSize: {value: vg.config.axis.tickLabelFontSize}, 5777 opacity: {value: 1e-6}, 5778 text: {field: "label"} 5779 }, 5780 exit: { opacity: {value: 1e-6} }, 5781 update: { opacity: {value: 1} } 5782 } 5783 }; 5784 } 5785 5786 function vg_axisTitle() { 5787 return { 5788 type: "text", 5789 interactive: true, 5790 properties: { 5791 enter: { 5792 font: {value: vg.config.axis.titleFont}, 5793 fontSize: {value: vg.config.axis.titleFontSize}, 5794 fontWeight: {value: vg.config.axis.titleFontWeight}, 5795 fill: {value: vg.config.axis.titleColor}, 5796 align: {value: "center"}, 5797 baseline: {value: "middle"}, 5798 text: {field: "data"} 5799 }, 5800 update: {} 5801 } 5802 }; 5803 } 5804 5805 function vg_axisDomain() { 5806 return { 5807 type: "path", 5808 interactive: false, 5809 properties: { 5810 enter: { 5811 x: {value: 0.5}, 5812 y: {value: 0.5}, 5813 stroke: {value: vg.config.axis.axisColor}, 5814 strokeWidth: {value: vg.config.axis.axisWidth} 5815 }, 5816 update: {} 5817 } 5818 }; 5819 } 5820 vg.scene.legend = function() { 5821 var size = null, 5822 shape = null, 5823 fill = null, 5824 stroke = null, 5825 spacing = null, 5826 values = null, 5827 format = null, 5828 title = undefined, 5829 orient = "right", 5830 offset = vg.config.legend.offset, 5831 padding = vg.config.legend.padding, 5832 legendDef, 5833 tickArguments = [5], 5834 legendStyle = {}, 5835 symbolStyle = {}, 5836 gradientStyle = {}, 5837 titleStyle = {}, 5838 labelStyle = {}; 5839 5840 var legend = {}, 5841 legendDef = null; 5842 5843 function reset() { legendDef = null; } 5844 5845 legend.def = function() { 5846 var scale = size || shape || fill || stroke; 5847 if (!legendDef) { 5848 legendDef = (scale===fill || scale===stroke) && !discrete(scale.type) 5849 ? quantDef(scale) 5850 : ordinalDef(scale); 5851 } 5852 legendDef.orient = orient; 5853 legendDef.offset = offset; 5854 legendDef.padding = padding; 5855 return legendDef; 5856 }; 5857 5858 function discrete(type) { 5859 return type==="ordinal" || type==="quantize" 5860 || type==="quantile" || type==="threshold"; 5861 } 5862 5863 function ordinalDef(scale) { 5864 var def = o_legend_def(size, shape, fill, stroke); 5865 5866 // generate data 5867 var data = (values == null 5868 ? (scale.ticks ? scale.ticks.apply(scale, tickArguments) : scale.domain()) 5869 : values).map(vg.data.ingest); 5870 var fmt = format==null ? (scale.tickFormat ? scale.tickFormat.apply(scale, tickArguments) : String) : format; 5871 5872 // determine spacing between legend entries 5873 var fs, range, offset, pad=5, domain = d3.range(data.length); 5874 if (size) { 5875 range = data.map(function(x) { return Math.sqrt(size(x.data)); }); 5876 offset = d3.max(range); 5877 range = range.reduce(function(a,b,i,z) { 5878 if (i > 0) a[i] = a[i-1] + z[i-1]/2 + pad; 5879 return (a[i] += b/2, a); }, [0]).map(Math.round); 5880 } else { 5881 offset = Math.round(Math.sqrt(vg.config.legend.symbolSize)); 5882 range = spacing 5883 || (fs = labelStyle.fontSize) && (fs.value + pad) 5884 || (vg.config.legend.labelFontSize + pad); 5885 range = domain.map(function(d,i) { 5886 return Math.round(offset/2 + i*range); 5887 }); 5888 } 5889 5890 // account for padding and title size 5891 var sz = padding, ts; 5892 if (title) { 5893 ts = titleStyle.fontSize; 5894 sz += 5 + ((ts && ts.value) || vg.config.legend.titleFontSize); 5895 } 5896 for (var i=0, n=range.length; i<n; ++i) range[i] += sz; 5897 5898 // build scale for label layout 5899 var scale = { 5900 name: "legend", 5901 type: "ordinal", 5902 points: true, 5903 domain: domain, 5904 range: range 5905 }; 5906 5907 // update legend def 5908 var tdata = (title ? [title] : []).map(vg.data.ingest); 5909 data.forEach(function(d) { 5910 d.label = fmt(d.data); 5911 d.offset = offset; 5912 }); 5913 def.scales = [ scale ]; 5914 def.marks[0].from = function() { return tdata; }; 5915 def.marks[1].from = function() { return data; }; 5916 def.marks[2].from = def.marks[1].from; 5917 return def; 5918 } 5919 5920 function o_legend_def(size, shape, fill, stroke) { 5921 // setup legend marks 5922 var titles = vg_legendTitle(), 5923 symbols = vg_legendSymbols(), 5924 labels = vg_vLegendLabels(); 5925 5926 // extend legend marks 5927 vg_legendSymbolExtend(symbols, size, shape, fill, stroke); 5928 5929 // add / override custom style properties 5930 vg.extend(titles.properties.update, titleStyle); 5931 vg.extend(symbols.properties.update, symbolStyle); 5932 vg.extend(labels.properties.update, labelStyle); 5933 5934 // padding from legend border 5935 titles.properties.enter.x.value += padding; 5936 titles.properties.enter.y.value += padding; 5937 labels.properties.enter.x.offset += padding + 1; 5938 symbols.properties.enter.x.offset = padding + 1; 5939 5940 return { 5941 type: "group", 5942 interactive: false, 5943 properties: { 5944 enter: vg.parse.properties("group", legendStyle), 5945 update: vg_legendUpdate 5946 }, 5947 marks: [titles, symbols, labels].map(vg.parse.mark) 5948 }; 5949 } 5950 5951 function quantDef(scale) { 5952 var def = q_legend_def(scale), 5953 dom = scale.domain(), 5954 data = dom.map(vg.data.ingest), 5955 width = (gradientStyle.width && gradientStyle.width.value) || vg.config.legend.gradientWidth, 5956 fmt = format==null ? (scale.tickFormat ? scale.tickFormat.apply(scale, tickArguments) : String) : format; 5957 5958 // build scale for label layout 5959 var layout = { 5960 name: "legend", 5961 type: scale.type, 5962 round: true, 5963 zero: false, 5964 domain: [dom[0], dom[dom.length-1]], 5965 range: [padding, width+padding] 5966 }; 5967 if (scale.type==="pow") layout.exponent = scale.exponent(); 5968 5969 // update legend def 5970 var tdata = (title ? [title] : []).map(vg.data.ingest); 5971 data.forEach(function(d,i) { 5972 d.label = fmt(d.data); 5973 d.align = i==(data.length-1) ? "right" : i==0 ? "left" : "center"; 5974 }); 5975 def.scales = [ layout ]; 5976 def.marks[0].from = function() { return tdata; }; 5977 def.marks[1].from = function() { return [1]; }; 5978 def.marks[2].from = function() { return data; }; 5979 return def; 5980 } 5981 5982 function q_legend_def(scale) { 5983 // setup legend marks 5984 var titles = vg_legendTitle(), 5985 gradient = vg_legendGradient(), 5986 labels = vg_hLegendLabels(), 5987 grad = new vg.Gradient(); 5988 5989 // setup color gradient 5990 var dom = scale.domain(), 5991 min = dom[0], 5992 max = dom[dom.length-1], 5993 f = scale.copy().domain([min, max]).range([0,1]); 5994 5995 var stops = (scale.type !== "linear" && scale.ticks) 5996 ? scale.ticks.call(scale, 15) : dom; 5997 if (min !== stops[0]) stops.unshift(min); 5998 if (max !== stops[stops.length-1]) stops.push(max); 5999 6000 for (var i=0, n=stops.length; i<n; ++i) { 6001 grad.stop(f(stops[i]), scale(stops[i])); 6002 } 6003 gradient.properties.enter.fill = {value: grad}; 6004 6005 // add / override custom style properties 6006 vg.extend(titles.properties.update, titleStyle); 6007 vg.extend(gradient.properties.update, gradientStyle); 6008 vg.extend(labels.properties.update, labelStyle); 6009 6010 // account for gradient size 6011 var gp = gradient.properties, gh = gradientStyle.height, 6012 hh = (gh && gh.value) || gp.enter.height.value; 6013 labels.properties.enter.y.value = hh; 6014 6015 // account for title size as needed 6016 if (title) { 6017 var tp = titles.properties, fs = titleStyle.fontSize, 6018 sz = 4 + ((fs && fs.value) || tp.enter.fontSize.value); 6019 gradient.properties.enter.y.value += sz; 6020 labels.properties.enter.y.value += sz; 6021 } 6022 6023 // padding from legend border 6024 titles.properties.enter.x.value += padding; 6025 titles.properties.enter.y.value += padding; 6026 gradient.properties.enter.x.value += padding; 6027 gradient.properties.enter.y.value += padding; 6028 labels.properties.enter.y.value += padding; 6029 6030 return { 6031 type: "group", 6032 interactive: false, 6033 properties: { 6034 enter: vg.parse.properties("group", legendStyle), 6035 update: vg_legendUpdate 6036 }, 6037 marks: [titles, gradient, labels].map(vg.parse.mark) 6038 }; 6039 } 6040 6041 legend.size = function(x) { 6042 if (!arguments.length) return size; 6043 if (size !== x) { size = x; reset(); } 6044 return legend; 6045 }; 6046 6047 legend.shape = function(x) { 6048 if (!arguments.length) return shape; 6049 if (shape !== x) { shape = x; reset(); } 6050 return legend; 6051 }; 6052 6053 legend.fill = function(x) { 6054 if (!arguments.length) return fill; 6055 if (fill !== x) { fill = x; reset(); } 6056 return legend; 6057 }; 6058 6059 legend.stroke = function(x) { 6060 if (!arguments.length) return stroke; 6061 if (stroke !== x) { stroke = x; reset(); } 6062 return legend; 6063 }; 6064 6065 legend.title = function(x) { 6066 if (!arguments.length) return title; 6067 if (title !== x) { title = x; reset(); } 6068 return legend; 6069 }; 6070 6071 legend.format = function(x) { 6072 if (!arguments.length) return format; 6073 if (format !== x) { format = x; reset(); } 6074 return legend; 6075 }; 6076 6077 legend.spacing = function(x) { 6078 if (!arguments.length) return spacing; 6079 if (spacing !== +x) { spacing = +x; reset(); } 6080 return legend; 6081 }; 6082 6083 legend.orient = function(x) { 6084 if (!arguments.length) return orient; 6085 orient = x in vg_legendOrients ? x + "" : vg.config.legend.orient; 6086 return legend; 6087 }; 6088 6089 legend.offset = function(x) { 6090 if (!arguments.length) return offset; 6091 offset = +x; 6092 return legend; 6093 }; 6094 6095 legend.values = function(x) { 6096 if (!arguments.length) return values; 6097 values = x; 6098 return legend; 6099 }; 6100 6101 legend.legendProperties = function(x) { 6102 if (!arguments.length) return legendStyle; 6103 legendStyle = x; 6104 return legend; 6105 }; 6106 6107 legend.symbolProperties = function(x) { 6108 if (!arguments.length) return symbolStyle; 6109 symbolStyle = x; 6110 return legend; 6111 }; 6112 6113 legend.gradientProperties = function(x) { 6114 if (!arguments.length) return gradientStyle; 6115 gradientStyle = x; 6116 return legend; 6117 }; 6118 6119 legend.labelProperties = function(x) { 6120 if (!arguments.length) return labelStyle; 6121 labelStyle = x; 6122 return legend; 6123 }; 6124 6125 legend.titleProperties = function(x) { 6126 if (!arguments.length) return titleStyle; 6127 titleStyle = x; 6128 return legend; 6129 }; 6130 6131 legend.reset = function() { reset(); }; 6132 6133 return legend; 6134 }; 6135 6136 var vg_legendOrients = {right: 1, left: 1}; 6137 6138 function vg_legendUpdate(item, group, trans) { 6139 var o = trans ? {} : item, 6140 offset = item.mark.def.offset, 6141 orient = item.mark.def.orient, 6142 pad = item.mark.def.padding * 2, 6143 gx1 = group.bounds ? group.bounds.x1 : 0, 6144 gx2 = group.bounds ? group.bounds.x2 : group.width, 6145 lw = ~~item.bounds.width() + (o.width ? 0 : pad), 6146 lh = ~~item.bounds.height() + (o.height ? 0 : pad); 6147 6148 o.x = 0.5; 6149 o.y = 0.5; 6150 o.width = lw; 6151 o.height = lh; 6152 6153 switch (orient) { 6154 case "left": { o.x += gx1 - offset - lw; break; }; 6155 case "right": { o.x += gx2 + offset; break; }; 6156 } 6157 6158 item.mark.def.properties.enter(item, group, trans); 6159 } 6160 6161 function vg_legendSymbolExtend(mark, size, shape, fill, stroke) { 6162 var props = mark.properties.enter; 6163 if (size) props.size = {scale: size.scaleName, field: "data"}; 6164 if (shape) props.shape = {scale: shape.scaleName, field: "data"}; 6165 if (fill) props.fill = {scale: fill.scaleName, field: "data"}; 6166 if (stroke) props.stroke = {scale: stroke.scaleName, field: "data"}; 6167 } 6168 6169 function vg_legendTitle() { 6170 var cfg = vg.config.legend; 6171 return { 6172 type: "text", 6173 interactive: false, 6174 key: "data", 6175 properties: { 6176 enter: { 6177 x: {value: 0}, 6178 y: {value: 0}, 6179 fill: {value: cfg.titleColor}, 6180 font: {value: cfg.titleFont}, 6181 fontSize: {value: cfg.titleFontSize}, 6182 fontWeight: {value: cfg.titleFontWeight}, 6183 baseline: {value: "top"}, 6184 text: {field: "data"}, 6185 opacity: {value: 1e-6} 6186 }, 6187 exit: { opacity: {value: 1e-6} }, 6188 update: { opacity: {value: 1} } 6189 } 6190 }; 6191 } 6192 6193 function vg_legendSymbols() { 6194 var cfg = vg.config.legend; 6195 return { 6196 type: "symbol", 6197 interactive: false, 6198 key: "data", 6199 properties: { 6200 enter: { 6201 x: {field: "offset", mult: 0.5}, 6202 y: {scale: "legend", field: "index"}, 6203 shape: {value: cfg.symbolShape}, 6204 size: {value: cfg.symbolSize}, 6205 stroke: {value: cfg.symbolColor}, 6206 strokeWidth: {value: cfg.symbolStrokeWidth}, 6207 opacity: {value: 1e-6} 6208 }, 6209 exit: { opacity: {value: 1e-6} }, 6210 update: { opacity: {value: 1} } 6211 } 6212 }; 6213 } 6214 6215 function vg_vLegendLabels() { 6216 var cfg = vg.config.legend; 6217 return { 6218 type: "text", 6219 interactive: false, 6220 key: "data", 6221 properties: { 6222 enter: { 6223 x: {field: "offset", offset: 5}, 6224 y: {scale: "legend", field: "index"}, 6225 fill: {value: cfg.labelColor}, 6226 font: {value: cfg.labelFont}, 6227 fontSize: {value: cfg.labelFontSize}, 6228 align: {value: cfg.labelAlign}, 6229 baseline: {value: cfg.labelBaseline}, 6230 text: {field: "label"}, 6231 opacity: {value: 1e-6} 6232 }, 6233 exit: { opacity: {value: 1e-6} }, 6234 update: { opacity: {value: 1} } 6235 } 6236 }; 6237 } 6238 6239 function vg_legendGradient() { 6240 var cfg = vg.config.legend; 6241 return { 6242 type: "rect", 6243 interactive: false, 6244 properties: { 6245 enter: { 6246 x: {value: 0}, 6247 y: {value: 0}, 6248 width: {value: cfg.gradientWidth}, 6249 height: {value: cfg.gradientHeight}, 6250 stroke: {value: cfg.gradientStrokeColor}, 6251 strokeWidth: {value: cfg.gradientStrokeWidth}, 6252 opacity: {value: 1e-6} 6253 }, 6254 exit: { opacity: {value: 1e-6} }, 6255 update: { opacity: {value: 1} } 6256 } 6257 }; 6258 } 6259 6260 function vg_hLegendLabels() { 6261 var cfg = vg.config.legend; 6262 return { 6263 type: "text", 6264 interactive: false, 6265 key: "data", 6266 properties: { 6267 enter: { 6268 x: {scale: "legend", field: "data"}, 6269 y: {value: 20}, 6270 dy: {value: 2}, 6271 fill: {value: cfg.labelColor}, 6272 font: {value: cfg.labelFont}, 6273 fontSize: {value: cfg.labelFontSize}, 6274 align: {field: "align"}, 6275 baseline: {value: "top"}, 6276 text: {field: "label"}, 6277 opacity: {value: 1e-6} 6278 }, 6279 exit: { opacity: {value: 1e-6} }, 6280 update: { opacity: {value: 1} } 6281 } 6282 }; 6283 }vg.Model = (function() { 6284 function model() { 6285 this._defs = null; 6286 this._data = {}; 6287 this._scene = null; 6288 this._reset = false; 6289 } 6290 6291 var prototype = model.prototype; 6292 6293 prototype.defs = function(defs) { 6294 if (!arguments.length) return this._defs; 6295 this._defs = defs; 6296 return this; 6297 }; 6298 6299 prototype.data = function(data) { 6300 if (!arguments.length) return this._data; 6301 6302 var tx = this._defs.data.flow || {}, 6303 keys = this._defs.data.defs.map(vg.accessor("name")), 6304 len = keys.length, i, k; 6305 6306 for (i=0; i<len; ++i) { 6307 if (!data[k=keys[i]]) continue; 6308 this.ingest(k, tx, data[k]); 6309 } 6310 6311 return this; 6312 }; 6313 6314 prototype.ingest = function(name, tx, input) { 6315 this._data[name] = tx[name] 6316 ? tx[name](input, this._data, this._defs.marks) 6317 : input; 6318 this.dependencies(name, tx); 6319 }; 6320 6321 prototype.dependencies = function(name, tx) { 6322 var source = this._defs.data.source[name], 6323 data = this._data[name], 6324 n = source ? source.length : 0, i, x; 6325 for (i=0; i<n; ++i) { 6326 x = vg_data_duplicate(data); 6327 if (vg.isTree(data)) vg_make_tree(x); 6328 this.ingest(source[i], tx, x); 6329 } 6330 }; 6331 6332 prototype.width = function(width) { 6333 if (this._defs) this._defs.width = width; 6334 if (this._defs && this._defs.marks) this._defs.marks.width = width; 6335 if (this._scene) this._scene.items[0].width = width; 6336 this._reset = true; 6337 return this; 6338 }; 6339 6340 prototype.height = function(height) { 6341 if (this._defs) this._defs.height = height; 6342 if (this._defs && this._defs.marks) this._defs.marks.height = height; 6343 if (this._scene) this._scene.items[0].height = height; 6344 this._reset = true; 6345 return this; 6346 }; 6347 6348 prototype.scene = function(node) { 6349 if (!arguments.length) return this._scene; 6350 this._scene = node; 6351 return this; 6352 }; 6353 6354 prototype.build = function() { 6355 var m = this, data = m._data, marks = m._defs.marks; 6356 m._scene = vg.scene.build.call(m, marks, data, m._scene); 6357 m._scene.items[0].width = marks.width; 6358 m._scene.items[0].height = marks.height; 6359 m._scene.interactive = false; 6360 return this; 6361 }; 6362 6363 prototype.encode = function(trans, request, item) { 6364 if (this._reset) { this.reset(); this._reset = false; } 6365 var m = this, scene = m._scene, defs = m._defs; 6366 vg.scene.encode.call(m, scene, defs.marks, trans, request, item); 6367 return this; 6368 }; 6369 6370 prototype.reset = function() { 6371 if (this._scene) { 6372 vg.scene.visit(this._scene, function(item) { 6373 if (item.axes) item.axes.forEach(function(axis) { axis.reset(); }); 6374 }); 6375 } 6376 return this; 6377 }; 6378 6379 return model; 6380 })();vg.View = (function() { 6381 var view = function(el, width, height) { 6382 this._el = null; 6383 this._build = false; 6384 this._model = new vg.Model(); 6385 this._width = this.__width = width || 500; 6386 this._height = this.__height = height || 500; 6387 this._autopad = 1; 6388 this._padding = {top:0, left:0, bottom:0, right:0}; 6389 this._viewport = null; 6390 this._renderer = null; 6391 this._handler = null; 6392 this._io = vg.canvas; 6393 if (el) this.initialize(el); 6394 }; 6395 6396 var prototype = view.prototype; 6397 6398 prototype.width = function(width) { 6399 if (!arguments.length) return this.__width; 6400 if (this.__width !== width) { 6401 this._width = this.__width = width; 6402 if (this._el) this.initialize(this._el.parentNode); 6403 this._model.width(width); 6404 if (this._strict) this._autopad = 1; 6405 } 6406 return this; 6407 }; 6408 6409 prototype.height = function(height) { 6410 if (!arguments.length) return this.__height; 6411 if (this.__height !== height) { 6412 this._height = this.__height = height; 6413 if (this._el) this.initialize(this._el.parentNode); 6414 this._model.height(this._height); 6415 if (this._strict) this._autopad = 1; 6416 } 6417 return this; 6418 }; 6419 6420 prototype.padding = function(pad) { 6421 if (!arguments.length) return this._padding; 6422 if (this._padding !== pad) { 6423 if (vg.isString(pad)) { 6424 this._autopad = 1; 6425 this._padding = {top:0, left:0, bottom:0, right:0}; 6426 this._strict = (pad === "strict"); 6427 } else { 6428 this._autopad = 0; 6429 this._padding = pad; 6430 this._strict = false; 6431 } 6432 if (this._el) { 6433 this._renderer.resize(this._width, this._height, pad); 6434 this._handler.padding(pad); 6435 } 6436 } 6437 return this; 6438 }; 6439 6440 prototype.autopad = function(opt) { 6441 if (this._autopad < 1) return this; 6442 else this._autopad = 0; 6443 6444 var pad = this._padding, 6445 b = this.model().scene().bounds, 6446 inset = vg.config.autopadInset, 6447 l = b.x1 < 0 ? Math.ceil(-b.x1) + inset : 0, 6448 t = b.y1 < 0 ? Math.ceil(-b.y1) + inset : 0, 6449 r = b.x2 > this._width ? Math.ceil(+b.x2 - this._width) + inset : 0, 6450 b = b.y2 > this._height ? Math.ceil(+b.y2 - this._height) + inset : 0; 6451 pad = {left:l, top:t, right:r, bottom:b}; 6452 6453 if (this._strict) { 6454 this._autopad = 0; 6455 this._padding = pad; 6456 this._width = Math.max(0, this.__width - (l+r)); 6457 this._height = Math.max(0, this.__height - (t+b)); 6458 this._model.width(this._width); 6459 this._model.height(this._height); 6460 if (this._el) this.initialize(this._el.parentNode); 6461 this.update({props:"enter"}).update({props:"update"}); 6462 } else { 6463 this.padding(pad).update(opt); 6464 } 6465 return this; 6466 }; 6467 6468 prototype.viewport = function(size) { 6469 if (!arguments.length) return this._viewport; 6470 if (this._viewport !== size) { 6471 this._viewport = size; 6472 if (this._el) this.initialize(this._el.parentNode); 6473 } 6474 return this; 6475 }; 6476 6477 prototype.renderer = function(type) { 6478 if (!arguments.length) return this._io; 6479 if (type === "canvas") type = vg.canvas; 6480 if (type === "svg") type = vg.svg; 6481 if (this._io !== type) { 6482 this._io = type; 6483 this._renderer = null; 6484 if (this._el) this.initialize(this._el.parentNode); 6485 if (this._build) this.render(); 6486 } 6487 return this; 6488 }; 6489 6490 prototype.defs = function(defs) { 6491 if (!arguments.length) return this._model.defs(); 6492 this._model.defs(defs); 6493 return this; 6494 }; 6495 6496 prototype.data = function(data) { 6497 if (!arguments.length) return this._model.data(); 6498 var ingest = vg.keys(data).reduce(function(d, k) { 6499 return (d[k] = vg.data.ingestAll(data[k]), d); 6500 }, {}); 6501 this._model.data(ingest); 6502 this._build = false; 6503 return this; 6504 }; 6505 6506 prototype.model = function(model) { 6507 if (!arguments.length) return this._model; 6508 if (this._model !== model) { 6509 this._model = model; 6510 if (this._handler) this._handler.model(model); 6511 } 6512 return this; 6513 }; 6514 6515 prototype.initialize = function(el) { 6516 var v = this, prevHandler, 6517 w = v._width, h = v._height, pad = v._padding; 6518 6519 // clear pre-existing container 6520 d3.select(el).select("div.vega").remove(); 6521 6522 // add div container 6523 this._el = el = d3.select(el) 6524 .append("div") 6525 .attr("class", "vega") 6526 .style("position", "relative") 6527 .node(); 6528 if (v._viewport) { 6529 d3.select(el) 6530 .style("width", (v._viewport[0] || w)+"px") 6531 .style("height", (v._viewport[1] || h)+"px") 6532 .style("overflow", "auto"); 6533 } 6534 6535 // renderer 6536 v._renderer = (v._renderer || new this._io.Renderer()) 6537 .initialize(el, w, h, pad); 6538 6539 // input handler 6540 prevHandler = v._handler; 6541 v._handler = new this._io.Handler() 6542 .initialize(el, pad, v) 6543 .model(v._model); 6544 6545 if (prevHandler) { 6546 prevHandler.handlers().forEach(function(h) { 6547 v._handler.on(h.type, h.handler); 6548 }); 6549 } 6550 6551 return this; 6552 }; 6553 6554 prototype.render = function(items) { 6555 this._renderer.render(this._model.scene(), items); 6556 return this; 6557 }; 6558 6559 prototype.on = function() { 6560 this._handler.on.apply(this._handler, arguments); 6561 return this; 6562 }; 6563 6564 prototype.off = function() { 6565 this._handler.off.apply(this._handler, arguments); 6566 return this; 6567 }; 6568 6569 prototype.update = function(opt) { 6570 opt = opt || {}; 6571 var view = this, 6572 trans = opt.duration 6573 ? vg.scene.transition(opt.duration, opt.ease) 6574 : null; 6575 6576 view._build = view._build || (view._model.build(), true); 6577 view._model.encode(trans, opt.props, opt.items); 6578 6579 if (trans) { 6580 trans.start(function(items) { 6581 view._renderer.render(view._model.scene(), items); 6582 }); 6583 } 6584 else view.render(opt.items); 6585 6586 return view.autopad(opt); 6587 }; 6588 6589 return view; 6590 })(); 6591 6592 // view constructor factory 6593 // takes definitions from parsed specification as input 6594 // returns a view constructor 6595 vg.ViewFactory = function(defs) { 6596 return function(opt) { 6597 opt = opt || {}; 6598 var v = new vg.View() 6599 .width(defs.width) 6600 .height(defs.height) 6601 .padding(defs.padding) 6602 .viewport(defs.viewport) 6603 .renderer(opt.renderer || "canvas") 6604 .defs(defs); 6605 6606 if (defs.data.load) v.data(defs.data.load); 6607 if (opt.data) v.data(opt.data); 6608 if (opt.el) v.initialize(opt.el); 6609 6610 if (opt.hover !== false) { 6611 v.on("mouseover", function(evt, item) { 6612 if (item.hasPropertySet("hover")) { 6613 this.update({props:"hover", items:item}); 6614 } 6615 }) 6616 .on("mouseout", function(evt, item) { 6617 if (item.hasPropertySet("hover")) { 6618 this.update({props:"update", items:item}); 6619 } 6620 }); 6621 } 6622 6623 return v; 6624 }; 6625 }; 6626 vg.Spec = (function() { 6627 var spec = function(s) { 6628 this.spec = { 6629 width: 500, 6630 height: 500, 6631 padding: 0, 6632 data: [], 6633 scales: [], 6634 axes: [], 6635 marks: [] 6636 }; 6637 if (s) vg.extend(this.spec, s); 6638 }; 6639 6640 var prototype = spec.prototype; 6641 6642 prototype.width = function(w) { 6643 this.spec.width = w; 6644 return this; 6645 }; 6646 6647 prototype.height = function(h) { 6648 this.spec.height = h; 6649 return this; 6650 }; 6651 6652 prototype.padding = function(p) { 6653 this.spec.padding = p; 6654 return this; 6655 }; 6656 6657 prototype.viewport = function(v) { 6658 this.spec.viewport = v; 6659 return this; 6660 }; 6661 6662 prototype.data = function(name, params) { 6663 if (!params) params = vg.isString(name) ? {name: name} : name; 6664 else params.name = name; 6665 this.spec.data.push(params); 6666 return this; 6667 }; 6668 6669 prototype.scale = function(name, params) { 6670 if (!params) params = vg.isString(name) ? {name: name} : name; 6671 else params.name = name; 6672 this.spec.scales.push(params); 6673 return this; 6674 }; 6675 6676 prototype.axis = function(params) { 6677 this.spec.axes.push(params); 6678 return this; 6679 }; 6680 6681 prototype.mark = function(type, mark) { 6682 if (!mark) mark = {type: type}; 6683 else mark.type = type; 6684 mark.properties = {}; 6685 this.spec.marks.push(mark); 6686 6687 var that = this; 6688 return { 6689 from: function(name, obj) { 6690 mark.from = obj 6691 ? (obj.data = name, obj) 6692 : vg.isString(name) ? {data: name} : name; 6693 return this; 6694 }, 6695 prop: function(name, obj) { 6696 mark.properties[name] = vg.keys(obj).reduce(function(o,k) { 6697 var v = obj[k]; 6698 return (o[k] = vg.isObject(v) ? v : {value: v}, o); 6699 }, {}); 6700 return this; 6701 }, 6702 done: function() { return that; } 6703 }; 6704 }; 6705 6706 prototype.parse = function(callback) { 6707 vg.parse.spec(this.spec, callback); 6708 }; 6709 6710 prototype.json = function() { 6711 return this.spec; 6712 }; 6713 6714 return spec; 6715 })(); 6716 6717 vg.spec = function(s) { 6718 return new vg.Spec(s); 6719 }; 6720 vg.headless = {};vg.headless.View = (function() { 6721 6722 var view = function(width, height, pad, type) { 6723 this._canvas = null; 6724 this._type = type; 6725 this._el = "body"; 6726 this._build = false; 6727 this._model = new vg.Model(); 6728 this._width = this.__width = width || 500; 6729 this._height = this.__height = height || 500; 6730 this._autopad = 1; 6731 this._padding = pad || {top:0, left:0, bottom:0, right:0}; 6732 this._renderer = new vg[type].Renderer(); 6733 this.initialize(); 6734 }; 6735 6736 var prototype = view.prototype; 6737 6738 prototype.el = function(el) { 6739 if (!arguments.length) return this._el; 6740 if (this._el !== el) { 6741 this._el = el; 6742 this.initialize(); 6743 } 6744 return this; 6745 }; 6746 6747 prototype.width = function(width) { 6748 if (!arguments.length) return this._width; 6749 if (this._width !== width) { 6750 this._width = width; 6751 this.initialize(); 6752 this._model.width(width); 6753 } 6754 return this; 6755 }; 6756 6757 prototype.height = function(height) { 6758 if (!arguments.length) return this._height; 6759 if (this._height !== height) { 6760 this._height = height; 6761 this.initialize(); 6762 this._model.height(this._height); 6763 } 6764 return this; 6765 }; 6766 6767 prototype.padding = function(pad) { 6768 if (!arguments.length) return this._padding; 6769 if (this._padding !== pad) { 6770 if (vg.isString(pad)) { 6771 this._autopad = 1; 6772 this._padding = {top:0, left:0, bottom:0, right:0}; 6773 this._strict = (pad === "strict"); 6774 } else { 6775 this._autopad = 0; 6776 this._padding = pad; 6777 this._strict = false; 6778 } 6779 this.initialize(); 6780 } 6781 return this; 6782 }; 6783 6784 prototype.autopad = function(opt) { 6785 if (this._autopad < 1) return this; 6786 else this._autopad = 0; 6787 6788 var pad = this._padding, 6789 b = this._model.scene().bounds, 6790 inset = vg.config.autopadInset, 6791 l = b.x1 < 0 ? Math.ceil(-b.x1) + inset : 0, 6792 t = b.y1 < 0 ? Math.ceil(-b.y1) + inset : 0, 6793 r = b.x2 > this._width ? Math.ceil(+b.x2 - this._width) + inset : 0, 6794 b = b.y2 > this._height ? Math.ceil(+b.y2 - this._height) + inset : 0; 6795 pad = {left:l, top:t, right:r, bottom:b}; 6796 6797 if (this._strict) { 6798 this._autopad = 0; 6799 this._padding = pad; 6800 this._width = Math.max(0, this.__width - (l+r)); 6801 this._height = Math.max(0, this.__height - (t+b)); 6802 this._model.width(this._width); 6803 this._model.height(this._height); 6804 if (this._el) this.initialize(); 6805 this.update({props:"enter"}).update({props:"update"}); 6806 } else { 6807 this.padding(pad).update(opt); 6808 } 6809 return this; 6810 }; 6811 6812 prototype.viewport = function() { 6813 if (!arguments.length) return null; 6814 return this; 6815 }; 6816 6817 prototype.defs = function(defs) { 6818 if (!arguments.length) return this._model.defs(); 6819 this._model.defs(defs); 6820 return this; 6821 }; 6822 6823 prototype.data = function(data) { 6824 if (!arguments.length) return this._model.data(); 6825 var ingest = vg.keys(data).reduce(function(d, k) { 6826 return (d[k] = vg.data.ingestAll(data[k]), d); 6827 }, {}); 6828 this._model.data(ingest); 6829 this._build = false; 6830 return this; 6831 }; 6832 6833 prototype.renderer = function() { 6834 return this._renderer; 6835 }; 6836 6837 prototype.canvas = function() { 6838 return this._canvas; 6839 }; 6840 6841 prototype.canvasAsync = function(callback) { 6842 var r = this._renderer, view = this; 6843 6844 function wait() { 6845 if (r.pendingImages() === 0) { 6846 view.render(); // re-render with all images 6847 callback(view._canvas); 6848 } else { 6849 setTimeout(wait, 10); 6850 } 6851 } 6852 6853 // if images loading, poll until ready 6854 (r.pendingImages() > 0) ? wait() : callback(this._canvas); 6855 }; 6856 6857 prototype.svg = function() { 6858 if (this._type !== "svg") return null; 6859 6860 var p = this._padding, 6861 w = this._width + (p ? p.left + p.right : 0), 6862 h = this._height + (p ? p.top + p.bottom : 0); 6863 6864 // build svg text 6865 var svg = d3.select(this._el) 6866 .select("svg").node().innerHTML 6867 .replace(/ href=/g, " xlink:href="); // ns hack. sigh. 6868 6869 return '<svg ' 6870 + 'width="' + w + '" ' 6871 + 'height="' + h + '" ' 6872 + vg.config.svgNamespace + '>' + svg + '</svg>' 6873 }; 6874 6875 prototype.initialize = function() { 6876 var w = this._width, 6877 h = this._height, 6878 pad = this._padding; 6879 6880 if (this._type === "svg") { 6881 this.initSVG(w, h, pad); 6882 } else { 6883 this.initCanvas(w, h, pad); 6884 } 6885 6886 return this; 6887 }; 6888 6889 prototype.initCanvas = function(w, h, pad) { 6890 var Canvas = require("canvas"), 6891 tw = w + pad.left + pad.right, 6892 th = h + pad.top + pad.bottom, 6893 canvas = this._canvas = new Canvas(tw, th), 6894 ctx = canvas.getContext("2d"); 6895 6896 // setup canvas context 6897 ctx.setTransform(1, 0, 0, 1, pad.left, pad.top); 6898 6899 // configure renderer 6900 this._renderer.context(ctx); 6901 this._renderer.resize(w, h, pad); 6902 }; 6903 6904 prototype.initSVG = function(w, h, pad) { 6905 var tw = w + pad.left + pad.right, 6906 th = h + pad.top + pad.bottom; 6907 6908 // configure renderer 6909 this._renderer.initialize(this._el, w, h, pad); 6910 } 6911 6912 prototype.render = function(items) { 6913 this._renderer.render(this._model.scene(), items); 6914 return this; 6915 }; 6916 6917 prototype.update = function(opt) { 6918 opt = opt || {}; 6919 var view = this; 6920 view._build = view._build || (view._model.build(), true); 6921 view._model.encode(null, opt.props, opt.items); 6922 view.render(opt.items); 6923 return view.autopad(opt); 6924 }; 6925 6926 return view; 6927 })(); 6928 6929 // headless view constructor factory 6930 // takes definitions from parsed specification as input 6931 // returns a view constructor 6932 vg.headless.View.Factory = function(defs) { 6933 return function(opt) { 6934 opt = opt || {}; 6935 var w = defs.width, 6936 h = defs.height, 6937 p = defs.padding, 6938 r = opt.renderer || "canvas", 6939 v = new vg.headless.View(w, h, p, r).defs(defs); 6940 if (defs.data.load) v.data(defs.data.load); 6941 if (opt.data) v.data(opt.data); 6942 return v; 6943 }; 6944 };vg.headless.render = function(opt, callback) { 6945 function draw(chart) { 6946 try { 6947 // create and render view 6948 var view = chart({ 6949 data: opt.data, 6950 renderer: opt.renderer 6951 }).update(); 6952 6953 if (opt.renderer === "svg") { 6954 // extract rendered svg 6955 callback(null, {svg: view.svg()}); 6956 } else { 6957 // extract rendered canvas, waiting for any images to load 6958 view.canvasAsync(function(canvas) { 6959 callback(null, {canvas: canvas}); 6960 }); 6961 } 6962 } catch (err) { 6963 callback(err, null); 6964 } 6965 } 6966 6967 vg.parse.spec(opt.spec, draw, vg.headless.View.Factory); 6968 }; return vg; 6969 })(d3, typeof topojson === "undefined" ? null : topojson); 6970 // assumes D3 and topojson in global namespace