| /* |
| * |
| * 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.min', '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(d, b.start + b.step * ~~((v - b.start) / b.step)); |
| }); |
| return input; |
| } |
| |
| bin.min = function (x) { |
| min = x; |
| return bin; |
| }; |
| |
| bin.max = function (x) { |
| max = x; |
| return bin; |
| }; |
| |
| bin.step = function (x) { |
| step = x; |
| return bin; |
| }; |
| |
| bin.maxbins = function (x) { |
| maxbins = x; |
| return bin; |
| }; |
| |
| bin.field = function (f) { |
| field = f; |
| accessor = vg.accessor(f); |
| return bin; |
| }; |
| |
| bin.output = function (f) { |
| output = f; |
| setter = vg.mutator(f); |
| return bin; |
| }; |
| |
| return bin; |
| }; |
| vg.data.copy = function () { |
| var from = vg.accessor("data"), |
| fields = [], |
| as = null; |
| |
| var copy = vg.data.mapper(function (d) { |
| var src = from(d), i, len, |
| source = fields, |
| target = as || fields; |
| for (i = 0, len = fields.length; i < len; ++i) { |
| d[target[i]] = src[fields[i]]; |
| } |
| return d; |
| }); |
| |
| copy.from = function (field) { |
| from = vg.accessor(field); |
| return copy; |
| }; |
| |
| copy.fields = function (fieldList) { |
| fields = vg.array(fieldList); |
| return copy; |
| }; |
| |
| copy.as = function (fieldList) { |
| as = vg.array(fieldList); |
| return copy; |
| }; |
| |
| return copy; |
| }; |
| vg.data.cross = function () { |
| var other = null, |
| nodiag = false, |
| output = {left: "a", right: "b"}; |
| |
| function cross(data) { |
| var result = [], |
| data2 = other || data, |
| o, i, j, n = data.length; |
| |
| for (i = 0; i < n; ++i) { |
| for (j = 0; j < n; ++j) { |
| if (nodiag && i === j) continue; |
| o = {}; |
| o[output.left] = data[i]; |
| o[output.right] = data2[j]; |
| result.push(o); |
| } |
| } |
| return result; |
| } |
| |
| cross["with"] = function (d) { |
| other = d; |
| return cross; |
| }; |
| |
| cross.diagonal = function (x) { |
| nodiag = !x; |
| return cross; |
| }; |
| |
| cross.output = function (map) { |
| vg.keys(output).forEach(function (k) { |
| if (map[k] !== undefined) { |
| output[k] = map[k]; |
| } |
| }); |
| return cross; |
| }; |
| |
| return cross; |
| }; |
| vg.data.facet = function () { |
| |
| var keys = [], |
| sort = null; |
| |
| function facet(data) { |
| var result = { |
| key: "", |
| keys: [], |
| values: [] |
| }, |
| map = {}, |
| vals = result.values, |
| obj, klist, kstr, len, i, j, k, kv, cmp; |
| |
| if (keys.length === 0) { |
| // if no keys, skip collation step |
| vals.push(obj = { |
| key: "", keys: [], index: 0, |
| values: sort ? data.slice() : data |
| }); |
| if (sort) sort(obj.values); |
| return result; |
| } |
| |
| for (i = 0, len = data.length; i < len; ++i) { |
| for (k = 0, klist = [], kstr = ""; k < keys.length; ++k) { |
| kv = keys[k](data[i]); |
| klist.push(kv); |
| kstr += (k > 0 ? "|" : "") + 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 < len; ++i) { |
| sort(vals[i].values); |
| } |
| } |
| |
| return result; |
| } |
| |
| facet.keys = function (k) { |
| keys = vg.array(k).map(vg.accessor); |
| return facet; |
| }; |
| |
| facet.sort = function (s) { |
| sort = vg.data.sort().by(s); |
| return facet; |
| }; |
| |
| return facet; |
| }; |
| vg.data.filter = function () { |
| |
| var test = null; |
| |
| function filter(data) { |
| return test ? data.filter(test) : data; |
| } |
| |
| filter.test = function (func) { |
| test = vg.isFunction(func) ? func : vg.parse.expr(func); |
| return filter; |
| }; |
| |
| return filter; |
| }; |
| vg.data.flatten = function () { |
| |
| function flatten(data) { |
| return flat(data, []); |
| } |
| |
| function flat(data, list) { |
| if (data.values) { |
| for (var i = 0, n = data.values.length; i < n; ++i) { |
| flat(data.values[i], list); |
| } |
| } else { |
| list.push(data); |
| } |
| return list; |
| } |
| |
| return flatten; |
| }; |
| vg.data.fold = function () { |
| var fields = [], |
| accessors = [], |
| output = { |
| key: "key", |
| value: "value" |
| }; |
| |
| function fold(data) { |
| var values = [], |
| item, i, j, n, m = fields.length; |
| |
| for (i = 0, n = data.length; i < n; ++i) { |
| item = data[i]; |
| for (j = 0; j < m; ++j) { |
| var o = { |
| index: values.length, |
| data: item.data |
| }; |
| o[output.key] = fields[j]; |
| o[output.value] = accessors[j](item); |
| values.push(o); |
| } |
| } |
| |
| return values; |
| } |
| |
| fold.fields = function (f) { |
| fields = vg.array(f); |
| accessors = fields.map(vg.accessor); |
| return fold; |
| }; |
| |
| fold.output = function (map) { |
| vg.keys(output).forEach(function (k) { |
| if (map[k] !== undefined) { |
| output[k] = map[k]; |
| } |
| }); |
| return fold; |
| }; |
| |
| return fold; |
| }; |
| vg.data.force = function () { |
| var layout = d3.layout.force(), |
| links = null, |
| linkDistance = 20, |
| linkStrength = 1, |
| charge = -30, |
| iterations = 500, |
| size = ["width", "height"], |
| params = [ |
| "friction", |
| "theta", |
| "gravity", |
| "alpha" |
| ]; |
| |
| function force(data, db, group) { |
| layout |
| .size(vg.data.size(size, group)) |
| .nodes(data); |
| |
| if (links && db[links]) { |
| layout.links(db[links]); |
| } |
| |
| layout.start(); |
| for (var i = 0; i < iterations; ++i) { |
| layout.tick(); |
| } |
| layout.stop(); |
| |
| return data; |
| } |
| |
| force.links = function (dataSetName) { |
| links = dataSetName; |
| return force; |
| }; |
| |
| force.size = function (sz) { |
| size = sz; |
| return force; |
| }; |
| |
| force.linkDistance = function (field) { |
| linkDistance = typeof field === 'number' |
| ? field |
| : vg.accessor(field); |
| layout.linkDistance(linkDistance); |
| return force; |
| }; |
| |
| force.linkStrength = function (field) { |
| linkStrength = typeof field === 'number' |
| ? field |
| : vg.accessor(field); |
| layout.linkStrength(linkStrength); |
| return force; |
| }; |
| |
| force.charge = function (field) { |
| charge = typeof field === 'number' |
| ? field |
| : vg.accessor(field); |
| layout.charge(charge); |
| return force; |
| }; |
| |
| force.iterations = function (iter) { |
| iterations = iter; |
| return force; |
| }; |
| |
| params.forEach(function (name) { |
| force[name] = function (x) { |
| layout[name](x); |
| return force; |
| } |
| }); |
| |
| return force; |
| }; |
| |
| vg.data.force.dependencies = ["links"]; |
| vg.data.formula = (function () { |
| |
| return function () { |
| var field = null, |
| expr = vg.identity; |
| |
| var formula = vg.data.mapper(function (d, i, list) { |
| if (field) d[field] = expr.call(null, d, i, list); |
| return d; |
| }); |
| |
| formula.field = function (name) { |
| field = name; |
| return formula; |
| }; |
| |
| formula.expr = function (func) { |
| expr = vg.isFunction(func) ? func : vg.parse.expr(func); |
| return formula; |
| }; |
| |
| return formula; |
| }; |
| })(); |
| vg.data.geo = (function () { |
| var params = [ |
| "center", |
| "scale", |
| "translate", |
| "rotate", |
| "precision", |
| "clipAngle" |
| ]; |
| |
| function geo() { |
| var opt = {}, |
| projection = "mercator", |
| func = d3.geo[projection](), |
| lat = vg.identity, |
| lon = vg.identity, |
| output = { |
| "x": "x", |
| "y": "y" |
| }; |
| |
| var map = vg.data.mapper(function (d) { |
| var ll = [lon(d), lat(d)], |
| xy = func(ll); |
| d[output.x] = xy[0]; |
| d[output.y] = xy[1]; |
| return d; |
| }); |
| |
| map.func = function () { |
| return func; |
| }; |
| |
| map.projection = function (p) { |
| if (projection !== p) { |
| projection = p; |
| func = d3.geo[projection](); |
| for (var name in opt) { |
| func[name](opt[name]); |
| } |
| } |
| return map; |
| }; |
| |
| params.forEach(function (name) { |
| map[name] = function (x) { |
| opt[name] = x; |
| func[name](x); |
| return map; |
| } |
| }); |
| |
| map.lon = function (field) { |
| lon = vg.accessor(field); |
| return map; |
| }; |
| |
| map.lat = function (field) { |
| lat = vg.accessor(field); |
| return map; |
| }; |
| |
| map.output = function (map) { |
| vg.keys(output).forEach(function (k) { |
| if (map[k] !== undefined) { |
| output[k] = map[k]; |
| } |
| }); |
| return map; |
| }; |
| |
| |
| return map; |
| }; |
| |
| geo.params = params; |
| return geo; |
| })(); |
| vg.data.geopath = function () { |
| var geopath = d3.geo.path().projection(d3.geo.mercator()), |
| projection = "mercator", |
| geojson = vg.identity, |
| opt = {}, |
| output = {"path": "path"}; |
| |
| var map = vg.data.mapper(function (d) { |
| d[output.path] = geopath(geojson(d)); |
| return d; |
| }); |
| |
| map.projection = function (proj) { |
| if (projection !== proj) { |
| projection = proj; |
| var p = d3.geo[projection](); |
| for (var name in opt) { |
| p[name](opt[name]); |
| } |
| geopath.projection(p); |
| } |
| return map; |
| }; |
| |
| vg.data.geo.params.forEach(function (name) { |
| map[name] = function (x) { |
| opt[name] = x; |
| (geopath.projection())[name](x); |
| return map; |
| } |
| }); |
| |
| map.value = function (field) { |
| geojson = vg.accessor(field); |
| return map; |
| }; |
| |
| map.output = function (map) { |
| vg.keys(output).forEach(function (k) { |
| if (map[k] !== undefined) { |
| output[k] = map[k]; |
| } |
| }); |
| return map; |
| }; |
| |
| return map; |
| }; |
| vg.data.link = function () { |
| var shape = "line", |
| source = vg.accessor("source"), |
| target = vg.accessor("target"), |
| tension = 0.2, |
| output = {"path": "path"}; |
| |
| function line(d) { |
| var s = source(d), |
| t = target(d); |
| return "M" + s.x + "," + s.y |
| + "L" + t.x + "," + t.y; |
| } |
| |
| function curve(d) { |
| var s = source(d), |
| t = target(d), |
| dx = t.x - s.x, |
| dy = t.y - s.y, |
| ix = tension * (dx + dy), |
| iy = tension * (dy - dx); |
| return "M" + s.x + "," + s.y |
| + "C" + (s.x + ix) + "," + (s.y + iy) |
| + " " + (t.x + iy) + "," + (t.y - ix) |
| + " " + t.x + "," + t.y; |
| } |
| |
| function diagonalX(d) { |
| var s = source(d), |
| t = target(d), |
| m = (s.x + t.x) / 2; |
| return "M" + s.x + "," + s.y |
| + "C" + m + "," + s.y |
| + " " + m + "," + t.y |
| + " " + t.x + "," + t.y; |
| } |
| |
| function diagonalY(d) { |
| var s = source(d), |
| t = target(d), |
| m = (s.y + t.y) / 2; |
| return "M" + s.x + "," + s.y |
| + "C" + s.x + "," + m |
| + " " + t.x + "," + m |
| + " " + t.x + "," + t.y; |
| } |
| |
| var shapes = { |
| line: line, |
| curve: curve, |
| diagonal: diagonalX, |
| diagonalX: diagonalX, |
| diagonalY: diagonalY |
| }; |
| |
| function link(data) { |
| var path = shapes[shape]; |
| |
| data.forEach(function (d) { |
| d[output.path] = path(d); |
| }); |
| |
| return data; |
| } |
| |
| link.shape = function (val) { |
| shape = val; |
| return link; |
| }; |
| |
| link.tension = function (val) { |
| tension = val; |
| return link; |
| }; |
| |
| link.source = function (field) { |
| source = vg.accessor(field); |
| return link; |
| }; |
| |
| link.target = function (field) { |
| target = vg.accessor(field); |
| return link; |
| }; |
| |
| link.output = function (map) { |
| vg.keys(output).forEach(function (k) { |
| if (map[k] !== undefined) { |
| output[k] = map[k]; |
| } |
| }); |
| return link; |
| }; |
| |
| return link; |
| }; |
| vg.data.pie = function () { |
| var one = function () { |
| return 1; |
| }, |
| value = one, |
| start = 0, |
| end = 2 * Math.PI, |
| sort = false, |
| output = { |
| "startAngle": "startAngle", |
| "endAngle": "endAngle", |
| "midAngle": "midAngle" |
| }; |
| |
| function pie(data) { |
| var values = data.map(function (d, i) { |
| return +value(d); |
| }), |
| a = start, |
| k = (end - start) / d3.sum(values), |
| index = d3.range(data.length); |
| |
| if (sort) { |
| index.sort(function (a, b) { |
| return values[a] - values[b]; |
| }); |
| } |
| |
| index.forEach(function (i) { |
| var d; |
| data[i].value = (d = values[i]); |
| data[i][output.startAngle] = a; |
| data[i][output.midAngle] = (a + 0.5 * d * k); |
| data[i][output.endAngle] = (a += d * k); |
| }); |
| |
| return data; |
| } |
| |
| pie.sort = function (b) { |
| sort = b; |
| return pie; |
| }; |
| |
| pie.value = function (field) { |
| value = field ? vg.accessor(field) : one; |
| return pie; |
| }; |
| |
| pie.startAngle = function (startAngle) { |
| start = Math.PI * startAngle / 180; |
| return pie; |
| }; |
| |
| pie.endAngle = function (endAngle) { |
| end = Math.PI * endAngle / 180; |
| return pie; |
| }; |
| |
| pie.output = function (map) { |
| vg.keys(output).forEach(function (k) { |
| if (map[k] !== undefined) { |
| output[k] = map[k]; |
| } |
| }); |
| return pie; |
| }; |
| |
| return pie; |
| }; |
| vg.data.slice = function () { |
| var by = null, |
| field = vg.accessor("data"); |
| |
| function slice(data) { |
| data = vg.values(data); |
| |
| if (by === "min") { |
| data = [data[vg.minIndex(data, field)]]; |
| } else if (by === "max") { |
| data = [data[vg.maxIndex(data, field)]]; |
| } else if (by === "median") { |
| var list = data.slice().sort(function (a, b) { |
| a = field(a); |
| b = field(b); |
| return a < b ? -1 : a > 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; i < n; ++i) data[i].index = i; // re-index |
| return data; |
| } |
| |
| sort.by = function (s) { |
| by = vg.comparator(s); |
| return sort; |
| }; |
| |
| return sort; |
| }; |
| vg.data.stack = function () { |
| var layout = d3.layout.stack(), |
| point = vg.accessor("index"), |
| height = vg.accessor("data"), |
| params = ["offset", "order"], |
| output = { |
| "y0": "y2", |
| "y1": "y", |
| "cy": "cy" |
| }; |
| |
| function stack(data) { |
| var out_y0 = output["y0"], |
| out_y1 = output["y1"], |
| out_cy = output["cy"]; |
| |
| var series = stacks(data); |
| if (series.length === 0) return data; |
| |
| layout.out(function (d, y0, y) { |
| if (d.datum) { |
| d.datum[out_y0] = y0; |
| d.datum[out_y1] = y + y0; |
| d.datum[out_cy] = y0 + y / 2; |
| } |
| })(series); |
| |
| return data; |
| } |
| |
| function stacks(data) { |
| var values = vg.values(data), |
| points = [], series = [], |
| a, i, n, j, m, k, p, v, x; |
| |
| // exit early if no data |
| if (values.length === 0) return series; |
| |
| // collect and sort data points |
| for (i = 0, n = values.length; i < n; ++i) { |
| a = vg.values(values[i]); |
| for (j = 0, m = a.length; j < m; ++j) { |
| points.push({x: point(a[j]), y: height(a[j]), z: i, datum: a[j]}); |
| } |
| series.push([]); |
| } |
| points.sort(function (a, b) { |
| return a.x < b.x ? -1 : a.x > b.x ? 1 : (a.z < b.z ? -1 : a.z > b.z ? 1 : 0); |
| }); |
| |
| // emit data series for stack layout |
| for (x = points[0].x, i = 0, j = 0, k = 0, n = points.length; k < n; ++k) { |
| p = points[k]; |
| if (p.x !== x) { |
| while (i < series.length) series[i++].push({x: j, y: 0}); |
| x = p.x; |
| i = 0; |
| j += 1; |
| } |
| while (p.z > 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 < len; ++i) { |
| v = list[i]; |
| if (v < min) min = v; |
| if (v > 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 < len; ++i) { |
| list[i].stats = v; |
| } |
| if (vg.isArray(data)) o = list; |
| } |
| |
| return o; |
| } |
| |
| function stats(data) { |
| if (vg.isArray(data)) { |
| return reduce(data); |
| } else { |
| return (data.values || []).map(reduce); |
| } |
| } |
| |
| stats.median = function (bool) { |
| median = bool || false; |
| return stats; |
| }; |
| |
| stats.value = function (field) { |
| value = vg.accessor(field); |
| return stats; |
| }; |
| |
| stats.assign = function (b) { |
| assign = b; |
| return stats; |
| }; |
| |
| stats.output = function (map) { |
| vg.keys(output).forEach(function (k) { |
| if (map[k] !== undefined) { |
| output[k] = map[k]; |
| } |
| }); |
| return stats; |
| }; |
| |
| return stats; |
| }; |
| vg.data.treemap = function () { |
| var layout = d3.layout.treemap() |
| .children(function (d) { |
| return d.values; |
| }), |
| value = vg.accessor("data"), |
| size = ["width", "height"], |
| params = ["round", "sticky", "ratio", "padding"], |
| output = { |
| "x": "x", |
| "y": "y", |
| "dx": "width", |
| "dy": "height" |
| }; |
| |
| function treemap(data, db, group) { |
| data = layout |
| .size(vg.data.size(size, group)) |
| .value(value) |
| .nodes(vg.isTree(data) ? data : {values: data}); |
| |
| var keys = vg.keys(output), |
| len = keys.length; |
| data.forEach(function (d) { |
| var key, val; |
| for (var i = 0; i < len; ++i) { |
| key = keys[i]; |
| if (key !== output[key]) { |
| val = d[key]; |
| delete d[key]; |
| d[output[key]] = val; |
| } |
| } |
| }); |
| |
| return data; |
| } |
| |
| treemap.size = function (sz) { |
| size = sz; |
| return treemap; |
| }; |
| |
| treemap.value = function (field) { |
| value = vg.accessor(field); |
| return treemap; |
| }; |
| |
| params.forEach(function (name) { |
| treemap[name] = function (x) { |
| layout[name](x); |
| return treemap; |
| } |
| }); |
| |
| treemap.output = function (map) { |
| vg.keys(output).forEach(function (k) { |
| if (map[k] !== undefined) { |
| output[k] = map[k]; |
| } |
| }); |
| return treemap; |
| }; |
| |
| return treemap; |
| }; |
| vg.data.truncate = function () { |
| var value = vg.accessor("data"), |
| as = "truncate", |
| position = "right", |
| ellipsis = "...", |
| wordBreak = true, |
| limit = 100; |
| |
| var truncate = vg.data.mapper(function (d) { |
| var text = vg.truncate(value(d), limit, position, wordBreak, ellipsis); |
| return (d[as] = text, d); |
| }); |
| |
| truncate.value = function (field) { |
| value = vg.accessor(field); |
| return truncate; |
| }; |
| |
| truncate.output = function (field) { |
| as = field; |
| return truncate; |
| }; |
| |
| truncate.limit = function (len) { |
| limit = +len; |
| return truncate; |
| }; |
| |
| truncate.position = function (pos) { |
| position = pos; |
| return truncate; |
| }; |
| |
| truncate.ellipsis = function (str) { |
| ellipsis = str + ""; |
| return truncate; |
| }; |
| |
| truncate.wordbreak = function (b) { |
| wordBreak = !!b; |
| return truncate; |
| }; |
| |
| return truncate; |
| }; |
| vg.data.unique = function () { |
| |
| var field = null, |
| as = "field"; |
| |
| function unique(data) { |
| return vg.unique(data, field) |
| .map(function (x) { |
| var o = {}; |
| o[as] = x; |
| return o; |
| }); |
| } |
| |
| unique.field = function (f) { |
| field = vg.accessor(f); |
| return unique; |
| }; |
| |
| unique.as = function (x) { |
| as = x; |
| return unique; |
| }; |
| |
| return unique; |
| }; |
| vg.data.window = function () { |
| |
| var size = 2, |
| step = 1; |
| |
| function win(data) { |
| data = vg.isArray(data) ? data : data.values || []; |
| var runs = [], i, j, n = data.length - size, curr; |
| for (i = 0; i <= n; i += step) { |
| for (j = 0, curr = []; j < size; ++j) curr.push(data[i + j]); |
| runs.push({key: i, values: curr}); |
| } |
| return {values: runs}; |
| } |
| |
| win.size = function (n) { |
| size = n; |
| return win; |
| }; |
| |
| win.step = function (n) { |
| step = n; |
| return win; |
| }; |
| |
| return win; |
| }; |
| vg.data.wordcloud = function () { |
| var layout = d3.layout.cloud().size([900, 500]), |
| text = vg.accessor("data"), |
| size = ["width", "height"], |
| fontSize = function () { |
| return 14; |
| }, |
| rotate = function () { |
| return 0; |
| }, |
| params = ["font", "fontStyle", "fontWeight", "padding"]; |
| |
| var output = { |
| "x": "x", |
| "y": "y", |
| "size": "fontSize", |
| "font": "font", |
| "rotate": "angle" |
| }; |
| |
| function cloud(data, db, group) { |
| function finish(tags, bounds) { |
| var size = layout.size(), |
| dx = size[0] / 2, |
| dy = size[1] / 2, |
| keys = vg.keys(output), |
| key, d, i, n, k, m = keys.length; |
| |
| // sort data to match wordcloud order |
| data.sort(function (a, b) { |
| return fontSize(b) - fontSize(a); |
| }); |
| |
| for (i = 0, n = tags.length; i < n; ++i) { |
| d = data[i]; |
| for (k = 0; k < m; ++k) { |
| key = keys[k]; |
| d[output[key]] = tags[i][key]; |
| if (key === "x") d[output.x] += dx; |
| if (key === "y") d[output.y] += dy; |
| } |
| } |
| } |
| |
| layout |
| .size(vg.data.size(size, group)) |
| .text(text) |
| .fontSize(fontSize) |
| .rotate(rotate) |
| .words(data) |
| .on("end", finish) |
| .start(); |
| return data; |
| } |
| |
| cloud.text = function (field) { |
| text = vg.accessor(field); |
| return cloud; |
| }; |
| |
| cloud.size = function (sz) { |
| size = sz; |
| return cloud; |
| }; |
| |
| cloud.fontSize = function (field) { |
| fontSize = vg.accessor(field); |
| return cloud; |
| }; |
| |
| cloud.rotate = function (x) { |
| var v; |
| if (vg.isObject(x) && !Array.isArray(x)) { |
| if (x.random !== undefined) { |
| v = (v = x.random) ? vg.array(v) : [0]; |
| rotate = function () { |
| return v[~~(Math.random() * v.length - 0.00001)]; |
| }; |
| } else if (x.alternate !== undefined) { |
| v = (v = x.alternate) ? vg.array(v) : [0]; |
| rotate = function (d, i) { |
| return v[i % v.length]; |
| }; |
| } |
| } else { |
| rotate = vg.accessor(field); |
| } |
| return cloud; |
| }; |
| |
| params.forEach(function (name) { |
| cloud[name] = function (x) { |
| layout[name](x); |
| return cloud; |
| } |
| }); |
| |
| cloud.output = function (map) { |
| vg.keys(output).forEach(function (k) { |
| if (map[k] !== undefined) { |
| output[k] = map[k]; |
| } |
| }); |
| return cloud; |
| }; |
| |
| return cloud; |
| }; |
| vg.data.zip = function () { |
| var z = null, |
| as = "zip", |
| key = vg.accessor("data"), |
| defaultValue = undefined, |
| withKey = null; |
| |
| function zip(data, db) { |
| var zdata = db[z], zlen = zdata.length, v, d, i, len, map; |
| |
| if (withKey) { |
| map = {}; |
| zdata.forEach(function (s) { |
| map[withKey(s)] = s; |
| }); |
| } |
| |
| for (i = 0, len = data.length; i < len; ++i) { |
| d = data[i]; |
| d[as] = map |
| ? ((v = map[key(d)]) != null ? v : defaultValue) |
| : zdata[i % zlen]; |
| } |
| |
| return data; |
| } |
| |
| zip["with"] = function (d) { |
| z = d; |
| return zip; |
| }; |
| |
| zip["default"] = function (d) { |
| defaultValue = d; |
| return zip; |
| }; |
| |
| zip.as = function (name) { |
| as = name; |
| return zip; |
| }; |
| |
| zip.key = function (k) { |
| key = vg.accessor(k); |
| return zip; |
| }; |
| |
| zip.withKey = function (k) { |
| withKey = vg.accessor(k); |
| return zip; |
| }; |
| |
| return zip; |
| }; |
| |
| vg.data.zip.dependencies = ["with"]; |
| vg.parse = {}; |
| vg.parse.axes = (function () { |
| var ORIENT = { |
| "x": "bottom", |
| "y": "left", |
| "top": "top", |
| "bottom": "bottom", |
| "left": "left", |
| "right": "right" |
| }; |
| |
| function axes(spec, axes, scales) { |
| (spec || []).forEach(function (def, index) { |
| axes[index] = axes[index] || vg.scene.axis(); |
| axis(def, index, axes[index], scales); |
| }); |
| }; |
| |
| function axis(def, index, axis, scales) { |
| // axis scale |
| if (def.scale !== undefined) { |
| axis.scale(scales[def.scale]); |
| } |
| |
| // axis orientation |
| axis.orient(def.orient || ORIENT[def.type]); |
| // axis offset |
| axis.offset(def.offset || 0); |
| // axis layer |
| axis.layer(def.layer || "front"); |
| // axis grid lines |
| axis.grid(def.grid || false); |
| // axis title |
| axis.title(def.title || null); |
| // axis title offset |
| axis.titleOffset(def.titleOffset != null |
| ? def.titleOffset : vg.config.axis.titleOffset); |
| // axis values |
| axis.tickValues(def.values || null); |
| // axis label formatting |
| axis.tickFormat(def.format || null); |
| // axis tick subdivision |
| axis.tickSubdivide(def.subdivide || 0); |
| // axis tick padding |
| axis.tickPadding(def.tickPadding || vg.config.axis.padding); |
| |
| // axis tick size(s) |
| var size = []; |
| if (def.tickSize !== undefined) { |
| for (var i = 0; i < 3; ++i) size.push(def.tickSize); |
| } else { |
| var ts = vg.config.axis.tickSize; |
| size = [ts, ts, ts]; |
| } |
| if (def.tickSizeMajor != null) size[0] = def.tickSizeMajor; |
| if (def.tickSizeMinor != null) size[1] = def.tickSizeMinor; |
| if (def.tickSizeEnd != null) size[2] = def.tickSizeEnd; |
| if (size.length) { |
| axis.tickSize.apply(axis, size); |
| } |
| |
| // tick arguments |
| if (def.ticks != null) { |
| var ticks = vg.isArray(def.ticks) ? def.ticks : [def.ticks]; |
| axis.ticks.apply(axis, ticks); |
| } else { |
| axis.ticks(vg.config.axis.ticks); |
| } |
| |
| // style properties |
| var p = def.properties; |
| if (p && p.ticks) { |
| axis.majorTickProperties(p.majorTicks |
| ? vg.extend({}, p.ticks, p.majorTicks) : p.ticks); |
| axis.minorTickProperties(p.minorTicks |
| ? vg.extend({}, p.ticks, p.minorTicks) : p.ticks); |
| } else { |
| axis.majorTickProperties(p && p.majorTicks || {}); |
| axis.minorTickProperties(p && p.minorTicks || {}); |
| } |
| axis.tickLabelProperties(p && p.labels || {}); |
| axis.titleProperties(p && p.title || {}); |
| axis.gridLineProperties(p && p.grid || {}); |
| axis.domainProperties(p && p.axis || {}); |
| } |
| |
| return axes; |
| })(); |
| vg.parse.data = function (spec, callback) { |
| var model = { |
| defs: spec, |
| load: {}, |
| flow: {}, |
| deps: {}, |
| source: {}, |
| sorted: null |
| }; |
| |
| var count = 0; |
| |
| function load(d) { |
| return function (error, data) { |
| if (error) { |
| vg.error("LOADING FAILED: " + d.url); |
| } else { |
| model.load[d.name] = vg.data.read(data.toString(), d.format); |
| } |
| if (--count === 0) callback(); |
| } |
| } |
| |
| // process each data set definition |
| (spec || []).forEach(function (d) { |
| if (d.url) { |
| count += 1; |
| vg.data.load(d.url, load(d)); |
| } else if (d.values) { |
| model.load[d.name] = vg.data.read(d.values, d.format); |
| } else if (d.source) { |
| (model.source[d.source] || (model.source[d.source] = [])).push(d.name); |
| } |
| |
| if (d.transform) { |
| var flow = vg.parse.dataflow(d); |
| model.flow[d.name] = flow; |
| flow.dependencies.forEach(function (dep) { |
| (model.deps[dep] || (model.deps[dep] = [])).push(d.name); |
| }); |
| } |
| }); |
| |
| // topological sort by dependencies |
| var names = (spec || []).map(vg.accessor("name")), |
| order = [], v = {}, n; |
| |
| function visit(n) { |
| if (v[n] === 1) return; // not a DAG! |
| if (!v[n]) { |
| v[n] = 1; |
| (model.source[n] || []).forEach(visit); |
| (model.deps[n] || []).forEach(visit); |
| v[n] = 2; |
| order.push(n); |
| } |
| } |
| |
| while (names.length) { |
| if (v[n = names.pop()] !== 2) visit(n); |
| } |
| model.sorted = order.reverse(); |
| |
| if (count === 0) setTimeout(callback, 1); |
| return model; |
| }; |
| vg.parse.dataflow = function (def) { |
| var tx = (def.transform || []).map(vg.parse.transform), |
| df = tx.length |
| ? function (data, db, group) { |
| return tx.reduce(function (d, t) { |
| return t(d, db, group); |
| }, data); |
| } |
| : vg.identity; |
| df.transforms = tx; |
| df.dependencies = vg.keys((def.transform || []) |
| .reduce(function (map, tdef) { |
| var deps = vg.data[tdef.type].dependencies; |
| if (deps) deps.forEach(function (d) { |
| if (tdef[d]) map[tdef[d]] = 1; |
| }); |
| return map; |
| }, {})); |
| return df; |
| }; |
| vg.parse.expr = (function () { |
| |
| var CONSTANT = { |
| "E": "Math.E", |
| "LN2": "Math.LN2", |
| "LN10": "Math.LN10", |
| "LOG2E": "Math.LOG2E", |
| "LOG10E": "Math.LOG10E", |
| "PI": "Math.PI", |
| "SQRT1_2": "Math.SQRT1_2", |
| "SQRT2": "Math.SQRT2" |
| }; |
| |
| var FUNCTION = { |
| "abs": "Math.abs", |
| "acos": "Math.acos", |
| "asin": "Math.asin", |
| "atan": "Math.atan", |
| "atan2": "Math.atan2", |
| "ceil": "Math.ceil", |
| "cos": "Math.cos", |
| "exp": "Math.exp", |
| "floor": "Math.floor", |
| "log": "Math.log", |
| "max": "Math.max", |
| "min": "Math.min", |
| "pow": "Math.pow", |
| "random": "Math.random", |
| "round": "Math.round", |
| "sin": "Math.sin", |
| "sqrt": "Math.sqrt", |
| "tan": "Math.tan" |
| }; |
| |
| var lexer = /([\"\']|[\=\<\>\~\&\|\?\:\+\-\/\*\%\!\^\,\;\[\]\{\}\(\) ]+)/; |
| |
| return function (x) { |
| if (vg.config.safeMode) { |
| vg.error("Safe mode: Expression parsing disabled."); |
| return vg.true; |
| } |
| |
| var tokens = x.split(lexer), |
| t, v, i, n, sq, dq; |
| |
| for (sq = 0, dq = 0, i = 0, n = tokens.length; i < n; ++i) { |
| var t = tokens[i]; |
| if (t === "'") { |
| if (!dq) sq = !sq; |
| continue; |
| } |
| if (t === '"') { |
| if (!sq) dq = !dq; |
| continue; |
| } |
| if (dq || sq) continue; |
| if (CONSTANT[t]) { |
| tokens[i] = CONSTANT[t]; |
| } |
| if (FUNCTION[t] && (v = tokens[i + 1]) && v[0] === "(") { |
| tokens[i] = FUNCTION[t]; |
| } |
| } |
| |
| return Function("d", "index", "data", "return (" + tokens.join("") + ");"); |
| }; |
| |
| })(); |
| vg.parse.legends = (function () { |
| |
| function legends(spec, legends, scales) { |
| (spec || []).forEach(function (def, index) { |
| legends[index] = legends[index] || vg.scene.legend(); |
| legend(def, index, legends[index], scales); |
| }); |
| }; |
| |
| function legend(def, index, legend, scales) { |
| // legend scales |
| legend.size(def.size ? scales[def.size] : null); |
| legend.shape(def.shape ? scales[def.shape] : null); |
| legend.fill(def.fill ? scales[def.fill] : null); |
| legend.stroke(def.stroke ? scales[def.stroke] : null); |
| |
| // legend orientation |
| if (def.orient) legend.orient(def.orient); |
| |
| // legend offset |
| if (def.offset != null) legend.offset(def.offset); |
| |
| // legend title |
| legend.title(def.title || null); |
| |
| // legend values |
| legend.values(def.values || null); |
| |
| // legend label formatting |
| legend.format(def.format !== undefined ? def.format : null); |
| |
| // style properties |
| var p = def.properties; |
| legend.titleProperties(p && p.title || {}); |
| legend.labelProperties(p && p.labels || {}); |
| legend.legendProperties(p && p.legend || {}); |
| legend.symbolProperties(p && p.symbols || {}); |
| legend.gradientProperties(p && p.gradient || {}); |
| } |
| |
| return legends; |
| })(); |
| vg.parse.mark = function (mark) { |
| var props = mark.properties, |
| group = mark.marks; |
| |
| // parse mark property definitions |
| vg.keys(props).forEach(function (k) { |
| props[k] = vg.parse.properties(mark.type, props[k]); |
| }); |
| |
| // parse delay function |
| if (mark.delay) { |
| mark.delay = vg.parse.properties(mark.type, {delay: mark.delay}); |
| } |
| |
| // parse mark data definition |
| if (mark.from) { |
| var name = mark.from.data, |
| tx = vg.parse.dataflow(mark.from); |
| mark.from = function (db, group, parentData) { |
| var data = vg.scene.data(name ? db[name] : null, parentData); |
| return tx(data, db, group); |
| }; |
| } |
| |
| // recurse if group type |
| if (group) { |
| mark.marks = group.map(vg.parse.mark); |
| } |
| |
| return mark; |
| }; |
| vg.parse.marks = function (spec, width, height) { |
| return { |
| type: "group", |
| width: width, |
| height: height, |
| scales: spec.scales || [], |
| axes: spec.axes || [], |
| legends: spec.legends || [], |
| marks: (spec.marks || []).map(vg.parse.mark) |
| }; |
| }; |
| vg.parse.padding = function (pad) { |
| if (pad == null) return "auto"; |
| else if (vg.isString(pad)) return pad === "strict" ? "strict" : "auto"; |
| else if (vg.isObject(pad)) return pad; |
| var p = vg.isNumber(pad) ? pad : 20; |
| return {top: p, left: p, right: p, bottom: p}; |
| }; |
| vg.parse.properties = (function () { |
| function compile(mark, spec) { |
| var code = "", |
| names = vg.keys(spec), |
| i, len, name, ref, vars = {}; |
| |
| code += "var o = trans ? {} : item;\n" |
| |
| for (i = 0, len = names.length; i < len; ++i) { |
| ref = spec[name = names[i]]; |
| code += (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.xc) { |
| if (vars.width) { |
| code += "\n o.x = (o.xc - o.width/2);"; |
| } else { |
| code += "\n o.x = o.xc;" |
| } |
| } |
| |
| 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 (vars.yc) { |
| if (vars.height) { |
| code += "\n o.y = (o.yc - o.height/2);"; |
| } else { |
| code += "\n o.y = o.yc;" |
| } |
| } |
| |
| 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", |
| QUANTILE = "quantile", |
| 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 dataDrivenRange = false, |
| pad = def.padding || 0, |
| outer = def.outerPadding || 0, |
| domain, sort, str, refs; |
| |
| // range pre-processing for data-driven ranges |
| if (vg.isObject(def.range) && !vg.isArray(def.range)) { |
| dataDrivenRange = true; |
| refs = def.range.fields || vg.array(def.range); |
| rng = extract(refs, db, data); |
| } |
| |
| // domain |
| sort = def.sort && !dataDrivenRange; |
| domain = domainValues(def, db, data, sort); |
| if (domain) scale.domain(domain); |
| |
| // width-defined range |
| if (def.bandWidth) { |
| var bw = def.bandWidth, |
| len = domain.length, |
| start = rng[0] || 0, |
| space = def.points ? (pad * bw) : (pad * bw * (len - 1) + 2 * outer); |
| rng = [start, start + (bw * len + space)]; |
| } |
| |
| // range |
| str = typeof rng[0] === 'string'; |
| if (str || rng.length > 2 || rng.length === 1 || dataDrivenRange) { |
| scale.range(rng); // color or shape values |
| } else if (def.points) { |
| scale.rangePoints(rng, pad); |
| } else if (def.round || def.round === undefined) { |
| scale.rangeRoundBands(rng, pad, outer); |
| } else { |
| scale.rangeBands(rng, pad, outer); |
| } |
| } |
| |
| function quantitative(def, scale, rng, db, data) { |
| var domain, interval; |
| |
| // domain |
| domain = (def.type === QUANTILE) |
| ? domainValues(def, db, data, false) |
| : domainMinMax(def, db, data); |
| 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 extract(refs, db, data) { |
| return 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); |
| }, []); |
| } |
| |
| function domainValues(def, db, data, sort) { |
| var domain = def.domain, values, refs; |
| if (vg.isArray(domain)) { |
| values = sort ? domain.slice() : domain; |
| } else if (vg.isObject(domain)) { |
| refs = domain.fields || vg.array(domain); |
| values = extract(refs, db, data); |
| } |
| if (values && sort) values.sort(vg.cmp); |
| return values; |
| } |
| |
| function domainMinMax(def, db, data) { |
| var domain = [null, null], refs, z; |
| |
| 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]); |
| } |
| return domain; |
| } |
| |
| 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 if (vg.isObject(def.range)) { |
| return null; // early exit |
| } 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.pathCache) this.pathCache = null; |
| if (this.mark.pathCache) this.mark.pathCache = null; |
| }; |
| |
| return item; |
| })(); |
| |
| vg.scene.item = function (mark) { |
| return new vg.scene.Item(mark); |
| }; |
| vg.scene.visit = function (node, func) { |
| var i, n, s, m, items; |
| if (func(node)) return true; |
| |
| var sets = ["items", "axisItems", "legendItems"]; |
| for (s = 0, m = sets.length; s < m; ++s) { |
| if (items = node[sets[s]]) { |
| for (i = 0, n = items.length; i < n; ++i) { |
| if (vg.scene.visit(items[i], func)) return true; |
| } |
| } |
| } |
| }; |
| vg.scene.build = (function () { |
| var GROUP = vg.scene.GROUP, |
| ENTER = vg.scene.ENTER, |
| UPDATE = vg.scene.UPDATE, |
| EXIT = vg.scene.EXIT, |
| DEFAULT = {"sentinel": 1}; |
| |
| function build(def, db, node, parentData, reentrant) { |
| var data = vg.scene.data( |
| def.from ? def.from(db, node, parentData) : null, |
| parentData); |
| |
| // build node and items |
| node = buildNode(def, node); |
| node.items = buildItems(def, data, node); |
| buildTrans(def, node); |
| |
| // recurse if group |
| if (def.type === GROUP) { |
| buildGroup(def, db, node, reentrant); |
| } |
| |
| return node; |
| }; |
| |
| function buildNode(def, node) { |
| node = node || {}; |
| node.def = def; |
| node.marktype = def.type; |
| node.interactive = !(def.interactive === false); |
| return node; |
| } |
| |
| function buildItems(def, data, node) { |
| var keyf = keyFunction(def.key), |
| prev = node.items || [], |
| next = [], |
| map = {}, |
| i, key, len, item, datum, enter; |
| |
| for (i = 0, len = prev.length; i < len; ++i) { |
| item = prev[i]; |
| item.status = EXIT; |
| if (keyf) map[item.key] = item; |
| } |
| |
| for (i = 0, len = data.length; i < len; ++i) { |
| datum = data[i]; |
| key = i; |
| item = keyf ? map[key = keyf(datum)] : prev[i]; |
| enter = item ? false : (item = vg.scene.item(node), true); |
| item.status = enter ? ENTER : UPDATE; |
| item.datum = datum; |
| item.key = key; |
| next.push(item); |
| } |
| |
| for (i = 0, len = prev.length; i < len; ++i) { |
| item = prev[i]; |
| if (item.status === EXIT) { |
| item.key = keyf ? item.key : next.length; |
| next.splice(item.index, 0, item); |
| } |
| } |
| |
| return next; |
| } |
| |
| function buildGroup(def, db, node, reentrant) { |
| var groups = node.items, |
| marks = def.marks, |
| i, len, m, mlen, name, group; |
| |
| for (i = 0, len = groups.length; i < len; ++i) { |
| group = groups[i]; |
| |
| // update scales |
| if (!reentrant && group.scales) for (name in group.scales) { |
| if (name.indexOf(":prev") < 0) { |
| group.scales[name + ":prev"] = group.scales[name].copy(); |
| } |
| } |
| |
| // build items |
| group.items = group.items || []; |
| for (m = 0, mlen = marks.length; m < mlen; ++m) { |
| group.items[m] = build(marks[m], db, group.items[m], group.datum); |
| group.items[m].group = group; |
| } |
| } |
| } |
| |
| function buildTrans(def, node) { |
| if (def.duration) node.duration = def.duration; |
| if (def.ease) node.ease = d3.ease(def.ease) |
| if (def.delay) { |
| var items = node.items, group = node.group, n = items.length, i; |
| for (i = 0; i < n; ++i) def.delay.call(this, items[i], group); |
| } |
| } |
| |
| function keyFunction(key) { |
| if (key == null) return null; |
| var f = vg.array(key).map(vg.accessor); |
| return function (d) { |
| for (var s = "", i = 0, n = f.length; i < n; ++i) { |
| if (i > 0) 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.pathCache || (o.pathCache = parse(o.path)) |
| : null; |
| return pathBounds(o, p, bounds); |
| } |
| |
| function area(o, bounds) { |
| var items = o.mark.items, o = items[0]; |
| var p = o.pathCache || (o.pathCache = parse(areaPath(items))); |
| return pathBounds(items[0], p, bounds); |
| } |
| |
| function line(o, bounds) { |
| var items = o.mark.items, o = items[0]; |
| var p = o.pathCache || (o.pathCache = 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 < ea; ++i, s += halfpi) { |
| angles.push(s); |
| } |
| |
| for (i = 0, n = angles.length; i < n; ++i) { |
| a = angles[i]; |
| x = Math.cos(a); |
| ix = ir * x; |
| ox = or * x; |
| y = Math.sin(a); |
| iy = ir * y; |
| oy = or * y; |
| xmin = Math.min(xmin, ix, ox); |
| xmax = Math.max(xmax, ix, ox); |
| ymin = Math.min(ymin, iy, oy); |
| ymax = Math.max(ymax, iy, oy); |
| } |
| |
| bounds.set(cx + xmin, cy + ymin, cx + xmax, cy + ymax); |
| if (o.stroke && o.opacity !== 0 && o.strokeWidth > 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, |
| r = o.radius || 0, |
| g = context(), w, t; |
| |
| g.font = vg.scene.fontString(o); |
| g.textAlign = a || "left"; |
| g.textBaseline = b || "alphabetic"; |
| w = g.measureText(o.text || "").width; |
| |
| if (r) { |
| t = (o.theta || 0) - Math.PI / 2; |
| x += r * Math.cos(t); |
| y += r * Math.sin(t); |
| } |
| |
| // 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 < m; ++j) { |
| bounds.union(axes[j].bounds); |
| } |
| for (j = 0, m = g.items.length; j < m; ++j) { |
| bounds.union(g.items[j].bounds); |
| } |
| if (includeLegends) { |
| for (j = 0, m = legends.length; j < m; ++j) { |
| bounds.union(legends[j].bounds); |
| } |
| if (g.width != null && g.height != null) { |
| bounds.add(g.width, g.height); |
| } |
| if (g.x != null && g.y != null) { |
| bounds.add(0, 0); |
| } |
| } |
| bounds.translate(g.x || 0, g.y || 0); |
| return bounds; |
| } |
| |
| var methods = { |
| group: group, |
| symbol: symbol, |
| image: image, |
| rect: rect, |
| rule: rule, |
| arc: arc, |
| text: text, |
| path: path, |
| area: area, |
| line: line |
| }; |
| |
| function itemBounds(item, func, opt) { |
| func = func || methods[item.mark.marktype]; |
| if (!item.bounds_prev) item['bounds:prev'] = new vg.Bounds(); |
| var b = item.bounds, pb = item['bounds:prev']; |
| if (b) pb.clear().union(b); |
| item.bounds = func(item, b ? b.clear() : new vg.Bounds(), opt); |
| if (!b) pb.clear().union(item.bounds); |
| return item.bounds; |
| } |
| |
| function markBounds(mark, bounds, opt) { |
| bounds = bounds || mark.bounds && mark.bounds.clear() || new vg.Bounds(); |
| var type = mark.marktype, |
| func = methods[type], |
| items = mark.items, |
| item, i, len; |
| |
| if (type === "area" || type === "line") { |
| if (items.length) { |
| items[0].bounds = func(items[0], bounds); |
| } |
| } else { |
| for (i = 0, len = items.length; i < len; ++i) { |
| bounds.union(itemBounds(items[i], func, opt)); |
| } |
| } |
| mark.bounds = bounds; |
| } |
| |
| return { |
| mark: markBounds, |
| item: itemBounds, |
| text: text, |
| group: group |
| }; |
| |
| })(); |
| vg.scene.encode = (function () { |
| var GROUP = vg.scene.GROUP, |
| ENTER = vg.scene.ENTER, |
| UPDATE = vg.scene.UPDATE, |
| EXIT = vg.scene.EXIT, |
| EMPTY = {}; |
| |
| function main(scene, def, trans, request, items) { |
| (request && items) |
| ? update.call(this, scene, def, trans, request, items) |
| : encode.call(this, scene, scene, def, trans, request); |
| return scene; |
| } |
| |
| function update(scene, def, trans, request, items) { |
| items = vg.array(items); |
| var i, len, item, group, props, prop; |
| for (i = 0, len = items.length; i < len; ++i) { |
| item = items[i]; |
| group = item.mark.group || null; |
| props = item.mark.def.properties; |
| prop = props && props[request]; |
| if (prop) { |
| prop.call(vg, item, group, trans); |
| vg.scene.bounds.item(item); |
| } |
| } |
| } |
| |
| function encode(group, scene, def, trans, request) { |
| encodeItems.call(this, group, scene.items, def, trans, request); |
| if (scene.marktype === GROUP) { |
| encodeGroup.call(this, scene, def, group, trans, request); |
| } else { |
| vg.scene.bounds.mark(scene); |
| } |
| } |
| |
| function encodeLegend(group, scene, def, trans, request) { |
| encodeGroup.call(this, scene, def, group, trans, request); |
| encodeItems.call(this, group, scene.items, def, trans, request); |
| vg.scene.bounds.mark(scene, null, true); |
| } |
| |
| function encodeGroup(scene, def, parent, trans, request) { |
| var i, len, m, mlen, group, scales, |
| axes, axisItems, axisDef, leg, legItems, legDef; |
| |
| for (i = 0, len = scene.items.length; i < len; ++i) { |
| group = scene.items[i]; |
| |
| // cascade scales recursively |
| // use parent scales if there are no group-level scale defs |
| scales = group.scales || (group.scales = |
| def.scales ? vg.extend({}, parent.scales) : parent.scales); |
| |
| // update group-level scales |
| if (def.scales) { |
| vg.parse.scales(def.scales, scales, this._data, group); |
| } |
| |
| // update group-level axes |
| if (def.axes) { |
| axes = group.axes || (group.axes = []); |
| axisItems = group.axisItems || (group.axisItems = []); |
| vg.parse.axes(def.axes, axes, group.scales); |
| axes.forEach(function (a, i) { |
| axisDef = a.def(); |
| axisItems[i] = vg.scene.build(axisDef, this._data, axisItems[i], null, 1); |
| axisItems[i].group = group; |
| encode.call(this, group, group.axisItems[i], axisDef, trans); |
| }); |
| } |
| |
| // encode children marks |
| for (m = 0, mlen = group.items.length; m < mlen; ++m) { |
| encode.call(this, group, group.items[m], def.marks[m], trans, request); |
| } |
| } |
| |
| // compute bounds (without legend) |
| vg.scene.bounds.mark(scene, null, !def.legends); |
| |
| // update legends |
| if (def.legends) { |
| for (i = 0, len = scene.items.length; i < len; ++i) { |
| group = scene.items[i]; |
| leg = group.legends || (group.legends = []); |
| legItems = group.legendItems || (group.legendItems = []); |
| vg.parse.legends(def.legends, leg, group.scales); |
| leg.forEach(function (l, i) { |
| legDef = l.def(); |
| legItems[i] = vg.scene.build(legDef, this._data, legItems[i], null, 1); |
| legItems[i].group = group; |
| encodeLegend.call(this, group, group.legendItems[i], legDef, trans); |
| }); |
| } |
| vg.scene.bounds.mark(scene, null, true); |
| } |
| } |
| |
| function encodeItems(group, items, def, trans, request) { |
| var props = def.properties || EMPTY, |
| enter = props.enter, |
| update = props.update, |
| exit = props.exit, |
| i, len, item, prop; |
| |
| if (request) { |
| if (prop = props[request]) { |
| for (i = 0, len = items.length; i < len; ++i) { |
| prop.call(vg, items[i], group, trans); |
| } |
| } |
| return; // exit early if given request |
| } |
| |
| for (i = 0; i < items.length; ++i) { |
| item = items[i]; |
| |
| // enter set |
| if (item.status === ENTER) { |
| if (enter) enter.call(vg, item, group); |
| item.status = UPDATE; |
| } |
| |
| // update set |
| if (item.status !== EXIT && update) { |
| update.call(vg, item, group, trans); |
| } |
| |
| // exit set |
| if (item.status === EXIT) { |
| if (exit) exit.call(vg, item, group, trans); |
| if (trans && !exit) trans.interpolate(item, EMPTY); |
| else if (!trans) items[i--].remove(); |
| } |
| } |
| } |
| |
| return main; |
| })(); |
| vg.scene.Transition = (function () { |
| function trans(duration, ease) { |
| this.duration = duration || 500; |
| this.ease = ease && d3.ease(ease) || d3.ease("cubic-in-out"); |
| this.updates = {next: null}; |
| } |
| |
| var prototype = trans.prototype; |
| |
| var skip = { |
| "text": 1, |
| "url": 1 |
| }; |
| |
| prototype.interpolate = function (item, values) { |
| var key, curr, next, interp, list = null; |
| |
| for (key in values) { |
| curr = item[key]; |
| next = values[key]; |
| if (curr !== next) { |
| if (skip[key] || curr === undefined) { |
| // skip interpolation for specific keys or undefined start values |
| item[key] = next; |
| } else if (typeof curr === "number" && !isFinite(curr)) { |
| // for NaN or infinite numeric values, skip to final value |
| item[key] = next; |
| } else { |
| // otherwise lookup interpolator |
| interp = d3.interpolate(curr, next); |
| interp.property = key; |
| (list || (list = [])).push(interp); |
| } |
| } |
| } |
| |
| if (list === null && item.status === vg.scene.EXIT) { |
| list = []; // ensure exiting items are included |
| } |
| |
| if (list != null) { |
| list.item = item; |
| list.ease = item.mark.ease || this.ease; |
| list.next = this.updates.next; |
| this.updates.next = list; |
| } |
| return this; |
| }; |
| |
| prototype.start = function (callback) { |
| var t = this, prev = t.updates, curr = prev.next; |
| for (; curr != null; prev = curr, curr = prev.next) { |
| if (curr.item.status === vg.scene.EXIT) curr.remove = true; |
| } |
| t.callback = callback; |
| d3.timer(function (elapsed) { |
| return step.call(t, elapsed); |
| }); |
| }; |
| |
| function step(elapsed) { |
| var list = this.updates, prev = list, curr = prev.next, |
| duration = this.duration, |
| item, delay, f, e, i, n, stop = true; |
| |
| for (; curr != null; prev = curr, curr = prev.next) { |
| item = curr.item; |
| delay = item.delay || 0; |
| |
| f = (elapsed - delay) / duration; |
| if (f < 0) { |
| stop = false; |
| continue; |
| } |
| if (f > 1) f = 1; |
| e = curr.ease(f); |
| |
| for (i = 0, n = curr.length; i < n; ++i) { |
| item[curr[i].property] = curr[i](e); |
| } |
| item.touch(); |
| vg.scene.bounds.item(item); |
| |
| if (f === 1) { |
| if (curr.remove) item.remove(); |
| prev.next = curr.next; |
| curr = prev; |
| } else { |
| stop = false; |
| } |
| } |
| |
| this.callback(); |
| return stop; |
| }; |
| |
| return trans; |
| |
| })(); |
| |
| vg.scene.transition = function (dur, ease) { |
| return new vg.scene.Transition(dur, ease); |
| }; |
| vg.scene.axis = function () { |
| var scale, |
| orient = vg.config.axis.orient, |
| offset = 0, |
| titleOffset = vg.config.axis.titleOffset, |
| axisDef = null, |
| layer = "front", |
| grid = false, |
| title = null, |
| tickMajorSize = vg.config.axis.tickSize, |
| tickMinorSize = vg.config.axis.tickSize, |
| tickEndSize = vg.config.axis.tickSize, |
| tickPadding = vg.config.axis.padding, |
| tickValues = null, |
| tickFormatString = null, |
| tickFormat = null, |
| tickSubdivide = 0, |
| tickArguments = [vg.config.axis.ticks], |
| gridLineStyle = {}, |
| tickLabelStyle = {}, |
| majorTickStyle = {}, |
| minorTickStyle = {}, |
| titleStyle = {}, |
| domainStyle = {}; |
| |
| var axis = {}; |
| |
| function reset() { |
| axisDef = null; |
| } |
| |
| axis.def = function () { |
| var def = axisDef ? axisDef : (axisDef = axis_def(scale)); |
| |
| // tick format |
| tickFormat = !tickFormatString ? null : ((scale.type === 'time') |
| ? d3.time.format(tickFormatString) |
| : d3.format(tickFormatString)); |
| |
| // generate data |
| var major = tickValues == null |
| ? (scale.ticks ? scale.ticks.apply(scale, tickArguments) : scale.domain()) |
| : tickValues; |
| var minor = vg_axisSubdivide(scale, major, tickSubdivide).map(vg.data.ingest); |
| major = major.map(vg.data.ingest); |
| var fmt = tickFormat == null ? (scale.tickFormat ? scale.tickFormat.apply(scale, tickArguments) : String) : tickFormat; |
| major.forEach(function (d) { |
| d.label = fmt(d.data); |
| }); |
| var tdata = title ? [title].map(vg.data.ingest) : []; |
| |
| // update axis def |
| def.marks[0].from = function () { |
| return grid ? major : []; |
| }; |
| def.marks[1].from = function () { |
| return major; |
| }; |
| def.marks[2].from = function () { |
| return minor; |
| }; |
| def.marks[3].from = def.marks[1].from; |
| def.marks[4].from = function () { |
| return [1]; |
| }; |
| def.marks[5].from = function () { |
| return tdata; |
| }; |
| def.offset = offset; |
| def.orient = orient; |
| def.layer = layer; |
| return def; |
| }; |
| |
| function axis_def(scale) { |
| // setup scale mapping |
| var newScale, oldScale, range; |
| if (scale.type === "ordinal") { |
| newScale = {scale: scale.scaleName, offset: 0.5 + scale.rangeBand() / 2}; |
| oldScale = newScale; |
| } else { |
| newScale = {scale: scale.scaleName, offset: 0.5}; |
| oldScale = {scale: scale.scaleName + ":prev", offset: 0.5}; |
| } |
| range = vg_axisScaleRange(scale); |
| |
| // setup axis marks |
| var gridLines = vg_axisTicks(); |
| var majorTicks = vg_axisTicks(); |
| var minorTicks = vg_axisTicks(); |
| var tickLabels = vg_axisTickLabels(); |
| var domain = vg_axisDomain(); |
| var title = vg_axisTitle(); |
| gridLines.properties.enter.stroke = {value: vg.config.axis.gridColor}; |
| |
| // extend axis marks based on axis orientation |
| vg_axisTicksExtend(orient, gridLines, oldScale, newScale, Infinity); |
| vg_axisTicksExtend(orient, majorTicks, oldScale, newScale, tickMajorSize); |
| vg_axisTicksExtend(orient, minorTicks, oldScale, newScale, tickMinorSize); |
| vg_axisLabelExtend(orient, tickLabels, oldScale, newScale, tickMajorSize, tickPadding); |
| |
| vg_axisDomainExtend(orient, domain, range, tickEndSize); |
| vg_axisTitleExtend(orient, title, range, titleOffset); // TODO get offset |
| |
| // add / override custom style properties |
| vg.extend(gridLines.properties.update, gridLineStyle); |
| vg.extend(majorTicks.properties.update, majorTickStyle); |
| vg.extend(minorTicks.properties.update, minorTickStyle); |
| vg.extend(tickLabels.properties.update, tickLabelStyle); |
| vg.extend(domain.properties.update, domainStyle); |
| vg.extend(title.properties.update, titleStyle); |
| |
| var marks = [gridLines, majorTicks, minorTicks, tickLabels, domain, title]; |
| return { |
| type: "group", |
| interactive: false, |
| properties: {enter: vg_axisUpdate, update: vg_axisUpdate}, |
| marks: marks.map(vg.parse.mark) |
| }; |
| } |
| |
| axis.scale = function (x) { |
| if (!arguments.length) return scale; |
| if (scale !== x) { |
| scale = x; |
| reset(); |
| } |
| return axis; |
| }; |
| |
| axis.orient = function (x) { |
| if (!arguments.length) return orient; |
| if (orient !== x) { |
| orient = x in vg_axisOrients ? x + "" : vg.config.axis.orient; |
| reset(); |
| } |
| return axis; |
| }; |
| |
| axis.title = function (x) { |
| if (!arguments.length) return title; |
| if (title !== x) { |
| title = x; |
| reset(); |
| } |
| return axis; |
| }; |
| |
| axis.ticks = function () { |
| if (!arguments.length) return tickArguments; |
| tickArguments = arguments; |
| return axis; |
| }; |
| |
| axis.tickValues = function (x) { |
| if (!arguments.length) return tickValues; |
| tickValues = x; |
| return axis; |
| }; |
| |
| axis.tickFormat = function (x) { |
| if (!arguments.length) return tickFormatString; |
| if (tickFormatString !== x) { |
| tickFormatString = x; |
| reset(); |
| } |
| return axis; |
| }; |
| |
| axis.tickSize = function (x, y) { |
| if (!arguments.length) return tickMajorSize; |
| var n = arguments.length - 1, |
| major = +x, |
| minor = n > 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[0] + range[1]) / 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.isArray(offset)) { |
| var ofx = offset[0], |
| ofy = offset[1]; |
| |
| switch (orient) { |
| case "left": |
| { |
| o.x = -ofx; |
| o.y = ofy; |
| break; |
| } |
| case "right": |
| { |
| o.x = width + ofx; |
| o.y = ofy; |
| break; |
| } |
| case "bottom": |
| { |
| o.x = ofx; |
| o.y = height + ofy; |
| break; |
| } |
| case "top": |
| { |
| o.x = ofx; |
| o.y = -ofy; |
| break; |
| } |
| default: |
| { |
| o.x = ofx; |
| o.y = ofy; |
| } |
| } |
| } else { |
| 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, |
| formatString = 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; |
| |
| format = !formatString ? null : ((scale.type === 'time') |
| ? d3.time.format(formatString) |
| : d3.format(formatString)); |
| |
| 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 < n; ++i) range[i] += sz; |
| |
| // build scale for label layout |
| var scale = { |
| name: "legend", |
| type: "ordinal", |
| points: true, |
| domain: domain, |
| range: range |
| }; |
| |
| // update legend def |
| var tdata = (title ? [title] : []).map(vg.data.ingest); |
| data.forEach(function (d) { |
| d.label = fmt(d.data); |
| d.offset = offset; |
| }); |
| def.scales = [scale]; |
| def.marks[0].from = function () { |
| return tdata; |
| }; |
| def.marks[1].from = function () { |
| return data; |
| }; |
| def.marks[2].from = def.marks[1].from; |
| return def; |
| } |
| |
| function o_legend_def(size, shape, fill, stroke) { |
| // setup legend marks |
| var titles = vg_legendTitle(), |
| symbols = vg_legendSymbols(), |
| labels = vg_vLegendLabels(); |
| |
| // extend legend marks |
| vg_legendSymbolExtend(symbols, size, shape, fill, stroke); |
| |
| // add / override custom style properties |
| vg.extend(titles.properties.update, titleStyle); |
| vg.extend(symbols.properties.update, symbolStyle); |
| vg.extend(labels.properties.update, labelStyle); |
| |
| // padding from legend border |
| titles.properties.enter.x.value += padding; |
| titles.properties.enter.y.value += padding; |
| labels.properties.enter.x.offset += padding + 1; |
| symbols.properties.enter.x.offset = padding + 1; |
| labels.properties.update.x.offset += padding + 1; |
| symbols.properties.update.x.offset = padding + 1; |
| |
| return { |
| type: "group", |
| interactive: false, |
| properties: { |
| enter: vg.parse.properties("group", legendStyle), |
| update: vg_legendUpdate |
| }, |
| marks: [titles, symbols, labels].map(vg.parse.mark) |
| }; |
| } |
| |
| function quantDef(scale) { |
| var def = q_legend_def(scale), |
| dom = scale.domain(), |
| data = dom.map(vg.data.ingest), |
| width = (gradientStyle.width && gradientStyle.width.value) || vg.config.legend.gradientWidth, |
| fmt = format == null ? (scale.tickFormat ? scale.tickFormat.apply(scale, tickArguments) : String) : format; |
| |
| // build scale for label layout |
| var layout = { |
| name: "legend", |
| type: scale.type, |
| round: true, |
| zero: false, |
| domain: [dom[0], dom[dom.length - 1]], |
| range: [padding, width + padding] |
| }; |
| if (scale.type === "pow") layout.exponent = scale.exponent(); |
| |
| // update legend def |
| var tdata = (title ? [title] : []).map(vg.data.ingest); |
| data.forEach(function (d, i) { |
| d.label = fmt(d.data); |
| d.align = i == (data.length - 1) ? "right" : i == 0 ? "left" : "center"; |
| }); |
| def.scales = [layout]; |
| def.marks[0].from = function () { |
| return tdata; |
| }; |
| def.marks[1].from = function () { |
| return [1]; |
| }; |
| def.marks[2].from = function () { |
| return data; |
| }; |
| return def; |
| } |
| |
| function q_legend_def(scale) { |
| // setup legend marks |
| var titles = vg_legendTitle(), |
| gradient = vg_legendGradient(), |
| labels = vg_hLegendLabels(), |
| grad = new vg.Gradient(); |
| |
| // setup color gradient |
| var dom = scale.domain(), |
| min = dom[0], |
| max = dom[dom.length - 1], |
| f = scale.copy().domain([min, max]).range([0, 1]); |
| |
| var stops = (scale.type !== "linear" && scale.ticks) |
| ? scale.ticks.call(scale, 15) : dom; |
| if (min !== stops[0]) stops.unshift(min); |
| if (max !== stops[stops.length - 1]) stops.push(max); |
| |
| for (var i = 0, n = stops.length; i < n; ++i) { |
| grad.stop(f(stops[i]), scale(stops[i])); |
| } |
| gradient.properties.enter.fill = {value: grad}; |
| |
| // add / override custom style properties |
| vg.extend(titles.properties.update, titleStyle); |
| vg.extend(gradient.properties.update, gradientStyle); |
| vg.extend(labels.properties.update, labelStyle); |
| |
| // account for gradient size |
| var gp = gradient.properties, gh = gradientStyle.height, |
| hh = (gh && gh.value) || gp.enter.height.value; |
| labels.properties.enter.y.value = hh; |
| labels.properties.update.y.value = hh; |
| |
| // account for title size as needed |
| if (title) { |
| var tp = titles.properties, fs = titleStyle.fontSize, |
| sz = 4 + ((fs && fs.value) || tp.enter.fontSize.value); |
| gradient.properties.enter.y.value += sz; |
| labels.properties.enter.y.value += sz; |
| gradient.properties.update.y.value += sz; |
| labels.properties.update.y.value += sz; |
| } |
| |
| // padding from legend border |
| titles.properties.enter.x.value += padding; |
| titles.properties.enter.y.value += padding; |
| gradient.properties.enter.x.value += padding; |
| gradient.properties.enter.y.value += padding; |
| labels.properties.enter.y.value += padding; |
| gradient.properties.update.x.value += padding; |
| gradient.properties.update.y.value += padding; |
| labels.properties.update.y.value += padding; |
| |
| return { |
| type: "group", |
| interactive: false, |
| properties: { |
| enter: vg.parse.properties("group", legendStyle), |
| update: vg_legendUpdate |
| }, |
| marks: [titles, gradient, labels].map(vg.parse.mark) |
| }; |
| } |
| |
| legend.size = function (x) { |
| if (!arguments.length) return size; |
| if (size !== x) { |
| size = x; |
| reset(); |
| } |
| return legend; |
| }; |
| |
| legend.shape = function (x) { |
| if (!arguments.length) return shape; |
| if (shape !== x) { |
| shape = x; |
| reset(); |
| } |
| return legend; |
| }; |
| |
| legend.fill = function (x) { |
| if (!arguments.length) return fill; |
| if (fill !== x) { |
| fill = x; |
| reset(); |
| } |
| return legend; |
| }; |
| |
| legend.stroke = function (x) { |
| if (!arguments.length) return stroke; |
| if (stroke !== x) { |
| stroke = x; |
| reset(); |
| } |
| return legend; |
| }; |
| |
| legend.title = function (x) { |
| if (!arguments.length) return title; |
| if (title !== x) { |
| title = x; |
| reset(); |
| } |
| return legend; |
| }; |
| |
| legend.format = function (x) { |
| if (!arguments.length) return formatString; |
| if (formatString !== x) { |
| formatString = x; |
| reset(); |
| } |
| return legend; |
| }; |
| |
| legend.spacing = function (x) { |
| if (!arguments.length) return spacing; |
| if (spacing !== +x) { |
| spacing = +x; |
| reset(); |
| } |
| return legend; |
| }; |
| |
| legend.orient = function (x) { |
| if (!arguments.length) return orient; |
| orient = x in vg_legendOrients ? x + "" : vg.config.legend.orient; |
| return legend; |
| }; |
| |
| legend.offset = function (x) { |
| if (!arguments.length) return offset; |
| offset = +x; |
| return legend; |
| }; |
| |
| legend.values = function (x) { |
| if (!arguments.length) return values; |
| values = x; |
| return legend; |
| }; |
| |
| legend.legendProperties = function (x) { |
| if (!arguments.length) return legendStyle; |
| legendStyle = x; |
| return legend; |
| }; |
| |
| legend.symbolProperties = function (x) { |
| if (!arguments.length) return symbolStyle; |
| symbolStyle = x; |
| return legend; |
| }; |
| |
| legend.gradientProperties = function (x) { |
| if (!arguments.length) return gradientStyle; |
| gradientStyle = x; |
| return legend; |
| }; |
| |
| legend.labelProperties = function (x) { |
| if (!arguments.length) return labelStyle; |
| labelStyle = x; |
| return legend; |
| }; |
| |
| legend.titleProperties = function (x) { |
| if (!arguments.length) return titleStyle; |
| titleStyle = x; |
| return legend; |
| }; |
| |
| legend.reset = function () { |
| reset(); |
| }; |
| |
| return legend; |
| }; |
| |
| var vg_legendOrients = {right: 1, left: 1}; |
| |
| function vg_legendUpdate(item, group, trans) { |
| var o = trans ? {} : item, gx, |
| offset = item.mark.def.offset, |
| orient = item.mark.def.orient, |
| pad = item.mark.def.padding * 2, |
| lw = ~~item.bounds.width() + (item.width ? 0 : pad), |
| lh = ~~item.bounds.height() + (item.height ? 0 : pad); |
| |
| o.x = 0.5; |
| o.y = 0.5; |
| o.width = lw; |
| o.height = lh; |
| |
| // HACK: use to estimate group bounds during animated transition |
| if (!trans && group.bounds) { |
| group.bounds.delta = group.bounds.x2 - group.width; |
| } |
| |
| switch (orient) { |
| case "left": |
| { |
| gx = group.bounds ? group.bounds.x1 : 0; |
| o.x += gx - offset - lw; |
| break; |
| } |
| ; |
| case "right": |
| { |
| gx = group.width; |
| if (group.bounds) gx = trans |
| ? group.width + group.bounds.delta |
| : group.bounds.x2; |
| o.x += gx + offset; |
| break; |
| } |
| ; |
| } |
| |
| if (trans) trans.interpolate(item, o); |
| item.mark.def.properties.enter(item, group, trans); |
| } |
| |
| function vg_legendSymbolExtend(mark, size, shape, fill, stroke) { |
| var e = mark.properties.enter, |
| u = mark.properties.update; |
| if (size) e.size = u.size = {scale: size.scaleName, field: "data"}; |
| if (shape) e.shape = u.shape = {scale: shape.scaleName, field: "data"}; |
| if (fill) e.fill = u.fill = {scale: fill.scaleName, field: "data"}; |
| if (stroke) e.stroke = u.stroke = {scale: stroke.scaleName, field: "data"}; |
| } |
| |
| function vg_legendTitle() { |
| var cfg = vg.config.legend; |
| return { |
| type: "text", |
| interactive: false, |
| key: "data", |
| properties: { |
| enter: { |
| x: {value: 0}, |
| y: {value: 0}, |
| fill: {value: cfg.titleColor}, |
| font: {value: cfg.titleFont}, |
| fontSize: {value: cfg.titleFontSize}, |
| fontWeight: {value: cfg.titleFontWeight}, |
| baseline: {value: "top"}, |
| text: {field: "data"}, |
| opacity: {value: 1e-6} |
| }, |
| exit: {opacity: {value: 1e-6}}, |
| update: {opacity: {value: 1}} |
| } |
| }; |
| } |
| |
| function vg_legendSymbols() { |
| var cfg = vg.config.legend; |
| return { |
| type: "symbol", |
| interactive: false, |
| key: "data", |
| properties: { |
| enter: { |
| x: {field: "offset", mult: 0.5}, |
| y: {scale: "legend", field: "index"}, |
| shape: {value: cfg.symbolShape}, |
| size: {value: cfg.symbolSize}, |
| stroke: {value: cfg.symbolColor}, |
| strokeWidth: {value: cfg.symbolStrokeWidth}, |
| opacity: {value: 1e-6} |
| }, |
| exit: {opacity: {value: 1e-6}}, |
| update: { |
| x: {field: "offset", mult: 0.5}, |
| y: {scale: "legend", field: "index"}, |
| opacity: {value: 1} |
| } |
| } |
| }; |
| } |
| |
| function vg_vLegendLabels() { |
| var cfg = vg.config.legend; |
| return { |
| type: "text", |
| interactive: false, |
| key: "data", |
| properties: { |
| enter: { |
| x: {field: "offset", offset: 5}, |
| y: {scale: "legend", field: "index"}, |
| fill: {value: cfg.labelColor}, |
| font: {value: cfg.labelFont}, |
| fontSize: {value: cfg.labelFontSize}, |
| align: {value: cfg.labelAlign}, |
| baseline: {value: cfg.labelBaseline}, |
| text: {field: "label"}, |
| opacity: {value: 1e-6} |
| }, |
| exit: {opacity: {value: 1e-6}}, |
| update: { |
| opacity: {value: 1}, |
| x: {field: "offset", offset: 5}, |
| y: {scale: "legend", field: "index"}, |
| } |
| } |
| }; |
| } |
| |
| function vg_legendGradient() { |
| var cfg = vg.config.legend; |
| return { |
| type: "rect", |
| interactive: false, |
| properties: { |
| enter: { |
| x: {value: 0}, |
| y: {value: 0}, |
| width: {value: cfg.gradientWidth}, |
| height: {value: cfg.gradientHeight}, |
| stroke: {value: cfg.gradientStrokeColor}, |
| strokeWidth: {value: cfg.gradientStrokeWidth}, |
| opacity: {value: 1e-6} |
| }, |
| exit: {opacity: {value: 1e-6}}, |
| update: { |
| x: {value: 0}, |
| y: {value: 0}, |
| opacity: {value: 1} |
| } |
| } |
| }; |
| } |
| |
| function vg_hLegendLabels() { |
| var cfg = vg.config.legend; |
| return { |
| type: "text", |
| interactive: false, |
| key: "data", |
| properties: { |
| enter: { |
| x: {scale: "legend", field: "data"}, |
| y: {value: 20}, |
| dy: {value: 2}, |
| fill: {value: cfg.labelColor}, |
| font: {value: cfg.labelFont}, |
| fontSize: {value: cfg.labelFontSize}, |
| align: {field: "align"}, |
| baseline: {value: "top"}, |
| text: {field: "label"}, |
| opacity: {value: 1e-6} |
| }, |
| exit: {opacity: {value: 1e-6}}, |
| update: { |
| x: {scale: "legend", field: "data"}, |
| y: {value: 20}, |
| opacity: {value: 1} |
| } |
| } |
| }; |
| } |
| |
| vg.Model = (function () { |
| function model() { |
| this._defs = null; |
| this._data = {}; |
| this._scene = null; |
| this._reset = {axes: false, legends: false}; |
| } |
| |
| var prototype = model.prototype; |
| |
| prototype.defs = function (defs) { |
| if (!arguments.length) return this._defs; |
| this._defs = defs; |
| return this; |
| }; |
| |
| prototype.data = function (data) { |
| if (!arguments.length) return this._data; |
| |
| var deps = {}, |
| defs = this._defs, |
| src = defs.data.source, |
| tx = defs.data.flow || {}, |
| keys = defs.data.sorted, |
| len = keys.length, i, k, x; |
| |
| // collect source data set dependencies |
| function sources(k) { |
| (src[k] || []).forEach(function (s) { |
| deps[s] = k; |
| sources(s); |
| }); |
| } |
| |
| vg.keys(data).forEach(sources); |
| |
| // update data sets in dependency-aware order |
| for (i = 0; i < len; ++i) { |
| if (data[k = keys[i]]) { |
| x = data[k]; |
| } else if (deps[k]) { |
| x = vg_data_duplicate(this._data[deps[k]]); |
| if (vg.isTree(data)) vg_make_tree(x); |
| } else continue; |
| this._data[k] = tx[k] ? tx[k](x, this._data, defs.marks) : x; |
| } |
| |
| this._reset.legends = true; |
| return this; |
| }; |
| |
| prototype.width = function (width) { |
| if (this._defs) this._defs.width = width; |
| if (this._defs && this._defs.marks) this._defs.marks.width = width; |
| if (this._scene) this._scene.items[0].width = width; |
| this._reset.axes = true; |
| return this; |
| }; |
| |
| prototype.height = function (height) { |
| if (this._defs) this._defs.height = height; |
| if (this._defs && this._defs.marks) this._defs.marks.height = height; |
| if (this._scene) this._scene.items[0].height = height; |
| this._reset.axes = true; |
| return this; |
| }; |
| |
| prototype.scene = function (node) { |
| if (!arguments.length) return this._scene; |
| this._scene = node; |
| return this; |
| }; |
| |
| prototype.build = function () { |
| var m = this, data = m._data, marks = m._defs.marks; |
| m._scene = vg.scene.build.call(m, marks, data, m._scene); |
| m._scene.items[0].width = marks.width; |
| m._scene.items[0].height = marks.height; |
| m._scene.interactive = false; |
| return this; |
| }; |
| |
| prototype.encode = function (trans, request, item) { |
| this.reset(); |
| var m = this, scene = m._scene, defs = m._defs; |
| vg.scene.encode.call(m, scene, defs.marks, trans, request, item); |
| return this; |
| }; |
| |
| prototype.reset = function () { |
| if (this._scene && this._reset.axes) { |
| vg.scene.visit(this._scene, function (item) { |
| if (item.axes) item.axes.forEach(function (axis) { |
| axis.reset(); |
| }); |
| }); |
| this._reset.axes = false; |
| } |
| if (this._scene && this._reset.legends) { |
| vg.scene.visit(this._scene, function (item) { |
| if (item.legends) item.legends.forEach(function (l) { |
| l.reset(); |
| }); |
| }); |
| this._reset.legends = false; |
| } |
| return this; |
| }; |
| |
| return model; |
| })(); |
| vg.View = (function () { |
| var view = function (el, width, height) { |
| this._el = null; |
| 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 = {top: 0, left: 0, bottom: 0, right: 0}; |
| this._viewport = null; |
| this._renderer = null; |
| this._handler = null; |
| this._io = vg.canvas; |
| if (el) this.initialize(el); |
| }; |
| |
| var prototype = view.prototype; |
| |
| prototype.width = function (width) { |
| if (!arguments.length) return this.__width; |
| if (this.__width !== width) { |
| this._width = this.__width = width; |
| if (this._el) this.initialize(this._el.parentNode); |
| this._model.width(width); |
| if (this._strict) this._autopad = 1; |
| } |
| return this; |
| }; |
| |
| prototype.height = function (height) { |
| if (!arguments.length) return this.__height; |
| if (this.__height !== height) { |
| this._height = this.__height = height; |
| if (this._el) this.initialize(this._el.parentNode); |
| this._model.height(this._height); |
| if (this._strict) this._autopad = 1; |
| } |
| 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; |
| } |
| if (this._el) { |
| this._renderer.resize(this._width, this._height, pad); |
| this._handler.padding(pad); |
| } |
| } |
| 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._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, vp) { |
| 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._padding = pad || {top: 0, left: 0, bottom: 0, right: 0}; |
| this._autopad = vg.isString(this._padding) ? 1 : 0; |
| this._renderer = new vg[type].Renderer(); |
| this._viewport = vp || null; |
| 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 (vp) { |
| if (!arguments.length) return _viewport; |
| this._viewport = vp; |
| this.initialize(); |
| 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); |
| |
| if (this._viewport) { |
| w = this._viewport[0] - (p ? p.left + p.right : 0); |
| h = this._viewport[1] - (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 ' |
| + 'width="' + w + '" ' |
| + 'height="' + h + '" ' |
| + vg.config.svgNamespace + '>' + svg + '</svg>' |
| }; |
| |
| prototype.initialize = function () { |
| var w = this._width, |
| h = this._height, |
| pad = this._padding; |
| |
| if (this._viewport) { |
| w = this._viewport[0] - (pad ? pad.left + pad.right : 0); |
| h = this._viewport[1] - (pad ? pad.top + pad.bottom : 0); |
| } |
| |
| 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, |
| vp = defs.viewport, |
| r = opt.renderer || "canvas", |
| v = new vg.headless.View(w, h, p, r, vp).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 module |
| return vg; |
| |
| //--------------------------------------------------- |
| // END code for this module |
| //--------------------------------------------------- |
| })); |