http://git-wip-us.apache.org/repos/asf/stratos/blob/19a021ae/extensions/das/modules/artifacts/monitoring-dashboard/capps/stratos-monitoring-service/GadgetScalingDetails/Scaling_Details/js/vega.js ---------------------------------------------------------------------- diff --git a/extensions/das/modules/artifacts/monitoring-dashboard/capps/stratos-monitoring-service/GadgetScalingDetails/Scaling_Details/js/vega.js b/extensions/das/modules/artifacts/monitoring-dashboard/capps/stratos-monitoring-service/GadgetScalingDetails/Scaling_Details/js/vega.js new file mode 100644 index 0000000..076e19b --- /dev/null +++ b/extensions/das/modules/artifacts/monitoring-dashboard/capps/stratos-monitoring-service/GadgetScalingDetails/Scaling_Details/js/vega.js @@ -0,0 +1,7583 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +// Define module using Universal Module Definition pattern +// https://github.com/umdjs/umd/blob/master/amdWeb.js + +(function (factory) { + if (typeof define === 'function' && define.amd) { + // Support AMD. Register as an anonymous module. + // NOTE: List all dependencies in AMD style + define(['d3', 'topojson'], factory); + } else { + // No AMD. Set module as a global variable + // NOTE: Pass dependencies to factory function + // (assume that both d3 and topojson are also global.) + var tj = (typeof topojson === 'undefined') ? null : topojson; + vg = factory(d3, tj); + } +}( +//NOTE: The dependencies are passed to this function +function (d3, topojson) { +//--------------------------------------------------- +// BEGIN code for this module +//--------------------------------------------------- + + var vg = { + version: "1.4.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.true = function() { return true; }; + +vg.extend = function(obj) { + for (var x, name, i=1, len=arguments.length; i<len; ++i) { + x = arguments[i]; + for (name in x) { obj[name] = x[name]; } + } + return obj; +}; + +vg.duplicate = function(obj) { + return JSON.parse(JSON.stringify(obj)); +}; + +vg.field = function(f) { + return f.split("\\.") + .map(function(d) { return d.split("."); }) + .reduce(function(a, b) { + if (a.length) { a[a.length-1] += "." + b.shift(); } + a.push.apply(a, b); + return a; + }, []); +}; + +vg.accessor = function(f) { + var s; + return (vg.isFunction(f) || f==null) + ? f : vg.isString(f) && (s=vg.field(f)).length > 1 + ? function(x) { return s.reduce(function(x,f) { + return x[f]; + }, x); + } + : function(x) { return x[f]; }; +}; + +vg.mutator = function(f) { + var s; + return vg.isString(f) && (s=vg.field(f)).length > 1 + ? function(x, v) { + for (var i=0; i<s.length-1; ++i) x = x[s[i]]; + x[s[i]] = v; + } + : function(x, v) { x[f] = v; }; +}; + +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<n; ++i) { + f = sort[i]; x = f(a); y = f(b); + if (x < y) return -1 * sign[i]; + if (x > y) return sign[i]; + } + return 0; + }; +}; + +vg.cmp = function(a, b) { return a<b ? -1 : a>b ? 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<n; ++i) { + v = f(data[i]); + if (results.indexOf(v) < 0) results.push(v); + } + return results; +}; + +vg.minIndex = function(data, f) { + if (!vg.isArray(data) || data.length==0) return -1; + f = f || vg.identity; + var idx = 0, min = f(data[0]), v = min; + for (var i=1, n=data.length; i<n; ++i) { + v = f(data[i]); + if (v < min) { min = v; idx = i; } + } + return idx; +}; + +vg.maxIndex = function(data, f) { + if (!vg.isArray(data) || data.length==0) return -1; + f = f || vg.identity; + var idx = 0, max = f(data[0]), v = max; + for (var i=1, n=data.length; i<n; ++i) { + v = f(data[i]); + if (v > 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; + +// Allows domain restriction when using data loading via XHR. +// To enable, set it to a list of allowed domains +// e.g., ['wikipedia.org', 'eff.org'] +vg.config.domainWhiteList = false; + +// If true, disable potentially unsafe transforms (filter, formula) +// involving possible JavaScript injection attacks. +vg.config.safeMode = false; + +// 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<len; i++) { + currentPath = path[i]; + chunks = currentPath.slice(1).trim().replace(re[2],'$1###-').split(re[3]); + chunksParsed = [currentPath.charAt(0)]; + + for (var j = 0, jlen = chunks.length; j < jlen; j++) { + parsed = parseFloat(chunks[j]); + if (!isNaN(parsed)) { + chunksParsed.push(parsed); + } + } + + var command = chunksParsed[0].toLowerCase(), + commandLength = cmdLength[command]; + + if (chunksParsed.length - 1 > 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<segs.length; i++) { + var bez = segmentToBezier.apply(null, segs[i]); + g.bezierCurveTo.apply(g, bez); + bounds.add(bez[0]-l, bez[1]-t); + bounds.add(bez[2]-l, bez[3]-t); + bounds.add(bez[4]-l, bez[5]-t); + } + } + + function boundArc(x, y, coords, bounds) { + 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<segs.length; i++) { + var bez = segmentToBezier.apply(null, segs[i]); + bounds.add(bez[0], bez[1]); + bounds.add(bez[2], bez[3]); + bounds.add(bez[4], bez[5]); + } + } + + var arcToSegmentsCache = { }, + segmentToBezierCache = { }, + join = Array.prototype.join, + argsStr; + + // Copied from Inkscape svgtopdf, thanks! + function arcToSegments(x, y, rx, ry, large, sweep, rotateX, ox, oy) { + argsStr = join.call(arguments); + if (arcToSegmentsCache[argsStr]) { + return arcToSegmentsCache[argsStr]; + } + + var th = rotateX * (Math.PI/180); + var sin_th = Math.sin(th); + var cos_th = Math.cos(th); + rx = Math.abs(rx); + ry = Math.abs(ry); + var px = cos_th * (ox - x) * 0.5 + sin_th * (oy - y) * 0.5; + var py = cos_th * (oy - y) * 0.5 - sin_th * (ox - x) * 0.5; + var pl = (px*px) / (rx*rx) + (py*py) / (ry*ry); + if (pl > 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<segments; i++) { + var th2 = th0 + i * th_arc / segments; + var th3 = th0 + (i+1) * th_arc / segments; + result[i] = [xc, yc, th2, th3, rx, ry, sin_th, cos_th]; + } + + return (arcToSegmentsCache[argsStr] = result); + } + + function segmentToBezier(cx, cy, th0, th1, rx, ry, sin_th, cos_th) { + argsStr = join.call(arguments); + if (segmentToBezierCache[argsStr]) { + return segmentToBezierCache[argsStr]; + } + + var a00 = cos_th * rx; + var a01 = -sin_th * ry; + var a10 = sin_th * rx; + var a11 = cos_th * ry; + + var cos_th0 = Math.cos(th0); + var sin_th0 = Math.sin(th0); + var cos_th1 = Math.cos(th1); + var sin_th1 = Math.sin(th1); + + var th_half = 0.5 * (th1 - th0); + var sin_th_h2 = Math.sin(th_half * 0.5); + var t = (8/3) * sin_th_h2 * sin_th_h2 / Math.sin(th_half); + var x1 = cx + cos_th0 - t * sin_th0; + var y1 = cy + sin_th0 + t * cos_th0; + var x3 = cx + cos_th1; + var y3 = cy + sin_th1; + var x2 = x3 + t * sin_th1; + var y2 = y3 - t * cos_th1; + + return (segmentToBezierCache[argsStr] = [ + a00 * x1 + a01 * y1, a10 * x1 + a11 * y1, + a00 * x2 + a01 * y2, a10 * x2 + a11 * y2, + a00 * x3 + a01 * y3, a10 * x3 + a11 * y3 + ]); + } + + function render(g, path, l, t) { + var current, // current instruction + previous = null, + x = 0, // current x + y = 0, // current y + controlX = 0, // current control point x + controlY = 0, // current control point y + tempX, + tempY, + tempControlX, + tempControlY, + bounds = new vg.Bounds(); + if (l == undefined) l = 0; + if (t == undefined) t = 0; + + g.beginPath(); + + for (var i=0, len=path.length; i<len; ++i) { + current = path[i]; + + switch (current[0]) { // first letter + + case 'l': // lineto, relative + x += current[1]; + y += current[2]; + g.lineTo(x + l, y + t); + bounds.add(x, y); + break; + + case 'L': // lineto, absolute + x = current[1]; + y = current[2]; + g.lineTo(x + l, y + t); + bounds.add(x, y); + break; + + case 'h': // horizontal lineto, relative + x += current[1]; + g.lineTo(x + l, y + t); + bounds.add(x, y); + break; + + case 'H': // horizontal lineto, absolute + x = current[1]; + g.lineTo(x + l, y + t); + bounds.add(x, y); + break; + + case 'v': // vertical lineto, relative + y += current[1]; + g.lineTo(x + l, y + t); + bounds.add(x, y); + break; + + case 'V': // verical lineto, absolute + y = current[1]; + g.lineTo(x + l, y + t); + bounds.add(x, y); + break; + + case 'm': // moveTo, relative + x += current[1]; + y += current[2]; + g.moveTo(x + l, y + t); + bounds.add(x, y); + break; + + case 'M': // moveTo, absolute + x = current[1]; + y = current[2]; + g.moveTo(x + l, y + t); + bounds.add(x, y); + break; + + case 'c': // bezierCurveTo, relative + tempX = x + current[5]; + tempY = y + current[6]; + controlX = x + current[3]; + controlY = y + current[4]; + g.bezierCurveTo( + x + current[1] + l, // x1 + y + current[2] + t, // y1 + controlX + l, // x2 + controlY + t, // y2 + tempX + l, + tempY + t + ); + bounds.add(x + current[1], y + current[2]); + bounds.add(controlX, controlY); + bounds.add(tempX, tempY); + x = tempX; + y = tempY; + break; + + case 'C': // bezierCurveTo, absolute + x = current[5]; + y = current[6]; + controlX = current[3]; + controlY = current[4]; + g.bezierCurveTo( + current[1] + l, + current[2] + t, + controlX + l, + controlY + t, + x + l, + y + t + ); + bounds.add(current[1], current[2]); + bounds.add(controlX, controlY); + bounds.add(x, y); + break; + + case 's': // shorthand cubic bezierCurveTo, relative + // transform to absolute x,y + tempX = x + current[3]; + tempY = y + current[4]; + // calculate reflection of previous control points + controlX = 2 * x - controlX; + controlY = 2 * y - controlY; + g.bezierCurveTo( + controlX + l, + controlY + t, + x + current[1] + l, + y + current[2] + t, + tempX + l, + tempY + t + ); + bounds.add(controlX, controlY); + bounds.add(x + current[1], y + current[2]); + bounds.add(tempX, tempY); + + // set control point to 2nd one of this command + // "... the first control point is assumed to be the reflection of the second control point on the previous command relative to the current point." + controlX = x + current[1]; + controlY = y + current[2]; + + x = tempX; + y = tempY; + break; + + case 'S': // shorthand cubic bezierCurveTo, absolute + tempX = current[3]; + tempY = current[4]; + // calculate reflection of previous control points + controlX = 2*x - controlX; + controlY = 2*y - controlY; + g.bezierCurveTo( + controlX + l, + controlY + t, + current[1] + l, + current[2] + t, + tempX + l, + tempY + t + ); + x = tempX; + y = tempY; + bounds.add(current[1], current[2]); + bounds.add(controlX, controlY); + bounds.add(tempX, tempY); + // set control point to 2nd one of this command + // "... the first control point is assumed to be the reflection of the second control point on the previous command relative to the current point." + controlX = current[1]; + controlY = current[2]; + + break; + + case 'q': // quadraticCurveTo, relative + // transform to absolute x,y + tempX = x + current[3]; + tempY = y + current[4]; + + controlX = x + current[1]; + controlY = y + current[2]; + + g.quadraticCurveTo( + controlX + l, + controlY + t, + tempX + l, + tempY + t + ); + x = tempX; + y = tempY; + bounds.add(controlX, controlY); + bounds.add(tempX, tempY); + break; + + case 'Q': // quadraticCurveTo, absolute + tempX = current[3]; + tempY = current[4]; + + g.quadraticCurveTo( + current[1] + l, + current[2] + t, + tempX + l, + tempY + t + ); + x = tempX; + y = tempY; + controlX = current[1]; + controlY = current[2]; + bounds.add(controlX, controlY); + bounds.add(tempX, tempY); + break; + + case 't': // shorthand quadraticCurveTo, relative + + // transform to absolute x,y + tempX = x + current[1]; + tempY = y + current[2]; + + if (previous[0].match(/[QqTt]/) === null) { + // If there is no previous command or if the previous command was not a Q, q, T or t, + // assume the control point is coincident with the current point + controlX = x; + controlY = y; + } + else if (previous[0] === 't') { + // calculate reflection of previous control points for t + controlX = 2 * x - tempControlX; + controlY = 2 * y - tempControlY; + } + else if (previous[0] === 'q') { + // calculate reflection of previous control points for q + controlX = 2 * x - controlX; + controlY = 2 * y - controlY; + } + + tempControlX = controlX; + tempControlY = controlY; + + g.quadraticCurveTo( + controlX + l, + controlY + t, + tempX + l, + tempY + t + ); + x = tempX; + y = tempY; + controlX = x + current[1]; + controlY = y + current[2]; + bounds.add(controlX, controlY); + bounds.add(tempX, tempY); + break; + + case 'T': + tempX = current[1]; + tempY = current[2]; + + // calculate reflection of previous control points + controlX = 2 * x - controlX; + controlY = 2 * y - controlY; + g.quadraticCurveTo( + controlX + l, + controlY + t, + tempX + l, + tempY + t + ); + x = tempX; + y = tempY; + bounds.add(controlX, controlY); + bounds.add(tempX, tempY); + break; + + case 'a': + drawArc(g, x + l, y + t, [ + current[1], + current[2], + current[3], + current[4], + current[5], + current[6] + x + l, + current[7] + y + t + ], bounds, l, t); + x += current[6]; + y += current[7]; + break; + + case 'A': + drawArc(g, x + l, y + t, [ + current[1], + current[2], + current[3], + current[4], + current[5], + current[6] + l, + current[7] + t + ], bounds, l, t); + x = current[6]; + y = current[7]; + break; + + case 'z': + case 'Z': + g.closePath(); + break; + } + previous = current; + } + return bounds.translate(l, t); + } + + function bounds(path, bounds) { + var current, // current instruction + previous = null, + x = 0, // current x + y = 0, // current y + controlX = 0, // current control point x + controlY = 0, // current control point y + tempX, + tempY, + tempControlX, + tempControlY; + + for (var i=0, len=path.length; i<len; ++i) { + current = path[i]; + + switch (current[0]) { // first letter + + case 'l': // lineto, relative + x += current[1]; + y += current[2]; + bounds.add(x, y); + break; + + case 'L': // lineto, absolute + x = current[1]; + y = current[2]; + bounds.add(x, y); + break; + + case 'h': // horizontal lineto, relative + x += current[1]; + bounds.add(x, y); + break; + + case 'H': // horizontal lineto, absolute + x = current[1]; + bounds.add(x, y); + break; + + case 'v': // vertical lineto, relative + y += current[1]; + bounds.add(x, y); + break; + + case 'V': // verical lineto, absolute + y = current[1]; + bounds.add(x, y); + break; + + case 'm': // moveTo, relative + x += current[1]; + y += current[2]; + bounds.add(x, y); + break; + + case 'M': // moveTo, absolute + x = current[1]; + y = current[2]; + bounds.add(x, y); + break; + + case 'c': // bezierCurveTo, relative + tempX = x + current[5]; + tempY = y + current[6]; + controlX = x + current[3]; + controlY = y + current[4]; + bounds.add(x + current[1], y + current[2]); + bounds.add(controlX, controlY); + bounds.add(tempX, tempY); + x = tempX; + y = tempY; + break; + + case 'C': // bezierCurveTo, absolute + x = current[5]; + y = current[6]; + controlX = current[3]; + controlY = current[4]; + bounds.add(current[1], current[2]); + bounds.add(controlX, controlY); + bounds.add(x, y); + break; + + case 's': // shorthand cubic bezierCurveTo, relative + // transform to absolute x,y + tempX = x + current[3]; + tempY = y + current[4]; + // calculate reflection of previous control points + controlX = 2 * x - controlX; + controlY = 2 * y - controlY; + bounds.add(controlX, controlY); + bounds.add(x + current[1], y + current[2]); + bounds.add(tempX, tempY); + + // set control point to 2nd one of this command + // "... the first control point is assumed to be the reflection of the second control point on the previous command relative to the current point." + controlX = x + current[1]; + controlY = y + current[2]; + + x = tempX; + y = tempY; + break; + + case 'S': // shorthand cubic bezierCurveTo, absolute + tempX = current[3]; + tempY = current[4]; + // calculate reflection of previous control points + controlX = 2*x - controlX; + controlY = 2*y - controlY; + x = tempX; + y = tempY; + bounds.add(current[1], current[2]); + bounds.add(controlX, controlY); + bounds.add(tempX, tempY); + // set control point to 2nd one of this command + // "... the first control point is assumed to be the reflection of the second control point on the previous command relative to the current point." + controlX = current[1]; + controlY = current[2]; + + break; + + case 'q': // quadraticCurveTo, relative + // transform to absolute x,y + tempX = x + current[3]; + tempY = y + current[4]; + + controlX = x + current[1]; + controlY = y + current[2]; + + x = tempX; + y = tempY; + bounds.add(controlX, controlY); + bounds.add(tempX, tempY); + break; + + case 'Q': // quadraticCurveTo, absolute + tempX = current[3]; + tempY = current[4]; + + x = tempX; + y = tempY; + controlX = current[1]; + controlY = current[2]; + bounds.add(controlX, controlY); + bounds.add(tempX, tempY); + break; + + case 't': // shorthand quadraticCurveTo, relative + + // transform to absolute x,y + tempX = x + current[1]; + tempY = y + current[2]; + + if (previous[0].match(/[QqTt]/) === null) { + // If there is no previous command or if the previous command was not a Q, q, T or t, + // assume the control point is coincident with the current point + controlX = x; + controlY = y; + } + else if (previous[0] === 't') { + // calculate reflection of previous control points for t + controlX = 2 * x - tempControlX; + controlY = 2 * y - tempControlY; + } + else if (previous[0] === 'q') { + // calculate reflection of previous control points for q + controlX = 2 * x - controlX; + controlY = 2 * y - controlY; + } + + tempControlX = controlX; + tempControlY = controlY; + + x = tempX; + y = tempY; + controlX = x + current[1]; + controlY = y + current[2]; + bounds.add(controlX, controlY); + bounds.add(tempX, tempY); + break; + + case 'T': + tempX = current[1]; + tempY = current[2]; + + // calculate reflection of previous control points + controlX = 2 * x - controlX; + controlY = 2 * y - controlY; + + x = tempX; + y = tempY; + bounds.add(controlX, controlY); + bounds.add(tempX, tempY); + break; + + case 'a': + boundArc(x, y, [ + current[1], + current[2], + current[3], + current[4], + current[5], + current[6] + x, + current[7] + y + ], bounds); + x += current[6]; + y += current[7]; + break; + + case 'A': + boundArc(x, y, [ + current[1], + current[2], + current[3], + current[4], + current[5], + current[6], + current[7] + ], bounds); + x = current[6]; + y = current[7]; + break; + + case 'z': + case 'Z': + break; + } + previous = current; + } + return bounds; + } + + function area(items) { + var o = items[0]; + var area; + + if (o.orient === "horizontal") { + area = d3.svg.area() + .y(function(d) { return d.y; }) + .x0(function(d) { return d.x; }) + .x1(function(d) { return d.x + d.width; }); + } else { + area = d3.svg.area() + .x(function(d) { return d.x; }) + .y1(function(d) { return d.y; }) + .y0(function(d) { return d.y + d.height; }); + } + + if (o.interpolate) area.interpolate(o.interpolate); + if (o.tension != null) area.tension(o.tension); + return area(items); + } + + function line(items) { + var o = items[0]; + var line = d3.svg.line() + .x(function(d) { return d.x; }) + .y(function(d) { return d.y; }); + if (o.interpolate) line.interpolate(o.interpolate); + if (o.tension != null) line.tension(o.tension); + return line(items); + } + + return { + parse: parse, + render: render, + bounds: bounds, + area: area, + line: line + }; + +})();vg.canvas.marks = (function() { + + var parsePath = vg.canvas.path.parse, + renderPath = vg.canvas.path.render, + halfpi = Math.PI / 2, + sqrt3 = Math.sqrt(3), + tan30 = Math.tan(30 * Math.PI / 180), + tmpBounds = new vg.Bounds(); + + // path generators + + function arcPath(g, o) { + var x = o.x || 0, + y = o.y || 0, + ir = o.innerRadius || 0, + or = o.outerRadius || 0, + sa = (o.startAngle || 0) - Math.PI/2, + ea = (o.endAngle || 0) - Math.PI/2; + g.beginPath(); + if (ir === 0) g.moveTo(x, y); + else g.arc(x, y, ir, sa, ea, 0); + g.arc(x, y, or, ea, sa, 1); + g.closePath(); + } + + function areaPath(g, items) { + var o = items[0], + m = o.mark, + p = m.pathCache || (m.pathCache = parsePath(vg.canvas.path.area(items))); + renderPath(g, p); + } + + function linePath(g, items) { + var o = items[0], + m = o.mark, + p = m.pathCache || (m.pathCache = parsePath(vg.canvas.path.line(items))); + renderPath(g, p); + } + + function pathPath(g, o) { + if (o.path == null) return; + var p = o.pathCache || (o.pathCache = parsePath(o.path)); + return renderPath(g, p, o.x, o.y); + } + + function symbolPath(g, o) { + g.beginPath(); + var size = o.size != null ? o.size : 100, + x = o.x, y = o.y, r, t, rx, ry; + + if (o.shape == null || o.shape === "circle") { + r = Math.sqrt(size/Math.PI); + g.arc(x, y, r, 0, 2*Math.PI, 0); + g.closePath(); + return; + } + + switch (o.shape) { + case "cross": + r = Math.sqrt(size / 5) / 2; + t = 3*r; + g.moveTo(x-t, y-r); + g.lineTo(x-r, y-r); + g.lineTo(x-r, y-t); + g.lineTo(x+r, y-t); + g.lineTo(x+r, y-r); + g.lineTo(x+t, y-r); + g.lineTo(x+t, y+r); + g.lineTo(x+r, y+r); + g.lineTo(x+r, y+t); + g.lineTo(x-r, y+t); + g.lineTo(x-r, y+r); + g.lineTo(x-t, y+r); + break; + + case "diamond": + ry = Math.sqrt(size / (2 * tan30)); + rx = ry * tan30; + g.moveTo(x, y-ry); + g.lineTo(x+rx, y); + g.lineTo(x, y+ry); + g.lineTo(x-rx, y); + break; + + case "square": + t = Math.sqrt(size); + r = t / 2; + g.rect(x-r, y-r, t, t); + break; + + case "triangle-down": + rx = Math.sqrt(size / sqrt3); + ry = rx * sqrt3 / 2; + g.moveTo(x, y+ry); + g.lineTo(x+rx, y-ry); + g.lineTo(x-rx, y-ry); + break; + + case "triangle-up": + rx = Math.sqrt(size / sqrt3); + ry = rx * sqrt3 / 2; + g.moveTo(x, y-ry); + g.lineTo(x+rx, y+ry); + g.lineTo(x-rx, y+ry); + } + g.closePath(); + } + + function lineStroke(g, items) { + var o = items[0], + lw = o.strokeWidth, + lc = o.strokeCap; + g.lineWidth = lw != null ? lw : vg.config.render.lineWidth; + g.lineCap = lc != null ? lc : vg.config.render.lineCap; + linePath(g, items); + } + + function ruleStroke(g, o) { + var x1 = o.x || 0, + y1 = o.y || 0, + x2 = o.x2 != null ? o.x2 : x1, + y2 = o.y2 != null ? o.y2 : y1, + lw = o.strokeWidth, + lc = o.strokeCap; + + g.lineWidth = lw != null ? lw : vg.config.render.lineWidth; + g.lineCap = lc != null ? lc : vg.config.render.lineCap; + g.beginPath(); + g.moveTo(x1, y1); + g.lineTo(x2, y2); + } + + // drawing functions + + function drawPathOne(path, g, o, items) { + var fill = o.fill, stroke = o.stroke, opac, lc, lw; + + path(g, items); + + opac = o.opacity == null ? 1 : o.opacity; + if (opac == 0 || !fill && !stroke) return; + + if (fill) { + g.globalAlpha = opac * (o.fillOpacity==null ? 1 : o.fillOpacity); + g.fillStyle = color(g, o, fill); + g.fill(); + } + + if (stroke) { + lw = (lw = o.strokeWidth) != null ? lw : vg.config.render.lineWidth; + if (lw > 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<len; ++i) { + item = scene.items[i]; + if (bounds && !bounds.intersects(item.bounds)) + continue; // bounds check + drawPathOne(path, g, item, item); + } + } + + function drawRect(g, scene, bounds) { + if (!scene.items.length) return; + var items = scene.items, + o, fill, stroke, opac, lc, lw, x, y, w, h; + + for (var i=0, len=items.length; i<len; ++i) { + o = items[i]; + if (bounds && !bounds.intersects(o.bounds)) + continue; // bounds check + + x = o.x || 0; + y = o.y || 0; + w = o.width || 0; + h = o.height || 0; + + opac = o.opacity == null ? 1 : o.opacity; + if (opac == 0) continue; + + if (fill = o.fill) { + g.globalAlpha = opac * (o.fillOpacity==null ? 1 : o.fillOpacity); + g.fillStyle = color(g, o, fill); + g.fillRect(x, y, w, h); + } + + if (stroke = o.stroke) { + lw = (lw = o.strokeWidth) != null ? lw : vg.config.render.lineWidth; + if (lw > 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<len; ++i) { + o = items[i]; + if (bounds && !bounds.intersects(o.bounds)) + continue; // bounds check + + x1 = o.x || 0; + y1 = o.y || 0; + x2 = o.x2 != null ? o.x2 : x1; + y2 = o.y2 != null ? o.y2 : y1; + + opac = o.opacity == null ? 1 : o.opacity; + if (opac == 0) continue; + + if (stroke = o.stroke) { + lw = (lw = o.strokeWidth) != null ? lw : vg.config.render.lineWidth; + if (lw > 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<len; ++i) { + o = items[i]; + if (bounds && !bounds.intersects(o.bounds)) + continue; // bounds check + + if (!(o.image && o.image.url === o.url)) { + o.image = renderer.loadImage(o.url); + o.image.url = o.url; + } + + var x, y, w, h, opac; + w = o.width || (o.image && o.image.width) || 0; + h = o.height || (o.image && o.image.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)); + + if (o.image.loaded) { + g.globalAlpha = (opac = o.opacity) != null ? opac : 1; + g.drawImage(o.image, x, y, w, h); + } + } + } + + function drawText(g, scene, bounds) { + if (!scene.items.length) return; + var items = scene.items, + o, fill, stroke, opac, lw, x, y, r, t; + + for (var i=0, len=items.length; i<len; ++i) { + o = items[i]; + if (bounds && !bounds.intersects(o.bounds)) + continue; // bounds check + + g.font = vg.scene.fontString(o); + g.textAlign = o.align || "left"; + g.textBaseline = o.baseline || "alphabetic"; + + opac = o.opacity == null ? 1 : o.opacity; + if (opac == 0) continue; + + x = o.x || 0; + y = o.y || 0; + if (r = o.radius) { + t = (o.theta || 0) - Math.PI/2; + x += r * Math.cos(t); + y += r * Math.sin(t); + } + + if (o.angle) { + g.save(); + g.translate(x, y); + g.rotate(o.angle * Math.PI/180); + x = o.dx || 0; + y = o.dy || 0; + } else { + x += (o.dx || 0); + y += (o.dy || 0); + } + + if (fill = o.fill) { + g.globalAlpha = opac * (o.fillOpacity==null ? 1 : o.fillOpacity); + g.fillStyle = color(g, o, fill); + g.fillText(o.text, x, y); + } + + if (stroke = o.stroke) { + lw = (lw = o.strokeWidth) != null ? lw : 1; + if (lw > 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<n; ++i) { + group = items[i]; + axes = group.axisItems || []; + legends = group.legendItems || []; + gx = group.x || 0; + gy = group.y || 0; + + // render group contents + g.save(); + g.translate(gx, gy); + if (group.clip) { + g.beginPath(); + g.rect(0, 0, group.width || 0, group.height || 0); + g.clip(); + } + + if (bounds) bounds.translate(-gx, -gy); + + for (j=0, m=axes.length; j<m; ++j) { + if (axes[j].def.layer === "back") { + renderer.draw(g, axes[j], bounds); + } + } + for (j=0, m=group.items.length; j<m; ++j) { + renderer.draw(g, group.items[j], bounds); + } + for (j=0, m=axes.length; j<m; ++j) { + if (axes[j].def.layer !== "back") { + renderer.draw(g, axes[j], bounds); + } + } + for (j=0, m=legends.length; j<m; ++j) { + renderer.draw(g, legends[j], bounds); + } + + if (bounds) bounds.translate(gx, gy); + g.restore(); + } + } + + function color(g, o, value) { + return (value.id) + ? gradient(g, value, o.bounds) + : value; + } + + function gradient(g, p, b) { + var w = b.width(), + h = b.height(), + x1 = b.x1 + p.x1 * w, + y1 = b.y1 + p.y1 * h, + x2 = b.x1 + p.x2 * w, + y2 = b.y1 + p.y2 * h, + grad = g.createLinearGradient(x1, y1, x2, y2), + stop = p.stops, + i, n; + + for (i=0, n=stop.length; i<n; ++i) { + grad.addColorStop(stop[i].offset, stop[i].color); + } + return grad; + } + + // hit testing + + function pickGroup(g, scene, x, y, gx, gy) { + if (scene.items.length === 0 || + scene.bounds && !scene.bounds.contains(gx, gy)) { + return false; + } + var items = scene.items, subscene, group, hit, dx, dy, + handler = this, i, j; + + for (i=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.group, 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 + group: function(g,o,x,y) { return o.fill || o.stroke; }, + 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<len; ++i) { + h[i].handler.call(this._obj, evt, a); + } + } + }; + + // add an event handler + prototype.on = function(type, handler) { + var name = eventName(type), + h = this._handlers; + h = h[name] || (h[name] = []); + h.push({ + type: type, + handler: handler + }); + return this; + }; + + // remove an event handler + prototype.off = function(type, handler) { + var name = eventName(type), + h = this._handlers[name]; + if (!h) return; + for (var i=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 + // 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 xw(o) { return o.x + o.width || 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_v = d3.svg.area().x(x).y1(y).y0(yh), + area_path_h = d3.svg.area().y(y).x0(xw).x1(x), + 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<n; ++i) { + prop = styleProps[i]; + name = styles[prop]; + value = o[prop]; + + if (value == null) { + if (name === "fill") this.style.setProperty(name, "none", null); + else this.style.removeProperty(name); + } else { + if (value.id) { + // ensure definition is included + vg.svg._cur._defs.gradient[value.id] = value; + value = "url(" + window.location.href + "#" + value.id + ")"; + } + this.style.setProperty(name, value+"", null); + } + } + } + + function arc(o) { + var x = o.x || 0, + y = o.y || 0; + this.setAttribute("transform", "translate("+x+","+y+")"); + this.setAttribute("d", arc_path(o)); + } + + function area(items) { + if (!items.length) return; + var o = items[0], + path = o.orient === "horizontal" ? area_path_h : area_path_v; + path + .interpolate(o.interpolate || "linear") + .tension(o.tension == null ? 0.7 : o.tension); + this.setAttribute("d", path(items)); + } + + function line(items) { + if (!items.length) return; + var o = items[0]; + line_path + .interpolate(o.interpolate || "linear") + .tension(o.tension == null ? 0.7 : o.tension); + this.setAttribute("d", line_path(items)); + } + + function path(o) { + var x = o.x || 0, + y = o.y || 0; + this.setAttribute("transform", "translate("+x+","+y+")"); + if (o.path != null) this.setAttribute("d", o.path); + } + + function rect(o) { + this.setAttribute("x", o.x || 0); + this.setAttribute("y", o.y || 0); + this.setAttribute("width", o.width || 0); + this.setAttribute("height", o.height || 0); + } + + function rule(o) { + var x1 = o.x || 0, + y1 = o.y || 0; + this.setAttribute("x1", x1); + this.setAttribute("y1", y1); + this.setAttribute("x2", o.x2 != null ? o.x2 : x1); + this.setAttribute("y2", o.y2 != null ? o.y2 : y1); + } + + function symbol(o) { + var x = o.x || 0, + y = o.y || 0; + this.setAttribute("transform", "translate("+x+","+y+")"); + this.setAttribute("d", symbol_path(o)); + } + + function image(o) { + var w = o.width || (o.image && o.image.width) || 0, + h = o.height || (o.image && o.image.height) || 0, + x = o.x - (o.align === "center" + ? w/2 : (o.align === "right" ? w : 0)), + y = o.y - (o.baseline === "middle" + ? h/2 : (o.baseline === "bottom" ? h : 0)), + url = vg.config.baseURL + o.url; + + this.setAttributeNS("http://www.w3.org/1999/xlink", "href", url); + this.setAttribute("x", x); + this.setAttribute("y", y); + this.setAttribute("width", w); + this.setAttribute("height", h); + } + + function fontString(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); + } + + function text(o) { + var x = o.x || 0, + y = o.y || 0, + dx = o.dx || 0, + dy = o.dy || 0, + a = o.angle || 0, + r = o.radius || 0, + align = textAlign[o.align || "left"], + base = o.baseline==="top" ? ".9em" + : o.baseline==="middle" ? ".35em" : 0; + + if (r) { + var t = (o.theta || 0) - Math.PI/2; + x += r * Math.cos(t); + y += r * Math.sin(t); + } + + this.setAttribute("x", x + dx); + this.setAttribute("y", y + dy); + this.setAttribute("text-anchor", align); + + if (a) this.setAttribute("transform", "rotate("+a+" "+x+","+y+")"); + else this.removeAttribute("transform"); + + if (base) this.setAttribute("dy", base); + else this.removeAttribute("dy"); + + this.textContent = o.text; + this.style.setProperty("font", fontString(o), null); + } + + function group(o) { + var x = o.x || 0, + y = o.y || 0; + this.setAttribute("transform", "translate("+x+","+y+")"); + + if (o.clip) { + var c = {width: o.width || 0, height: o.height || 0}, + id = o.clip_id || (o.clip_id = "clip" + clip_id++); + vg.svg._cur._defs.clipping[id] = c; + this.setAttribute("clip-path", "url(#"+id+")"); + } + } + + function group_bg(o) { + var w = o.width || 0, + h = o.height || 0; + this.setAttribute("width", w); + this.setAttribute("height", h); + } + + function cssClass(def) { + var cls = "type-" + def.type; + if (def.name) cls += " " + def.name; + return cls; + } + + function draw(tag, attr, nest) { + return function(g, scene, index) { + drawMark(g, scene, index, "mark_", tag, attr, nest); + }; + } + + function drawMark(g, scene, index, prefix, tag, attr, nest) { + var data = nest ? [scene.items] : scene.items, + evts = scene.interactive===false ? "none" : null, + grps = g.node().childNodes, + notG = (tag !== "g"), + p = (p = grps[index+1]) // +1 to skip group background rect + ? d3.select(p) + : g.append("g") + .attr("id", "g"+(++mark_id)) + .attr("class", cssClass(scene.def)); + + var id = p.attr("id"), + s = "#" + id + " > " + 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<n; ++i) { + var items = c[i].__data__.items, + legends = c[i].__data__.legendItems || [], + axes = c[i].__data__.axisItems || [], + sel = d3.select(c[i]), + idx = 0; + + for (j=0, m=axes.length; j<m; ++j) { + if (axes[j].def.layer === "back") { + drawGroup.call(this, sel, axes[j], idx++, "axis_"); + } + } + for (j=0, m=items.length; j<m; ++j) { + this.draw(sel, items[j], idx++); + } + for (j=0, m=axes.length; j<m; ++j) { + if (axes[j].def.layer !== "back") { + drawGroup.call(this, sel, axes[j], idx++, "axis_"); + } + } + for (j=0, m=legends.length; j<m; ++j) { + drawGroup.call(this, sel, legends[j], idx++, "legend_"); + } + } + } + + return { + update: { + group: rect, + area: area, + line: line, + arc: arc, + path: path, + symbol: symbol, + rect: rect, + rule: rule, + text: text, + image: image + }, + nested: { + "area": true, + "line": true + }, + style: style, + draw: { + group: drawGroup, + area: draw("path", area, true), + line: draw("path", line, true), + arc: draw("path", arc), + path: draw("path", path), + symbol: draw("path", symbol), + rect: draw("rect", rect), + rule: draw("line", rule), + text: draw("text", text), + image: draw("image", image), + draw: draw // expose for extensibility + } + }; + +})();vg.svg.Renderer = (function() { + var renderer = function() { + this._svg = null; + this._ctx = null; + this._el = null; + this._defs = { + gradient: {}, + clipping: {} + }; + }; + + var prototype = renderer.prototype; + + prototype.initialize = function(el, width, height, pad) { + this._el = el; + + // remove any existing svg element + d3.select(el).select("svg.marks").remove(); + + // create svg element and initialize attributes + this._svg = d3.select(el) + .append("svg") + .attr("class", "marks"); + + // set the svg root group + this._ctx = this._svg.append("g"); + + return this.resize(width, height, pad); + }; + + prototype.resize = function(width, height, pad) { + this._width = width; + this._height = height; + this._padding = pad; + + this._svg + .attr("width", width + pad.left + pad.right) + .attr("height", height + pad.top + pad.bottom); + + this._ctx + .attr("transform", "translate("+pad.left+","+pad.top+")"); + + return this; + }; + + prototype.context = function() { + return this._ctx; + }; + + prototype.element = function() { + return this._el; + }; + + prototype.updateDefs = function() { + var svg = this._svg, + all = this._defs, + dgrad = vg.keys(all.gradient), + dclip = vg.keys(all.clipping), + defs = svg.select("defs"), grad, clip; + + // get or create svg defs block + if (dgrad.length===0 && dclip.length==0) { defs.remove(); return; } + if (defs.empty()) defs = svg.insert("defs", ":first-child"); + + grad = defs.selectAll("linearGradient").data(dgrad, vg.identity); + grad.enter().append("linearGradient").attr("id", vg.identity); + grad.exit().remove(); + grad.each(function(id) { + var def = all.gradient[id], + grd = d3.select(this); + + // set gradient coordinates + grd.attr({x1: def.x1, x2: def.x2, y1: def.y1, y2: def.y2}); + + // set gradient stops + stop = grd.selectAll("stop").data(def.stops); + stop.enter().append("stop"); + stop.exit().remove(); + stop.attr("offset", function(d) { return d.offset; }) + .attr("stop-color", function(d) { return d.color; }); + }); + + clip = defs.selectAll("clipPath").data(dclip, vg.identity); + clip.enter().append("clipPath").attr("id", vg.identity); + clip.exit().remove(); + clip.each(function(id) { + var def = all.clipping[id], + cr = d3.select(this).selectAll("rect").data([1]); + cr.enter().append("rect"); + cr.attr("x", 0) + .attr("y", 0) + .attr("width", def.width) + .attr("height", def.height); + }); + }; + + prototype.render = function(scene, items) { + vg.svg._cur = this; + + if (items) { + this.renderItems(vg.array(items)); + } else { + this.draw(this._ctx, scene, -1); + } + this.updateDefs(); + + delete vg.svg._cur; + }; + + prototype.renderItems = function(items) { + var item, node, type, nest, i, n, + marks = vg.svg.marks; + + for (i=0, n=items.length; i<n; ++i) { + item = items[i]; + node = item._svg; + type = item.mark.marktype; + + item = marks.nested[type] ? item.mark.items : item; + marks.update[type].call(node, item); + marks.style.call(node, item); + } + } + + prototype.draw = function(ctx, scene, index) { + var marktype = scene.marktype, + renderer = vg.svg.marks.draw[marktype]; + renderer.call(this, ctx, scene, index); + }; + + return renderer; +})();vg.svg.Handler = (function() { + var handler = function(el, model) { + this._active = null; + this._handlers = {}; + if (el) this.initialize(el); + if (model) this.model(model); + }; + + function svgHandler(handler) { + var that = this; + return function(evt) { + var target = evt.target, + item = target.__data__; + if (item) { + item = item.mark ? item : item[0]; + handler.call(that._obj, evt, item); + } + }; + } + + function eventName(name) { + var i = name.indexOf("."); + return i < 0 ? name : name.slice(0,i); + } + + var prototype = handler.prototype; + + prototype.initialize = function(el, pad, obj) { + this._el = d3.select(el).node(); + this._svg = d3.select(el).select("svg.marks").node(); + this._padding = pad; + this._obj = obj || null; + 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); + }, []); + }; + + // add an event handler + prototype.on = function(type, handler) { + var name = eventName(type), + h = this._handlers, + dom = d3.select(this._svg).node(); + + var x = { + type: type, + handler: handler, + svg: svgHandler.call(this, handler) + }; + h = h[name] || (h[name] = []); + h.push(x); + + dom.addEventListener(name, x.svg); + return this; + }; + + // remove an event handler + prototype.off = function(type, handler) { + var name = eventName(type), + h = this._handlers[name], + dom = d3.select(this._svg).node(); + if (!h) return; + for (var i=h.length; --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<n; ++i) { + d.values[i] = vg.data.ingestTree(c[i], children, i); + } + } + return d; +}; + +function vg_make_tree(d) { + d.__vgtree__ = true; + d.nodes = function() { return vg_tree_nodes(this, []); }; + return d; +} + +function vg_tree_nodes(root, nodes) { + var c = root.values, + n = c ? c.length : 0, i; + nodes.push(root); + for (i=0; i<n; ++i) { vg_tree_nodes(c[i], nodes); } + return nodes; +} + +function vg_data_duplicate(d) { + var x=d, i, n; + if (vg.isArray(d)) { + x = []; + for (i=0, n=d.length; i<n; ++i) { + x.push(vg_data_duplicate(d[i])); + } + } else if (vg.isObject(d)) { + x = {}; + for (i in d) { + x[i] = vg_data_duplicate(d[i]); + } + } + return x; +} + +vg.data.mapper = function(func) { + return function(data) { + data.forEach(func); + return data; + } +}; + +vg.data.size = function(size, group) { + size = vg.isArray(size) ? size : [0, size]; + size = size.map(function(d) { + return (typeof d === 'string') ? group[d] : d; + }); + return size; +};vg.data.load = function(uri, callback) { + var url = vg_load_hasProtocol(uri) ? uri : vg.config.baseURL + uri; + if (vg.config.isNode) { + // in node.js, consult url and select file or http + var get = vg_load_isFile(url) ? vg_load_file : vg_load_http; + get(url, callback); + } else { + // in browser, use xhr + vg_load_xhr(url, callback); + } +}; + +var vg_load_protocolRE = /^[A-Za-z]+\:\/\//; +var vg_load_fileProtocol = "file://"; + +function vg_load_hasProtocol(url) { + return vg_load_protocolRE.test(url); +} + +function vg_load_isFile(url) { + return url.indexOf(vg_load_fileProtocol) === 0; +} + +function vg_load_xhr(url, callback) { + vg.log("LOAD: " + url); + if (!vg_url_check(url)) { + vg.error("URL is not whitelisted: " + url); + return; + } + d3.xhr(url, function(err, resp) { + if (resp) resp = resp.responseText; + callback(err, resp); + }); +} + +function vg_url_check(url) { + // If vg.config.domainWhiteList is set, only allows url, whose hostname + // * Is the same as the origin (window.location.hostname) + // * Equals one of the values in the whitelist + // * Is a proper subdomain of one of the values in the whitelist + if (!vg.config.domainWhiteList) + return true; + + var a = document.createElement("a"); + a.href = url; + var domain = a.hostname.toLowerCase(); + + return window.location.hostname === domain || + vg.config.domainWhiteList.some(function(d) { + var ind = domain.length - d.length; + return d === domain || + (ind > 1 && domain[ind-1] === '.' && domain.lastIndexOf(d) === ind); + }); +} + +function vg_load_file(file, callback) { + vg.log("LOAD FILE: " + file); + var idx = file.indexOf(vg_load_fileProtocol); + if (idx >= 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; i<len; ++i) { + d = data[i]; + for (j=0, clen=cols.length; j<clen; ++j) { + d[cols[j]] = p[j](d[cols[j]]); + } + if (tree && d.values) parseValues(d, cols, p, true); + } + } + + read.formats = formats; + read.parse = parseValues; + return read; +})();vg.data.aggregate = function() { + var groupby = [], + fields = [], + gaccess, + faccess; + + var OPS = { + "count": function() {}, + "sum": function(c, s, x) { return s + x; }, + "avg": function(c, s, x) { return s + (x-s)/c.count; }, + "min": function(c, s, x) { return x < s ? x : s; }, + "max": function(c, s, x) { return x > s ? x : s; } + }; + OPS.min.init = function() { return +Infinity; } + OPS.max.init = function() { return -Infinity; } + + function fkey(x) { + return x.op + "_" + x.field; + } + + var cells = {}; + + function cell(x) { + // consider other key constructions... + var k = gaccess.reduce(function(v,f) { + return (v.push(f(x)), v); + }, []).join("|"); + return cells[k] || (cells[k] = new_cell(x)); + } + + function new_cell(x) { + var o = {}; + // dimensions + for (var i=0, f; i<groupby.length; ++i) { + o[groupby[i]] = gaccess[i](x); + } + // measures + o.count = 0; + for (i=0; i<fields.length; ++i) { + if (fields[i].op === "count") continue; + var op = OPS[fields[i].op]; + o[fkey(fields[i])] = op.init ? op.init() : 0; + } + return o; + } + + function aggregate(input) { + var output = [], k; + var keys = fields.map(fkey); + var ops = fields.map(function(x) { return OPS[x.op]; }); + + // compute aggregates + input.forEach(function(x) { + var c = cell(x); + + // compute aggregates... + c.count += 1; + for (var i=0; i<fields.length; ++i) { + c[keys[i]] = ops[i](c, c[keys[i]], faccess[i](x)); + } + }); + // collect output tuples + var index = 0; + for (k in cells) { + output.push({index:index++, data:cells[k]}); + } + cells = {}; // clear internal state + return output; + }; + + aggregate.fields = function(f) { + fields = vg.array(f); + faccess = fields.map(function(x,i) { + var xf = x.field; + if (xf.indexOf("data.") === 0) { + fields[i] = {op:x.op, field:xf.slice(5)}; + } + return vg.accessor(xf); + }); + return aggregate; + }; + + aggregate.groupby = function(f) { + groupby = vg.array(f); + gaccess = groupby.map(function(x,i) { + if (x.indexOf("data.") === 0) { + groupby[i] = x.slice(5); + } + return vg.accessor(x); + }); + return aggregate; + }; + + return aggregate; +};vg.data.array = function() { + var fields = []; + + function array(data) { + return data.map(function(d) { + var list = []; + for (var i=0, len=fields.length; i<len; ++i) { + list.push(fields[i](d)); + } + return list; + }); + } + + array.fields = function(fieldList) { + fields = vg.array(fieldList).map(vg.accessor); + return array; + }; + + return array; +};vg.data.bin = function() { + + var field, + accessor, + setter, + min = undefined, + max = undefined, + step = undefined, + maxbins = 20, + output = "bin"; + + function compare(a, b) { + return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN; + } + + function bisectLeft(a, x, lo, hi) { + if (arguments.length < 3) { lo = 0; } + if (arguments.length < 4) { hi = a.length; } + while (lo < hi) { + var mid = lo + hi >>> 1; + if (compare(a[mid], x) < 0) { lo = mid + 1; } + else { hi = mid; } + } + return lo; + } + + function bins(opt) { + opt = opt || {}; + + // determine range + var maxb = opt.maxbins || 1024, + base = opt.base || 10, + div = opt.div || [5, 2], + mins = opt.minstep || 0, + logb = Math.log(base), + level = Math.ceil(Math.log(maxb) / logb), + min = opt.min, + max = opt.max, + span = max - min, + step = Math.max(mins, Math.pow(base, Math.round(Math.log(span) / logb) - level)), + nbins = Math.ceil(span / step), + precision, v, i, eps; + + if (opt.step != null) { + step = opt.step; + } else if (opt.steps) { + // if provided, limit choice to acceptable step sizes + step = opt.steps[Math.min( + opt.steps.length - 1, + bisectLeft(opt.steps, span / maxb) + )]; + } else { + // increase step size if too many bins + do { + step *= base; + nbins = Math.ceil(span / step); + } while (nbins > maxb); + + // decrease step size if allowed + for (i = 0; i < div.length; ++i) { + v = step / div[i]; + if (v >= mins && span / v <= maxb) { + step = v; + nbins = Math.ceil(span / step); + } + } + } + + // update precision, min and max + v = Math.log(step); + precision = v >= 0 ? 0 : ~~(-v / logb) + 1; + eps = Math.pow(base, -precision - 1); + + // outer Math.min to remove some rounding errors: + min = Math.min(min, Math.floor(min / step + eps) * step); + max = Math.ceil(max / step) * step; + + return { + start: min, + stop: max, + step: step, + unit: precision + }; + } + + function bin(input) { + var opt = { + min: min != null ? min : +Infinity, + max: max != null ? max : -Infinity, + step: step != null ? step : null, + maxbins: maxbins + }; + if (min == null || max == null) { + input.forEach(function(d) { + var v = accessor(d); + if (min == null && v > opt.max) opt.max = v; + if (max == null && v < opt.min) opt.min = v; + }); + } + var b = bins(opt); + input.forEach(function(d) { + var v = accessor(d); + setter
<TRUNCATED>
