Skip to content

Instantly share code, notes, and snippets.

@timelyportfolio
Last active September 12, 2022 18:26
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
Star You must be signed in to star a gist
Embed
What would you like to do?
experiment with vega formula transform and new zip functionality
[{"yield":27,"variety":"Manchuria","year":1931,"site":"University Farm"},
{"yield":48.86667,"variety":"Manchuria","year":1931,"site":"Waseca"},
{"yield":27.43334,"variety":"Manchuria","year":1931,"site":"Morris"},
{"yield":39.93333,"variety":"Manchuria","year":1931,"site":"Crookston"},
{"yield":32.96667,"variety":"Manchuria","year":1931,"site":"Grand Rapids"},
{"yield":28.96667,"variety":"Manchuria","year":1931,"site":"Duluth"},
{"yield":43.06666,"variety":"Glabron","year":1931,"site":"University Farm"},
{"yield":55.2,"variety":"Glabron","year":1931,"site":"Waseca"},
{"yield":28.76667,"variety":"Glabron","year":1931,"site":"Morris"},
{"yield":38.13333,"variety":"Glabron","year":1931,"site":"Crookston"},
{"yield":29.13333,"variety":"Glabron","year":1931,"site":"Grand Rapids"},
{"yield":29.66667,"variety":"Glabron","year":1931,"site":"Duluth"},
{"yield":35.13333,"variety":"Svansota","year":1931,"site":"University Farm"},
{"yield":47.33333,"variety":"Svansota","year":1931,"site":"Waseca"},
{"yield":25.76667,"variety":"Svansota","year":1931,"site":"Morris"},
{"yield":40.46667,"variety":"Svansota","year":1931,"site":"Crookston"},
{"yield":29.66667,"variety":"Svansota","year":1931,"site":"Grand Rapids"},
{"yield":25.7,"variety":"Svansota","year":1931,"site":"Duluth"},
{"yield":39.9,"variety":"Velvet","year":1931,"site":"University Farm"},
{"yield":50.23333,"variety":"Velvet","year":1931,"site":"Waseca"},
{"yield":26.13333,"variety":"Velvet","year":1931,"site":"Morris"},
{"yield":41.33333,"variety":"Velvet","year":1931,"site":"Crookston"},
{"yield":23.03333,"variety":"Velvet","year":1931,"site":"Grand Rapids"},
{"yield":26.3,"variety":"Velvet","year":1931,"site":"Duluth"},
{"yield":36.56666,"variety":"Trebi","year":1931,"site":"University Farm"},
{"yield":63.8333,"variety":"Trebi","year":1931,"site":"Waseca"},
{"yield":43.76667,"variety":"Trebi","year":1931,"site":"Morris"},
{"yield":46.93333,"variety":"Trebi","year":1931,"site":"Crookston"},
{"yield":29.76667,"variety":"Trebi","year":1931,"site":"Grand Rapids"},
{"yield":33.93333,"variety":"Trebi","year":1931,"site":"Duluth"},
{"yield":43.26667,"variety":"No. 457","year":1931,"site":"University Farm"},
{"yield":58.1,"variety":"No. 457","year":1931,"site":"Waseca"},
{"yield":28.7,"variety":"No. 457","year":1931,"site":"Morris"},
{"yield":45.66667,"variety":"No. 457","year":1931,"site":"Crookston"},
{"yield":32.16667,"variety":"No. 457","year":1931,"site":"Grand Rapids"},
{"yield":33.6,"variety":"No. 457","year":1931,"site":"Duluth"},
{"yield":36.6,"variety":"No. 462","year":1931,"site":"University Farm"},
{"yield":65.7667,"variety":"No. 462","year":1931,"site":"Waseca"},
{"yield":30.36667,"variety":"No. 462","year":1931,"site":"Morris"},
{"yield":48.56666,"variety":"No. 462","year":1931,"site":"Crookston"},
{"yield":24.93334,"variety":"No. 462","year":1931,"site":"Grand Rapids"},
{"yield":28.1,"variety":"No. 462","year":1931,"site":"Duluth"},
{"yield":32.76667,"variety":"Peatland","year":1931,"site":"University Farm"},
{"yield":48.56666,"variety":"Peatland","year":1931,"site":"Waseca"},
{"yield":29.86667,"variety":"Peatland","year":1931,"site":"Morris"},
{"yield":41.6,"variety":"Peatland","year":1931,"site":"Crookston"},
{"yield":34.7,"variety":"Peatland","year":1931,"site":"Grand Rapids"},
{"yield":32,"variety":"Peatland","year":1931,"site":"Duluth"},
{"yield":24.66667,"variety":"No. 475","year":1931,"site":"University Farm"},
{"yield":46.76667,"variety":"No. 475","year":1931,"site":"Waseca"},
{"yield":22.6,"variety":"No. 475","year":1931,"site":"Morris"},
{"yield":44.1,"variety":"No. 475","year":1931,"site":"Crookston"},
{"yield":19.7,"variety":"No. 475","year":1931,"site":"Grand Rapids"},
{"yield":33.06666,"variety":"No. 475","year":1931,"site":"Duluth"},
{"yield":39.3,"variety":"Wisconsin No. 38","year":1931,"site":"University Farm"},
{"yield":58.8,"variety":"Wisconsin No. 38","year":1931,"site":"Waseca"},
{"yield":29.46667,"variety":"Wisconsin No. 38","year":1931,"site":"Morris"},
{"yield":49.86667,"variety":"Wisconsin No. 38","year":1931,"site":"Crookston"},
{"yield":34.46667,"variety":"Wisconsin No. 38","year":1931,"site":"Grand Rapids"},
{"yield":31.6,"variety":"Wisconsin No. 38","year":1931,"site":"Duluth"},
{"yield":26.9,"variety":"Manchuria","year":1932,"site":"University Farm"},
{"yield":33.46667,"variety":"Manchuria","year":1932,"site":"Waseca"},
{"yield":34.36666,"variety":"Manchuria","year":1932,"site":"Morris"},
{"yield":32.96667,"variety":"Manchuria","year":1932,"site":"Crookston"},
{"yield":22.13333,"variety":"Manchuria","year":1932,"site":"Grand Rapids"},
{"yield":22.56667,"variety":"Manchuria","year":1932,"site":"Duluth"},
{"yield":36.8,"variety":"Glabron","year":1932,"site":"University Farm"},
{"yield":37.73333,"variety":"Glabron","year":1932,"site":"Waseca"},
{"yield":35.13333,"variety":"Glabron","year":1932,"site":"Morris"},
{"yield":26.16667,"variety":"Glabron","year":1932,"site":"Crookston"},
{"yield":14.43333,"variety":"Glabron","year":1932,"site":"Grand Rapids"},
{"yield":25.86667,"variety":"Glabron","year":1932,"site":"Duluth"},
{"yield":27.43334,"variety":"Svansota","year":1932,"site":"University Farm"},
{"yield":38.5,"variety":"Svansota","year":1932,"site":"Waseca"},
{"yield":35.03333,"variety":"Svansota","year":1932,"site":"Morris"},
{"yield":20.63333,"variety":"Svansota","year":1932,"site":"Crookston"},
{"yield":16.63333,"variety":"Svansota","year":1932,"site":"Grand Rapids"},
{"yield":22.23333,"variety":"Svansota","year":1932,"site":"Duluth"},
{"yield":26.8,"variety":"Velvet","year":1932,"site":"University Farm"},
{"yield":37.4,"variety":"Velvet","year":1932,"site":"Waseca"},
{"yield":38.83333,"variety":"Velvet","year":1932,"site":"Morris"},
{"yield":32.06666,"variety":"Velvet","year":1932,"site":"Crookston"},
{"yield":32.23333,"variety":"Velvet","year":1932,"site":"Grand Rapids"},
{"yield":22.46667,"variety":"Velvet","year":1932,"site":"Duluth"},
{"yield":29.06667,"variety":"Trebi","year":1932,"site":"University Farm"},
{"yield":49.2333,"variety":"Trebi","year":1932,"site":"Waseca"},
{"yield":46.63333,"variety":"Trebi","year":1932,"site":"Morris"},
{"yield":41.83333,"variety":"Trebi","year":1932,"site":"Crookston"},
{"yield":20.63333,"variety":"Trebi","year":1932,"site":"Grand Rapids"},
{"yield":30.6,"variety":"Trebi","year":1932,"site":"Duluth"},
{"yield":26.43334,"variety":"No. 457","year":1932,"site":"University Farm"},
{"yield":42.2,"variety":"No. 457","year":1932,"site":"Waseca"},
{"yield":43.53334,"variety":"No. 457","year":1932,"site":"Morris"},
{"yield":34.33333,"variety":"No. 457","year":1932,"site":"Crookston"},
{"yield":19.46667,"variety":"No. 457","year":1932,"site":"Grand Rapids"},
{"yield":22.7,"variety":"No. 457","year":1932,"site":"Duluth"},
{"yield":25.56667,"variety":"No. 462","year":1932,"site":"University Farm"},
{"yield":44.7,"variety":"No. 462","year":1932,"site":"Waseca"},
{"yield":47,"variety":"No. 462","year":1932,"site":"Morris"},
{"yield":30.53333,"variety":"No. 462","year":1932,"site":"Crookston"},
{"yield":19.9,"variety":"No. 462","year":1932,"site":"Grand Rapids"},
{"yield":22.5,"variety":"No. 462","year":1932,"site":"Duluth"},
{"yield":28.06667,"variety":"Peatland","year":1932,"site":"University Farm"},
{"yield":36.03333,"variety":"Peatland","year":1932,"site":"Waseca"},
{"yield":43.2,"variety":"Peatland","year":1932,"site":"Morris"},
{"yield":25.23333,"variety":"Peatland","year":1932,"site":"Crookston"},
{"yield":26.76667,"variety":"Peatland","year":1932,"site":"Grand Rapids"},
{"yield":31.36667,"variety":"Peatland","year":1932,"site":"Duluth"},
{"yield":30,"variety":"No. 475","year":1932,"site":"University Farm"},
{"yield":41.26667,"variety":"No. 475","year":1932,"site":"Waseca"},
{"yield":44.23333,"variety":"No. 475","year":1932,"site":"Morris"},
{"yield":32.13333,"variety":"No. 475","year":1932,"site":"Crookston"},
{"yield":15.23333,"variety":"No. 475","year":1932,"site":"Grand Rapids"},
{"yield":27.36667,"variety":"No. 475","year":1932,"site":"Duluth"},
{"yield":38,"variety":"Wisconsin No. 38","year":1932,"site":"University Farm"},
{"yield":58.16667,"variety":"Wisconsin No. 38","year":1932,"site":"Waseca"},
{"yield":47.16667,"variety":"Wisconsin No. 38","year":1932,"site":"Morris"},
{"yield":35.9,"variety":"Wisconsin No. 38","year":1932,"site":"Crookston"},
{"yield":20.66667,"variety":"Wisconsin No. 38","year":1932,"site":"Grand Rapids"},
{"yield":29.33333,"variety":"Wisconsin No. 38","year":1932,"site":"Duluth"}]
{
"width": 200,
"height": 720,
"padding": {"top": 0, "left": 100, "bottom": 30, "right": 10},
"data": [
{
"name": "barley",
"url": "barley.json"
},
{
"name" : "totalstats",
"source" : "barley",
"transform" : [
{"type" : "stats", "value" : "data.yield"}
]
},
{
"name": "variety",
"source": "barley",
"transform": [
{"type": "facet", "keys": ["data.variety"]},
{"type": "stats", "value": "data.yield", "median": true},
{"type": "sort", "by": "-median"}
]
},
{
"name": "site",
"source": "barley",
"transform": [
{"type": "facet", "keys": ["data.site"]},
{"type": "stats", "value": "data.yield", "median": true},
{"type": "sort", "by": "-median"},
{"type": "zip", "with":"totalstats"},
{"type": "formula",
"field":"diffFromTotalMean",
"expr":"d.values.forEach(function(dd,ii) {dd.data.diffFromTotalMean=dd.data.yield-d.zip.mean;console.log(d.zip);})"
},
{"type": "formula",
"field":"diffFromMean",
"expr":"d.values.forEach(function(dd,ii) {dd.data.diffFromMean=dd.data.yield-d.mean;console.log(dd.data.diffFromMean);})"
}
]
}
],
"scales": [
{
"name": "g",
"type": "ordinal",
"range": "height",
"domain": {"data": "site", "field": "key"}
},
{
"name": "x",
"type": "linear",
"nice": true,
"range": "width",
"domain": {"data": "barley", "field": "data.diffFromMean"}
},
{
"name": "y",
"type": "ordinal",
"range": [2, 101], "points": true, "padding": 0.9,
"domain": {"data": "variety", "field": "key"}
},
{
"name": "c",
"type": "ordinal",
"range": "category10"
}
],
"axes": [
{"type": "x", "scale": "x", "offset": -1}
],
"marks": [
{
"type": "text",
"from": {"data": "site"},
"properties": {
"update": {
"x": {"value": 100},
"y": {"scale": "g", "field": "key"},
"dy": {"value": 12},
"fontWeight": {"value": "bold"},
"text": {"field": "key"},
"align": {"value": "center"},
"fill": {"value": "#000"}
}
}
},
{
"type": "group",
"from": {"data": "site"},
"properties": {
"update": {
"x": {"value": 0.5},
"y": {"scale": "g", "field": "key", "offset": 15.5},
"width": {"value": 200},
"height": {"value": 103},
"stroke": {"value": "#ccc"},
"strokeWidth": {"value": 1}
}
},
"marks": [
{
"type": "text",
"from": {"data": "variety"},
"properties": {
"update": {
"x": {"value": -4},
"y": {"scale": "y", "field": "key"},
"text": {"field": "key"},
"align": {"value": "right"},
"baseline": {"value": "middle"},
"fill": {"value": "#000"}
}
}
},
{
"type": "symbol",
"properties": {
"update": {
"x": {"scale": "x", "field": "data.diffFromMean"},
"y": {"scale": "y", "field": "data.variety"},
"size": {"value": 50},
"stroke": {"scale": "c", "field": "data.year"},
"strokeWidth": {"value": 2}
}
}
}
]
}
]
}
<html>
<head>
<title>Vega Examples</title>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="vega.js"></script>
<style>
* { font-family: Helvetica Neue, Arial, sans-serif; font-size: 14px; }
#ctrl { margin-bottom: 1em; }
</style>
</head>
<body>
<h3>Yield Compared to Site Average</h3>
<div id="vis" class="vis"></div>
<script type="text/javascript">
function parse(spec) {
vg.parse.spec(spec, function(chart) {
var view = chart({ el:"#vis" });
view.viewport(null)
.renderer("svg")
.update();
});
}
parse("barley_formula_total.json");
</script>
</body>
</html>
//Copyright (c) 2013, Trifacta Inc.
//All rights reserved.////
//Redistribution and use in source and binary forms, with or without
//modification, are permitted provided that the following conditions are met:
//* Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//* Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//* Neither the name of Trifacta Inc. nor the names of its contributors may be
// used to endorse or promote products derived from this software without
// specific prior written permission.
//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
//AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
//IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
//FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
//DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
//SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
//CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
//OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
//OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
vg = (function(d3){ // take d3 instance as sole import
var vg = {};
// semantic versioning
vg.version = '1.2.0';
// 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.number = function(s) { return +s; };
vg.boolean = function(s) { return !!s; };
// utility functions
vg.identity = function(x) { return x; };
vg.extend = function(obj) {
for (var x, name, i=1, len=arguments.length; i<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.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.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(str) {
return vg.isArray(str)
? "[" + str.map(vg.str) + "]"
: vg.isString(str) ? ("'"+str+"'") : str;
};
vg.keys = function(x) {
var keys = [];
for (var key in x) keys.push(key);
return keys;
};
vg.unique = function(data, f) {
f = f || vg.identity;
var results = [], v;
for (var i=0; i<data.length; ++i) {
v = f(data[i]);
if (results.indexOf(v) < 0) results.push(v);
}
return results;
};
// Logging
function vg_write(msg) {
vg.config.isNode
? process.stderr.write(msg + "\n")
: console.log(msg);
}
vg.log = function(msg) {
vg_write("[Vega Log] " + msg);
};
vg.error = function(msg) {
msg = "[Vega Err] " + msg;
vg_write(msg);
if (typeof alert !== "undefined") alert(msg);
};vg.config = {};
// are we running in node.js?
// via timetler.com/2012/10/13/environment-detection-in-javascript/
vg.config.isNode = typeof exports !== 'undefined' && this.exports !== exports;
// base url for loading external data files
// used only for server-side operation
vg.config.baseURL = "";
// default axis properties
vg.config.axis = {
ticks: 10,
padding: 3,
axisColor: "#000",
tickColor: "#000",
tickLabelColor: "#000",
axisWidth: 1,
tickWidth: 1,
tickSize: 6,
tickLabelFontSize: 11,
tickLabelFont: "sans-serif"
};
// 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.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);
}
}
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);
};
return {
parse: parse,
render: render
};
})();vg.canvas.marks = (function() {
var parsePath = vg.canvas.path.parse,
renderPath = vg.canvas.path.render,
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();
return new vg.Bounds()
.set(x-or, y-or, x+or, y+or);
}
function pathPath(g, o) {
return renderPath(g, parsePath(o.path), o.x, o.y);
}
function symbolPath(g, o) {
g.beginPath();
var size = o.size != undefined ? o.size : 100,
x = o.x, y = o.y, r, t, rx, ry,
bounds = new vg.Bounds();
if (o.shape == undefined || o.shape === "circle") {
r = Math.sqrt(size/Math.PI);
g.arc(x, y, r, 0, 2*Math.PI, 0);
g.closePath();
return bounds.set(x-r, y-r, x+r, y+r);
}
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);
bounds.set(x-t, y-t, x+y, y+t);
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);
bounds.set(x-rx, y-ry, x+rx, y+ry);
break;
case "square":
t = Math.sqrt(size);
r = t / 2;
g.rect(x-r, y-r, t, t);
bounds.set(x-r, y-r, x+r, y+r);
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);
bounds.set(x-rx, y-ry, 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);
bounds.set(x-rx, y-ry, x+rx, y+ry);
}
g.closePath();
return bounds;
}
function areaPath(g, items) {
var area = d3.svg.area()
.x(function(d) { return d.x; })
.y1(function(d) { return d.y; })
.y0(function(d) { return d.y + d.height; });
var o = items[0];
if (o.interpolate) area.interpolate(o.interpolate);
if (o.tension != undefined) area.tension(o.tension);
return renderPath(g, parsePath(area(items)));
}
function linePath(g, items) {
var line = d3.svg.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; });
var o = items[0];
if (o.interpolate) line.interpolate(o.interpolate);
if (o.tension != undefined) line.tension(o.tension);
return renderPath(g, parsePath(line(items)));
}
// drawing functions
function drawPathOne(path, g, o, items) {
var fill = o.fill, stroke = o.stroke, opac, lc, lw;
o.bounds = 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 = fill;
g.fill();
}
if (stroke) {
lw = (lw = o.strokeWidth) != undefined ? lw : 1;
if (lw > 0) {
g.globalAlpha = opac * (o.strokeOpacity==null ? 1 : o.strokeOpacity);
g.strokeStyle = stroke;
g.lineWidth = lw;
g.lineCap = (lc = o.strokeCap) != undefined ? lc : "butt";
g.stroke();
o.bounds.expand(lw);
}
}
}
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;
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;
o.bounds = (o.bounds || new vg.Bounds())
.set(x, y, x+o.width, y+o.height);
opac = o.opacity == null ? 1 : o.opacity;
if (opac == 0) return;
if (fill = o.fill) {
g.globalAlpha = opac * (o.fillOpacity==null ? 1 : o.fillOpacity);
g.fillStyle = fill;
g.fillRect(x, y, o.width, o.height);
}
if (stroke = o.stroke) {
lw = (lw = o.strokeWidth) != undefined ? lw : 1;
if (lw > 0) {
g.globalAlpha = opac * (o.strokeOpacity==null ? 1 : o.strokeOpacity);
g.strokeStyle = stroke;
g.lineWidth = lw;
g.lineCap = (lc = o.strokeCap) != undefined ? lc : "butt";
g.strokeRect(x, y, o.width, o.height);
o.bounds.expand(lw);
}
}
}
}
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 !== undefined ? o.x2 : x1;
y2 = o.y2 !== undefined ? o.y2 : y1;
o.bounds = (o.bounds || new vg.Bounds())
.set(x1, y1, x2, y2);
opac = o.opacity == null ? 1 : o.opacity;
if (opac == 0) return;
if (stroke = o.stroke) {
lw = (lw = o.strokeWidth) != undefined ? lw : 1;
if (lw > 0) {
g.globalAlpha = opac * (o.strokeOpacity==null ? 1 : o.strokeOpacity);
g.strokeStyle = stroke;
g.lineWidth = lw;
g.lineCap = (lc = o.strokeCap) != undefined ? lc : "butt";
g.beginPath();
g.moveTo(x1, y1);
g.lineTo(x2, y2);
g.stroke();
o.bounds.expand(lw);
}
}
}
}
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 - (o.align === "center"
? w/2 : (o.align === "right" ? w : 0));
y = o.y - (o.baseline === "middle"
? h/2 : (o.baseline === "bottom" ? h : 0));
o.bounds = (o.bounds || new vg.Bounds()).set(x, y, x+w, y+h);
g.globalAlpha = (opac = o.opacity) != undefined ? opac : 1;
g.drawImage(o.image, x, y, w, h);
}
}
function fontString(o) {
return (o.fontStyle ? o.fontStyle + " " : "")
+ (o.fontVariant ? o.fontVariant + " " : "")
+ (o.fontWeight ? o.fontWeight + " " : "")
+ (o.fontSize != undefined ? o.fontSize + "px " : "11px ")
+ (o.font || "sans-serif");
}
function drawText(g, scene, bounds) {
if (!scene.items.length) return;
var items = scene.items,
o, fill, stroke, opac, lw, text, ta, tb;
for (var i=0, len=items.length; i<len; ++i) {
o = items[i];
if (bounds && !bounds.intersects(o.bounds))
continue; // bounds check
g.font = fontString(o);
g.textAlign = o.align || "left";
g.textBaseline = o.baseline || "alphabetic";
o.bounds = textBounds(g, o, (o.bounds || new vg.Bounds())).expand(1);
opac = o.opacity == null ? 1 : o.opacity;
if (opac == 0) return;
if (o.angle) {
g.save();
g.translate(o.x, o.y);
g.rotate(o.angle * Math.PI/180);
x = o.dx || 0;
y = o.dy || 0;
} else {
x = o.x + (o.dx || 0);
y = o.y + (o.dy || 0);
}
if (fill = o.fill) {
g.globalAlpha = opac * (o.fillOpacity==null ? 1 : o.fillOpacity);
g.fillStyle = fill;
g.fillText(o.text, x, y);
}
if (stroke = o.stroke) {
lw = (lw = o.strokeWidth) != undefined ? lw : 1;
if (lw > 0) {
g.globalAlpha = opac * (o.strokeOpacity==null ? 1 : o.strokeOpacity);
g.strokeStyle = stroke;
g.lineWidth = lw;
g.strokeText(o.text, x, y);
}
}
if (o.angle) {
g.restore();
}
}
}
function textBounds(g, o, bounds, noRotate) {
var x = o.x + (o.dx || 0),
y = o.y + (o.dy || 0),
w = g.measureText(o.text).width,
h = o.fontSize,
a = o.align,
b = o.baseline,
angle, cos, sin, cx, cy;
// 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 {
// alphabetic by default
y = y - 4*h/5;
}
bounds.set(x, y, x+w, y+h);
if (!noRotate && o.angle) {
bounds.rotate(o.angle*Math.PI/180, o.x, o.y);
}
return bounds;
}
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,
renderer = this, gx, gy, i, n, j, m;
drawRect(g, scene, bounds);
for (i=0, n=items.length; i<n; ++i) {
group = items[i];
gx = group.x || 0;
gy = group.y || 0;
// render group contents
g.save();
g.translate(gx, gy);
if (bounds) bounds.translate(-gx, -gy);
for (j=0, m=group.items.length; j<m; ++j) {
renderer.draw(g, group.items[j], bounds);
}
if (group.axis) {
for (j=0, m=group.axis.length; j<m; ++j) {
renderer.draw(g, group.axis[j], bounds);
}
}
if (bounds) bounds.translate(gx, gy);
g.restore();
}
}
// 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;
for (var i=0, len=items.length; i<len; ++i) {
group = items[i];
dx = group.x || 0;
dy = group.y || 0;
g.save();
g.translate(dx, dy);
for (var j=0, llen=group.items.length; j<llen; ++j) {
subscene = group.items[j];
if (subscene.interactive === false) continue;
hit = handler.pick(subscene, x, y, gx-dx, gy-dy);
if (hit) {
g.restore();
return hit;
}
}
g.restore();
}
return scene.interactive
? pickAll(hitTests.rect, g, scene, x, y, gx, gy)
: false;
}
function pickAll(test, g, scene, x, y, gx, gy) {
if (!scene.items.length) return false;
var o, b, i;
if (g._ratio !== 1) {
x *= g._ratio;
y *= g._ratio;
}
for (i=scene.items.length; --i >= 0;) {
o = scene.items[i]; b = o.bounds;
// first hit test against bounding box
if ((b && !b.contains(gx, gy)) || !b) continue;
// if in bounding box, perform more careful test
if (test(g, o, x, y, gx, gy)) return o;
}
return false;
}
function pickArea(g, scene, x, y, gx, gy) {
if (!scene.items.length) return false;
var items = scene.items,
o, b, i, di, dd, od, dx, dy;
b = items[0].bounds;
if (b && !b.contains(gx, gy)) return false;
if (g._ratio !== 1) {
x *= g._ratio;
y *= g._ratio;
}
if (!hitTests.area(g, items, x, y)) return false;
return items[0];
}
function pickLine(g, scene, x, y, gx, gy) {
// TODO...
return false;
}
function pickRule(g, scene, x, y, gx, gy) {
// TODO...
return false;
}
function pick(test) {
return function (g, scene, x, y, gx, gy) {
return pickAll(test, g, scene, x, y, gx, gy);
};
}
var hitTests = {
text: hitTestText,
rect: function(g,o,x,y) { return true; }, // bounds test is sufficient
image: function(g,o,x,y) { return true; }, // bounds test is sufficient
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);},
};
function hitTestText(g, o, x, y, gx, gy) {
if (!o.fontSize) return false;
if (!o.angle) return true; // bounds sufficient if no rotation
g.font = fontString(o);
var b = textBounds(g, 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);
}
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: pickRule,
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;
};
var prototype = renderer.prototype;
prototype.initialize = function(el, width, height, pad) {
this._el = el;
this._width = width;
this._height = height;
this._padding = pad;
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");
// 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);
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;
}
prototype.context = function(ctx) {
if (ctx) { this._ctx = ctx; return this; }
else return this._ctx;
};
prototype.element = function() {
return this._el;
};
function translatedBounds(item) {
var b = new vg.Bounds(item.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));
}, 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 = this._scene;
var image = new Image();
image.onload = function() {
vg.log("LOAD IMAGE: "+this.src);
renderer.renderAsync(scene);
};
image.src = uri;
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.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
// returns an array of scenegraph items, from leaf node up to the root
// x, y -- the absolute x, y mouse coordinates on the canvas element
// gx, gy -- the relative coordinates within the current group
prototype.pick = function(scene, x, y, gx, gy) {
var g = this.context(),
marktype = scene.marktype,
picker = vg.canvas.marks.pick[marktype];
return picker.call(this, g, scene, x, y, gx, gy);
};
return handler;
})();vg.svg = {};vg.svg.marks = (function() {
function x(o) { return o.x || 0; }
function y(o) { return o.y || 0; }
function yh(o) { return o.y + o.height || 0; }
function key(o) { return o.key; }
function size(o) { return o.size==null ? 100 : o.size; }
function shape(o) { return o.shape || "circle"; }
var arc_path = d3.svg.arc(),
area_path = d3.svg.area().x(x).y1(y).y0(yh),
line_path = d3.svg.line().x(x).y(y),
symbol_path = d3.svg.symbol().type(shape).size(size);
var textAlign = {
"left": "start",
"center": "middle",
"right": "end"
};
var styles = {
"fill": "fill",
"fillOpacity": "fill-opacity",
"stroke": "stroke",
"strokeWidth": "stroke-width",
"strokeOpacity": "stroke-opacity",
"opacity": "opacity"
};
var styleProps = vg.keys(styles);
function style(d) {
var o = d.mark ? d : d[0],
i, n, prop, name, value;
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 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) {
var o = items[0];
area_path
.interpolate(o.interpolate || "linear")
.tension(o.tension == undefined ? 0.7 : o.tension);
this.setAttribute("d", area_path(items));
}
function line(items) {
var o = items[0];
line_path
.interpolate(o.interpolate || "linear")
.tension(o.tension == undefined ? 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+")");
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 !== undefined ? o.x2 : x1);
this.setAttribute("y2", o.y2 !== undefined ? o.y2 : y1);
}
function group_bg(o) {
this.setAttribute("width", o.width || 0);
this.setAttribute("height", o.height || 0);
}
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));
this.setAttributeNS("http://www.w3.org/1999/xlink", "href", o.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 != undefined ? o.fontSize + "px " : "11px ")
+ (o.font || "sans-serif");
}
function text(o) {
var x = o.x || 0,
y = o.y || 0,
dx = o.dx || 0,
dy = o.dy || 0,
a = o.angle || 0,
align = textAlign[o.align || "left"],
base = o.baseline==="top" ? ".9em"
: o.baseline==="middle" ? ".35em" : 0;
this.setAttribute("x", x + dx);
this.setAttribute("y", y + dy);
this.setAttribute("dy", 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+")");
}
function draw(tag, attr, nest) {
return function(g, scene, index) {
drawMark(g, scene, index, "mark_", tag, attr, nest);
};
}
var mark_id = 0;
function drawMark(g, scene, index, prefix, tag, attr, nest) {
var className = prefix + index,
data = nest ? [scene.items] : scene.items,
evts = scene.interactive===false ? "none" : null,
p = g.select("."+className);
if (p.empty()) p = g.append("g")
.attr("id", "g"+(++mark_id))
.attr("class", className);
var id = "#" + p.attr("id"),
m = p.selectAll(id+" > "+tag).data(data),
e = m.enter().append(tag);
if (tag !== "g") {
p.style("pointer-events", evts);
e.each(function(d) { (d.mark ? d : d[0])._svg = this; });
} else {
e.append("rect")
.attr("class", "background")
.style("pointer-events", evts);
}
m.exit().remove();
m.each(attr);
if (tag !== "g") {
m.each(style);
} else {
p.selectAll(id+" > "+tag+" > rect.background")
.each(group_bg).each(style);
}
}
function drawGroup(g, scene, index) {
var renderer = this;
drawMark(g, scene, index, "group_", "g", group);
var x = g.select(".group_"+index).node(), i, n, j, m;
for (i=0, n=x.childNodes.length; i<n; ++i) {
var sel = d3.select(x.childNodes[i]),
data = x.childNodes[i].__data__,
axis = data.axis || [],
items = data.items;
for (j=0, m=items.length; j<m; ++j) {
renderer.draw(sel, items[j], j);
}
for (j=0, m=axis.length; j<m; ++j) {
renderer.draw(sel, axis[j], j + items.length);
}
}
}
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._ctx = null;
this._el = null;
};
var prototype = renderer.prototype;
prototype.initialize = function(el, width, height, pad) {
this._el = el;
this._width = width;
this._height = height;
this._padding = pad;
// remove any existing svg element
d3.select(el).select("svg.marks").remove();
// create svg element and initialize attributes
var svg = d3.select(el)
.append("svg")
.attr("class", "marks")
.attr("width", width + pad.left + pad.right)
.attr("height", height + pad.top + pad.bottom);
// set the svg root group
this._ctx = svg.append("g")
.attr("transform", "translate("+pad.left+","+pad.top+")");
return this;
};
prototype.context = function() {
return this._ctx;
};
prototype.element = function() {
return this._el;
};
prototype.render = function(scene, items) {
if (items) this.renderItems(vg.array(items));
else this.draw(this._ctx, scene, 0);
};
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.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.ingest = function(datum, index) {
return {
data: datum,
index: index
};
};
vg.data.mapper = function(func) {
return function(data) {
data.forEach(func);
return data;
}
};
vg.data.size = function(size, group) {
size = Array.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) {
if (vg.config.isNode) {
// in node.js, consult base url and select file or http
var url = vg_load_hasProtocol(uri) ? uri : vg.config.baseURL + uri,
get = vg_load_isFile(url) ? vg_load_file : vg_load_http;
get(url, callback);
} else {
// in browser, use xhr
vg_load_xhr(uri, 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);
d3.xhr(url, function(err, resp) {
if (resp) resp = resp.responseText;
callback(err, resp);
});
}
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, {encoding:"utf8"}, callback);
}
function vg_load_http(url, callback) {
vg.log("LOAD HTTP: " + url);
var req = require("http").request(url, function(res) {
var data = "";
res.setEncoding("utf8");
res.on("error", function(err) { callback(err, null); });
res.on("data", function(chunk) { data += chunk; });
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 = 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;
};
function parseValues(data, types) {
var cols = vg.keys(types),
p = cols.map(function(col) { return parsers[types[col]]; }),
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]]);
}
}
}
read.formats = formats;
read.parse = parseValues;
return read;
})();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.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.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 : data.slice()
});
if (sort) obj.values.sort(sort);
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) {
vals[i].values.sort(sort);
}
}
return result;
}
facet.keys = function(k) {
keys = vg.array(k).map(vg.accessor);
return facet;
};
facet.sort = function(s) {
sort = vg.comparator(s);
return facet;
};
return facet;
};vg.data.filter = function() {
var test = null;
function filter(data) {
return test ? data.filter(test) : data;
}
filter.test = function(t) {
// TODO security check
test = vg.isFunction(t)
? t
: new Function("d", "return " + t);
return filter;
};
return filter;
};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.formula = (function() {
// TODO security check
// TODO remove with, perform parse?
function code(str) {
return "with (Math) { return ("+str+"); }";
}
return function() {
var field = null,
expr = vg.identity;
var formula = vg.data.mapper(function(d) {
if (field) d[field] = expr.call(null, d);
return d;
});
formula.field = function(name) {
field = name;
return formula;
};
formula.expr = function(func) {
expr = vg.isFunction(func)
? func
: new Function("d", code(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 = "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"
};
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.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.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()
.values(function(d) { return d.values; }),
point = null,
height = null,
params = ["offset", "order"],
output = {
"y0": "y2",
"y1": "y"
};
function stack(data) {
var out_y0 = output["y0"],
out_y1 = output["y1"];
return layout
.x(point)
.y(height)
.out(function(d, y0, y) {
d[out_y0] = y0;
d[out_y1] = y + y0;
})
(data.values);
}
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"),
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);
return o;
}
function stats(data) {
return (Array.isArray(data) ? [data] : data.values || [])
.map(reduce); // no pun intended
}
stats.median = function(bool) {
median = bool || false;
return stats;
};
stats.value = function(field) {
value = vg.accessor(field);
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(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.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.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"),
withKey = null;
function zip(data, db) {
var zdata = db[z], zlen = zdata.length, 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 ? map[key(d)] : zdata[i % zlen];
}
return data;
}
zip["with"] = function(d) {
z = d;
return zip;