Giant namespace fix.
Reindent the whole render.js and put it inside a function to hide the
internal implementation details and make it safer to import the javascript
into your own project.
Not too useful yet, since the render() function still makes a lot of
assumptions about your html file, so you still probably just always need to
use render.html still.
diff --git a/edit.html b/edit.html
index 2d0f03d..9be9bda 100644
--- a/edit.html
+++ b/edit.html
@@ -60,7 +60,7 @@
for (var parti in parts) {
var part = parts[parti];
if (part) {
- var bits = trySplitOne(part, '=');
+ var bits = afterquery.trySplitOne(part, '=');
if (bits) {
out.push(niceEncode(bits[0]) + '=' + niceEncode(bits[1]));
} else {
@@ -90,7 +90,7 @@
lastloaded = null;
var exampleurl = (window.location.protocol + '//' + window.location.host +
'/example1.json');
-args = parseArgs(window.location.search || ('?url=' + exampleurl));
+args = afterquery.parseArgs(window.location.search || ('?url=' + exampleurl));
argsToForm(args);
updateUrl();
</script>
diff --git a/render.js b/render.js
index 6998e31..9945178 100644
--- a/render.js
+++ b/render.js
@@ -1,1119 +1,1121 @@
"use strict";
-
-// Mostly for konqueror compatibility
-if (!window.console) {
- var console = {};
- console.debug = function() {};
-}
-
-
-function err(s) {
- $('#vizlog').append('\n' + s);
-}
-
-
-function showstatus(s, s2) {
- $('#statustext').html(s);
- $('#statussub').text(s2 || '');
- if (s || s2) {
- console.debug('status message:', s, s2);
- $('#vizstatus').show();
- } else {
- $('#vizstatus').hide();
+var afterquery = (function() {
+ // Mostly for konqueror compatibility
+ var console = window.console;
+ if (!console) {
+ console = {};
+ console.debug = function() {};
}
-}
-function parseArgs(query) {
- var kvlist = query.substr(1).split('&');
- var out = {};
- var outlist = [];
- for (var i in kvlist) {
- var kv = kvlist[i].split('=');
- var key = decodeURIComponent(kv.shift());
- var value = decodeURIComponent(kv.join('='));
- out[key] = value;
- outlist.push([key, value]);
+ function err(s) {
+ $('#vizlog').append('\n' + s);
}
- console.debug('query args:', out);
- console.debug('query arglist:', outlist);
- return {
- get: function(key) { return out[key]; },
- all: outlist
- };
-}
-function dataToGvizTable(grid, options) {
- if (!options) options = {};
- var headers = grid.headers, data = grid.data, types = grid.types;
- var dheaders = [];
- for (var i in headers) {
- dheaders.push({
- id: headers[i],
- label: headers[i],
- type: types[i]
+ function showstatus(s, s2) {
+ $('#statustext').html(s);
+ $('#statussub').text(s2 || '');
+ if (s || s2) {
+ console.debug('status message:', s, s2);
+ $('#vizstatus').show();
+ } else {
+ $('#vizstatus').hide();
+ }
+ }
+
+
+ function parseArgs(query) {
+ var kvlist = query.substr(1).split('&');
+ var out = {};
+ var outlist = [];
+ for (var i in kvlist) {
+ var kv = kvlist[i].split('=');
+ var key = decodeURIComponent(kv.shift());
+ var value = decodeURIComponent(kv.join('='));
+ out[key] = value;
+ outlist.push([key, value]);
+ }
+ console.debug('query args:', out);
+ console.debug('query arglist:', outlist);
+ return {
+ get: function(key) { return out[key]; },
+ all: outlist
+ };
+ }
+
+
+ function dataToGvizTable(grid, options) {
+ if (!options) options = {};
+ var headers = grid.headers, data = grid.data, types = grid.types;
+ var dheaders = [];
+ for (var i in headers) {
+ dheaders.push({
+ id: headers[i],
+ label: headers[i],
+ type: types[i]
+ });
+ }
+ var ddata = [];
+ for (var rowi in data) {
+ var row = [];
+ for (var coli in data[rowi]) {
+ var col = { v: data[rowi][coli] };
+ if (options.show_only_lastseg && col.v && col.v.split) {
+ var lastseg = col.v.split('|').pop();
+ if (lastseg != col.v) {
+ col.f = lastseg;
+ }
+ }
+ row.push(col);
+ }
+ ddata.push({c: row});
+ }
+ return new google.visualization.DataTable({
+ cols: dheaders,
+ rows: ddata
});
}
- var ddata = [];
- for (var rowi in data) {
- var row = [];
- for (var coli in data[rowi]) {
- var col = { v: data[rowi][coli] };
- if (options.show_only_lastseg && col.v && col.v.split) {
- var lastseg = col.v.split('|').pop();
- if (lastseg != col.v) {
- col.f = lastseg;
- }
- }
- row.push(col);
- }
- ddata.push({c: row});
- }
- return new google.visualization.DataTable({
- cols: dheaders,
- rows: ddata
- });
-}
-var CANT_NUM = 1;
-var CANT_BOOL = 2;
-var CANT_DATE = 4;
-var CANT_DATETIME = 8;
+ var CANT_NUM = 1;
+ var CANT_BOOL = 2;
+ var CANT_DATE = 4;
+ var CANT_DATETIME = 8;
-var T_NUM = 'number';
-var T_DATE = 'date';
-var T_DATETIME = 'datetime';
-var T_BOOL = 'boolean';
-var T_STRING = 'string';
+ var T_NUM = 'number';
+ var T_DATE = 'date';
+ var T_DATETIME = 'datetime';
+ var T_BOOL = 'boolean';
+ var T_STRING = 'string';
-function guessTypes(data) {
- console.debug('guessTypes');
- var impossible = [];
- for (var rowi in data) {
- var row = data[rowi];
- for (var coli in row) {
- impossible[coli] += 0;
- var cell = row[coli];
- if (cell == '' || cell == null) continue;
- var d = myParseDate(cell);
- if (isNaN(d)) {
- impossible[coli] |= CANT_DATE | CANT_DATETIME;
- } else if (d.getHours() || d.getMinutes() || d.getSeconds()) {
- impossible[coli] |= CANT_DATE; // has time, so isn't a pure date
- }
- var f = cell * 1;
- if (isNaN(f)) impossible[coli] |= CANT_NUM;
- if (!(cell == 0 || cell == 1 ||
- cell == 'true' || cell == 'false' ||
- cell == true || cell == false ||
- cell == 'True' || cell == 'False')) impossible[coli] |= CANT_BOOL;
- }
- }
- console.debug('guessTypes impossibility list:', impossible);
- var types = [];
- for (var coli in impossible) {
- var imp = impossible[coli];
- if (!(imp & CANT_BOOL)) {
- types[coli] = T_BOOL;
- } else if (!(imp & CANT_DATE)) {
- types[coli] = T_DATE;
- } else if (!(imp & CANT_DATETIME)) {
- types[coli] = T_DATETIME;
- } else if (!(imp & CANT_NUM)) {
- types[coli] = T_NUM;
- } else {
- types[coli] = T_STRING;
- }
- }
- return types;
-}
-
-
-var DATE_RE1 = RegExp('^(\\d{4})[-/](\\d{1,2})(?:[-/](\\d{1,2})(?:[T\\s](\\d{1,2}):(\\d\\d)(?::(\\d\\d))?)?)?$');
-var DATE_RE2 = RegExp('^Date\\((\\d+),(\\d+),(\\d+)(?:,(\\d+),(\\d+)(?:,(\\d+)(?:,(\\d+))?)?)?\\)$');
-function myParseDate(s) {
- if (s == null) return s;
- if (s && s.getDate) return s;
- var g = DATE_RE1.exec(s) || DATE_RE2.exec(s);
- if (g) {
- return new Date(g[1], g[2]-1, g[3] || 1,
- g[4] || 0, g[5] || 0, g[6] || 0, g[7] || 0);
- }
- return NaN;
-}
-
-
-function parseDates(data, types) {
- for (var coli in types) {
- var type = types[coli];
- if (type === T_DATE || type === T_DATETIME) {
- for (var rowi in data) {
- data[rowi][coli] = myParseDate(data[rowi][coli]);
+ function guessTypes(data) {
+ console.debug('guessTypes');
+ var impossible = [];
+ for (var rowi in data) {
+ var row = data[rowi];
+ for (var coli in row) {
+ impossible[coli] += 0;
+ var cell = row[coli];
+ if (cell == '' || cell == null) continue;
+ var d = myParseDate(cell);
+ if (isNaN(d)) {
+ impossible[coli] |= CANT_DATE | CANT_DATETIME;
+ } else if (d.getHours() || d.getMinutes() || d.getSeconds()) {
+ impossible[coli] |= CANT_DATE; // has time, so isn't a pure date
+ }
+ var f = cell * 1;
+ if (isNaN(f)) impossible[coli] |= CANT_NUM;
+ if (!(cell == 0 || cell == 1 ||
+ cell == 'true' || cell == 'false' ||
+ cell == true || cell == false ||
+ cell == 'True' || cell == 'False')) impossible[coli] |= CANT_BOOL;
}
}
- }
-}
-
-
-function colNameToColNum(grid, colname) {
- var keycol = colname=='*' ? 0 : grid.headers.indexOf(colname);
- if (keycol < 0) {
- throw new Error('unknown column name "' + key + '"');
- }
- return keycol;
-}
-
-
-var FUNC_RE = /^(\w+)\((.*)\)$/;
-function keyToColNum(grid, key) {
- var g = FUNC_RE.exec(key);
- if (g) {
- return colNameToColNum(grid, g[2]);
- } else {
- return colNameToColNum(grid, key);
- }
-}
-
-
-function _groupByLoop(ingrid, keys, initval, addcols_func, putvalues_func) {
- var outgrid = {headers: [], data: [], types: []};
- var keycols = [];
- for (var keyi in keys) {
- var colnum = keyToColNum(ingrid, keys[keyi]);
- keycols.push(colnum);
- outgrid.headers.push(ingrid.headers[colnum]);
- outgrid.types.push(ingrid.types[colnum]);
- }
-
- addcols_func(outgrid);
-
- var out = {};
- for (var rowi in ingrid.data) {
- var row = ingrid.data[rowi];
- var key = [];
- for (var kcoli in keycols) {
- key.push(row[keycols[kcoli]]);
- }
- var orow = out[key];
- if (!orow) {
- orow = [];
- for (var keyi in keys) {
- orow[keyi] = row[keycols[keyi]];
- }
- for (var i = keys.length; i < outgrid.headers.length; i++) {
- orow[i] = initval;
- }
- out[key] = orow;
- // deliberately preserve sequencing as much as possible. The first
- // time we see a given key is when we add it to the outgrid.
- outgrid.data.push(orow);
- }
- putvalues_func(outgrid, key, orow, row);
- }
- return outgrid;
-}
-
-
-var agg_types = {
- count: T_NUM,
- sum: T_NUM
-};
-
-
-var agg_funcs = {
- first: function(l) {
- return l[0];
- },
-
- last: function(l) {
- return l.slice(l.length-1)[0];
- },
-
- only: function(l) {
- if (l.length == 1) {
- return l[0];
- } else if (l.length < 1) {
- return null;
- } else {
- throw new Error('cell has more than one value: only(' + l + ')')
- }
- },
-
- min: function(l) {
- var out = null;
- for (var i in l) {
- if (out == null || l[i] < out) {
- out = l[i];
- }
- }
- return out;
- },
-
- max: function(l) {
- var out = null;
- for (var i in l) {
- if (out == null || l[i] > out) {
- out = l[i];
- }
- }
- return out;
- },
-
- cat: function(l) {
- return l.join(' ');
- },
-
- count: function(l) {
- return l.length;
- },
-
- count_distinct: function(l) {
- var a = {};
- for (var i in l) {
- a[l[i]] = 1;
- }
- var acc = 0;
- for (var i in a) {
- acc += 1;
- }
- return acc;
- },
-
- sum: function(l) {
- var acc;
- if (l.length) acc = 0;
- for (var i in l) {
- acc += parseFloat(l[i]);
- }
- return acc;
- }
-};
-agg_funcs.count.return_type = T_NUM;
-agg_funcs.sum.return_type = T_NUM;
-
-
-function groupBy(ingrid, keys, values) {
- // add one value column for every column listed in values.
- var valuecols = [];
- var valuefuncs = [];
- var addcols_func = function(outgrid) {
- for (var valuei in values) {
- var g = FUNC_RE.exec(values[valuei]);
- var field, func;
- if (g) {
- func = agg_funcs[g[1]];
- if (!func) {
- throw new Error('unknown aggregation function "' + g[1] + '"');
- }
- field = g[2];
+ console.debug('guessTypes impossibility list:', impossible);
+ var types = [];
+ for (var coli in impossible) {
+ var imp = impossible[coli];
+ if (!(imp & CANT_BOOL)) {
+ types[coli] = T_BOOL;
+ } else if (!(imp & CANT_DATE)) {
+ types[coli] = T_DATE;
+ } else if (!(imp & CANT_DATETIME)) {
+ types[coli] = T_DATETIME;
+ } else if (!(imp & CANT_NUM)) {
+ types[coli] = T_NUM;
} else {
- func = null;
- field = values[valuei];
- }
- var colnum = keyToColNum(ingrid, field);
- console.debug('v', values[valuei], func, field);
- if (!func) {
- if (ingrid.types[colnum] === T_NUM) {
- func = agg_funcs.sum;
- } else {
- func = agg_funcs.count;
- }
- }
- valuecols.push(colnum);
- valuefuncs.push(func);
- outgrid.headers.push(field=='*' ? '_count' : ingrid.headers[colnum]);
- outgrid.types.push(func.return_type || ingrid.types[colnum]);
- }
- };
-
- // by default, we do a count(*) operation for non-numeric value
- // columns, and sum(*) otherwise.
- var putvalues_func = function(outgrid, key, orow, row) {
- for (var valuei in values) {
- var incoli = valuecols[valuei];
- var outcoli = key.length + parseInt(valuei);
- var cell = row[incoli];
- if (!orow[outcoli]) orow[outcoli] = [];
- if (cell != null) {
- orow[outcoli].push(cell);
+ types[coli] = T_STRING;
}
}
- };
+ return types;
+ }
- var outgrid = _groupByLoop(ingrid, keys, 0,
- addcols_func, putvalues_func);
-
- for (var rowi in outgrid.data) {
- var row = outgrid.data[rowi];
- for (var valuei in values) {
- var outcoli = keys.length + parseInt(valuei);
- var func = valuefuncs[valuei];
- row[outcoli] = func(row[outcoli]);
+
+ var DATE_RE1 = RegExp('^(\\d{4})[-/](\\d{1,2})(?:[-/](\\d{1,2})(?:[T\\s](\\d{1,2}):(\\d\\d)(?::(\\d\\d))?)?)?$');
+ var DATE_RE2 = RegExp('^Date\\((\\d+),(\\d+),(\\d+)(?:,(\\d+),(\\d+)(?:,(\\d+)(?:,(\\d+))?)?)?\\)$');
+ function myParseDate(s) {
+ if (s == null) return s;
+ if (s && s.getDate) return s;
+ var g = DATE_RE1.exec(s) || DATE_RE2.exec(s);
+ if (g) {
+ return new Date(g[1], g[2]-1, g[3] || 1,
+ g[4] || 0, g[5] || 0, g[6] || 0, g[7] || 0);
+ }
+ return NaN;
+ }
+
+
+ function parseDates(data, types) {
+ for (var coli in types) {
+ var type = types[coli];
+ if (type === T_DATE || type === T_DATETIME) {
+ for (var rowi in data) {
+ data[rowi][coli] = myParseDate(data[rowi][coli]);
+ }
+ }
}
}
-
- return outgrid;
-}
-function pivotBy(ingrid, rowkeys, colkeys, valkeys) {
- // We generate a list of value columns based on all the unique combinations
- // of (values in colkeys)*(column names in valkeys)
- var valuecols = {};
- var colkey_outcols = {};
- var colkey_incols = [];
- for (var coli in colkeys) {
- colkey_incols.push(keyToColNum(ingrid, colkeys[coli]));
+ function colNameToColNum(grid, colname) {
+ var keycol = colname=='*' ? 0 : grid.headers.indexOf(colname);
+ if (keycol < 0) {
+ throw new Error('unknown column name "' + key + '"');
+ }
+ return keycol;
}
- var addcols_func = function(outgrid) {
+
+
+ var FUNC_RE = /^(\w+)\((.*)\)$/;
+ function keyToColNum(grid, key) {
+ var g = FUNC_RE.exec(key);
+ if (g) {
+ return colNameToColNum(grid, g[2]);
+ } else {
+ return colNameToColNum(grid, key);
+ }
+ }
+
+
+ function _groupByLoop(ingrid, keys, initval, addcols_func, putvalues_func) {
+ var outgrid = {headers: [], data: [], types: []};
+ var keycols = [];
+ for (var keyi in keys) {
+ var colnum = keyToColNum(ingrid, keys[keyi]);
+ keycols.push(colnum);
+ outgrid.headers.push(ingrid.headers[colnum]);
+ outgrid.types.push(ingrid.types[colnum]);
+ }
+
+ addcols_func(outgrid);
+
+ var out = {};
for (var rowi in ingrid.data) {
var row = ingrid.data[rowi];
+ var key = [];
+ for (var kcoli in keycols) {
+ key.push(row[keycols[kcoli]]);
+ }
+ var orow = out[key];
+ if (!orow) {
+ orow = [];
+ for (var keyi in keys) {
+ orow[keyi] = row[keycols[keyi]];
+ }
+ for (var i = keys.length; i < outgrid.headers.length; i++) {
+ orow[i] = initval;
+ }
+ out[key] = orow;
+ // deliberately preserve sequencing as much as possible. The first
+ // time we see a given key is when we add it to the outgrid.
+ outgrid.data.push(orow);
+ }
+ putvalues_func(outgrid, key, orow, row);
+ }
+ return outgrid;
+ }
+
+
+ var agg_types = {
+ count: T_NUM,
+ sum: T_NUM
+ };
+
+
+ var agg_funcs = {
+ first: function(l) {
+ return l[0];
+ },
+
+ last: function(l) {
+ return l.slice(l.length-1)[0];
+ },
+
+ only: function(l) {
+ if (l.length == 1) {
+ return l[0];
+ } else if (l.length < 1) {
+ return null;
+ } else {
+ throw new Error('cell has more than one value: only(' + l + ')')
+ }
+ },
+
+ min: function(l) {
+ var out = null;
+ for (var i in l) {
+ if (out == null || l[i] < out) {
+ out = l[i];
+ }
+ }
+ return out;
+ },
+
+ max: function(l) {
+ var out = null;
+ for (var i in l) {
+ if (out == null || l[i] > out) {
+ out = l[i];
+ }
+ }
+ return out;
+ },
+
+ cat: function(l) {
+ return l.join(' ');
+ },
+
+ count: function(l) {
+ return l.length;
+ },
+
+ count_distinct: function(l) {
+ var a = {};
+ for (var i in l) {
+ a[l[i]] = 1;
+ }
+ var acc = 0;
+ for (var i in a) {
+ acc += 1;
+ }
+ return acc;
+ },
+
+ sum: function(l) {
+ var acc;
+ if (l.length) acc = 0;
+ for (var i in l) {
+ acc += parseFloat(l[i]);
+ }
+ return acc;
+ }
+ };
+ agg_funcs.count.return_type = T_NUM;
+ agg_funcs.sum.return_type = T_NUM;
+
+
+ function groupBy(ingrid, keys, values) {
+ // add one value column for every column listed in values.
+ var valuecols = [];
+ var valuefuncs = [];
+ var addcols_func = function(outgrid) {
+ for (var valuei in values) {
+ var g = FUNC_RE.exec(values[valuei]);
+ var field, func;
+ if (g) {
+ func = agg_funcs[g[1]];
+ if (!func) {
+ throw new Error('unknown aggregation function "' + g[1] + '"');
+ }
+ field = g[2];
+ } else {
+ func = null;
+ field = values[valuei];
+ }
+ var colnum = keyToColNum(ingrid, field);
+ console.debug('v', values[valuei], func, field);
+ if (!func) {
+ if (ingrid.types[colnum] === T_NUM) {
+ func = agg_funcs.sum;
+ } else {
+ func = agg_funcs.count;
+ }
+ }
+ valuecols.push(colnum);
+ valuefuncs.push(func);
+ outgrid.headers.push(field=='*' ? '_count' : ingrid.headers[colnum]);
+ outgrid.types.push(func.return_type || ingrid.types[colnum]);
+ }
+ };
+
+ // by default, we do a count(*) operation for non-numeric value
+ // columns, and sum(*) otherwise.
+ var putvalues_func = function(outgrid, key, orow, row) {
+ for (var valuei in values) {
+ var incoli = valuecols[valuei];
+ var outcoli = key.length + parseInt(valuei);
+ var cell = row[incoli];
+ if (!orow[outcoli]) orow[outcoli] = [];
+ if (cell != null) {
+ orow[outcoli].push(cell);
+ }
+ }
+ };
+
+ var outgrid = _groupByLoop(ingrid, keys, 0,
+ addcols_func, putvalues_func);
+
+ for (var rowi in outgrid.data) {
+ var row = outgrid.data[rowi];
+ for (var valuei in values) {
+ var outcoli = keys.length + parseInt(valuei);
+ var func = valuefuncs[valuei];
+ row[outcoli] = func(row[outcoli]);
+ }
+ }
+
+ return outgrid;
+ }
+
+
+ function pivotBy(ingrid, rowkeys, colkeys, valkeys) {
+ // We generate a list of value columns based on all the unique combinations
+ // of (values in colkeys)*(column names in valkeys)
+ var valuecols = {};
+ var colkey_outcols = {};
+ var colkey_incols = [];
+ for (var coli in colkeys) {
+ colkey_incols.push(keyToColNum(ingrid, colkeys[coli]));
+ }
+ var addcols_func = function(outgrid) {
+ for (var rowi in ingrid.data) {
+ var row = ingrid.data[rowi];
+ var colkey = [];
+ for (var coli in colkey_incols) {
+ var colnum = colkey_incols[coli];
+ colkey.push(row[colnum]);
+ }
+ for (var coli in valkeys) {
+ var xcolkey = colkey.concat([valkeys[coli]]);
+ if (!(xcolkey in colkey_outcols)) {
+ // if there's only one valkey (the common case), don't include the
+ // name of the old value column in the new column names; it's
+ // just clutter.
+ var name = valkeys.length>1 ? xcolkey.join(' ') : colkey.join(' ');
+ var colnum = keyToColNum(ingrid, valkeys[coli]);
+ colkey_outcols[xcolkey] = outgrid.headers.length;
+ valuecols[xcolkey] = colnum;
+ outgrid.headers.push(name);
+ outgrid.types.push(ingrid.types[colnum]);
+ }
+ }
+ }
+ console.debug('pivot colkey_outcols', colkey_outcols);
+ console.debug('pivot valuecols:', valuecols);
+ };
+
+ // by the time pivotBy is called, we're guaranteed that there's only one
+ // row with a given (rowkeys+colkeys) key, so there is only one value
+ // for each value cell. Thus we don't need to worry about count/sum here;
+ // we just assign the values directly as we see them.
+ var putvalues_func = function(outgrid, rowkey, orow, row) {
var colkey = [];
for (var coli in colkey_incols) {
- var colnum = colkey_incols[coli];
- colkey.push(row[colnum]);
+ var colnum = colkey_incols[coli];
+ colkey.push(row[colnum]);
}
for (var coli in valkeys) {
- var xcolkey = colkey.concat([valkeys[coli]]);
- if (!(xcolkey in colkey_outcols)) {
- // if there's only one valkey (the common case), don't include the
- // name of the old value column in the new column names; it's
- // just clutter.
- var name = valkeys.length>1 ? xcolkey.join(' ') : colkey.join(' ');
- var colnum = keyToColNum(ingrid, valkeys[coli]);
- colkey_outcols[xcolkey] = outgrid.headers.length;
- valuecols[xcolkey] = colnum;
- outgrid.headers.push(name);
- outgrid.types.push(ingrid.types[colnum]);
- }
+ var xcolkey = colkey.concat([valkeys[coli]]);
+ var outcolnum = colkey_outcols[xcolkey];
+ var valuecol = valuecols[xcolkey];
+ orow[outcolnum] = row[valuecol];
}
- }
- console.debug('pivot colkey_outcols', colkey_outcols);
- console.debug('pivot valuecols:', valuecols);
- };
-
- // by the time pivotBy is called, we're guaranteed that there's only one
- // row with a given (rowkeys+colkeys) key, so there is only one value
- // for each value cell. Thus we don't need to worry about count/sum here;
- // we just assign the values directly as we see them.
- var putvalues_func = function(outgrid, rowkey, orow, row) {
- var colkey = [];
- for (var coli in colkey_incols) {
- var colnum = colkey_incols[coli];
- colkey.push(row[colnum]);
- }
- for (var coli in valkeys) {
- var xcolkey = colkey.concat([valkeys[coli]]);
- var outcolnum = colkey_outcols[xcolkey];
- var valuecol = valuecols[xcolkey];
- orow[outcolnum] = row[valuecol];
- }
- };
+ };
- return _groupByLoop(ingrid, rowkeys, undefined,
- addcols_func, putvalues_func);
-}
-
-
-function stringifiedCols(row, types) {
- var out = []
- for (var coli in types) {
- if (types[coli] === T_DATE) {
- out.push(row[coli].strftime('%Y-%m-%d') || '');
- } else if (types[coli] === T_DATETIME) {
- out.push(row[coli].strftime('%Y-%m-%d %H:%M:%S') || '');
- } else {
- out.push((row[coli] + '') || '(none)');
- }
+ return _groupByLoop(ingrid, rowkeys, undefined,
+ addcols_func, putvalues_func);
}
- return out;
-}
-var KEY_ALL = ['ALL'];
-function treeify(ingrid, nkeys) {
- var outgrid = {
- headers: ['_id', '_parent'].concat(ingrid.headers.slice(nkeys)),
- types: [T_STRING, T_STRING].concat(ingrid.types.slice(nkeys)),
- data: []
- };
-
- var seen = {};
- var missing = {};
-
- var add = function(key, values) {
- var pkey = key.slice(0, key.length - 1);
- if (!pkey.length && key != KEY_ALL) pkey = KEY_ALL;
- outgrid.data.push([key.join('|'), pkey.join('|')].concat(values));
- if (pkey.length && !(pkey in seen)) {
- missing[pkey] = pkey;
- }
- if (key in missing) {
- delete missing[key];
- }
- seen[key] = 1;
- }
-
- for (var rowi in ingrid.data) {
- var row = ingrid.data[rowi];
- var key = row.slice(0, nkeys);
- add(stringifiedCols(row.slice(0, nkeys),
- ingrid.types.slice(0, nkeys)),
- row.slice(nkeys));
- }
- var done = 0;
- for (var i = 0; i < ingrid.data.length * nkeys && !done; i++) {
- for (var missi in missing) {
- var miss = missing[missi];
- add(miss, []);
- done = 0;
- break;
- }
- }
- return outgrid;
-}
-
-
-function splitNoEmpty(s, splitter) {
- if (!s) return [];
- return s.split(splitter);
-}
-
-
-function keysOtherThan(grid, keys) {
- var out = [];
- var keynames = [];
- for (var keyi in keys) {
- // this converts func(x) notation to just 'x'
- keynames.push(grid.headers[keyToColNum(grid, keys[keyi])]);
- }
- for (var coli in grid.headers) {
- if (keynames.indexOf(grid.headers[coli]) < 0) {
- out.push(grid.headers[coli]);
- }
- }
- return out;
-}
-
-
-function doGroupBy(grid, argval) {
- console.debug('groupBy:', argval);
- var parts = argval.split(';', 2);
- var keys = splitNoEmpty(parts[0], ',');
- var values;
- if (parts.length >= 2) {
- // if there's a ';' separator, the names after it are the desired
- // value columns (and that list may be empty).
- var tmpvalues = splitNoEmpty(parts[1], ',');
- values = [];
- for (var tmpi in tmpvalues) {
- var tmpval = tmpvalues[tmpi];
- if (tmpval == '*') {
- values = values.concat(keysOtherThan(grid, keys.concat(values)));
+ function stringifiedCols(row, types) {
+ var out = []
+ for (var coli in types) {
+ if (types[coli] === T_DATE) {
+ out.push(row[coli].strftime('%Y-%m-%d') || '');
+ } else if (types[coli] === T_DATETIME) {
+ out.push(row[coli].strftime('%Y-%m-%d %H:%M:%S') || '');
} else {
- values.push(tmpval);
+ out.push((row[coli] + '') || '(none)');
}
}
- } else {
- // if there is no ';' at all, the default is to just pull in all the
- // remaining non-key columns as values.
- values = keysOtherThan(grid, keys);
- }
- console.debug('grouping by', keys, values);
- grid = groupBy(grid, keys, values);
- console.debug('grid:', grid);
- return grid;
-}
-
-
-function doTreeGroupBy(grid, argval) {
- console.debug('treeGroupBy:', argval);
- var parts = argval.split(';', 2);
- var keys = splitNoEmpty(parts[0], ',');
- var values;
- if (parts.length >= 2) {
- // if there's a ';' separator, the names after it are the desired
- // value columns (and that list may be empty).
- values = splitNoEmpty(parts[1], ',');
- } else {
- // if there is no ';' at all, the default is to just pull in all the
- // remaining non-key columns as values.
- values = keysOtherThan(grid, keys);
- }
- console.debug('treegrouping by', keys, values);
- grid = groupBy(grid, keys, values);
- grid = treeify(grid, keys.length);
- console.debug('grid:', grid);
- return grid;
-}
-
-
-function doPivotBy(grid, argval) {
- console.debug('pivotBy:', argval);
-
- // the parts are rowkeys;colkeys;values
- var parts = argval.split(';', 3);
- var rowkeys = splitNoEmpty(parts[0], ',');
- var colkeys = splitNoEmpty(parts[1], ',');
- var values;
- if (parts.length >= 3) {
- // if there's a second ';' separator, the names after it are the desired
- // value columns.
- values = splitNoEmpty(parts[2], ',');
- } else {
- // if there is no second ';' at all, the default is to just pull
- // in all the remaining non-key columns as values.
- values = keysOtherThan(grid, rowkeys.concat(colkeys));
+ return out;
}
- // first group by the rowkeys+colkeys, so there is only one row for each
- // unique rowkeys+colkeys combination.
- grid = groupBy(grid, rowkeys.concat(colkeys), values);
- console.debug('tmpgrid:', grid);
- // now actually do the pivot.
- grid = pivotBy(grid, rowkeys, colkeys, values);
+ var KEY_ALL = ['ALL'];
+ function treeify(ingrid, nkeys) {
+ var outgrid = {
+ headers: ['_id', '_parent'].concat(ingrid.headers.slice(nkeys)),
+ types: [T_STRING, T_STRING].concat(ingrid.types.slice(nkeys)),
+ data: []
+ };
- return grid;
-}
+ var seen = {};
+ var missing = {};
+
+ var add = function(key, values) {
+ var pkey = key.slice(0, key.length - 1);
+ if (!pkey.length && key != KEY_ALL) pkey = KEY_ALL;
+ outgrid.data.push([key.join('|'), pkey.join('|')].concat(values));
+ if (pkey.length && !(pkey in seen)) {
+ missing[pkey] = pkey;
+ }
+ if (key in missing) {
+ delete missing[key];
+ }
+ seen[key] = 1;
+ }
+
+ for (var rowi in ingrid.data) {
+ var row = ingrid.data[rowi];
+ var key = row.slice(0, nkeys);
+ add(stringifiedCols(row.slice(0, nkeys),
+ ingrid.types.slice(0, nkeys)),
+ row.slice(nkeys));
+ }
+ var done = 0;
+ for (var i = 0; i < ingrid.data.length * nkeys && !done; i++) {
+ for (var missi in missing) {
+ var miss = missing[missi];
+ add(miss, []);
+ done = 0;
+ break;
+ }
+ }
+ return outgrid;
+ }
-function filterBy(ingrid, key, op, values) {
- var outgrid = {headers: ingrid.headers, data: [], types: ingrid.types};
- var keycol = keyToColNum(ingrid, key);
- var wantvals = [];
- for (var valuei in values) {
- if (ingrid.types[keycol] === T_NUM) {
- wantvals.push(parseFloat(values[valuei]));
- } else if (ingrid.types[keycol] === T_DATE ||
- ingrid.types[keycol] === T_DATETIME) {
- wantvals.push(myParseDate(values[valuei]));
+ function splitNoEmpty(s, splitter) {
+ if (!s) return [];
+ return s.split(splitter);
+ }
+
+
+ function keysOtherThan(grid, keys) {
+ var out = [];
+ var keynames = [];
+ for (var keyi in keys) {
+ // this converts func(x) notation to just 'x'
+ keynames.push(grid.headers[keyToColNum(grid, keys[keyi])]);
+ }
+ for (var coli in grid.headers) {
+ if (keynames.indexOf(grid.headers[coli]) < 0) {
+ out.push(grid.headers[coli]);
+ }
+ }
+ return out;
+ }
+
+
+ function doGroupBy(grid, argval) {
+ console.debug('groupBy:', argval);
+ var parts = argval.split(';', 2);
+ var keys = splitNoEmpty(parts[0], ',');
+ var values;
+ if (parts.length >= 2) {
+ // if there's a ';' separator, the names after it are the desired
+ // value columns (and that list may be empty).
+ var tmpvalues = splitNoEmpty(parts[1], ',');
+ values = [];
+ for (var tmpi in tmpvalues) {
+ var tmpval = tmpvalues[tmpi];
+ if (tmpval == '*') {
+ values = values.concat(keysOtherThan(grid, keys.concat(values)));
+ } else {
+ values.push(tmpval);
+ }
+ }
} else {
- wantvals.push(values[valuei]);
+ // if there is no ';' at all, the default is to just pull in all the
+ // remaining non-key columns as values.
+ values = keysOtherThan(grid, keys);
}
+ console.debug('grouping by', keys, values);
+ grid = groupBy(grid, keys, values);
+ console.debug('grid:', grid);
+ return grid;
}
- for (var rowi in ingrid.data) {
- var row = ingrid.data[rowi];
- var cell = row[keycol];
- var found = 0;
- for (var valuei in wantvals) {
- if (op == '=' && cell == wantvals[valuei]) {
- found = 1;
- } else if (op == '==' && cell == wantvals[valuei]) {
- found = 1;
- } else if (op == '>=' && cell >= wantvals[valuei]) {
- found = 1;
- } else if (op == '<=' && cell <= wantvals[valuei]) {
- found = 1;
- } else if (op == '>' && cell > wantvals[valuei]) {
- found = 1;
- } else if (op == '<' && cell < wantvals[valuei]) {
- found = 1;
- } else if (op == '!=' && cell != wantvals[valuei]) {
- found = 1;
- } else if (op == '<>' && cell != wantvals[valuei]) {
- found = 1;
+
+ function doTreeGroupBy(grid, argval) {
+ console.debug('treeGroupBy:', argval);
+ var parts = argval.split(';', 2);
+ var keys = splitNoEmpty(parts[0], ',');
+ var values;
+ if (parts.length >= 2) {
+ // if there's a ';' separator, the names after it are the desired
+ // value columns (and that list may be empty).
+ values = splitNoEmpty(parts[1], ',');
+ } else {
+ // if there is no ';' at all, the default is to just pull in all the
+ // remaining non-key columns as values.
+ values = keysOtherThan(grid, keys);
+ }
+ console.debug('treegrouping by', keys, values);
+ grid = groupBy(grid, keys, values);
+ grid = treeify(grid, keys.length);
+ console.debug('grid:', grid);
+ return grid;
+ }
+
+
+ function doPivotBy(grid, argval) {
+ console.debug('pivotBy:', argval);
+
+ // the parts are rowkeys;colkeys;values
+ var parts = argval.split(';', 3);
+ var rowkeys = splitNoEmpty(parts[0], ',');
+ var colkeys = splitNoEmpty(parts[1], ',');
+ var values;
+ if (parts.length >= 3) {
+ // if there's a second ';' separator, the names after it are the desired
+ // value columns.
+ values = splitNoEmpty(parts[2], ',');
+ } else {
+ // if there is no second ';' at all, the default is to just pull
+ // in all the remaining non-key columns as values.
+ values = keysOtherThan(grid, rowkeys.concat(colkeys));
+ }
+
+ // first group by the rowkeys+colkeys, so there is only one row for each
+ // unique rowkeys+colkeys combination.
+ grid = groupBy(grid, rowkeys.concat(colkeys), values);
+ console.debug('tmpgrid:', grid);
+
+ // now actually do the pivot.
+ grid = pivotBy(grid, rowkeys, colkeys, values);
+
+ return grid;
+ }
+
+
+ function filterBy(ingrid, key, op, values) {
+ var outgrid = {headers: ingrid.headers, data: [], types: ingrid.types};
+ var keycol = keyToColNum(ingrid, key);
+ var wantvals = [];
+ for (var valuei in values) {
+ if (ingrid.types[keycol] === T_NUM) {
+ wantvals.push(parseFloat(values[valuei]));
+ } else if (ingrid.types[keycol] === T_DATE ||
+ ingrid.types[keycol] === T_DATETIME) {
+ wantvals.push(myParseDate(values[valuei]));
+ } else {
+ wantvals.push(values[valuei]);
}
- if (found) break;
}
- if (found) outgrid.data.push(row);
+
+ for (var rowi in ingrid.data) {
+ var row = ingrid.data[rowi];
+ var cell = row[keycol];
+ var found = 0;
+ for (var valuei in wantvals) {
+ if (op == '=' && cell == wantvals[valuei]) {
+ found = 1;
+ } else if (op == '==' && cell == wantvals[valuei]) {
+ found = 1;
+ } else if (op == '>=' && cell >= wantvals[valuei]) {
+ found = 1;
+ } else if (op == '<=' && cell <= wantvals[valuei]) {
+ found = 1;
+ } else if (op == '>' && cell > wantvals[valuei]) {
+ found = 1;
+ } else if (op == '<' && cell < wantvals[valuei]) {
+ found = 1;
+ } else if (op == '!=' && cell != wantvals[valuei]) {
+ found = 1;
+ } else if (op == '<>' && cell != wantvals[valuei]) {
+ found = 1;
+ }
+ if (found) break;
+ }
+ if (found) outgrid.data.push(row);
+ }
+ return outgrid;
}
- return outgrid;
-}
-function trySplitOne(argval, splitstr) {
- var pos = argval.indexOf(splitstr);
- if (pos >= 0) {
- return [argval.substr(0, pos).trim(),
- argval.substr(pos + splitstr.length).trim()];
- } else {
- return;
- }
-}
-
-
-function doFilterBy(grid, argval) {
- console.debug('filterBy:', argval);
- var ops = ['>=', '<=', '==', '!=', '<>', '>', '<', '='];
- var parts;
- for (var opi in ops) {
- var op = ops[opi];
- if ((parts = trySplitOne(argval, op))) {
- grid = filterBy(grid, parts[0], op, parts[1].split(','));
- console.debug('grid:', grid);
- return grid;
+ function trySplitOne(argval, splitstr) {
+ var pos = argval.indexOf(splitstr);
+ if (pos >= 0) {
+ return [argval.substr(0, pos).trim(),
+ argval.substr(pos + splitstr.length).trim()];
+ } else {
+ return;
}
}
- throw new Error('unknown filter operation in "' + argval + '"');
- return grid;
-}
-function queryBy(ingrid, words) {
- var outgrid = {headers: ingrid.headers, data: [], types: ingrid.types};
- for (var rowi in ingrid.data) {
- var row = ingrid.data[rowi];
- var found = 0;
- for (var wordi in words) {
+ function doFilterBy(grid, argval) {
+ console.debug('filterBy:', argval);
+ var ops = ['>=', '<=', '==', '!=', '<>', '>', '<', '='];
+ var parts;
+ for (var opi in ops) {
+ var op = ops[opi];
+ if ((parts = trySplitOne(argval, op))) {
+ grid = filterBy(grid, parts[0], op, parts[1].split(','));
+ console.debug('grid:', grid);
+ return grid;
+ }
+ }
+ throw new Error('unknown filter operation in "' + argval + '"');
+ return grid;
+ }
+
+
+ function queryBy(ingrid, words) {
+ var outgrid = {headers: ingrid.headers, data: [], types: ingrid.types};
+ for (var rowi in ingrid.data) {
+ var row = ingrid.data[rowi];
+ var found = 0;
+ for (var wordi in words) {
+ for (var coli in row) {
+ var cell = row[coli];
+ if (cell.indexOf && cell.indexOf(words[wordi]) >= 0) {
+ found = 1;
+ break;
+ }
+ }
+ if (found) break;
+ }
+ if (found) {
+ outgrid.data.push(row);
+ }
+ }
+ return outgrid;
+ }
+
+
+ function doQueryBy(grid, argval) {
+ console.debug('queryBy:', argval);
+ grid = queryBy(grid, argval.split(','));
+ console.debug('grid:', grid);
+ return grid;
+ }
+
+
+ function orderBy(grid, keys) {
+ var keycols = [];
+ for (var keyi in keys) {
+ var key = keys[keyi];
+ var invert = 1;
+ if (key[0] == '-') {
+ invert = -1;
+ key = key.substr(1);
+ }
+ keycols.push([keyToColNum(grid, key), invert]);
+ }
+ console.debug('sort keycols', keycols);
+ var comparator = function(a, b) {
+ for (var keyi in keycols) {
+ var keycol = keycols[keyi][0], invert = keycols[keyi][1];
+ var av = a[keycol], bv = b[keycol];
+ if (grid.types[keycol] === T_NUM) {
+ av = parseFloat(av);
+ bv = parseFloat(bv);
+ }
+ if (av < bv) {
+ return -1 * invert;
+ } else if (av > bv) {
+ return 1 * invert;
+ }
+ }
+ return 0;
+ }
+ var outdata = grid.data.concat();
+ outdata.sort(comparator);
+ return { headers: grid.headers, data: outdata, types: grid.types };
+ }
+
+
+ function doOrderBy(grid, argval) {
+ console.debug('orderBy:', argval);
+ grid = orderBy(grid, argval.split(','));
+ console.debug('grid:', grid);
+ return grid;
+ }
+
+
+ function extractRegexp(grid, colname, regexp) {
+ var r = RegExp(regexp);
+ var colnum = keyToColNum(grid, colname);
+ for (var rowi in grid.data) {
+ var row = grid.data[rowi];
+ var match = r.exec(row[colnum]);
+ if (match) {
+ row[colnum] = match.slice(1).join('');
+ } else {
+ row[colnum] = '';
+ }
+ }
+ return grid;
+ }
+
+
+ function doExtractRegexp(grid, argval) {
+ console.debug('extractRegexp:', argval);
+ var parts = trySplitOne(argval, '=');
+ var colname = parts[0], regexp = parts[1];
+ grid = extractRegexp(grid, colname, regexp);
+ console.debug('grid:', grid);
+ return grid;
+ }
+
+
+ function doLimit(ingrid, limit) {
+ limit = parseInt(limit)
+ if (ingrid.data.length > limit) {
+ return {
+ headers: ingrid.headers,
+ data: ingrid.data.slice(0, limit),
+ types: ingrid.types
+ };
+ } else {
+ return ingrid;
+ }
+ }
+
+
+ function fillNullsWithZero(grid) {
+ for (var rowi in grid.data) {
+ var row = grid.data[rowi];
for (var coli in row) {
- var cell = row[coli];
- if (cell.indexOf && cell.indexOf(words[wordi]) >= 0) {
- found = 1;
- break;
- }
- }
- if (found) break;
- }
- if (found) {
- outgrid.data.push(row);
- }
- }
- return outgrid;
-}
-
-
-function doQueryBy(grid, argval) {
- console.debug('queryBy:', argval);
- grid = queryBy(grid, argval.split(','));
- console.debug('grid:', grid);
- return grid;
-}
-
-
-function orderBy(grid, keys) {
- var keycols = [];
- for (var keyi in keys) {
- var key = keys[keyi];
- var invert = 1;
- if (key[0] == '-') {
- invert = -1;
- key = key.substr(1);
- }
- keycols.push([keyToColNum(grid, key), invert]);
- }
- console.debug('sort keycols', keycols);
- var comparator = function(a, b) {
- for (var keyi in keycols) {
- var keycol = keycols[keyi][0], invert = keycols[keyi][1];
- var av = a[keycol], bv = b[keycol];
- if (grid.types[keycol] === T_NUM) {
- av = parseFloat(av);
- bv = parseFloat(bv);
- }
- if (av < bv) {
- return -1 * invert;
- } else if (av > bv) {
- return 1 * invert;
+ if (grid.types[coli] === T_NUM && row[coli] == undefined) {
+ row[coli] = 0;
+ }
}
}
- return 0;
+ return grid;
}
- var outdata = grid.data.concat();
- outdata.sort(comparator);
- return { headers: grid.headers, data: outdata, types: grid.types };
-}
-function doOrderBy(grid, argval) {
- console.debug('orderBy:', argval);
- grid = orderBy(grid, argval.split(','));
- console.debug('grid:', grid);
- return grid;
-}
+ function gridFromData(gotdata) {
+ var headers, data, types;
+ var err;
+ if (gotdata.errors && gotdata.errors.length) {
+ err = gotdata.errors[0];
+ } else if (gotdata.error) {
+ err = gotdata.error;
+ }
+ if (err) {
+ var msglist = [];
+ if (err.message) msglist.push(err.message);
+ if (err.detailed_message) msglist.push(err.detailed_message);
+ throw new Error('Data provider returned an error: ' + msglist.join(': '));
+ }
-function extractRegexp(grid, colname, regexp) {
- var r = RegExp(regexp);
- var colnum = keyToColNum(grid, colname);
- for (var rowi in grid.data) {
- var row = grid.data[rowi];
- var match = r.exec(row[colnum]);
- if (match) {
- row[colnum] = match.slice(1).join('');
+ if (gotdata.table) {
+ // gviz format
+ headers = [];
+ for (var headeri in gotdata.table.cols) {
+ headers.push(gotdata.table.cols[headeri].label ||
+ gotdata.table.cols[headeri].id);
+ }
+ data = [];
+ for (var rowi in gotdata.table.rows) {
+ var row = gotdata.table.rows[rowi];
+ var orow = [];
+ for (var coli in row.c) {
+ var col = row.c[coli];
+ var g;
+ if (!col) {
+ orow.push(null);
+ } else {
+ orow.push(col.v);
+ }
+ }
+ data.push(orow);
+ }
+ } else if (gotdata.data && gotdata.cols) {
+ // eqldata.com format
+ headers = [];
+ for (var coli in gotdata.cols) {
+ var col = gotdata.cols[coli];
+ headers.push(col.caption);
+ }
+ data = gotdata.data;
} else {
- row[colnum] = '';
+ // assume simple [[cols...]...] (two-dimensional array) format, where
+ // the first row is the headers.
+ headers = gotdata.shift();
+ data = gotdata;
}
+ types = guessTypes(data);
+ parseDates(data, types);
+ return {headers: headers, data: data, types: types};
}
- return grid;
-}
-function doExtractRegexp(grid, argval) {
- console.debug('extractRegexp:', argval);
- var parts = trySplitOne(argval, '=');
- var colname = parts[0], regexp = parts[1];
- grid = extractRegexp(grid, colname, regexp);
- console.debug('grid:', grid);
- return grid;
-}
+ var _queue = [];
-function doLimit(ingrid, limit) {
- limit = parseInt(limit)
- if (ingrid.data.length > limit) {
- return {
- headers: ingrid.headers,
- data: ingrid.data.slice(0, limit),
- types: ingrid.types
+ function enqueue() {
+ _queue.push([].slice.apply(arguments));
+ }
+
+
+ function runqueue(after_each) {
+ var step = function(i) {
+ if (i < _queue.length) {
+ var el = _queue[i]
+ var text = el[0], func = el[1], args = el.slice(2);
+ showstatus('Running step ' + (+i+1) + ' of ' + _queue.length + '...',
+ text);
+ setTimeout(function() {
+ var start = Date.now();
+ wrap(func).apply(null, args);
+ var end = Date.now();
+ if (after_each) {
+ after_each(i + 1, _queue.length, text, end-start);
+ }
+ step(i + 1);
+ }, 0);
+ } else {
+ showstatus('');
+ }
+ }
+ step(0);
+ }
+
+
+ function gotData(args, gotdata) {
+ var grid;
+ enqueue('parse', function() {
+ console.debug('gotdata:', gotdata);
+ grid = gridFromData(gotdata);
+ console.debug('grid:', grid);
+ });
+
+ var argi;
+ var transform = function(f, arg) {
+ enqueue(args.all[argi][0] + '=' + args.all[argi][1], function() {
+ grid = f(grid, arg);
+ });
};
- } else {
- return ingrid;
- }
-}
-
-function fillNullsWithZero(grid) {
- for (var rowi in grid.data) {
- var row = grid.data[rowi];
- for (var coli in row) {
- if (grid.types[coli] === T_NUM && row[coli] == undefined) {
- row[coli] = 0;
+ for (var argi in args.all) {
+ var argkey = args.all[argi][0], argval = args.all[argi][1];
+ if (argkey == 'group') {
+ transform(doGroupBy, argval);
+ } else if (argkey == 'treegroup') {
+ transform(doTreeGroupBy, argval);
+ } else if (argkey == 'pivot') {
+ transform(doPivotBy, argval);
+ } else if (argkey == 'filter') {
+ transform(doFilterBy, argval);
+ } else if (argkey == 'q') {
+ transform(doQueryBy, argval);
+ } else if (argkey == 'limit') {
+ transform(doLimit, argval);
+ } else if (argkey == 'order') {
+ transform(doOrderBy, argval);
+ } else if (argkey == 'extract_regexp') {
+ transform(doExtractRegexp, argval);
}
}
- }
- return grid;
-}
+ var chartops = args.get('chart'), trace = args.get('trace');
+ var t, datatable;
+ var options = {};
-function gridFromData(gotdata) {
- var headers, data, types;
-
- var err;
- if (gotdata.errors && gotdata.errors.length) {
- err = gotdata.errors[0];
- } else if (gotdata.error) {
- err = gotdata.error;
- }
- if (err) {
- var msglist = [];
- if (err.message) msglist.push(err.message);
- if (err.detailed_message) msglist.push(err.detailed_message);
- throw new Error('Data provider returned an error: ' + msglist.join(': '));
- }
-
- if (gotdata.table) {
- // gviz format
- headers = [];
- for (var headeri in gotdata.table.cols) {
- headers.push(gotdata.table.cols[headeri].label ||
- gotdata.table.cols[headeri].id);
- }
- data = [];
- for (var rowi in gotdata.table.rows) {
- var row = gotdata.table.rows[rowi];
- var orow = [];
- for (var coli in row.c) {
- var col = row.c[coli];
- var g;
- if (!col) {
- orow.push(null);
- } else {
- orow.push(col.v);
- }
- }
- data.push(orow);
- }
- } else if (gotdata.data && gotdata.cols) {
- // eqldata.com format
- headers = [];
- for (var coli in gotdata.cols) {
- var col = gotdata.cols[coli];
- headers.push(col.caption);
- }
- data = gotdata.data;
- } else {
- // assume simple [[cols...]...] (two-dimensional array) format, where
- // the first row is the headers.
- headers = gotdata.shift();
- data = gotdata;
- }
- types = guessTypes(data);
- parseDates(data, types);
- return {headers: headers, data: data, types: types};
-}
-
-
-var _queue = [];
-
-
-function enqueue() {
- _queue.push([].slice.apply(arguments));
-}
-
-
-function runqueue(after_each) {
- var step = function(i) {
- if (i < _queue.length) {
- var el = _queue[i]
- var text = el[0], func = el[1], args = el.slice(2);
- showstatus('Running step ' + (+i+1) + ' of ' + _queue.length + '...',
- text);
- setTimeout(function() {
- var start = Date.now();
- wrap(func).apply(null, args);
- var end = Date.now();
- if (after_each) {
- after_each(i + 1, _queue.length, text, end-start);
- }
- step(i + 1);
- }, 0);
- } else {
- showstatus('');
- }
- }
- step(0);
-}
-
-
-function gotData(args, gotdata) {
- var grid;
- enqueue('parse', function() {
- console.debug('gotdata:', gotdata);
- grid = gridFromData(gotdata);
- console.debug('grid:', grid);
- });
-
- var argi;
- var transform = function(f, arg) {
- enqueue(args.all[argi][0] + '=' + args.all[argi][1], function() {
- grid = f(grid, arg);
- });
- };
-
- for (var argi in args.all) {
- var argkey = args.all[argi][0], argval = args.all[argi][1];
- if (argkey == 'group') {
- transform(doGroupBy, argval);
- } else if (argkey == 'treegroup') {
- transform(doTreeGroupBy, argval);
- } else if (argkey == 'pivot') {
- transform(doPivotBy, argval);
- } else if (argkey == 'filter') {
- transform(doFilterBy, argval);
- } else if (argkey == 'q') {
- transform(doQueryBy, argval);
- } else if (argkey == 'limit') {
- transform(doLimit, argval);
- } else if (argkey == 'order') {
- transform(doOrderBy, argval);
- } else if (argkey == 'extract_regexp') {
- transform(doExtractRegexp, argval);
- }
- }
-
- var chartops = args.get('chart'), trace = args.get('trace');
- var t, datatable;
- var options = {};
-
- enqueue('gentable', function() {
- if (chartops) {
- if (chartops == 'stacked' || chartops == 'stackedarea') {
- // Some charts react badly to missing values, so fill them in.
- grid = fillNullsWithZero(grid);
- }
- var el = document.getElementById('vizchart');
- if (args.get('title')) {
- options.title = args.get('title');
- }
- if (chartops == 'stackedarea' || chartops == 'stacked') {
- t = new google.visualization.AreaChart(el);
- options.isStacked = true;
- } else if (chartops == 'column') {
- t = new google.visualization.ColumnChart(el);
- } else if (chartops == 'bar') {
- t = new google.visualization.BarChart(el);
- } else if (chartops == 'line') {
- t = new google.visualization.LineChart(el);
- } else if (chartops == 'spark') {
- // sparkline chart: get rid of everything but the data series.
- // Looks best when small.
- options.hAxis = {};
- options.hAxis.baselineColor = 'none';
- options.hAxis.textPosition = 'none';
- options.hAxis.gridlines = {};
- options.hAxis.gridlines.color = 'none';
- options.vAxis = {};
- options.vAxis.baselineColor = 'none';
- options.vAxis.textPosition = 'none';
- options.vAxis.gridlines = {};
- options.vAxis.gridlines.color = 'none';
- options.theme = 'maximized';
- options.legend = {};
- options.legend.position = 'none';
- t = new google.visualization.LineChart(el);
- } else if (chartops == 'pie') {
- t = new google.visualization.PieChart(el);
- } else if (chartops == 'tree') {
- options.maxDepth = 3;
- options.maxPostDepth = 1;
- options.showScale = 1;
- t = new google.visualization.TreeMap(el);
- } else if (chartops == 'candle' || chartops == 'candlestick') {
- t = new google.visualization.CandlestickChart(el);
- } else if (chartops == 'timeline') {
- t = new google.visualization.AnnotatedTimeLine(el);
- } else if (chartops == 'dygraph' || chartops == 'dygraph+errors') {
- t = new Dygraph.GVizChart(el);
- options.showRoller = true;
- if (chartops == 'dygraph+errors') {
- options.errorBars = true;
- }
+ enqueue('gentable', function() {
+ if (chartops) {
+ if (chartops == 'stacked' || chartops == 'stackedarea') {
+ // Some charts react badly to missing values, so fill them in.
+ grid = fillNullsWithZero(grid);
+ }
+ var el = document.getElementById('vizchart');
+ if (args.get('title')) {
+ options.title = args.get('title');
+ }
+ if (chartops == 'stackedarea' || chartops == 'stacked') {
+ t = new google.visualization.AreaChart(el);
+ options.isStacked = true;
+ } else if (chartops == 'column') {
+ t = new google.visualization.ColumnChart(el);
+ } else if (chartops == 'bar') {
+ t = new google.visualization.BarChart(el);
+ } else if (chartops == 'line') {
+ t = new google.visualization.LineChart(el);
+ } else if (chartops == 'spark') {
+ // sparkline chart: get rid of everything but the data series.
+ // Looks best when small.
+ options.hAxis = {};
+ options.hAxis.baselineColor = 'none';
+ options.hAxis.textPosition = 'none';
+ options.hAxis.gridlines = {};
+ options.hAxis.gridlines.color = 'none';
+ options.vAxis = {};
+ options.vAxis.baselineColor = 'none';
+ options.vAxis.textPosition = 'none';
+ options.vAxis.gridlines = {};
+ options.vAxis.gridlines.color = 'none';
+ options.theme = 'maximized';
+ options.legend = {};
+ options.legend.position = 'none';
+ t = new google.visualization.LineChart(el);
+ } else if (chartops == 'pie') {
+ t = new google.visualization.PieChart(el);
+ } else if (chartops == 'tree') {
+ options.maxDepth = 3;
+ options.maxPostDepth = 1;
+ options.showScale = 1;
+ t = new google.visualization.TreeMap(el);
+ } else if (chartops == 'candle' || chartops == 'candlestick') {
+ t = new google.visualization.CandlestickChart(el);
+ } else if (chartops == 'timeline') {
+ t = new google.visualization.AnnotatedTimeLine(el);
+ } else if (chartops == 'dygraph' || chartops == 'dygraph+errors') {
+ t = new Dygraph.GVizChart(el);
+ options.showRoller = true;
+ if (chartops == 'dygraph+errors') {
+ options.errorBars = true;
+ }
+ } else {
+ throw new Error('unknown chart type "' + chartops + '"');
+ }
+ $(el).height(window.innerHeight);
+ datatable = dataToGvizTable(grid, { show_only_lastseg: true });
} else {
- throw new Error('unknown chart type "' + chartops + '"');
+ var el = document.getElementById('viztable');
+ t = new google.visualization.Table(el);
+ datatable = dataToGvizTable(grid);
}
- $(el).height(window.innerHeight);
- datatable = dataToGvizTable(grid, { show_only_lastseg: true });
+
+ var wantwidth = trace ? window.innerWidth - 40 : window.innerWidth;
+ $(el).width(wantwidth);
+
+ var dateformat = new google.visualization.DateFormat({
+ pattern: 'yyyy-MM-dd'
+ });
+ var datetimeformat = new google.visualization.DateFormat({
+ pattern: 'yyyy-MM-dd HH:mm:ss'
+ });
+ for (var coli = 0; coli < grid.types.length; coli++) {
+ if (grid.types[coli] === T_DATE) {
+ dateformat.format(datatable, coli);
+ } else if (grid.types[coli] === T_DATETIME) {
+ datetimeformat.format(datatable, coli);
+ }
+ }
+ });
+
+ enqueue(chartops ? 'chart=' + chartops : 'view', function() {
+ t.draw(datatable, options);
+ });
+
+ if (trace) {
+ var prevdata;
+ var after_each = function(stepi, nsteps, text, msec_time) {
+ $('#vizlog').append('<div class="vizstep" id="step' + stepi + '">' +
+ ' <div class="text"></div>' +
+ ' <div class="grid"></div>' +
+ '</div>');
+ $('#step' + stepi + ' .text').text('Step ' + stepi +
+ ' (' + msec_time + 'ms): ' +
+ text);
+ var viewel = $('#step' + stepi + ' .grid');
+ if (prevdata != grid.data) {
+ var t = new google.visualization.Table(viewel[0]);
+ var datatable = dataToGvizTable({
+ headers: grid.headers,
+ data: grid.data.slice(0, 1000),
+ types: grid.types
+ });
+ t.draw(datatable);
+ prevdata = grid.data;
+ } else {
+ viewel.text('(unchanged)');
+ }
+ if (stepi == nsteps) {
+ $('.vizstep').show();
+ }
+ };
+ runqueue(after_each);
} else {
- var el = document.getElementById('viztable');
- t = new google.visualization.Table(el);
- datatable = dataToGvizTable(grid);
+ runqueue();
}
+ }
- var wantwidth = trace ? window.innerWidth - 40 : window.innerWidth;
- $(el).width(wantwidth);
- var dateformat = new google.visualization.DateFormat({
- pattern: 'yyyy-MM-dd'
- });
- var datetimeformat = new google.visualization.DateFormat({
- pattern: 'yyyy-MM-dd HH:mm:ss'
- });
- for (var coli = 0; coli < grid.types.length; coli++) {
- if (grid.types[coli] === T_DATE) {
- dateformat.format(datatable, coli);
- } else if (grid.types[coli] === T_DATETIME) {
- datetimeformat.format(datatable, coli);
+ function gotError(url, jqxhr, status) {
+ showstatus('');
+ $('#vizraw').html('<a href="' + encodeURI(url) + '">' +
+ encodeURI(url) +
+ '</a>');
+ throw new Error('error getting url "' + url + '": ' +
+ status + ': ' +
+ 'visit the data page and ensure it\'s valid jsonp.');
+ }
+
+
+ function wrap(func) {
+ var pre_args = [].slice.call(arguments, 1);
+ var f = function() {
+ try {
+ return func.apply(null, pre_args.concat([].slice.call(arguments)));
+ } catch (e) {
+ $('#vizchart').hide();
+ $('#viztable').hide();
+ $('#vizstatus').css('position', 'relative');
+ $('.vizstep').show();
+ err(e);
+ err("<p><a href='/help'>here's the documentation</a>");
+ throw e;
}
}
- });
-
- enqueue(chartops ? 'chart=' + chartops : 'view', function() {
- t.draw(datatable, options);
- });
-
-
- if (trace) {
- var prevdata;
- var after_each = function(stepi, nsteps, text, msec_time) {
- $('#vizlog').append('<div class="vizstep" id="step' + stepi + '">' +
- ' <div class="text"></div>' +
- ' <div class="grid"></div>' +
- '</div>');
- $('#step' + stepi + ' .text').text('Step ' + stepi +
- ' (' + msec_time + 'ms): ' +
- text);
- var viewel = $('#step' + stepi + ' .grid');
- if (prevdata != grid.data) {
- var t = new google.visualization.Table(viewel[0]);
- var datatable = dataToGvizTable({
- headers: grid.headers,
- data: grid.data.slice(0, 1000),
- types: grid.types
- });
- t.draw(datatable);
- prevdata = grid.data;
- } else {
- viewel.text('(unchanged)');
- }
- if (stepi == nsteps) {
- $('.vizstep').show();
+ return f;
+ }
+
+
+ function getUrlData(url, success_func, error_func) {
+ // some services expect callback=, some expect jsonp=, so supply both
+ var plus = 'callback=jsonp&jsonp=jsonp';
+ var nurl;
+ if (url.indexOf('?') >= 0) {
+ nurl = url + '&' + plus;
+ } else {
+ nurl = url + '?' + plus;
+ }
+
+ var iframe = document.createElement('iframe');
+ iframe.style.display = 'none';
+ document.body.insertBefore(iframe, 0);
+
+ // the default jsonp callback
+ iframe.contentWindow.jsonp = success_func;
+
+ // some services are hardcoded to use the gviz callback, so supply that too
+ iframe.contentWindow.google = {
+ visualization: {
+ Query: {
+ setResponse: success_func
+ }
}
};
- runqueue(after_each);
- } else {
- runqueue();
+
+ iframe.contentWindow.onerror = function(message, xurl, lineno) {
+ error(null, message + ' url=' + xurl + ' line=' + lineno);
+ }
+
+ iframe.contentWindow.loaded = function() {
+ alert('loaded');
+ }
+
+ iframe.contentDocument.write(
+ '<script async onerror="loaded" onload="loaded" src="' + encodeURI(url) + '"></script>');
}
-}
-function gotError(url, jqxhr, status) {
- showstatus('');
- $('#vizraw').html('<a href="' + encodeURI(url) + '">' +
- encodeURI(url) +
- '</a>');
- throw new Error('error getting url "' + url + '": ' +
- status + ': ' +
- 'visit the data page and ensure it\'s valid jsonp.');
-}
-
-
-function wrap(func) {
- var pre_args = [].slice.call(arguments, 1);
- var f = function() {
- try {
- return func.apply(null, pre_args.concat([].slice.call(arguments)));
- } catch (e) {
- $('#vizchart').hide();
- $('#viztable').hide();
- $('#vizstatus').css('position', 'relative');
- $('.vizstep').show();
- err(e);
- err("<p><a href='/help'>here's the documentation</a>");
- throw e;
+ function _run(query) {
+ var args = parseArgs(query);
+ var url = args.get('url');
+ if (!url) throw new Error("Missing url= in query parameter");
+ showstatus('Loading <a href="' + encodeURI(url) + '">data</a>...');
+ getUrlData(url, wrap(gotData, args), wrap(gotError, url));
+ var editlink = args.get('editlink');
+ if (editlink == 0) {
+ $('#editmenu').hide();
}
}
- return f;
-}
-
-function getUrlData(url, success_func, error_func) {
- // some services expect callback=, some expect jsonp=, so supply both
- var plus = 'callback=jsonp&jsonp=jsonp';
- var nurl;
- if (url.indexOf('?') >= 0) {
- nurl = url + '&' + plus;
- } else {
- nurl = url + '?' + plus;
+ return {
+ parseArgs: parseArgs,
+ trySplitOne: trySplitOne,
+ render: wrap(_run)
}
-
- var iframe = document.createElement('iframe');
- iframe.style.display = 'none';
- document.body.insertBefore(iframe, 0);
-
- // the default jsonp callback
- iframe.contentWindow.jsonp = success_func;
-
- // some services are hardcoded to use the gviz callback, so supply that too
- iframe.contentWindow.google = {
- visualization: {
- Query: {
- setResponse: success_func
- }
- }
- };
-
- iframe.contentWindow.onerror = function(message, xurl, lineno) {
- error(null, message + ' url=' + xurl + ' line=' + lineno);
- }
-
- iframe.contentWindow.loaded = function() {
- alert('loaded');
- }
-
- iframe.contentDocument.write(
- '<script async onerror="loaded" onload="loaded" src="' + encodeURI(url) + '"></script>');
-}
-
-
-function _run(query) {
- var args = parseArgs(query);
- var url = args.get('url');
- if (!url) throw new Error("Missing url= in query parameter");
- showstatus('Loading <a href="' + encodeURI(url) + '">data</a>...');
- getUrlData(url, wrap(gotData, args), wrap(gotError, url));
- var editlink = args.get('editlink');
- if (editlink == 0) {
- $('#editmenu').hide();
- }
-}
-
-
-var afterquery = {
- render: wrap(_run)
-};
+})();