diff --git a/.gitignore b/.gitignore index 76a5efdd..f5a4aa25 100755 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .DS_Store sandbox/* .*.swp +.*.swo _site/* diff --git a/src/backend.gdocs.js b/src/backend.gdocs.js index 085ac385..5901b347 100644 --- a/src/backend.gdocs.js +++ b/src/backend.gdocs.js @@ -31,8 +31,9 @@ this.recline.Backend.GDocs = this.recline.Backend.GDocs || {}; my.fetch = function(dataset) { var dfd = $.Deferred(); var url = my.getSpreadsheetAPIUrl(dataset.url); + $.getJSON(url, function(d) { - result = my.parseData(d); + var result = my.parseData(d); var fields = _.map(result.fields, function(fieldId) { return {id: fieldId}; }); @@ -42,6 +43,7 @@ this.recline.Backend.GDocs = this.recline.Backend.GDocs || {}; useMemoryStore: true }); }); + 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. my.parseData = function(gdocsSpreadsheet) { - var options = {}; - if (arguments.length > 1) { - options = arguments[1]; - } + var options = arguments[1] || {}; + var colTypes = options.colTypes || {}; var results = { - fields: [], + fields : [], records: [] }; - // default is no special info on type of columns - var colTypes = {}; - if (options.colTypes) { - colTypes = options.colTypes; - } - if (gdocsSpreadsheet.feed.entry.length > 0) { - for (var k in gdocsSpreadsheet.feed.entry[0]) { - if (k.substr(0, 3) == 'gsx') { - var col = k.substr(4); - results.fields.push(col); - } + var entries = gdocsSpreadsheet.feed.entry || []; + var key; + var colName; + // percentage values (e.g. 23.3%) + var rep = /^([\d\.\-]+)\%$/; + + for(key in entries[0]) { + // it's barely possible it has inherited keys starting with 'gsx$' + if(/^gsx/.test(key)) { + colName = key.substr(4); + results.fields.push(colName); } } // converts non numberical values that should be numerical (22.3%[string] -> 0.223[float]) - var rep = /^([\d\.\-]+)\%$/; - results.records = _.map(gdocsSpreadsheet.feed.entry, function(entry) { + results.records = _.map(entries, function(entry) { var row = {}; + _.each(results.fields, function(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 (colTypes[col] == 'percent') { - if (rep.test(value)) { - var value2 = rep.exec(value); - var value3 = parseFloat(value2); - value = value3 / 100; - } + if(colTypes[col] === 'percent' && rep.test(value)) { + num = rep.exec(value)[1]; + value = parseFloat(num) / 100; } + row[col] = value; }); + return row; }); + return results; }; // Convenience function to get GDocs JSON API Url from standard URL my.getSpreadsheetAPIUrl = function(url) { - if (url.indexOf('feeds/list') != -1) { - return url; - } else { - // https://docs.google.com/spreadsheet/ccc?key=XXXX#gid=0 - var regex = /.*spreadsheet\/ccc?.*key=([^#?&+]+).*/; - var matches = url.match(regex); - if (matches) { - var key = matches[1]; - var worksheet = 1; - var out = 'https://spreadsheets.google.com/feeds/list/' + key + '/' + worksheet + '/public/values?alt=json'; - return out; - } else { - alert('Failed to extract gdocs key from ' + url); - } + // https://docs.google.com/spreadsheet/ccc?key=XXXX#gid=0 + var regex = /.*spreadsheet\/ccc?.*key=([^#?&+]+).*/; + var matches = url.match(regex); + var key; + // TODO check possible worksheet options + var worksheet = 1; + + if(!!matches) { + key = matches[1]; + url = 'https://spreadsheets.google.com/feeds/list/'+ key +'/'+ worksheet +'/public/values?alt=json'; } + return url; }; }(jQuery, this.recline.Backend.GDocs)); - diff --git a/src/view.graph.js b/src/view.graph.js index 1fd0c71c..14d2ebbb 100644 --- a/src/view.graph.js +++ b/src/view.graph.js @@ -115,22 +115,9 @@ my.Graph = Backbone.View.extend({ // @param typeId graphType id (lines, lines-and-points etc) getGraphOptions: function(typeId) { var self = this; - // special tickformatter to show labels rather than numbers - // TODO: we should really use tickFormatter and 1 interval ticks if (and - // only if) x-axis values are non-numeric - // 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 tickFormatter = function (x) { + return getFormattedX(x); }; var trackFormatter = function (obj) { @@ -142,17 +129,8 @@ my.Graph = Backbone.View.extend({ x = y; 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 - var xfield = self.model.fields.get(self.state.attributes.group); - var isDateTime = xfield.get('type') === 'date'; - if (isDateTime) { - x = x.toLocaleDateString(); - } + + x = getFormattedX(x); var content = _.template('<%= group %> = <%= x %>, <%= series %> = <%= y %>', { group: self.state.attributes.group, @@ -164,14 +142,26 @@ my.Graph = Backbone.View.extend({ 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 = {}; - // check for time series on x-axis - 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; - }; + xaxis.tickFormatter = tickFormatter; + var yaxis = {}; yaxis.autoscale = true; yaxis.autoscaleMargin = 0.02; @@ -217,7 +207,8 @@ my.Graph = Backbone.View.extend({ legend: legend, colors: this.graphColors, lines: { show: false }, - yaxis: yaxis, + xaxis: yaxis, + yaxis: xaxis, mouse: { track: true, relative: true, @@ -237,6 +228,7 @@ my.Graph = Backbone.View.extend({ legend: legend, colors: this.graphColors, lines: { show: false }, + xaxis: xaxis, yaxis: yaxis, mouse: { track: true, @@ -258,7 +250,7 @@ my.Graph = Backbone.View.extend({ return optionsPerGraphType[typeId]; }, - createSeries: function () { + createSeries: function() { var self = this; var series = []; _.each(this.state.attributes.series, function(field) { @@ -266,19 +258,30 @@ my.Graph = Backbone.View.extend({ _.each(self.model.records.models, function(doc, index) { var xfield = self.model.fields.get(self.state.attributes.group); var x = doc.getFieldValue(xfield); + // time series var isDateTime = xfield.get('type') === 'date'; + if (isDateTime) { - x = moment(x).toDate(); - } - var yfield = self.model.fields.get(field); - var y = doc.getFieldValue(yfield); - if (typeof x === 'string') { + // 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; + } + } else if (typeof x === 'string') { + // string x = parseFloat(x); if (isNaN(x)) { x = index; } } + + var yfield = self.model.fields.get(field); + var y = doc.getFieldValue(yfield); + // horizontal bar chart if (self.state.attributes.graphType == 'bars') { points.push([y, x]);