vg = (function(d3, topojson) { // take d3 & topojson as imports var vg = { version: "1.3.3", // semantic versioning d3: d3, // stash d3 for use in property functions topojson: topojson // stash topojson similarly }; // type checking functions var toString = Object.prototype.toString; vg.isObject = function(obj) { return obj === Object(obj); }; vg.isFunction = function(obj) { return toString.call(obj) == '[object Function]'; }; vg.isString = function(obj) { return toString.call(obj) == '[object String]'; }; vg.isArray = Array.isArray || function(obj) { return toString.call(obj) == '[object Array]'; }; vg.isNumber = function(obj) { return toString.call(obj) == '[object Number]'; }; vg.isBoolean = function(obj) { return toString.call(obj) == '[object Boolean]'; }; vg.isTree = function(obj) { return obj && obj.__vgtree__; }; vg.tree = function(obj, children) { var d = [obj]; d.__vgtree__ = true; d.children = children || "children"; return d; }; vg.number = function(s) { return +s; }; vg["boolean"] = function(s) { return !!s; }; // utility functions vg.identity = function(x) { return x; }; vg.extend = function(obj) { for (var x, name, i=1, len=arguments.length; i 1 ? function(x) { return s.reduce(function(x,f) { return x[f]; }, x); } : function(x) { return x[f]; }; }; vg.comparator = function(sort) { var sign = []; if (sort === undefined) sort = []; sort = vg.array(sort).map(function(f) { var s = 1; if (f[0] === "-") { s = -1; f = f.slice(1); } else if (f[0] === "+") { s = +1; f = f.slice(1); } sign.push(s); return vg.accessor(f); }); return function(a,b) { var i, n, f, x, y; for (i=0, n=sort.length; i y) return sign[i]; } return 0; }; }; vg.cmp = function(a, b) { return ab ? 1 : 0; }; vg.numcmp = function(a, b) { return a - b; }; vg.array = function(x) { return x != null ? (vg.isArray(x) ? x : [x]) : []; }; vg.values = function(x) { return (vg.isObject(x) && !vg.isArray(x) && x.values) ? x.values : x; }; vg.str = function(x) { return vg.isArray(x) ? "[" + x.map(vg.str) + "]" : vg.isObject(x) ? JSON.stringify(x) : vg.isString(x) ? ("'"+vg_escape_str(x)+"'") : x; }; var escape_str_re = /(^|[^\\])'/g; function vg_escape_str(x) { return x.replace(escape_str_re, "$1\\'"); } vg.keys = function(x) { var keys = []; for (var key in x) keys.push(key); return keys; }; vg.unique = function(data, f, results) { if (!vg.isArray(data) || data.length==0) return []; f = f || vg.identity; results = results || []; for (var v, i=0, n=data.length; i max) { max = v; idx = i; } } return idx; }; vg.truncate = function(s, length, pos, word, ellipsis) { var len = s.length; if (len <= length) return s; ellipsis = ellipsis || "..."; var l = Math.max(0, length - ellipsis.length); switch (pos) { case "left": return ellipsis + (word ? vg_truncateOnWord(s,l,1) : s.slice(len-l)); case "middle": case "center": var l1 = Math.ceil(l/2), l2 = Math.floor(l/2); return (word ? vg_truncateOnWord(s,l1) : s.slice(0,l1)) + ellipsis + (word ? vg_truncateOnWord(s,l2,1) : s.slice(len-l2)); default: return (word ? vg_truncateOnWord(s,l) : s.slice(0,l)) + ellipsis; } } function vg_truncateOnWord(s, len, rev) { var cnt = 0, tok = s.split(vg_truncate_word_re); if (rev) { s = (tok = tok.reverse()) .filter(function(w) { cnt += w.length; return cnt <= len; }) .reverse(); } else { s = tok.filter(function(w) { cnt += w.length; return cnt <= len; }); } return s.length ? s.join("").trim() : tok[0].slice(0, len); } 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])/; // Logging function vg_write(msg) { vg.config.isNode ? process.stderr.write(msg + "\n") : console.log(msg); } vg.log = function(msg) { vg_write("[Vega Log] " + msg); }; vg.error = function(msg) { msg = "[Vega Err] " + msg; vg_write(msg); if (typeof alert !== "undefined") alert(msg); };vg.config = {}; // are we running in node.js? // via timetler.com/2012/10/13/environment-detection-in-javascript/ vg.config.isNode = typeof exports !== 'undefined' && this.exports !== exports; // base url for loading external data files // used only for server-side operation vg.config.baseURL = ""; // version and namepsaces for exported svg vg.config.svgNamespace = 'version="1.1" xmlns="http://www.w3.org/2000/svg" ' + 'xmlns:xlink="http://www.w3.org/1999/xlink"'; // inset padding for automatic padding calculation vg.config.autopadInset = 5; // extensible scale lookup table // all d3.scale.* instances also supported vg.config.scale = { time: d3.time.scale, utc: d3.time.scale.utc }; // default rendering settings vg.config.render = { lineWidth: 1, lineCap: "butt", font: "sans-serif", fontSize: 11 }; // default axis properties vg.config.axis = { orient: "bottom", ticks: 10, padding: 3, axisColor: "#000", gridColor: "#d8d8d8", tickColor: "#000", tickLabelColor: "#000", axisWidth: 1, tickWidth: 1, tickSize: 6, tickLabelFontSize: 11, tickLabelFont: "sans-serif", titleColor: "#000", titleFont: "sans-serif", titleFontSize: 11, titleFontWeight: "bold", titleOffset: 35 }; // default legend properties vg.config.legend = { orient: "right", offset: 10, padding: 3, gradientStrokeColor: "#888", gradientStrokeWidth: 1, gradientHeight: 16, gradientWidth: 100, labelColor: "#000", labelFontSize: 10, labelFont: "sans-serif", labelAlign: "left", labelBaseline: "middle", labelOffset: 8, symbolShape: "circle", symbolSize: 50, symbolColor: "#888", symbolStrokeWidth: 1, titleColor: "#000", titleFont: "sans-serif", titleFontSize: 11, titleFontWeight: "bold" }; // default color values vg.config.color = { rgb: [128, 128, 128], lab: [50, 0, 0], hcl: [0, 0, 50], hsl: [0, 0, 0.5] }; // default scale ranges vg.config.range = { category10: [ "#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf" ], category20: [ "#1f77b4", "#aec7e8", "#ff7f0e", "#ffbb78", "#2ca02c", "#98df8a", "#d62728", "#ff9896", "#9467bd", "#c5b0d5", "#8c564b", "#c49c94", "#e377c2", "#f7b6d2", "#7f7f7f", "#c7c7c7", "#bcbd22", "#dbdb8d", "#17becf", "#9edae5" ], shapes: [ "circle", "cross", "diamond", "square", "triangle-down", "triangle-up" ] };vg.Bounds = (function() { var bounds = function(b) { this.clear(); if (b) this.union(b); }; var prototype = bounds.prototype; prototype.clear = function() { this.x1 = +Number.MAX_VALUE; this.y1 = +Number.MAX_VALUE; this.x2 = -Number.MAX_VALUE; this.y2 = -Number.MAX_VALUE; return this; }; prototype.set = function(x1, y1, x2, y2) { this.x1 = x1; this.y1 = y1; this.x2 = x2; this.y2 = y2; return this; }; prototype.add = function(x, y) { if (x < this.x1) this.x1 = x; if (y < this.y1) this.y1 = y; if (x > this.x2) this.x2 = x; if (y > this.y2) this.y2 = y; return this; }; prototype.expand = function(d) { this.x1 -= d; this.y1 -= d; this.x2 += d; this.y2 += d; return this; }; prototype.round = function() { this.x1 = Math.floor(this.x1); this.y1 = Math.floor(this.y1); this.x2 = Math.ceil(this.x2); this.y2 = Math.ceil(this.y2); return this; }; prototype.translate = function(dx, dy) { this.x1 += dx; this.x2 += dx; this.y1 += dy; this.y2 += dy; return this; }; prototype.rotate = function(angle, x, y) { var cos = Math.cos(angle), sin = Math.sin(angle), cx = x - x*cos + y*sin, cy = y - x*sin - y*cos, x1 = this.x1, x2 = this.x2, y1 = this.y1, y2 = this.y2; return this.clear() .add(cos*x1 - sin*y1 + cx, sin*x1 + cos*y1 + cy) .add(cos*x1 - sin*y2 + cx, sin*x1 + cos*y2 + cy) .add(cos*x2 - sin*y1 + cx, sin*x2 + cos*y1 + cy) .add(cos*x2 - sin*y2 + cx, sin*x2 + cos*y2 + cy); } prototype.union = function(b) { if (b.x1 < this.x1) this.x1 = b.x1; if (b.y1 < this.y1) this.y1 = b.y1; if (b.x2 > this.x2) this.x2 = b.x2; if (b.y2 > this.y2) this.y2 = b.y2; return this; }; prototype.encloses = function(b) { return b && ( this.x1 <= b.x1 && this.x2 >= b.x2 && this.y1 <= b.y1 && this.y2 >= b.y2 ); }; prototype.intersects = function(b) { return b && !( this.x2 < b.x1 || this.x1 > b.x2 || this.y2 < b.y1 || this.y1 > b.y2 ); }; prototype.contains = function(x, y) { return !( x < this.x1 || x > this.x2 || y < this.y1 || y > this.y2 ); }; prototype.width = function() { return this.x2 - this.x1; }; prototype.height = function() { return this.y2 - this.y1; }; return bounds; })();vg.Gradient = (function() { function gradient(type) { this.id = "grad_" + (vg_gradient_id++); this.type = type || "linear"; this.stops = []; this.x1 = 0; this.x2 = 1; this.y1 = 0; this.y2 = 0; }; var prototype = gradient.prototype; prototype.stop = function(offset, color) { this.stops.push({ offset: offset, color: color }); return this; }; return gradient; })(); var vg_gradient_id = 0;vg.canvas = {};vg.canvas.path = (function() { // Path parsing and rendering code taken from fabric.js -- Thanks! var cmdLength = { m:2, l:2, h:1, v:1, c:6, s:4, q:4, t:2, a:7 }, re = [/([MLHVCSQTAZmlhvcsqtaz])/g, /###/, /(\d)-/g, /\s|,|###/]; function parse(path) { var result = [], currentPath, chunks, parsed; // First, break path into command sequence path = path.slice().replace(re[0], '###$1').split(re[1]).slice(1); // Next, parse each command in turn for (var i=0, j, chunksParsed, len=path.length; i commandLength) { for (var k = 1, klen = chunksParsed.length; k < klen; k += commandLength) { result.push([ chunksParsed[0] ].concat(chunksParsed.slice(k, k + commandLength))); } } else { result.push(chunksParsed); } } return result; } function drawArc(g, x, y, coords, bounds, l, t) { var rx = coords[0]; var ry = coords[1]; var rot = coords[2]; var large = coords[3]; var sweep = coords[4]; var ex = coords[5]; var ey = coords[6]; var segs = arcToSegments(ex, ey, rx, ry, large, sweep, rot, x, y); for (var i=0; i 1) { pl = Math.sqrt(pl); rx *= pl; ry *= pl; } var a00 = cos_th / rx; var a01 = sin_th / rx; var a10 = (-sin_th) / ry; var a11 = (cos_th) / ry; var x0 = a00 * ox + a01 * oy; var y0 = a10 * ox + a11 * oy; var x1 = a00 * x + a01 * y; var y1 = a10 * x + a11 * y; var d = (x1-x0) * (x1-x0) + (y1-y0) * (y1-y0); var sfactor_sq = 1 / d - 0.25; if (sfactor_sq < 0) sfactor_sq = 0; var sfactor = Math.sqrt(sfactor_sq); if (sweep == large) sfactor = -sfactor; var xc = 0.5 * (x0 + x1) - sfactor * (y1-y0); var yc = 0.5 * (y0 + y1) + sfactor * (x1-x0); var th0 = Math.atan2(y0-yc, x0-xc); var th1 = Math.atan2(y1-yc, x1-xc); var th_arc = th1-th0; if (th_arc < 0 && sweep == 1){ th_arc += 2*Math.PI; } else if (th_arc > 0 && sweep == 0) { th_arc -= 2 * Math.PI; } var segments = Math.ceil(Math.abs(th_arc / (Math.PI * 0.5 + 0.001))); var result = []; for (var i=0; i 0) { g.globalAlpha = opac * (o.strokeOpacity==null ? 1 : o.strokeOpacity); g.strokeStyle = color(g, o, stroke); g.lineWidth = lw; g.lineCap = (lc = o.strokeCap) != null ? lc : vg.config.render.lineCap; g.vgLineDash(o.strokeDash || null); g.vgLineDashOffset(o.strokeDashOffset || 0); g.stroke(); } } } function drawPathAll(path, g, scene, bounds) { var i, len, item; for (i=0, len=scene.items.length; i 0) { g.globalAlpha = opac * (o.strokeOpacity==null ? 1 : o.strokeOpacity); g.strokeStyle = color(g, o, stroke); g.lineWidth = lw; g.lineCap = (lc = o.strokeCap) != null ? lc : vg.config.render.lineCap; g.vgLineDash(o.strokeDash || null); g.vgLineDashOffset(o.strokeDashOffset || 0); g.strokeRect(x, y, w, h); } } } } function drawRule(g, scene, bounds) { if (!scene.items.length) return; var items = scene.items, o, stroke, opac, lc, lw, x1, y1, x2, y2; for (var i=0, len=items.length; i 0) { g.globalAlpha = opac * (o.strokeOpacity==null ? 1 : o.strokeOpacity); g.strokeStyle = color(g, o, stroke); g.lineWidth = lw; g.lineCap = (lc = o.strokeCap) != null ? lc : vg.config.render.lineCap; g.vgLineDash(o.strokeDash || null); g.vgLineDashOffset(o.strokeDashOffset || 0); g.beginPath(); g.moveTo(x1, y1); g.lineTo(x2, y2); g.stroke(); } } } } function drawImage(g, scene, bounds) { if (!scene.items.length) return; var renderer = this, items = scene.items, o; for (var i=0, len=items.length; i 0) { g.globalAlpha = opac * (o.strokeOpacity==null ? 1 : o.strokeOpacity); g.strokeStyle = color(o, stroke); g.lineWidth = lw; g.strokeText(o.text, x, y); } } if (o.angle) g.restore(); } } function drawAll(pathFunc) { return function(g, scene, bounds) { drawPathAll(pathFunc, g, scene, bounds); } } function drawOne(pathFunc) { return function(g, scene, bounds) { if (!scene.items.length) return; if (bounds && !bounds.intersects(scene.items[0].bounds)) return; // bounds check drawPathOne(pathFunc, g, scene.items[0], scene.items); } } function drawGroup(g, scene, bounds) { if (!scene.items.length) return; var items = scene.items, group, axes, legends, renderer = this, gx, gy, gb, i, n, j, m; drawRect(g, scene, bounds); for (i=0, n=items.length; i=0;) { group = items[i]; dx = group.x || 0; dy = group.y || 0; g.save(); g.translate(dx, dy); for (j=group.items.length; --j >= 0;) { subscene = group.items[j]; if (subscene.interactive === false) continue; hit = handler.pick(subscene, x, y, gx-dx, gy-dy); if (hit) { g.restore(); return hit; } } g.restore(); } return scene.interactive ? pickAll(hitTests.rect, g, scene, x, y, gx, gy) : false; } function pickAll(test, g, scene, x, y, gx, gy) { if (!scene.items.length) return false; var o, b, i; if (g._ratio !== 1) { x *= g._ratio; y *= g._ratio; } for (i=scene.items.length; --i >= 0;) { o = scene.items[i]; b = o.bounds; // first hit test against bounding box if ((b && !b.contains(gx, gy)) || !b) continue; // if in bounding box, perform more careful test if (test(g, o, x, y, gx, gy)) return o; } return false; } function pickArea(g, scene, x, y, gx, gy) { if (!scene.items.length) return false; var items = scene.items, o, b, i, di, dd, od, dx, dy; b = items[0].bounds; if (b && !b.contains(gx, gy)) return false; if (g._ratio !== 1) { x *= g._ratio; y *= g._ratio; } if (!hitTests.area(g, items, x, y)) return false; return items[0]; } function pickLine(g, scene, x, y, gx, gy) { if (!scene.items.length) return false; var items = scene.items, o, b, i, di, dd, od, dx, dy; b = items[0].bounds; if (b && !b.contains(gx, gy)) return false; if (g._ratio !== 1) { x *= g._ratio; y *= g._ratio; } if (!hitTests.line(g, items, x, y)) return false; return items[0]; } function pick(test) { return function (g, scene, x, y, gx, gy) { return pickAll(test, g, scene, x, y, gx, gy); }; } function textHit(g, o, x, y, gx, gy) { if (!o.fontSize) return false; if (!o.angle) return true; // bounds sufficient if no rotation var b = vg.scene.bounds.text(o, tmpBounds, true), a = -o.angle * Math.PI / 180, cos = Math.cos(a), sin = Math.sin(a), x = o.x, y = o.y, px = cos*gx - sin*gy + (x - x*cos + y*sin), py = sin*gx + cos*gy + (y - x*sin - y*cos); return b.contains(px, py); } var hitTests = { text: textHit, rect: function(g,o,x,y) { return true; }, // bounds test is sufficient image: function(g,o,x,y) { return true; }, // bounds test is sufficient rule: function(g,o,x,y) { if (!g.isPointInStroke) return false; ruleStroke(g,o); return g.isPointInStroke(x,y); }, line: function(g,s,x,y) { if (!g.isPointInStroke) return false; lineStroke(g,s); return g.isPointInStroke(x,y); }, arc: function(g,o,x,y) { arcPath(g,o); return g.isPointInPath(x,y); }, area: function(g,s,x,y) { areaPath(g,s); return g.isPointInPath(x,y); }, path: function(g,o,x,y) { pathPath(g,o); return g.isPointInPath(x,y); }, symbol: function(g,o,x,y) { symbolPath(g,o); return g.isPointInPath(x,y); } }; return { draw: { group: drawGroup, area: drawOne(areaPath), line: drawOne(linePath), arc: drawAll(arcPath), path: drawAll(pathPath), symbol: drawAll(symbolPath), rect: drawRect, rule: drawRule, text: drawText, image: drawImage, drawOne: drawOne, // expose for extensibility drawAll: drawAll // expose for extensibility }, pick: { group: pickGroup, area: pickArea, line: pickLine, arc: pick(hitTests.arc), path: pick(hitTests.path), symbol: pick(hitTests.symbol), rect: pick(hitTests.rect), rule: pick(hitTests.rule), text: pick(hitTests.text), image: pick(hitTests.image), pickAll: pickAll // expose for extensibility } }; })();vg.canvas.Renderer = (function() { var renderer = function() { this._ctx = null; this._el = null; this._imgload = 0; }; var prototype = renderer.prototype; prototype.initialize = function(el, width, height, pad) { this._el = el; if (!el) return this; // early exit if no DOM element // select canvas element var canvas = d3.select(el) .selectAll("canvas.marks") .data([1]); // create new canvas element if needed canvas.enter() .append("canvas") .attr("class", "marks"); // remove extraneous canvas if needed canvas.exit().remove(); return this.resize(width, height, pad); }; prototype.resize = function(width, height, pad) { this._width = width; this._height = height; this._padding = pad; if (this._el) { var canvas = d3.select(this._el).select("canvas.marks"); // initialize canvas attributes canvas .attr("width", width + pad.left + pad.right) .attr("height", height + pad.top + pad.bottom); // get the canvas graphics context var s; this._ctx = canvas.node().getContext("2d"); this._ctx._ratio = (s = scaleCanvas(canvas.node(), this._ctx) || 1); this._ctx.setTransform(s, 0, 0, s, s*pad.left, s*pad.top); } initializeLineDash(this._ctx); return this; }; function scaleCanvas(canvas, ctx) { // get canvas pixel data var devicePixelRatio = window.devicePixelRatio || 1, backingStoreRatio = ( ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio) || 1, ratio = devicePixelRatio / backingStoreRatio; if (devicePixelRatio !== backingStoreRatio) { var w = canvas.width, h = canvas.height; // set actual and visible canvas size canvas.setAttribute("width", w * ratio); canvas.setAttribute("height", h * ratio); canvas.style.width = w + 'px'; canvas.style.height = h + 'px'; } return ratio; } function initializeLineDash(ctx) { if (ctx.vgLineDash) return; // already set var NODASH = []; if (ctx.setLineDash) { ctx.vgLineDash = function(dash) { this.setLineDash(dash || NODASH); }; ctx.vgLineDashOffset = function(off) { this.lineDashOffset = off; }; } else if (ctx.webkitLineDash !== undefined) { ctx.vgLineDash = function(dash) { this.webkitLineDash = dash || NODASH; }; ctx.vgLineDashOffset = function(off) { this.webkitLineDashOffset = off; }; } else if (ctx.mozDash !== undefined) { ctx.vgLineDash = function(dash) { this.mozDash = dash; }; ctx.vgLineDashOffset = function(off) { /* unsupported */ }; } else { ctx.vgLineDash = function(dash) { /* unsupported */ }; ctx.vgLineDashOffset = function(off) { /* unsupported */ }; } } prototype.context = function(ctx) { if (ctx) { this._ctx = ctx; return this; } else return this._ctx; }; prototype.element = function() { return this._el; }; prototype.pendingImages = function() { return this._imgload; }; function translatedBounds(item, bounds) { var b = new vg.Bounds(bounds); while ((item = item.mark.group) != null) { b.translate(item.x || 0, item.y || 0); } return b; } function getBounds(items) { return !items ? null : vg.array(items).reduce(function(b, item) { return b.union(translatedBounds(item, item.bounds)) .union(translatedBounds(item, item['bounds:prev'])); }, new vg.Bounds()); } function setBounds(g, bounds) { var bbox = null; if (bounds) { bbox = (new vg.Bounds(bounds)).round(); g.beginPath(); g.rect(bbox.x1, bbox.y1, bbox.width(), bbox.height()); g.clip(); } return bbox; } prototype.render = function(scene, items) { var g = this._ctx, pad = this._padding, w = this._width + pad.left + pad.right, h = this._height + pad.top + pad.bottom, bb = null, bb2; // setup this._scene = scene; g.save(); bb = setBounds(g, getBounds(items)); g.clearRect(-pad.left, -pad.top, w, h); // render this.draw(g, scene, bb); // render again to handle possible bounds change if (items) { g.restore(); g.save(); bb2 = setBounds(g, getBounds(items)); if (!bb.encloses(bb2)) { g.clearRect(-pad.left, -pad.top, w, h); this.draw(g, scene, bb2); } } // takedown g.restore(); this._scene = null; }; prototype.draw = function(ctx, scene, bounds) { var marktype = scene.marktype, renderer = vg.canvas.marks.draw[marktype]; renderer.call(this, ctx, scene, bounds); }; prototype.renderAsync = function(scene) { // TODO make safe for multiple scene rendering? var renderer = this; if (renderer._async_id) { clearTimeout(renderer._async_id); } renderer._async_id = setTimeout(function() { renderer.render(scene); delete renderer._async_id; }, 50); }; prototype.loadImage = function(uri) { var renderer = this, scene = renderer._scene, image = null, url; renderer._imgload += 1; if (vg.config.isNode) { image = new (require("canvas").Image)(); vg.data.load(uri, function(err, data) { if (err) { vg.error(err); return; } image.src = data; image.loaded = true; renderer._imgload -= 1; }); } else { image = new Image(); url = vg.config.baseURL + uri; image.onload = function() { vg.log("LOAD IMAGE: "+url); image.loaded = true; renderer._imgload -= 1; renderer.renderAsync(scene); }; image.src = url; } return image; }; return renderer; })();vg.canvas.Handler = (function() { var handler = function(el, model) { this._active = null; this._handlers = {}; if (el) this.initialize(el); if (model) this.model(model); }; var prototype = handler.prototype; prototype.initialize = function(el, pad, obj) { this._el = d3.select(el).node(); this._canvas = d3.select(el).select("canvas.marks").node(); this._padding = pad; this._obj = obj || null; // add event listeners var canvas = this._canvas, that = this; events.forEach(function(type) { canvas.addEventListener(type, function(evt) { prototype[type].call(that, evt); }); }); return this; }; prototype.padding = function(pad) { this._padding = pad; return this; }; prototype.model = function(model) { if (!arguments.length) return this._model; this._model = model; return this; }; prototype.handlers = function() { var h = this._handlers; return vg.keys(h).reduce(function(a, k) { return h[k].reduce(function(a, x) { return (a.push(x), a); }, a); }, []); }; // setup events var events = [ "mousedown", "mouseup", "click", "dblclick", "wheel", "keydown", "keypress", "keyup", "mousewheel" ]; events.forEach(function(type) { prototype[type] = function(evt) { this.fire(type, evt); }; }); events.push("mousemove"); events.push("mouseout"); function eventName(name) { var i = name.indexOf("."); return i < 0 ? name : name.slice(0,i); } prototype.mousemove = function(evt) { var pad = this._padding, b = evt.target.getBoundingClientRect(), x = evt.clientX - b.left, y = evt.clientY - b.top, a = this._active, p = this.pick(this._model.scene(), x, y, x-pad.left, y-pad.top); if (p === a) { this.fire("mousemove", evt); return; } else if (a) { this.fire("mouseout", evt); } this._active = p; if (p) { this.fire("mouseover", evt); } }; prototype.mouseout = function(evt) { if (this._active) { this.fire("mouseout", evt); } this._active = null; }; // to keep firefox happy prototype.DOMMouseScroll = function(evt) { this.fire("mousewheel", evt); }; // fire an event prototype.fire = function(type, evt) { var a = this._active, h = this._handlers[type]; if (a && h) { for (var i=0, len=h.length; i=0;) { if (h[i].type !== type) continue; if (!handler || h[i].handler === handler) h.splice(i, 1); } return this; }; // retrieve the current canvas context prototype.context = function() { return this._canvas.getContext("2d"); }; // find the scenegraph item at the current mouse position // returns an array of scenegraph items, from leaf node up to the root // x, y -- the absolute x, y mouse coordinates on the canvas element // gx, gy -- the relative coordinates within the current group prototype.pick = function(scene, x, y, gx, gy) { var g = this.context(), marktype = scene.marktype, picker = vg.canvas.marks.pick[marktype]; return picker.call(this, g, scene, x, y, gx, gy); }; return handler; })();vg.svg = {};vg.svg.marks = (function() { function x(o) { return o.x || 0; } function y(o) { return o.y || 0; } function yh(o) { return o.y + o.height || 0; } function key(o) { return o.key; } function size(o) { return o.size==null ? 100 : o.size; } function shape(o) { return o.shape || "circle"; } var arc_path = d3.svg.arc(), area_path = d3.svg.area().x(x).y1(y).y0(yh), line_path = d3.svg.line().x(x).y(y), symbol_path = d3.svg.symbol().type(shape).size(size); var mark_id = 0, clip_id = 0; var textAlign = { "left": "start", "center": "middle", "right": "end" }; var styles = { "fill": "fill", "fillOpacity": "fill-opacity", "stroke": "stroke", "strokeWidth": "stroke-width", "strokeOpacity": "stroke-opacity", "strokeCap": "stroke-linecap", "strokeDash": "stroke-dasharray", "strokeDashOffset": "stroke-dashoffset", "opacity": "opacity" }; var styleProps = vg.keys(styles); function style(d) { var i, n, prop, name, value, o = d.mark ? d : d.length ? d[0] : null; if (o === null) return; for (i=0, n=styleProps.length; i " + tag, m = p.selectAll(s).data(data), e = m.enter().append(tag); if (notG) { p.style("pointer-events", evts); e.each(function(d) { if (d.mark) d._svg = this; else if (d.length) d[0]._svg = this; }); } else { e.append("rect").attr("class","background").style("pointer-events",evts); } m.exit().remove(); m.each(attr); if (notG) m.each(style); else p.selectAll(s+" > rect.background").each(group_bg).each(style); return p; } function drawGroup(g, scene, index, prefix) { var p = drawMark(g, scene, index, prefix || "group_", "g", group), c = p.node().childNodes, n = c.length, i, j, m; for (i=0; i=0;) { if (h[i].type !== type) continue; if (!handler || h[i].handler === handler) { dom.removeEventListener(name, h[i].svg); h.splice(i, 1); } } return this; }; return handler; })();vg.data = {}; vg.data.ingestAll = function(data) { return vg.isTree(data) ? vg_make_tree(vg.data.ingestTree(data[0], data.children)) : data.map(vg.data.ingest); }; vg.data.ingest = function(datum, index) { return { data: datum, index: index }; }; vg.data.ingestTree = function(node, children, index) { var d = vg.data.ingest(node, index || 0), c = node[children], n, i; if (c && (n = c.length)) { d.values = Array(n); for (i=0; i= 0) file = file.slice(vg_load_fileProtocol.length); require("fs").readFile(file, callback); } function vg_load_http(url, callback) { vg.log("LOAD HTTP: " + url); var req = require("http").request(url, function(res) { var pos=0, data = new Buffer(parseInt(res.headers['content-length'],10)); res.on("error", function(err) { callback(err, null); }); res.on("data", function(x) { x.copy(data, pos); pos += x.length; }); res.on("end", function() { callback(null, data); }); }); req.on("error", function(err) { callback(err); }); req.end(); }vg.data.read = (function() { var formats = {}, parsers = { "number": vg.number, "boolean": vg["boolean"], "date": Date.parse }; function read(data, format) { var type = (format && format.type) || "json"; data = formats[type](data, format); if (format && format.parse) parseValues(data, format.parse); return data; } formats.json = function(data, format) { var d = vg.isObject(data) ? data : JSON.parse(data); if (format && format.property) { d = vg.accessor(format.property)(d); } return d; }; formats.csv = function(data, format) { var d = d3.csv.parse(data); return d; }; formats.tsv = function(data, format) { var d = d3.tsv.parse(data); return d; }; formats.topojson = function(data, format) { if (topojson == null) { vg.error("TopoJSON library not loaded."); return []; } var t = vg.isObject(data) ? data : JSON.parse(data), obj = []; if (format && format.feature) { obj = (obj = t.objects[format.feature]) ? topojson.feature(t, obj).features : (vg.error("Invalid TopoJSON object: "+format.feature), []); } else if (format && format.mesh) { obj = (obj = t.objects[format.mesh]) ? [topojson.mesh(t, t.objects[format.mesh])] : (vg.error("Invalid TopoJSON object: " + format.mesh), []); } else { vg.error("Missing TopoJSON feature or mesh parameter."); } return obj; }; formats.treejson = function(data, format) { data = vg.isObject(data) ? data : JSON.parse(data); return vg.tree(data, format.children); }; function parseValues(data, types) { var cols = vg.keys(types), p = cols.map(function(col) { return parsers[types[col]]; }), tree = vg.isTree(data); vg_parseArray(tree ? [data] : data, cols, p, tree); } function vg_parseArray(data, cols, p, tree) { var d, i, j, len, clen; for (i=0, len=data.length; i0 ? "|" : "") + String(kv); } obj = map[kstr]; if (obj === undefined) { vals.push(obj = map[kstr] = { key: kstr, keys: klist, index: vals.length, values: [] }); } obj.values.push(data[i]); } if (sort) { for (i=0, len=vals.length; i b ? 1 : 0; }); data = [data[~~(list.length/2)]]; } else { var idx = vg.array(by); data = data.slice(idx[0], idx[1]); } return data; } slice.by = function(x) { by = x; return slice; }; slice.field = function(f) { field = vg.accessor(f); return slice; }; return slice; };vg.data.sort = function() { var by = null; function sort(data) { data = (vg.isArray(data) ? data : data.values || []); data.sort(by); for (var i=0, n=data.length; ib.x ? 1 : (a.zb.z ? 1 : 0); }); // emit data series for stack layout for (x=points[0].x, i=0, j=0, k=0, n=points.length; k i) series[i++].push({x:j, y:0}); p.x = j; series[i++].push(p); } while (i < series.length) series[i++].push({x:j, y:0}); return series; } stack.point = function(field) { point = vg.accessor(field); return stack; }; stack.height = function(field) { height = vg.accessor(field); return stack; }; params.forEach(function(name) { stack[name] = function(x) { layout[name](x); return stack; } }); stack.output = function(map) { d3.keys(output).forEach(function(k) { if (map[k] !== undefined) { output[k] = map[k]; } }); return stack; }; return stack; };vg.data.stats = function() { var value = vg.accessor("data"), assign = false, median = false, output = { "count": "count", "min": "min", "max": "max", "sum": "sum", "mean": "mean", "variance": "variance", "stdev": "stdev", "median": "median" }; function reduce(data) { var min = +Infinity, max = -Infinity, sum = 0, mean = 0, M2 = 0, i, len, v, delta; var list = (vg.isArray(data) ? data : data.values || []).map(value); // compute aggregates for (i=0, len=list.length; i max) max = v; sum += v; delta = v - mean; mean = mean + delta / (i+1); M2 = M2 + delta * (v - mean); } M2 = M2 / (len - 1); var o = vg.isArray(data) ? {} : data; if (median) { list.sort(vg.numcmp); i = list.length >> 1; o[output.median] = list.length % 2 ? list[i] : (list[i-1] + list[i])/2; } o[output.count] = len; o[output.min] = min; o[output.max] = max; o[output.sum] = sum; o[output.mean] = mean; o[output.variance] = M2; o[output.stdev] = Math.sqrt(M2); if (assign) { list = (vg.isArray(data) ? data : data.values); v = {}; v[output.count] = len; v[output.min] = min; v[output.max] = max; v[output.sum] = sum; v[output.mean] = mean; v[output.variance] = M2; v[output.stdev] = Math.sqrt(M2); if (median) v[output.median] = o[output.median]; for (i=0, len=list.length; i\~\&\|\?\:\+\-\/\*\%\!\^\,\;\[\]\{\}\(\) ]+)/; return function(x) { var tokens = x.split(lexer), t, v, i, n, sq, dq; for (sq=0, dq=0, i=0, n=tokens.length; i 0) ? "\n " : " "; code += "o."+name+" = "+valueRef(name, ref)+";"; vars[name] = true; } if (vars.x2) { if (vars.x) { code += "\n if (o.x > o.x2) { " + "var t = o.x; o.x = o.x2; o.x2 = t; };"; code += "\n o.width = (o.x2 - o.x);"; } else if (vars.width) { code += "\n o.x = (o.x2 - o.width);"; } else { code += "\n o.x = o.x2;" } } if (vars.y2) { if (vars.y) { code += "\n if (o.y > o.y2) { " + "var t = o.y; o.y = o.y2; o.y2 = t; };"; code += "\n o.height = (o.y2 - o.y);"; } else if (vars.height) { code += "\n o.y = (o.y2 - o.height);"; } else { code += "\n o.y = o.y2;" } } if (hasPath(mark, vars)) code += "\n item.touch();"; code += "\n if (trans) trans.interpolate(item, o);"; try { return Function("item", "group", "trans", code); } catch (e) { vg.error(e); vg.log(code); } } function hasPath(mark, vars) { return vars.path || ((mark==="area" || mark==="line") && (vars.x || vars.x2 || vars.width || vars.y || vars.y2 || vars.height || vars.tension || vars.interpolate)); } var GROUP_VARS = { "width": 1, "height": 1, "mark.group.width": 1, "mark.group.height": 1 }; function valueRef(name, ref) { if (ref == null) return null; var isColor = name==="fill" || name==="stroke"; if (isColor) { if (ref.c) { return colorRef("hcl", ref.h, ref.c, ref.l); } else if (ref.h || ref.s) { return colorRef("hsl", ref.h, ref.s, ref.l); } else if (ref.l || ref.a) { return colorRef("lab", ref.l, ref.a, ref.b); } else if (ref.r || ref.g || ref.b) { return colorRef("rgb", ref.r, ref.g, ref.b); } } // initialize value var val = "item.datum.data"; if (ref.value !== undefined) { val = vg.str(ref.value); } // get field reference for enclosing group if (ref.group != null) { var grp = "group.datum"; if (vg.isString(ref.group)) { grp = GROUP_VARS[ref.group] ? "group." + ref.group : "group.datum["+vg.field(ref.group).map(vg.str).join("][")+"]"; } } // get data field value if (ref.field != null) { if (vg.isString(ref.field)) { val = "item.datum["+vg.field(ref.field).map(vg.str).join("][")+"]"; if (ref.group != null) { val = "this.accessor("+val+")("+grp+")"; } } else { val = "this.accessor(group.datum[" + vg.field(ref.field.group).map(vg.str).join("][") + "])(item.datum.data)"; } } else if (ref.group != null) { val = grp; } // run through scale function if (ref.scale != null) { var scale = vg.isString(ref.scale) ? vg.str(ref.scale) : (ref.scale.group ? "group" : "item") + ".datum[" + vg.str(ref.scale.group || ref.scale.field) + "]"; scale = "group.scales[" + scale + "]"; val = scale + (ref.band ? ".rangeBand()" : "("+val+")"); } // multiply, offset, return value val = "(" + (ref.mult?(vg.number(ref.mult)+" * "):"") + val + ")" + (ref.offset ? " + " + vg.number(ref.offset) : ""); return val; } function colorRef(type, x, y, z) { var xx = x ? valueRef("", x) : vg.config.color[type][0], yy = y ? valueRef("", y) : vg.config.color[type][1], zz = z ? valueRef("", z) : vg.config.color[type][2]; return "(this.d3." + type + "(" + [xx,yy,zz].join(",") + ') + "")'; } return compile; })();vg.parse.scales = (function() { var LINEAR = "linear", ORDINAL = "ordinal", LOG = "log", POWER = "pow", TIME = "time", GROUP_PROPERTY = {width: 1, height: 1}; function scales(spec, scales, db, group) { return (spec || []).reduce(function(o, def) { var name = def.name, prev = name + ":prev"; o[name] = scale(def, o[name], db, group); o[prev] = o[prev] || o[name]; return o; }, scales || {}); } function scale(def, scale, db, group) { var s = instance(def, scale), m = s.type===ORDINAL ? ordinal : quantitative, rng = range(def, group), data = vg.values(group.datum); m(def, s, rng, db, data); return s; } function instance(def, scale) { var type = def.type || LINEAR; if (!scale || type !== scale.type) { var ctor = vg.config.scale[type] || d3.scale[type]; if (!ctor) vg.error("Unrecognized scale type: " + type); (scale = ctor()).type = scale.type || type; scale.scaleName = def.name; } return scale; } function ordinal(def, scale, rng, db, data) { var domain, refs, values, str; // domain domain = def.domain; if (vg.isArray(domain)) { scale.domain(domain); } else if (vg.isObject(domain)) { refs = def.domain.fields || vg.array(def.domain); values = refs.reduce(function(values, r) { var dat = vg.values(db[r.data] || data), get = vg.accessor(vg.isString(r.field) ? r.field : "data." + vg.accessor(r.field.group)(data)); return vg.unique(dat, get, values); }, []); if (def.sort) values.sort(vg.cmp); scale.domain(values); } // range str = typeof rng[0] === 'string'; if (str || rng.length > 2) { scale.range(rng); // color or shape values } else if (def.points) { scale.rangePoints(rng, def.padding||0); } else if (def.round || def.round===undefined) { scale.rangeRoundBands(rng, def.padding||0); } else { scale.rangeBands(rng, def.padding||0); } } function quantitative(def, scale, rng, db, data) { var domain, refs, interval, z; // domain domain = [null, null]; function extract(ref, min, max, z) { var dat = vg.values(db[ref.data] || data); var fields = vg.array(ref.field).map(function(f) { return vg.isString(f) ? f : "data." + vg.accessor(f.group)(data); }); fields.forEach(function(f,i) { f = vg.accessor(f); if (min) domain[0] = d3.min([domain[0], d3.min(dat, f)]); if (max) domain[z] = d3.max([domain[z], d3.max(dat, f)]); }); } if (def.domain !== undefined) { if (vg.isArray(def.domain)) { domain = def.domain.slice(); } else if (vg.isObject(def.domain)) { refs = def.domain.fields || vg.array(def.domain); refs.forEach(function(r) { extract(r,1,1,1); }); } else { domain = def.domain; } } z = domain.length - 1; if (def.domainMin !== undefined) { if (vg.isObject(def.domainMin)) { domain[0] = null; refs = def.domainMin.fields || vg.array(def.domainMin); refs.forEach(function(r) { extract(r,1,0,z); }); } else { domain[0] = def.domainMin; } } if (def.domainMax !== undefined) { if (vg.isObject(def.domainMax)) { domain[z] = null; refs = def.domainMax.fields || vg.array(def.domainMax); refs.forEach(function(r) { extract(r,0,1,z); }); } else { domain[z] = def.domainMax; } } if (def.type !== LOG && def.type !== TIME && (def.zero || def.zero===undefined)) { domain[0] = Math.min(0, domain[0]); domain[z] = Math.max(0, domain[z]); } scale.domain(domain); // range // vertical scales should flip by default, so use XOR here if (def.range === "height") rng = rng.reverse(); scale[def.round && scale.rangeRound ? "rangeRound" : "range"](rng); if (def.exponent && def.type===POWER) scale.exponent(def.exponent); if (def.clamp) scale.clamp(true); if (def.nice) { if (def.type === TIME) { interval = d3.time[def.nice]; if (!interval) vg.error("Unrecognized interval: " + interval); scale.nice(interval); } else { scale.nice(); } } } function range(def, group) { var rng = [null, null]; if (def.range !== undefined) { if (typeof def.range === 'string') { if (GROUP_PROPERTY[def.range]) { rng = [0, group[def.range]]; } else if (vg.config.range[def.range]) { rng = vg.config.range[def.range]; } else { vg.error("Unrecogized range: "+def.range); return rng; } } else if (vg.isArray(def.range)) { rng = def.range; } else { rng = [0, def.range]; } } if (def.rangeMin !== undefined) { rng[0] = def.rangeMin; } if (def.rangeMax !== undefined) { rng[rng.length-1] = def.rangeMax; } if (def.reverse !== undefined) { var rev = def.reverse; if (vg.isObject(rev)) { rev = vg.accessor(rev.field)(group.datum); } if (rev) rng = rng.reverse(); } return rng; } return scales; })(); vg.parse.spec = function(spec, callback, viewFactory) { viewFactory = viewFactory || vg.ViewFactory; function parse(spec) { // protect against subsequent spec modification spec = vg.duplicate(spec); var width = spec.width || 500, height = spec.height || 500, viewport = spec.viewport || null; var defs = { width: width, height: height, viewport: viewport, padding: vg.parse.padding(spec.padding), marks: vg.parse.marks(spec, width, height), data: vg.parse.data(spec.data, function() { callback(viewConstructor); }) }; var viewConstructor = viewFactory(defs); } vg.isObject(spec) ? parse(spec) : d3.json(spec, function(error, json) { error ? vg.error(error) : parse(json); }); };vg.parse.transform = function(def) { var tx = vg.data[def.type](); vg.keys(def).forEach(function(k) { if (k === 'type') return; (tx[k])(def[k]); }); return tx; };vg.scene = {}; vg.scene.GROUP = "group", vg.scene.ENTER = 0, vg.scene.UPDATE = 1, vg.scene.EXIT = 2; vg.scene.DEFAULT_DATA = {"sentinel":1} vg.scene.data = function(data, parentData) { var DEFAULT = vg.scene.DEFAULT_DATA; // if data is undefined, inherit or use default data = vg.values(data || parentData || [DEFAULT]); // if inheriting default data, ensure its in an array if (data === DEFAULT) data = [DEFAULT]; return data; }; vg.scene.fontString = function(o) { return (o.fontStyle ? o.fontStyle + " " : "") + (o.fontVariant ? o.fontVariant + " " : "") + (o.fontWeight ? o.fontWeight + " " : "") + (o.fontSize != null ? o.fontSize : vg.config.render.fontSize) + "px " + (o.font || vg.config.render.font); };vg.scene.Item = (function() { function item(mark) { this.mark = mark; } var prototype = item.prototype; prototype.hasPropertySet = function(name) { var props = this.mark.def.properties; return props && props[name] != null; }; prototype.cousin = function(offset, index) { if (offset === 0) return this; offset = offset || -1; var mark = this.mark, group = mark.group, iidx = index==null ? mark.items.indexOf(this) : index, midx = group.items.indexOf(mark) + offset; return group.items[midx].items[iidx]; }; prototype.sibling = function(offset) { if (offset === 0) return this; offset = offset || -1; var mark = this.mark, iidx = mark.items.indexOf(this) + offset; return mark.items[iidx]; }; prototype.remove = function() { var item = this, list = item.mark.items, i = list.indexOf(item); if (i >= 0) (i===list.length-1) ? list.pop() : list.splice(i, 1); return item; }; prototype.touch = function() { if (this.cache) this.cache = null; if (this.mark.cache) this.mark.cache = null; }; return item; })(); vg.scene.item = function(mark) { return new vg.scene.Item(mark); };vg.scene.visit = function(node, func) { var i, n, items; if (func(node)) return true; if (items = node.items) { for (i=0, n=items.length; i0) s += "|"; s += String(f[i](d)); } return s; } } return build; })();vg.scene.bounds = (function() { var parse = vg.canvas.path.parse, boundPath = vg.canvas.path.bounds, areaPath = vg.canvas.path.area, linePath = vg.canvas.path.line, halfpi = Math.PI / 2, sqrt3 = Math.sqrt(3), tan30 = Math.tan(30 * Math.PI / 180), gfx = null; function context() { return gfx || (gfx = (vg.config.isNode ? new (require("canvas"))(1,1) : d3.select("body").append("canvas") .attr("class", "vega_hidden") .attr("width", 1) .attr("height", 1) .style("display", "none") .node()) .getContext("2d")); } function pathBounds(o, path, bounds) { if (path == null) { bounds.set(0, 0, 0, 0); } else { boundPath(path, bounds); if (o.stroke && o.opacity !== 0 && o.strokeWidth > 0) { bounds.expand(o.strokeWidth); } } return bounds; } function path(o, bounds) { var p = o.path ? o["path:parsed"] || (o["path:parsed"] = parse(o.path)) : null; return pathBounds(o, p, bounds); } function area(o, bounds) { var items = o.mark.items, o = items[0]; var p = o["path:parsed"] || (o["path:parsed"]=parse(areaPath(items))); return pathBounds(items[0], p, bounds); } function line(o, bounds) { var items = o.mark.items, o = items[0]; var p = o["path:parsed"] || (o["path:parsed"]=parse(linePath(items))); return pathBounds(items[0], p, bounds); } function rect(o, bounds) { var x = o.x || 0, y = o.y || 0, w = (x + o.width) || 0, h = (y + o.height) || 0; bounds.set(x, y, w, h); if (o.stroke && o.opacity !== 0 && o.strokeWidth > 0) { bounds.expand(o.strokeWidth); } return bounds; } function image(o, bounds) { var w = o.width || 0, h = o.height || 0, x = (o.x||0) - (o.align === "center" ? w/2 : (o.align === "right" ? w : 0)), y = (o.y||0) - (o.baseline === "middle" ? h/2 : (o.baseline === "bottom" ? h : 0)); return bounds.set(x, y, x+w, y+h); } function rule(o, bounds) { var x1, y1; bounds.set( x1 = o.x || 0, y1 = o.y || 0, o.x2 != null ? o.x2 : x1, o.y2 != null ? o.y2 : y1 ); if (o.stroke && o.opacity !== 0 && o.strokeWidth > 0) { bounds.expand(o.strokeWidth); } return bounds; } function arc(o, bounds) { var cx = o.x || 0, cy = o.y || 0, ir = o.innerRadius || 0, or = o.outerRadius || 0, sa = (o.startAngle || 0) - halfpi, ea = (o.endAngle || 0) - halfpi, xmin = Infinity, xmax = -Infinity, ymin = Infinity, ymax = -Infinity, a, i, n, x, y, ix, iy, ox, oy; var angles = [sa, ea], s = sa - (sa%halfpi); for (i=0; i<4 && s 0) { bounds.expand(o.strokeWidth); } return bounds; } function symbol(o, bounds) { var size = o.size != null ? o.size : 100, x = o.x || 0, y = o.y || 0, r, t, rx, ry; switch (o.shape) { case "cross": r = Math.sqrt(size / 5) / 2; t = 3*r; bounds.set(x-t, y-r, x+t, y+r); break; case "diamond": ry = Math.sqrt(size / (2 * tan30)); rx = ry * tan30; bounds.set(x-rx, y-ry, x+rx, y+ry); break; case "square": t = Math.sqrt(size); r = t / 2; bounds.set(x-r, y-r, x+r, y+r); break; case "triangle-down": rx = Math.sqrt(size / sqrt3); ry = rx * sqrt3 / 2; bounds.set(x-rx, y-ry, x+rx, y+ry); break; case "triangle-up": rx = Math.sqrt(size / sqrt3); ry = rx * sqrt3 / 2; bounds.set(x-rx, y-ry, x+rx, y+ry); break; default: r = Math.sqrt(size/Math.PI); bounds.set(x-r, y-r, x+r, y+r); } if (o.stroke && o.opacity !== 0 && o.strokeWidth > 0) { bounds.expand(o.strokeWidth); } return bounds; } function text(o, bounds, noRotate) { var x = (o.x || 0) + (o.dx || 0), y = (o.y || 0) + (o.dy || 0), h = o.fontSize || vg.config.render.fontSize, a = o.align, b = o.baseline, g = context(), w; g.font = vg.scene.fontString(o); g.textAlign = a || "left"; g.textBaseline = b || "alphabetic"; w = g.measureText(o.text || "").width; // horizontal if (a === "center") { x = x - (w / 2); } else if (a === "right") { x = x - w; } else { // left by default, do nothing } /// TODO find a robust solution for heights. /// These offsets work for some but not all fonts. // vertical if (b === "top") { y = y + (h/5); } else if (b === "bottom") { y = y - h; } else if (b === "middle") { y = y - (h/2) + (h/10); } else { y = y - 4*h/5; // alphabetic by default } bounds.set(x, y, x+w, y+h); if (o.angle && !noRotate) { bounds.rotate(o.angle*Math.PI/180, o.x||0, o.y||0); } return bounds.expand(noRotate ? 0 : 1); } function group(g, bounds, includeLegends) { var axes = g.axisItems || [], legends = g.legendItems || [], j, m; for (j=0, m=axes.length; j 1) f = 1; e = curr.ease(f); for (i=0, n=curr.length; i 1 ? +y : tickMajorSize, end = n > 0 ? +arguments[n] : tickMajorSize; if (tickMajorSize !== major || tickMinorSize !== minor || tickEndSize !== end) { reset(); } tickMajorSize = major; tickMinorSize = minor; tickEndSize = end; return axis; }; axis.tickSubdivide = function(x) { if (!arguments.length) return tickSubdivide; tickSubdivide = +x; return axis; }; axis.offset = function(x) { if (!arguments.length) return offset; offset = vg.isObject(x) ? x : +x; return axis; }; axis.tickPadding = function(x) { if (!arguments.length) return tickPadding; if (tickPadding !== +x) { tickPadding = +x; reset(); } return axis; }; axis.titleOffset = function(x) { if (!arguments.length) return titleOffset; if (titleOffset !== +x) { titleOffset = +x; reset(); } return axis; }; axis.layer = function(x) { if (!arguments.length) return layer; if (layer !== x) { layer = x; reset(); } return axis; }; axis.grid = function(x) { if (!arguments.length) return grid; if (grid !== x) { grid = x; reset(); } return axis; }; axis.gridLineProperties = function(x) { if (!arguments.length) return gridLineStyle; if (gridLineStyle !== x) { gridLineStyle = x; } return axis; }; axis.majorTickProperties = function(x) { if (!arguments.length) return majorTickStyle; if (majorTickStyle !== x) { majorTickStyle = x; } return axis; }; axis.minorTickProperties = function(x) { if (!arguments.length) return minorTickStyle; if (minorTickStyle !== x) { minorTickStyle = x; } return axis; }; axis.tickLabelProperties = function(x) { if (!arguments.length) return tickLabelStyle; if (tickLabelStyle !== x) { tickLabelStyle = x; } return axis; }; axis.titleProperties = function(x) { if (!arguments.length) return titleStyle; if (titleStyle !== x) { titleStyle = x; } return axis; }; axis.domainProperties = function(x) { if (!arguments.length) return domainStyle; if (domainStyle !== x) { domainStyle = x; } return axis; }; axis.reset = function() { reset(); }; return axis; }; var vg_axisOrients = {top: 1, right: 1, bottom: 1, left: 1}; function vg_axisSubdivide(scale, ticks, m) { subticks = []; if (m && ticks.length > 1) { var extent = vg_axisScaleExtent(scale.domain()), subticks, i = -1, n = ticks.length, d = (ticks[1] - ticks[0]) / ++m, j, v; while (++i < n) { for (j = m; --j > 0;) { if ((v = +ticks[i] - j * d) >= extent[0]) { subticks.push(v); } } } for (--i, j = 0; ++j < m && (v = +ticks[i] + j * d) < extent[1];) { subticks.push(v); } } return subticks; } function vg_axisScaleExtent(domain) { var start = domain[0], stop = domain[domain.length - 1]; return start < stop ? [start, stop] : [stop, start]; } function vg_axisScaleRange(scale) { return scale.rangeExtent ? scale.rangeExtent() : vg_axisScaleExtent(scale.range()); } var vg_axisAlign = { bottom: "center", top: "center", left: "right", right: "left" }; var vg_axisBaseline = { bottom: "top", top: "bottom", left: "middle", right: "middle" }; function vg_axisLabelExtend(orient, labels, oldScale, newScale, size, pad) { size = Math.max(size, 0) + pad; if (orient === "left" || orient === "top") { size *= -1; } if (orient === "top" || orient === "bottom") { vg.extend(labels.properties.enter, { x: oldScale, y: {value: size}, }); vg.extend(labels.properties.update, { x: newScale, y: {value: size}, align: {value: "center"}, baseline: {value: vg_axisBaseline[orient]} }); } else { vg.extend(labels.properties.enter, { x: {value: size}, y: oldScale, }); vg.extend(labels.properties.update, { x: {value: size}, y: newScale, align: {value: vg_axisAlign[orient]}, baseline: {value: "middle"} }); } } function vg_axisTicksExtend(orient, ticks, oldScale, newScale, size) { var sign = (orient === "left" || orient === "top") ? -1 : 1; if (size === Infinity) { size = (orient === "top" || orient === "bottom") ? {group: "mark.group.height", mult: -sign} : {group: "mark.group.width", mult: -sign}; } else { size = {value: sign * size}; } if (orient === "top" || orient === "bottom") { vg.extend(ticks.properties.enter, { x: oldScale, y: {value: 0}, y2: size }); vg.extend(ticks.properties.update, { x: newScale, y: {value: 0}, y2: size }); vg.extend(ticks.properties.exit, { x: newScale, }); } else { vg.extend(ticks.properties.enter, { x: {value: 0}, x2: size, y: oldScale }); vg.extend(ticks.properties.update, { x: {value: 0}, x2: size, y: newScale }); vg.extend(ticks.properties.exit, { y: newScale, }); } } function vg_axisTitleExtend(orient, title, range, offset) { var mid = ~~((range[1] - range[0]) / 2), sign = (orient === "top" || orient === "left") ? -1 : 1; if (orient === "bottom" || orient === "top") { vg.extend(title.properties.update, { x: {value: mid}, y: {value: sign*offset}, angle: {value: 0} }); } else { vg.extend(title.properties.update, { x: {value: sign*offset}, y: {value: mid}, angle: {value: -90} }); } } function vg_axisDomainExtend(orient, domain, range, size) { var path; if (orient === "top" || orient === "left") { size = -1 * size; } if (orient === "bottom" || orient === "top") { path = "M" + range[0] + "," + size + "V0H" + range[1] + "V" + size; } else { path = "M" + size + "," + range[0] + "H0V" + range[1] + "H" + size; } domain.properties.update.path = {value: path}; } function vg_axisUpdate(item, group, trans) { var o = trans ? {} : item, offset = item.mark.def.offset, orient = item.mark.def.orient, width = group.width, height = group.height; // TODO fallback to global w,h? if (vg.isObject(offset)) { offset = -group.scales[offset.scale](offset.value); } switch (orient) { case "left": { o.x = -offset; o.y = 0; break; } case "right": { o.x = width + offset; o.y = 0; break; } case "bottom": { o.x = 0; o.y = height + offset; break; } case "top": { o.x = 0; o.y = -offset; break; } default: { o.x = 0; o.y = 0; } } if (trans) trans.interpolate(item, o); } function vg_axisTicks() { return { type: "rule", interactive: false, key: "data", properties: { enter: { stroke: {value: vg.config.axis.tickColor}, strokeWidth: {value: vg.config.axis.tickWidth}, opacity: {value: 1e-6} }, exit: { opacity: {value: 1e-6} }, update: { opacity: {value: 1} } } }; } function vg_axisTickLabels() { return { type: "text", interactive: true, key: "data", properties: { enter: { fill: {value: vg.config.axis.tickLabelColor}, font: {value: vg.config.axis.tickLabelFont}, fontSize: {value: vg.config.axis.tickLabelFontSize}, opacity: {value: 1e-6}, text: {field: "label"} }, exit: { opacity: {value: 1e-6} }, update: { opacity: {value: 1} } } }; } function vg_axisTitle() { return { type: "text", interactive: true, properties: { enter: { font: {value: vg.config.axis.titleFont}, fontSize: {value: vg.config.axis.titleFontSize}, fontWeight: {value: vg.config.axis.titleFontWeight}, fill: {value: vg.config.axis.titleColor}, align: {value: "center"}, baseline: {value: "middle"}, text: {field: "data"} }, update: {} } }; } function vg_axisDomain() { return { type: "path", interactive: false, properties: { enter: { x: {value: 0.5}, y: {value: 0.5}, stroke: {value: vg.config.axis.axisColor}, strokeWidth: {value: vg.config.axis.axisWidth} }, update: {} } }; } vg.scene.legend = function() { var size = null, shape = null, fill = null, stroke = null, spacing = null, values = null, format = null, title = undefined, orient = "right", offset = vg.config.legend.offset, padding = vg.config.legend.padding, legendDef, tickArguments = [5], legendStyle = {}, symbolStyle = {}, gradientStyle = {}, titleStyle = {}, labelStyle = {}; var legend = {}, legendDef = null; function reset() { legendDef = null; } legend.def = function() { var scale = size || shape || fill || stroke; if (!legendDef) { legendDef = (scale===fill || scale===stroke) && !discrete(scale.type) ? quantDef(scale) : ordinalDef(scale); } legendDef.orient = orient; legendDef.offset = offset; legendDef.padding = padding; return legendDef; }; function discrete(type) { return type==="ordinal" || type==="quantize" || type==="quantile" || type==="threshold"; } function ordinalDef(scale) { var def = o_legend_def(size, shape, fill, stroke); // generate data var data = (values == null ? (scale.ticks ? scale.ticks.apply(scale, tickArguments) : scale.domain()) : values).map(vg.data.ingest); var fmt = format==null ? (scale.tickFormat ? scale.tickFormat.apply(scale, tickArguments) : String) : format; // determine spacing between legend entries var fs, range, offset, pad=5, domain = d3.range(data.length); if (size) { range = data.map(function(x) { return Math.sqrt(size(x.data)); }); offset = d3.max(range); range = range.reduce(function(a,b,i,z) { if (i > 0) a[i] = a[i-1] + z[i-1]/2 + pad; return (a[i] += b/2, a); }, [0]).map(Math.round); } else { offset = Math.round(Math.sqrt(vg.config.legend.symbolSize)); range = spacing || (fs = labelStyle.fontSize) && (fs.value + pad) || (vg.config.legend.labelFontSize + pad); range = domain.map(function(d,i) { return Math.round(offset/2 + i*range); }); } // account for padding and title size var sz = padding, ts; if (title) { ts = titleStyle.fontSize; sz += 5 + ((ts && ts.value) || vg.config.legend.titleFontSize); } for (var i=0, n=range.length; i this._width ? Math.ceil(+b.x2 - this._width) + inset : 0, b = b.y2 > this._height ? Math.ceil(+b.y2 - this._height) + inset : 0; pad = {left:l, top:t, right:r, bottom:b}; if (this._strict) { this._autopad = 0; this._padding = pad; this._width = Math.max(0, this.__width - (l+r)); this._height = Math.max(0, this.__height - (t+b)); this._model.width(this._width); this._model.height(this._height); if (this._el) this.initialize(this._el.parentNode); this.update({props:"enter"}).update({props:"update"}); } else { this.padding(pad).update(opt); } return this; }; prototype.viewport = function(size) { if (!arguments.length) return this._viewport; if (this._viewport !== size) { this._viewport = size; if (this._el) this.initialize(this._el.parentNode); } return this; }; prototype.renderer = function(type) { if (!arguments.length) return this._io; if (type === "canvas") type = vg.canvas; if (type === "svg") type = vg.svg; if (this._io !== type) { this._io = type; this._renderer = null; if (this._el) this.initialize(this._el.parentNode); if (this._build) this.render(); } return this; }; prototype.defs = function(defs) { if (!arguments.length) return this._model.defs(); this._model.defs(defs); return this; }; prototype.data = function(data) { if (!arguments.length) return this._model.data(); var ingest = vg.keys(data).reduce(function(d, k) { return (d[k] = vg.data.ingestAll(data[k]), d); }, {}); this._model.data(ingest); this._build = false; return this; }; prototype.model = function(model) { if (!arguments.length) return this._model; if (this._model !== model) { this._model = model; if (this._handler) this._handler.model(model); } return this; }; prototype.initialize = function(el) { var v = this, prevHandler, w = v._width, h = v._height, pad = v._padding; // clear pre-existing container d3.select(el).select("div.vega").remove(); // add div container this._el = el = d3.select(el) .append("div") .attr("class", "vega") .style("position", "relative") .node(); if (v._viewport) { d3.select(el) .style("width", (v._viewport[0] || w)+"px") .style("height", (v._viewport[1] || h)+"px") .style("overflow", "auto"); } // renderer v._renderer = (v._renderer || new this._io.Renderer()) .initialize(el, w, h, pad); // input handler prevHandler = v._handler; v._handler = new this._io.Handler() .initialize(el, pad, v) .model(v._model); if (prevHandler) { prevHandler.handlers().forEach(function(h) { v._handler.on(h.type, h.handler); }); } return this; }; prototype.render = function(items) { this._renderer.render(this._model.scene(), items); return this; }; prototype.on = function() { this._handler.on.apply(this._handler, arguments); return this; }; prototype.off = function() { this._handler.off.apply(this._handler, arguments); return this; }; prototype.update = function(opt) { opt = opt || {}; var view = this, trans = opt.duration ? vg.scene.transition(opt.duration, opt.ease) : null; view._build = view._build || (view._model.build(), true); view._model.encode(trans, opt.props, opt.items); if (trans) { trans.start(function(items) { view._renderer.render(view._model.scene(), items); }); } else view.render(opt.items); return view.autopad(opt); }; return view; })(); // view constructor factory // takes definitions from parsed specification as input // returns a view constructor vg.ViewFactory = function(defs) { return function(opt) { opt = opt || {}; var v = new vg.View() .width(defs.width) .height(defs.height) .padding(defs.padding) .viewport(defs.viewport) .renderer(opt.renderer || "canvas") .defs(defs); if (defs.data.load) v.data(defs.data.load); if (opt.data) v.data(opt.data); if (opt.el) v.initialize(opt.el); if (opt.hover !== false) { v.on("mouseover", function(evt, item) { if (item.hasPropertySet("hover")) { this.update({props:"hover", items:item}); } }) .on("mouseout", function(evt, item) { if (item.hasPropertySet("hover")) { this.update({props:"update", items:item}); } }); } return v; }; }; vg.Spec = (function() { var spec = function(s) { this.spec = { width: 500, height: 500, padding: 0, data: [], scales: [], axes: [], marks: [] }; if (s) vg.extend(this.spec, s); }; var prototype = spec.prototype; prototype.width = function(w) { this.spec.width = w; return this; }; prototype.height = function(h) { this.spec.height = h; return this; }; prototype.padding = function(p) { this.spec.padding = p; return this; }; prototype.viewport = function(v) { this.spec.viewport = v; return this; }; prototype.data = function(name, params) { if (!params) params = vg.isString(name) ? {name: name} : name; else params.name = name; this.spec.data.push(params); return this; }; prototype.scale = function(name, params) { if (!params) params = vg.isString(name) ? {name: name} : name; else params.name = name; this.spec.scales.push(params); return this; }; prototype.axis = function(params) { this.spec.axes.push(params); return this; }; prototype.mark = function(type, mark) { if (!mark) mark = {type: type}; else mark.type = type; mark.properties = {}; this.spec.marks.push(mark); var that = this; return { from: function(name, obj) { mark.from = obj ? (obj.data = name, obj) : vg.isString(name) ? {data: name} : name; return this; }, prop: function(name, obj) { mark.properties[name] = vg.keys(obj).reduce(function(o,k) { var v = obj[k]; return (o[k] = vg.isObject(v) ? v : {value: v}, o); }, {}); return this; }, done: function() { return that; } }; }; prototype.parse = function(callback) { vg.parse.spec(this.spec, callback); }; prototype.json = function() { return this.spec; }; return spec; })(); vg.spec = function(s) { return new vg.Spec(s); }; vg.headless = {};vg.headless.View = (function() { var view = function(width, height, pad, type) { this._canvas = null; this._type = type; this._el = "body"; this._build = false; this._model = new vg.Model(); this._width = this.__width = width || 500; this._height = this.__height = height || 500; this._autopad = 1; this._padding = pad || {top:0, left:0, bottom:0, right:0}; this._renderer = new vg[type].Renderer(); this.initialize(); }; var prototype = view.prototype; prototype.el = function(el) { if (!arguments.length) return this._el; if (this._el !== el) { this._el = el; this.initialize(); } return this; }; prototype.width = function(width) { if (!arguments.length) return this._width; if (this._width !== width) { this._width = width; this.initialize(); this._model.width(width); } return this; }; prototype.height = function(height) { if (!arguments.length) return this._height; if (this._height !== height) { this._height = height; this.initialize(); this._model.height(this._height); } return this; }; prototype.padding = function(pad) { if (!arguments.length) return this._padding; if (this._padding !== pad) { if (vg.isString(pad)) { this._autopad = 1; this._padding = {top:0, left:0, bottom:0, right:0}; this._strict = (pad === "strict"); } else { this._autopad = 0; this._padding = pad; this._strict = false; } this.initialize(); } return this; }; prototype.autopad = function(opt) { if (this._autopad < 1) return this; else this._autopad = 0; var pad = this._padding, b = this._model.scene().bounds, inset = vg.config.autopadInset, l = b.x1 < 0 ? Math.ceil(-b.x1) + inset : 0, t = b.y1 < 0 ? Math.ceil(-b.y1) + inset : 0, r = b.x2 > this._width ? Math.ceil(+b.x2 - this._width) + inset : 0, b = b.y2 > this._height ? Math.ceil(+b.y2 - this._height) + inset : 0; pad = {left:l, top:t, right:r, bottom:b}; if (this._strict) { this._autopad = 0; this._padding = pad; this._width = Math.max(0, this.__width - (l+r)); this._height = Math.max(0, this.__height - (t+b)); this._model.width(this._width); this._model.height(this._height); if (this._el) this.initialize(); this.update({props:"enter"}).update({props:"update"}); } else { this.padding(pad).update(opt); } return this; }; prototype.viewport = function() { if (!arguments.length) return null; return this; }; prototype.defs = function(defs) { if (!arguments.length) return this._model.defs(); this._model.defs(defs); return this; }; prototype.data = function(data) { if (!arguments.length) return this._model.data(); var ingest = vg.keys(data).reduce(function(d, k) { return (d[k] = vg.data.ingestAll(data[k]), d); }, {}); this._model.data(ingest); this._build = false; return this; }; prototype.renderer = function() { return this._renderer; }; prototype.canvas = function() { return this._canvas; }; prototype.canvasAsync = function(callback) { var r = this._renderer, view = this; function wait() { if (r.pendingImages() === 0) { view.render(); // re-render with all images callback(view._canvas); } else { setTimeout(wait, 10); } } // if images loading, poll until ready (r.pendingImages() > 0) ? wait() : callback(this._canvas); }; prototype.svg = function() { if (this._type !== "svg") return null; var p = this._padding, w = this._width + (p ? p.left + p.right : 0), h = this._height + (p ? p.top + p.bottom : 0); // build svg text var svg = d3.select(this._el) .select("svg").node().innerHTML .replace(/ href=/g, " xlink:href="); // ns hack. sigh. return '' + svg + '' }; prototype.initialize = function() { var w = this._width, h = this._height, pad = this._padding; if (this._type === "svg") { this.initSVG(w, h, pad); } else { this.initCanvas(w, h, pad); } return this; }; prototype.initCanvas = function(w, h, pad) { var Canvas = require("canvas"), tw = w + pad.left + pad.right, th = h + pad.top + pad.bottom, canvas = this._canvas = new Canvas(tw, th), ctx = canvas.getContext("2d"); // setup canvas context ctx.setTransform(1, 0, 0, 1, pad.left, pad.top); // configure renderer this._renderer.context(ctx); this._renderer.resize(w, h, pad); }; prototype.initSVG = function(w, h, pad) { var tw = w + pad.left + pad.right, th = h + pad.top + pad.bottom; // configure renderer this._renderer.initialize(this._el, w, h, pad); } prototype.render = function(items) { this._renderer.render(this._model.scene(), items); return this; }; prototype.update = function(opt) { opt = opt || {}; var view = this; view._build = view._build || (view._model.build(), true); view._model.encode(null, opt.props, opt.items); view.render(opt.items); return view.autopad(opt); }; return view; })(); // headless view constructor factory // takes definitions from parsed specification as input // returns a view constructor vg.headless.View.Factory = function(defs) { return function(opt) { opt = opt || {}; var w = defs.width, h = defs.height, p = defs.padding, r = opt.renderer || "canvas", v = new vg.headless.View(w, h, p, r).defs(defs); if (defs.data.load) v.data(defs.data.load); if (opt.data) v.data(opt.data); return v; }; };vg.headless.render = function(opt, callback) { function draw(chart) { try { // create and render view var view = chart({ data: opt.data, renderer: opt.renderer }).update(); if (opt.renderer === "svg") { // extract rendered svg callback(null, {svg: view.svg()}); } else { // extract rendered canvas, waiting for any images to load view.canvasAsync(function(canvas) { callback(null, {canvas: canvas}); }); } } catch (err) { callback(err, null); } } vg.parse.spec(opt.spec, draw, vg.headless.View.Factory); }; return vg; })(d3, typeof topojson === "undefined" ? null : topojson); // assumes D3 and topojson in global namespace