Merge branch 'master' of github.com:okfn/recline
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
sandbox/*
|
sandbox/*
|
||||||
.*.swp
|
.*.swp
|
||||||
|
.*.swo
|
||||||
_site/*
|
_site/*
|
||||||
|
|||||||
@@ -31,8 +31,9 @@ this.recline.Backend.GDocs = this.recline.Backend.GDocs || {};
|
|||||||
my.fetch = function(dataset) {
|
my.fetch = function(dataset) {
|
||||||
var dfd = $.Deferred();
|
var dfd = $.Deferred();
|
||||||
var url = my.getSpreadsheetAPIUrl(dataset.url);
|
var url = my.getSpreadsheetAPIUrl(dataset.url);
|
||||||
|
|
||||||
$.getJSON(url, function(d) {
|
$.getJSON(url, function(d) {
|
||||||
result = my.parseData(d);
|
var result = my.parseData(d);
|
||||||
var fields = _.map(result.fields, function(fieldId) {
|
var fields = _.map(result.fields, function(fieldId) {
|
||||||
return {id: fieldId};
|
return {id: fieldId};
|
||||||
});
|
});
|
||||||
@@ -42,6 +43,7 @@ this.recline.Backend.GDocs = this.recline.Backend.GDocs || {};
|
|||||||
useMemoryStore: true
|
useMemoryStore: true
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return dfd.promise();
|
return dfd.promise();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -56,67 +58,66 @@ this.recline.Backend.GDocs = this.recline.Backend.GDocs || {};
|
|||||||
//
|
//
|
||||||
// Issues: seems google docs return columns in rows in random order and not even sure whether consistent across rows.
|
// Issues: seems google docs return columns in rows in random order and not even sure whether consistent across rows.
|
||||||
my.parseData = function(gdocsSpreadsheet) {
|
my.parseData = function(gdocsSpreadsheet) {
|
||||||
var options = {};
|
var options = arguments[1] || {};
|
||||||
if (arguments.length > 1) {
|
var colTypes = options.colTypes || {};
|
||||||
options = arguments[1];
|
|
||||||
}
|
|
||||||
var results = {
|
var results = {
|
||||||
fields : [],
|
fields : [],
|
||||||
records: []
|
records: []
|
||||||
};
|
};
|
||||||
// default is no special info on type of columns
|
var entries = gdocsSpreadsheet.feed.entry || [];
|
||||||
var colTypes = {};
|
var key;
|
||||||
if (options.colTypes) {
|
var colName;
|
||||||
colTypes = options.colTypes;
|
// percentage values (e.g. 23.3%)
|
||||||
}
|
var rep = /^([\d\.\-]+)\%$/;
|
||||||
if (gdocsSpreadsheet.feed.entry.length > 0) {
|
|
||||||
for (var k in gdocsSpreadsheet.feed.entry[0]) {
|
for(key in entries[0]) {
|
||||||
if (k.substr(0, 3) == 'gsx') {
|
// it's barely possible it has inherited keys starting with 'gsx$'
|
||||||
var col = k.substr(4);
|
if(/^gsx/.test(key)) {
|
||||||
results.fields.push(col);
|
colName = key.substr(4);
|
||||||
}
|
results.fields.push(colName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// converts non numberical values that should be numerical (22.3%[string] -> 0.223[float])
|
// converts non numberical values that should be numerical (22.3%[string] -> 0.223[float])
|
||||||
var rep = /^([\d\.\-]+)\%$/;
|
results.records = _.map(entries, function(entry) {
|
||||||
results.records = _.map(gdocsSpreadsheet.feed.entry, function(entry) {
|
|
||||||
var row = {};
|
var row = {};
|
||||||
|
|
||||||
_.each(results.fields, function(col) {
|
_.each(results.fields, function(col) {
|
||||||
var _keyname = 'gsx$' + col;
|
var _keyname = 'gsx$' + col;
|
||||||
var value = entry[_keyname]['$t'];
|
var value = entry[_keyname].$t;
|
||||||
|
var num;
|
||||||
|
|
||||||
|
// TODO decide the entry format of percentage data to be parsed
|
||||||
|
// TODO cover this part of code with test
|
||||||
|
// TODO use the regexp only once
|
||||||
// if labelled as % and value contains %, convert
|
// if labelled as % and value contains %, convert
|
||||||
if (colTypes[col] == 'percent') {
|
if(colTypes[col] === 'percent' && rep.test(value)) {
|
||||||
if (rep.test(value)) {
|
num = rep.exec(value)[1];
|
||||||
var value2 = rep.exec(value);
|
value = parseFloat(num) / 100;
|
||||||
var value3 = parseFloat(value2);
|
|
||||||
value = value3 / 100;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
row[col] = value;
|
row[col] = value;
|
||||||
});
|
});
|
||||||
|
|
||||||
return row;
|
return row;
|
||||||
});
|
});
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Convenience function to get GDocs JSON API Url from standard URL
|
// Convenience function to get GDocs JSON API Url from standard URL
|
||||||
my.getSpreadsheetAPIUrl = function(url) {
|
my.getSpreadsheetAPIUrl = function(url) {
|
||||||
if (url.indexOf('feeds/list') != -1) {
|
|
||||||
return url;
|
|
||||||
} else {
|
|
||||||
// https://docs.google.com/spreadsheet/ccc?key=XXXX#gid=0
|
// https://docs.google.com/spreadsheet/ccc?key=XXXX#gid=0
|
||||||
var regex = /.*spreadsheet\/ccc?.*key=([^#?&+]+).*/;
|
var regex = /.*spreadsheet\/ccc?.*key=([^#?&+]+).*/;
|
||||||
var matches = url.match(regex);
|
var matches = url.match(regex);
|
||||||
if (matches) {
|
var key;
|
||||||
var key = matches[1];
|
// TODO check possible worksheet options
|
||||||
var worksheet = 1;
|
var worksheet = 1;
|
||||||
var out = 'https://spreadsheets.google.com/feeds/list/' + key + '/' + worksheet + '/public/values?alt=json';
|
|
||||||
return out;
|
if(!!matches) {
|
||||||
} else {
|
key = matches[1];
|
||||||
alert('Failed to extract gdocs key from ' + url);
|
url = 'https://spreadsheets.google.com/feeds/list/'+ key +'/'+ worksheet +'/public/values?alt=json';
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return url;
|
||||||
};
|
};
|
||||||
}(jQuery, this.recline.Backend.GDocs));
|
}(jQuery, this.recline.Backend.GDocs));
|
||||||
|
|
||||||
|
|||||||
@@ -115,22 +115,9 @@ my.Graph = Backbone.View.extend({
|
|||||||
// @param typeId graphType id (lines, lines-and-points etc)
|
// @param typeId graphType id (lines, lines-and-points etc)
|
||||||
getGraphOptions: function(typeId) {
|
getGraphOptions: function(typeId) {
|
||||||
var self = this;
|
var self = this;
|
||||||
// special tickformatter to show labels rather than numbers
|
|
||||||
// TODO: we should really use tickFormatter and 1 interval ticks if (and
|
var tickFormatter = function (x) {
|
||||||
// only if) x-axis values are non-numeric
|
return getFormattedX(x);
|
||||||
// However, that is non-trivial to work out from a dataset (datasets may
|
|
||||||
// have no field type info). Thus at present we only do this for bars.
|
|
||||||
var tickFormatter = function (val) {
|
|
||||||
if (self.model.records.models[val]) {
|
|
||||||
var out = self.model.records.models[val].get(self.state.attributes.group);
|
|
||||||
// if the value was in fact a number we want that not the
|
|
||||||
if (typeof(out) == 'number') {
|
|
||||||
return val;
|
|
||||||
} else {
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return val;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var trackFormatter = function (obj) {
|
var trackFormatter = function (obj) {
|
||||||
@@ -142,17 +129,8 @@ my.Graph = Backbone.View.extend({
|
|||||||
x = y;
|
x = y;
|
||||||
y = _tmp;
|
y = _tmp;
|
||||||
}
|
}
|
||||||
// convert back from 'index' value on x-axis (e.g. in cases where non-number values)
|
|
||||||
//if (self.model.records.models[x]) {
|
|
||||||
// x = self.model.records.models[x].get(self.state.attributes.group);
|
|
||||||
//};
|
|
||||||
|
|
||||||
// is it time series
|
x = getFormattedX(x);
|
||||||
var xfield = self.model.fields.get(self.state.attributes.group);
|
|
||||||
var isDateTime = xfield.get('type') === 'date';
|
|
||||||
if (isDateTime) {
|
|
||||||
x = x.toLocaleDateString();
|
|
||||||
}
|
|
||||||
|
|
||||||
var content = _.template('<%= group %> = <%= x %>, <%= series %> = <%= y %>', {
|
var content = _.template('<%= group %> = <%= x %>, <%= series %> = <%= y %>', {
|
||||||
group: self.state.attributes.group,
|
group: self.state.attributes.group,
|
||||||
@@ -164,14 +142,26 @@ my.Graph = Backbone.View.extend({
|
|||||||
return content;
|
return content;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var getFormattedX = function (x) {
|
||||||
|
var xfield = self.model.fields.get(self.state.attributes.group);
|
||||||
|
|
||||||
|
// time series
|
||||||
|
var isDateTime = xfield.get('type') === 'date';
|
||||||
|
|
||||||
|
if (self.model.records.models[parseInt(x)]) {
|
||||||
|
x = self.model.records.models[parseInt(x)].get(self.state.attributes.group);
|
||||||
|
if (isDateTime) {
|
||||||
|
x = new Date(x).toLocaleDateString();
|
||||||
|
}
|
||||||
|
} else if (isDateTime) {
|
||||||
|
x = new Date(parseInt(x)).toLocaleDateString();
|
||||||
|
}
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
var xaxis = {};
|
var xaxis = {};
|
||||||
// check for time series on x-axis
|
xaxis.tickFormatter = tickFormatter;
|
||||||
if (this.model.fields.get(this.state.get('group')).get('type') === 'date') {
|
|
||||||
xaxis.mode = 'time';
|
|
||||||
xaxis.timeformat = '%y-%b';
|
|
||||||
xaxis.autoscale = true;
|
|
||||||
xaxis.autoscaleMargin = 0.02;
|
|
||||||
};
|
|
||||||
var yaxis = {};
|
var yaxis = {};
|
||||||
yaxis.autoscale = true;
|
yaxis.autoscale = true;
|
||||||
yaxis.autoscaleMargin = 0.02;
|
yaxis.autoscaleMargin = 0.02;
|
||||||
@@ -217,7 +207,8 @@ my.Graph = Backbone.View.extend({
|
|||||||
legend: legend,
|
legend: legend,
|
||||||
colors: this.graphColors,
|
colors: this.graphColors,
|
||||||
lines: { show: false },
|
lines: { show: false },
|
||||||
yaxis: yaxis,
|
xaxis: yaxis,
|
||||||
|
yaxis: xaxis,
|
||||||
mouse: {
|
mouse: {
|
||||||
track: true,
|
track: true,
|
||||||
relative: true,
|
relative: true,
|
||||||
@@ -237,6 +228,7 @@ my.Graph = Backbone.View.extend({
|
|||||||
legend: legend,
|
legend: legend,
|
||||||
colors: this.graphColors,
|
colors: this.graphColors,
|
||||||
lines: { show: false },
|
lines: { show: false },
|
||||||
|
xaxis: xaxis,
|
||||||
yaxis: yaxis,
|
yaxis: yaxis,
|
||||||
mouse: {
|
mouse: {
|
||||||
track: true,
|
track: true,
|
||||||
@@ -266,19 +258,30 @@ my.Graph = Backbone.View.extend({
|
|||||||
_.each(self.model.records.models, function(doc, index) {
|
_.each(self.model.records.models, function(doc, index) {
|
||||||
var xfield = self.model.fields.get(self.state.attributes.group);
|
var xfield = self.model.fields.get(self.state.attributes.group);
|
||||||
var x = doc.getFieldValue(xfield);
|
var x = doc.getFieldValue(xfield);
|
||||||
|
|
||||||
// time series
|
// time series
|
||||||
var isDateTime = xfield.get('type') === 'date';
|
var isDateTime = xfield.get('type') === 'date';
|
||||||
|
|
||||||
if (isDateTime) {
|
if (isDateTime) {
|
||||||
x = moment(x).toDate();
|
// datetime
|
||||||
|
if (self.state.attributes.graphType != 'bars' && self.state.attributes.graphType != 'columns') {
|
||||||
|
// not bar or column
|
||||||
|
x = new Date(x).getTime();
|
||||||
|
} else {
|
||||||
|
// bar or column
|
||||||
|
x = index;
|
||||||
}
|
}
|
||||||
var yfield = self.model.fields.get(field);
|
} else if (typeof x === 'string') {
|
||||||
var y = doc.getFieldValue(yfield);
|
// string
|
||||||
if (typeof x === 'string') {
|
|
||||||
x = parseFloat(x);
|
x = parseFloat(x);
|
||||||
if (isNaN(x)) {
|
if (isNaN(x)) {
|
||||||
x = index;
|
x = index;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var yfield = self.model.fields.get(field);
|
||||||
|
var y = doc.getFieldValue(yfield);
|
||||||
|
|
||||||
// horizontal bar chart
|
// horizontal bar chart
|
||||||
if (self.state.attributes.graphType == 'bars') {
|
if (self.state.attributes.graphType == 'bars') {
|
||||||
points.push([y, x]);
|
points.push([y, x]);
|
||||||
|
|||||||
Reference in New Issue
Block a user