diff --git a/_includes/recline-deps.html b/_includes/recline-deps.html index 84d43041..7b78093b 100644 --- a/_includes/recline-deps.html +++ b/_includes/recline-deps.html @@ -13,7 +13,6 @@ - @@ -23,11 +22,9 @@ - - @@ -66,8 +63,8 @@ - + diff --git a/_layouts/default.html b/_layouts/default.html index 03f35f4f..f293915c 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -59,9 +59,9 @@ diff --git a/css-site/style.css b/css-site/style.css index b1c5b023..dea128c3 100644 --- a/css-site/style.css +++ b/css-site/style.css @@ -21,13 +21,25 @@ Author URI: http://www.mintcanary.com/ @import url(http://fonts.googleapis.com/css?family=PT+Sans:400,400italic,700); +body, p { + font-family: 'PT Sans',Helvetica,Arial,sans-serif; + font-size: 15px; +} + h1, h2, h3, h4, h5, h6 { font-family:'PT Sans',Helvetica, Arial, sans-serif; } -body, p { - font-family: 'PT Sans',Helvetica,Arial,sans-serif; - font-size: 15px; +h2 { + font-size: 24px; +} + +h3 { + font-size: 20px; +} + +h4 { + font-size: 18px; } a { diff --git a/css/flot.css b/css/flot.css index 03f21e56..cf203cda 100644 --- a/css/flot.css +++ b/css/flot.css @@ -1,23 +1,23 @@ -.recline-graph .graph { +.recline-flot .graph { height: 500px; overflow: hidden; } -.recline-graph .legend table { +.recline-flot .legend table { width: auto; margin-bottom: 0; } -.recline-graph .legend td { +.recline-flot .legend td { padding: 5px; line-height: 13px; } -.recline-graph .graph .alert { +.recline-flot .graph .alert { width: 450px; } -#recline-graph-tooltip { +#recline-flot-tooltip { position: absolute; background-color: #FEE !important; color: #000000 !important; diff --git a/css/graph.css b/css/flotr2.css similarity index 100% rename from css/graph.css rename to css/flotr2.css diff --git a/demos/search/demo.search.app.js b/demos/search/demo.search.app.js index 72b5937a..49f5f9b9 100644 --- a/demos/search/demo.search.app.js +++ b/demos/search/demo.search.app.js @@ -207,32 +207,6 @@ var templates = { data: data }); }, - 'http://openspending.org/api/search': function(record) { - record['time'] = record['time.label_facet'] - var template = '
\ -

\ - {{record.dataset}} {{record.time}} \ - – {{amount_formatted}} \ -

\ - \ -
\ - '; - var data = []; - _.each(_.keys(record), function(key) { - if (key !='_id' && key != 'id') { - data.push({ key: key, value: record[key] }); - } - }); - return Mustache.render(template, { - record: record, - amount_formatted: formatAmount(record['amount']), - data: data - }); - }, 'https://docs.google.com/spreadsheet/ccc?key=0Aon3JiuouxLUdExXSTl2Y01xZEszOTBFZjVzcGtzVVE': function(record) { var template = '
\

\ diff --git a/demos/search/index.html b/demos/search/index.html index e19ada5b..28d3bb6a 100644 --- a/demos/search/index.html +++ b/demos/search/index.html @@ -86,7 +86,7 @@ ul.facet-items {

This demo shows how Recline can be used to build a search app. It includes faceting as well as search. You can find the source javascript here (plus prettified version of source for readability) – please feel free to reuse!

-

The default setup uses local example data but you can also connect directly to any other backend supported by Recline, for example SOLR, ElasticSearch or even a google docs spreadsheet. As an example: here's an example running against the SOLR-style OpenSpending API and here's an example running against a GDocs spreadsheet (Oil spills in the Niger Delta).

+

The default setup uses local example data but you can also connect directly to any other backend supported by Recline, for example SOLR, ElasticSearch or even a google docs spreadsheet. Here's an example running against a GDocs spreadsheet (Oil spills in the Niger Delta).


diff --git a/dist/recline.css b/dist/recline.css index 610a56a7..bc71116c 100644 --- a/dist/recline.css +++ b/dist/recline.css @@ -1,23 +1,23 @@ -.recline-graph .graph { +.recline-flot .graph { height: 500px; overflow: hidden; } -.recline-graph .legend table { +.recline-flot .legend table { width: auto; margin-bottom: 0; } -.recline-graph .legend td { +.recline-flot .legend td { padding: 5px; line-height: 13px; } -.recline-graph .graph .alert { +.recline-flot .graph .alert { width: 450px; } -#recline-graph-tooltip { +#recline-flot-tooltip { position: absolute; background-color: #FEE !important; color: #000000 !important; diff --git a/dist/recline.dataset.js b/dist/recline.dataset.js index 23fce729..92183702 100644 --- a/dist/recline.dataset.js +++ b/dist/recline.dataset.js @@ -4,6 +4,7 @@ this.recline.Model = this.recline.Model || {}; (function(my) { +// use either jQuery or Underscore Deferred depending on what is available var Deferred = _.isUndefined(this.jQuery) ? _.Deferred : jQuery.Deferred; // ## Dataset @@ -596,6 +597,9 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {}; (function(my) { my.__type__ = 'memory'; + // private data - use either jQuery or Underscore Deferred depending on what is available + var Deferred = _.isUndefined(this.jQuery) ? _.Deferred : jQuery.Deferred; + // ## Data Wrapper // // Turn a simple array of JS objects into a mini data-store with @@ -639,7 +643,7 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {}; this.save = function(changes, dataset) { var self = this; - var dfd = new _.Deferred(); + var dfd = new Deferred(); // TODO _.each(changes.creates) { ... } _.each(changes.updates, function(record) { self.update(record); @@ -652,7 +656,7 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {}; }, this.query = function(queryObj) { - var dfd = new _.Deferred(); + var dfd = new Deferred(); var numRows = queryObj.size || this.records.length; var start = queryObj.from || 0; var results = this.records; @@ -820,7 +824,7 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {}; }; this.transform = function(editFunc) { - var dfd = new _.Deferred(); + var dfd = new Deferred(); // TODO: should we clone before mapping? Do not see the point atm. self.records = _.map(self.records, editFunc); // now deal with deletes (i.e. nulls) diff --git a/dist/recline.js b/dist/recline.js index 192b861b..297cc26b 100644 --- a/dist/recline.js +++ b/dist/recline.js @@ -27,6 +27,9 @@ this.recline.Backend.Ckan = this.recline.Backend.Ckan || {}; my.__type__ = 'ckan'; + // private - use either jQuery or Underscore Deferred depending on what is available + var Deferred = _.isUndefined(this.jQuery) ? _.Deferred : jQuery.Deferred; + // Default CKAN API endpoint used for requests (you can change this but it will affect every request!) // // DEPRECATION: this will be removed in v0.7. Please set endpoint attribute on dataset instead @@ -41,7 +44,7 @@ this.recline.Backend.Ckan = this.recline.Backend.Ckan || {}; dataset.id = out.resource_id; var wrapper = my.DataStore(out.endpoint); } - var dfd = new _.Deferred(); + var dfd = new Deferred(); var jqxhr = wrapper.search({resource_id: dataset.id, limit: 0}); jqxhr.done(function(results) { // map ckan types to our usual types ... @@ -84,7 +87,7 @@ this.recline.Backend.Ckan = this.recline.Backend.Ckan || {}; var wrapper = my.DataStore(out.endpoint); } var actualQuery = my._normalizeQuery(queryObj, dataset); - var dfd = new _.Deferred(); + var dfd = new Deferred(); var jqxhr = wrapper.search(actualQuery); jqxhr.done(function(results) { var out = { @@ -145,6 +148,9 @@ this.recline.Backend.CSV = this.recline.Backend.CSV || {}; (function(my) { my.__type__ = 'csv'; + // use either jQuery or Underscore Deferred depending on what is available + var Deferred = _.isUndefined(this.jQuery) ? _.Deferred : jQuery.Deferred; + // ## fetch // // fetch supports 3 options depending on the attribute provided on the dataset argument @@ -163,7 +169,7 @@ this.recline.Backend.CSV = this.recline.Backend.CSV || {}; // } // my.fetch = function(dataset) { - var dfd = new _.Deferred(); + var dfd = new Deferred(); if (dataset.file) { var reader = new FileReader(); var encoding = dataset.encoding || 'UTF-8'; @@ -438,6 +444,10 @@ this.recline.Backend.DataProxy = this.recline.Backend.DataProxy || {}; // Needed because use JSONP so do not receive e.g. 500 errors my.timeout = 5000; + + // use either jQuery or Underscore Deferred depending on what is available + var Deferred = _.isUndefined(this.jQuery) ? _.Deferred : jQuery.Deferred; + // ## load // // Load data from a URL via the [DataProxy](http://github.com/okfn/dataproxy). @@ -454,7 +464,7 @@ this.recline.Backend.DataProxy = this.recline.Backend.DataProxy || {}; data: data, dataType: 'jsonp' }); - var dfd = new _.Deferred(); + var dfd = new Deferred(); _wrapInTimeout(jqxhr).done(function(results) { if (results.error) { dfd.reject(results.error); @@ -478,7 +488,7 @@ this.recline.Backend.DataProxy = this.recline.Backend.DataProxy || {}; // Many of backends use JSONP and so will not get error messages and this is // a crude way to catch those errors. var _wrapInTimeout = function(ourFunction) { - var dfd = new _.Deferred(); + var dfd = new Deferred(); var timer = setTimeout(function() { dfd.reject({ message: 'Request Error: Backend did not respond after ' + (my.timeout / 1000) + ' seconds' @@ -504,6 +514,9 @@ this.recline.Backend.ElasticSearch = this.recline.Backend.ElasticSearch || {}; (function($, my) { my.__type__ = 'elasticsearch'; + // use either jQuery or Underscore Deferred depending on what is available + var Deferred = _.isUndefined(this.jQuery) ? _.Deferred : jQuery.Deferred; + // ## ElasticSearch Wrapper // // A simple JS wrapper around an [ElasticSearch](http://www.elasticsearch.org/) endpoints. @@ -678,7 +691,7 @@ this.recline.Backend.ElasticSearch = this.recline.Backend.ElasticSearch || {}; // ### fetch my.fetch = function(dataset) { var es = new my.Wrapper(dataset.url, my.esOptions); - var dfd = new _.Deferred(); + var dfd = new Deferred(); es.mapping().done(function(schema) { if (!schema){ @@ -706,7 +719,7 @@ this.recline.Backend.ElasticSearch = this.recline.Backend.ElasticSearch || {}; my.save = function(changes, dataset) { var es = new my.Wrapper(dataset.url, my.esOptions); if (changes.creates.length + changes.updates.length + changes.deletes.length > 1) { - var dfd = new _.Deferred(); + var dfd = new Deferred(); msg = 'Saving more than one item at a time not yet supported'; alert(msg); dfd.reject(msg); @@ -724,7 +737,7 @@ this.recline.Backend.ElasticSearch = this.recline.Backend.ElasticSearch || {}; // ### query my.query = function(queryObj, dataset) { - var dfd = new _.Deferred(); + var dfd = new Deferred(); var es = new my.Wrapper(dataset.url, my.esOptions); var jqxhr = es.query(queryObj); jqxhr.done(function(results) { @@ -786,6 +799,9 @@ this.recline.Backend.GDocs = this.recline.Backend.GDocs || {}; (function(my) { my.__type__ = 'gdocs'; + // use either jQuery or Underscore Deferred depending on what is available + var Deferred = _.isUndefined(this.jQuery) ? _.Deferred : jQuery.Deferred; + // ## Google spreadsheet backend // // Fetch data from a Google Docs spreadsheet. @@ -810,13 +826,13 @@ this.recline.Backend.GDocs = this.recline.Backend.GDocs || {}; // * fields: array of Field objects // * records: array of objects for each row my.fetch = function(dataset) { - var dfd = new _.Deferred(); + var dfd = new Deferred(); var urls = my.getGDocsAPIUrls(dataset.url); // TODO cover it with tests // get the spreadsheet title (function () { - var titleDfd = new _.Deferred(); + var titleDfd = new Deferred(); jQuery.getJSON(urls.spreadsheet, function (d) { titleDfd.resolve({ @@ -950,6 +966,9 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {}; (function(my) { my.__type__ = 'memory'; + // private data - use either jQuery or Underscore Deferred depending on what is available + var Deferred = _.isUndefined(this.jQuery) ? _.Deferred : jQuery.Deferred; + // ## Data Wrapper // // Turn a simple array of JS objects into a mini data-store with @@ -993,7 +1012,7 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {}; this.save = function(changes, dataset) { var self = this; - var dfd = new _.Deferred(); + var dfd = new Deferred(); // TODO _.each(changes.creates) { ... } _.each(changes.updates, function(record) { self.update(record); @@ -1006,7 +1025,7 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {}; }, this.query = function(queryObj) { - var dfd = new _.Deferred(); + var dfd = new Deferred(); var numRows = queryObj.size || this.records.length; var start = queryObj.from || 0; var results = this.records; @@ -1174,7 +1193,7 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {}; }; this.transform = function(editFunc) { - var dfd = new _.Deferred(); + var dfd = new Deferred(); // TODO: should we clone before mapping? Do not see the point atm. self.records = _.map(self.records, editFunc); // now deal with deletes (i.e. nulls) @@ -1194,6 +1213,9 @@ this.recline.Backend.Solr = this.recline.Backend.Solr || {}; (function($, my) { my.__type__ = 'solr'; + // use either jQuery or Underscore Deferred depending on what is available + var Deferred = _.isUndefined(this.jQuery) ? _.Deferred : jQuery.Deferred; + // ### fetch // // dataset must have a solr or url attribute pointing to solr endpoint @@ -1207,7 +1229,7 @@ this.recline.Backend.Solr = this.recline.Backend.Solr || {}; dataType: 'jsonp', jsonp: 'json.wrf' }); - var dfd = new _.Deferred(); + var dfd = new Deferred(); jqxhr.done(function(results) { // if we get 0 results we cannot get fields var fields = [] @@ -1240,7 +1262,7 @@ this.recline.Backend.Solr = this.recline.Backend.Solr || {}; dataType: 'jsonp', jsonp: 'json.wrf' }); - var dfd = new _.Deferred(); + var dfd = new Deferred(); jqxhr.done(function(results) { var out = { total: results.response.numFound, @@ -1391,6 +1413,7 @@ this.recline.Model = this.recline.Model || {}; (function(my) { +// use either jQuery or Underscore Deferred depending on what is available var Deferred = _.isUndefined(this.jQuery) ? _.Deferred : jQuery.Deferred; // ## Dataset @@ -2001,7 +2024,7 @@ this.recline.View = this.recline.View || {}; // generate the element itself (you can then append view.el to the DOM. my.Flot = Backbone.View.extend({ template: ' \ -
\ +
\
\
\

Hey there!

\ @@ -2068,9 +2091,6 @@ my.Flot = Backbone.View.extend({ // check we have something to plot if (this.state.get('group') && this.state.get('series')) { - // faff around with width because flot draws axes *outside* of the element - // width which means graph can get push down as it hits element next to it - this.$graph.width(this.el.width() - 240); var series = this.createSeries(); var options = this.getGraphOptions(this.state.attributes.graphType, series[0].data.length); this.plot = $.plot(this.$graph, series, options); @@ -2091,7 +2111,7 @@ my.Flot = Backbone.View.extend({ this.previousTooltipPoint.y !== item.seriesIndex) { this.previousTooltipPoint.x = item.dataIndex; this.previousTooltipPoint.y = item.seriesIndex; - $("#recline-graph-tooltip").remove(); + $("#recline-flot-tooltip").remove(); var x = item.datapoint[0].toFixed(2), y = item.datapoint[1].toFixed(2); @@ -2113,13 +2133,13 @@ my.Flot = Backbone.View.extend({ yLocation = item.pageY - 20; } - $('
' + content + '
').css({ + $('
' + content + '
').css({ top: yLocation, left: xLocation }).appendTo("body").fadeIn(200); } } else { - $("#recline-graph-tooltip").remove(); + $("#recline-flot-tooltip").remove(); this.previousTooltipPoint.x = null; this.previousTooltipPoint.y = null; } @@ -2485,7 +2505,7 @@ this.recline.View = this.recline.View || {}; (function($, my) { -// ## Graph view for a Dataset using Flot graphing library. +// ## Graph view for a Dataset using Flotr2 graphing library. // // Initialization arguments (in a hash in first parameter): // @@ -2501,7 +2521,7 @@ this.recline.View = this.recline.View || {}; // // NB: should *not* provide an el argument to the view but must let the view // generate the element itself (you can then append view.el to the DOM. -my.Graph = Backbone.View.extend({ +my.Flotr2 = Backbone.View.extend({ template: ' \
\
\ @@ -2535,7 +2555,7 @@ my.Graph = Backbone.View.extend({ options.state ); this.state = new recline.Model.ObjectState(stateData); - this.editor = new my.GraphControls({ + this.editor = new my.Flotr2Controls({ model: this.model, state: this.state.toJSON() }); @@ -2556,9 +2576,9 @@ my.Graph = Backbone.View.extend({ }, redraw: function() { - // There appear to be issues generating a Flot graph if either: + // There appear to be issues generating a Flotr2 graph if either: - // * The relevant div that graph attaches to his hidden at the moment of creating the plot -- Flot will complain with + // * The relevant div that graph attaches to his hidden at the moment of creating the plot -- Flotr2 will complain with // // Uncaught Invalid dimensions for plot, width = 0, height = 0 // * There is no data for the plot -- either same error or may have issues later with errors like 'non-existent node-value' @@ -2587,7 +2607,7 @@ my.Graph = Backbone.View.extend({ // ### getGraphOptions // - // Get options for Flot Graph + // Get options for Flotr2 Graph // // needs to be function as can depend on state // @@ -2784,7 +2804,7 @@ my.Graph = Backbone.View.extend({ } }); -my.GraphControls = Backbone.View.extend({ +my.Flotr2Controls = Backbone.View.extend({ className: "editor", template: ' \
\ @@ -2940,6 +2960,11 @@ my.GraphControls = Backbone.View.extend({ })(jQuery, recline.View); +this.recline = this.recline || {}; +this.recline.View = this.recline.View || {}; +this.recline.View.Graph = this.recline.View.Flot; +this.recline.View.GraphControls = this.recline.View.FlotControls; + /*jshint multistr:true */ this.recline = this.recline || {}; diff --git a/docs/tutorial-maps.markdown b/docs/tutorial-maps.markdown index 6d67f62d..914d7d52 100644 --- a/docs/tutorial-maps.markdown +++ b/docs/tutorial-maps.markdown @@ -20,9 +20,7 @@ See the instructions in the [basic views tutorial](tutorial-views.html). ### Creating a Dataset -Again like the views tutorial: - -Here's some example data We are going to work with: +Just like in the main tutorial, here's some example data We are going to work with: {% highlight javascript %} {% include data.js %} diff --git a/download.markdown b/download.markdown index 8ad29dde..41b72ea0 100644 --- a/download.markdown +++ b/download.markdown @@ -85,7 +85,7 @@ All the views require, in addition to those needed for recline.dataset.js: Individual views have additional dependencies such as: -* [JQuery Flot](http://code.google.com/p/flot/) >= 0.7 (required for for graph view) +* [JQuery Flot](http://www.flotcharts.org/) >= 0.7 (required for for graph view) * [Leaflet](http://leaflet.cloudmade.com/) >= 0.4.4 (required for map view) * [Leaflet.markercluster](https://github.com/danzel/Leaflet.markercluster) as of 2012-09-12 (required for marker clustering) * [Verite Timeline](https://github.com/VeriteCo/Timeline/) as of 2012-05-02 (required for the timeline view) diff --git a/src/backend.ckan.js b/src/backend.ckan.js index dafe6ecd..62fc91e7 100644 --- a/src/backend.ckan.js +++ b/src/backend.ckan.js @@ -27,6 +27,9 @@ this.recline.Backend.Ckan = this.recline.Backend.Ckan || {}; my.__type__ = 'ckan'; + // private - use either jQuery or Underscore Deferred depending on what is available + var Deferred = _.isUndefined(this.jQuery) ? _.Deferred : jQuery.Deferred; + // Default CKAN API endpoint used for requests (you can change this but it will affect every request!) // // DEPRECATION: this will be removed in v0.7. Please set endpoint attribute on dataset instead @@ -41,7 +44,7 @@ this.recline.Backend.Ckan = this.recline.Backend.Ckan || {}; dataset.id = out.resource_id; var wrapper = my.DataStore(out.endpoint); } - var dfd = new _.Deferred(); + var dfd = new Deferred(); var jqxhr = wrapper.search({resource_id: dataset.id, limit: 0}); jqxhr.done(function(results) { // map ckan types to our usual types ... @@ -84,7 +87,7 @@ this.recline.Backend.Ckan = this.recline.Backend.Ckan || {}; var wrapper = my.DataStore(out.endpoint); } var actualQuery = my._normalizeQuery(queryObj, dataset); - var dfd = new _.Deferred(); + var dfd = new Deferred(); var jqxhr = wrapper.search(actualQuery); jqxhr.done(function(results) { var out = { diff --git a/src/backend.couchdb.js b/src/backend.couchdb.js index 8c2c9ad9..994cfc9b 100755 --- a/src/backend.couchdb.js +++ b/src/backend.couchdb.js @@ -5,6 +5,9 @@ this.recline.Backend.CouchDB = this.recline.Backend.CouchDB || {}; (function($, my) { my.__type__ = 'couchdb'; +// use either jQuery or Underscore Deferred depending on what is available +var Deferred = _.isUndefined(this.jQuery) ? _.Deferred : jQuery.Deferred; + // ## CouchDB Wrapper // // Connecting to [CouchDB] (http://www.couchdb.apache.org/) endpoints. @@ -197,7 +200,7 @@ my.__type__ = 'couchdb'; var db_url = dataset.db_url; var view_url = dataset.view_url; var cdb = new my.CouchDBWrapper(db_url, view_url); - var dfd = new _.Deferred(); + var dfd = new Deferred(); // if 'doc' attribute is present, return schema of that // else return schema of 'value' attribute which contains @@ -239,7 +242,7 @@ my.__type__ = 'couchdb'; // // my.save = function (changes, dataset) { - var dfd = new _.Deferred(); + var dfd = new Deferred(); var total = changes.creates.length + changes.updates.length + changes.deletes.length; var results = {'done': [], 'fail': [] }; @@ -280,7 +283,7 @@ my.save = function (changes, dataset) { // @param {Object} recline.Dataset instance // @param {Object} recline.Query instance. my.query = function(queryObj, dataset) { - var dfd = new _.Deferred(); + var dfd = new Deferred(); var db_url = dataset.db_url; var view_url = dataset.view_url; var query_options = dataset.query_options; @@ -475,7 +478,7 @@ function randomId(length, chars) { } _createDocument = function (new_doc, dataset) { - var dfd = new _.Deferred(); + var dfd = new Deferred(); var db_url = dataset.db_url; var view_url = dataset.view_url; var _id = new_doc['id']; @@ -497,7 +500,7 @@ _createDocument = function (new_doc, dataset) { }; _updateDocument = function (new_doc, dataset) { - var dfd = new _.Deferred(); + var dfd = new Deferred(); var db_url = dataset.db_url; var view_url = dataset.view_url; var _id = new_doc['id']; @@ -527,7 +530,7 @@ _updateDocument = function (new_doc, dataset) { }; _deleteDocument = function (del_doc, dataset) { - var dfd = new _.Deferred(); + var dfd = new Deferred(); var db_url = dataset.db_url; var view_url = dataset.view_url; var _id = del_doc['id']; diff --git a/src/backend.csv.js b/src/backend.csv.js index 454015f0..aacfc5e9 100644 --- a/src/backend.csv.js +++ b/src/backend.csv.js @@ -6,6 +6,9 @@ this.recline.Backend.CSV = this.recline.Backend.CSV || {}; (function(my) { my.__type__ = 'csv'; + // use either jQuery or Underscore Deferred depending on what is available + var Deferred = _.isUndefined(this.jQuery) ? _.Deferred : jQuery.Deferred; + // ## fetch // // fetch supports 3 options depending on the attribute provided on the dataset argument @@ -24,7 +27,7 @@ this.recline.Backend.CSV = this.recline.Backend.CSV || {}; // } // my.fetch = function(dataset) { - var dfd = new _.Deferred(); + var dfd = new Deferred(); if (dataset.file) { var reader = new FileReader(); var encoding = dataset.encoding || 'UTF-8'; diff --git a/src/backend.dataproxy.js b/src/backend.dataproxy.js index b8f17826..44db50cf 100644 --- a/src/backend.dataproxy.js +++ b/src/backend.dataproxy.js @@ -10,6 +10,10 @@ this.recline.Backend.DataProxy = this.recline.Backend.DataProxy || {}; // Needed because use JSONP so do not receive e.g. 500 errors my.timeout = 5000; + + // use either jQuery or Underscore Deferred depending on what is available + var Deferred = _.isUndefined(this.jQuery) ? _.Deferred : jQuery.Deferred; + // ## load // // Load data from a URL via the [DataProxy](http://github.com/okfn/dataproxy). @@ -26,7 +30,7 @@ this.recline.Backend.DataProxy = this.recline.Backend.DataProxy || {}; data: data, dataType: 'jsonp' }); - var dfd = new _.Deferred(); + var dfd = new Deferred(); _wrapInTimeout(jqxhr).done(function(results) { if (results.error) { dfd.reject(results.error); @@ -50,7 +54,7 @@ this.recline.Backend.DataProxy = this.recline.Backend.DataProxy || {}; // Many of backends use JSONP and so will not get error messages and this is // a crude way to catch those errors. var _wrapInTimeout = function(ourFunction) { - var dfd = new _.Deferred(); + var dfd = new Deferred(); var timer = setTimeout(function() { dfd.reject({ message: 'Request Error: Backend did not respond after ' + (my.timeout / 1000) + ' seconds' diff --git a/src/backend.elasticsearch.js b/src/backend.elasticsearch.js index 56075a3c..82ba52de 100644 --- a/src/backend.elasticsearch.js +++ b/src/backend.elasticsearch.js @@ -5,6 +5,9 @@ this.recline.Backend.ElasticSearch = this.recline.Backend.ElasticSearch || {}; (function($, my) { my.__type__ = 'elasticsearch'; + // use either jQuery or Underscore Deferred depending on what is available + var Deferred = _.isUndefined(this.jQuery) ? _.Deferred : jQuery.Deferred; + // ## ElasticSearch Wrapper // // A simple JS wrapper around an [ElasticSearch](http://www.elasticsearch.org/) endpoints. @@ -179,7 +182,7 @@ this.recline.Backend.ElasticSearch = this.recline.Backend.ElasticSearch || {}; // ### fetch my.fetch = function(dataset) { var es = new my.Wrapper(dataset.url, my.esOptions); - var dfd = new _.Deferred(); + var dfd = new Deferred(); es.mapping().done(function(schema) { if (!schema){ @@ -207,7 +210,7 @@ this.recline.Backend.ElasticSearch = this.recline.Backend.ElasticSearch || {}; my.save = function(changes, dataset) { var es = new my.Wrapper(dataset.url, my.esOptions); if (changes.creates.length + changes.updates.length + changes.deletes.length > 1) { - var dfd = new _.Deferred(); + var dfd = new Deferred(); msg = 'Saving more than one item at a time not yet supported'; alert(msg); dfd.reject(msg); @@ -225,7 +228,7 @@ this.recline.Backend.ElasticSearch = this.recline.Backend.ElasticSearch || {}; // ### query my.query = function(queryObj, dataset) { - var dfd = new _.Deferred(); + var dfd = new Deferred(); var es = new my.Wrapper(dataset.url, my.esOptions); var jqxhr = es.query(queryObj); jqxhr.done(function(results) { diff --git a/src/backend.gdocs.js b/src/backend.gdocs.js index 3f1812a8..4d18aa9b 100644 --- a/src/backend.gdocs.js +++ b/src/backend.gdocs.js @@ -5,6 +5,9 @@ this.recline.Backend.GDocs = this.recline.Backend.GDocs || {}; (function(my) { my.__type__ = 'gdocs'; + // use either jQuery or Underscore Deferred depending on what is available + var Deferred = _.isUndefined(this.jQuery) ? _.Deferred : jQuery.Deferred; + // ## Google spreadsheet backend // // Fetch data from a Google Docs spreadsheet. @@ -29,13 +32,13 @@ this.recline.Backend.GDocs = this.recline.Backend.GDocs || {}; // * fields: array of Field objects // * records: array of objects for each row my.fetch = function(dataset) { - var dfd = new _.Deferred(); + var dfd = new Deferred(); var urls = my.getGDocsAPIUrls(dataset.url); // TODO cover it with tests // get the spreadsheet title (function () { - var titleDfd = new _.Deferred(); + var titleDfd = new Deferred(); jQuery.getJSON(urls.spreadsheet, function (d) { titleDfd.resolve({ diff --git a/src/backend.solr.js b/src/backend.solr.js deleted file mode 100644 index 51b4ab71..00000000 --- a/src/backend.solr.js +++ /dev/null @@ -1,65 +0,0 @@ -this.recline = this.recline || {}; -this.recline.Backend = this.recline.Backend || {}; -this.recline.Backend.Solr = this.recline.Backend.Solr || {}; - -(function($, my) { - my.__type__ = 'solr'; - - // ### fetch - // - // dataset must have a solr or url attribute pointing to solr endpoint - my.fetch = function(dataset) { - var jqxhr = $.ajax({ - url: dataset.solr || dataset.url, - data: { - rows: 1, - wt: 'json' - }, - dataType: 'jsonp', - jsonp: 'json.wrf' - }); - var dfd = new _.Deferred(); - jqxhr.done(function(results) { - // if we get 0 results we cannot get fields - var fields = [] - if (results.response.numFound > 0) { - fields = _.map(_.keys(results.response.docs[0]), function(fieldName) { - return { id: fieldName }; - }); - } - var out = { - fields: fields, - useMemoryStore: false - }; - dfd.resolve(out); - }); - return dfd.promise(); - } - - // TODO - much work on proper query support is needed!! - my.query = function(queryObj, dataset) { - var q = queryObj.q || '*:*'; - var data = { - q: q, - rows: queryObj.size, - start: queryObj.from, - wt: 'json' - }; - var jqxhr = $.ajax({ - url: dataset.solr || dataset.url, - data: data, - dataType: 'jsonp', - jsonp: 'json.wrf' - }); - var dfd = new _.Deferred(); - jqxhr.done(function(results) { - var out = { - total: results.response.numFound, - hits: results.response.docs - }; - dfd.resolve(out); - }); - return dfd.promise(); - }; - -}(jQuery, this.recline.Backend.Solr)); diff --git a/src/model.js b/src/model.js index c279b655..2f405762 100644 --- a/src/model.js +++ b/src/model.js @@ -4,7 +4,7 @@ this.recline.Model = this.recline.Model || {}; (function(my) { -// private - use either jQuery or Underscore Deferred depending on what is available +// use either jQuery or Underscore Deferred depending on what is available var Deferred = _.isUndefined(this.jQuery) ? _.Deferred : jQuery.Deferred; // ## Dataset diff --git a/src/view.flot.js b/src/view.flot.js index d810b723..ea87f40f 100644 --- a/src/view.flot.js +++ b/src/view.flot.js @@ -23,7 +23,7 @@ this.recline.View = this.recline.View || {}; // generate the element itself (you can then append view.el to the DOM. my.Flot = Backbone.View.extend({ template: ' \ -
\ +
\
\
\

Hey there!

\ @@ -90,9 +90,6 @@ my.Flot = Backbone.View.extend({ // check we have something to plot if (this.state.get('group') && this.state.get('series')) { - // faff around with width because flot draws axes *outside* of the element - // width which means graph can get push down as it hits element next to it - this.$graph.width(this.el.width() - 240); var series = this.createSeries(); var options = this.getGraphOptions(this.state.attributes.graphType, series[0].data.length); this.plot = $.plot(this.$graph, series, options); @@ -113,7 +110,7 @@ my.Flot = Backbone.View.extend({ this.previousTooltipPoint.y !== item.seriesIndex) { this.previousTooltipPoint.x = item.dataIndex; this.previousTooltipPoint.y = item.seriesIndex; - $("#recline-graph-tooltip").remove(); + $("#recline-flot-tooltip").remove(); var x = item.datapoint[0].toFixed(2), y = item.datapoint[1].toFixed(2); @@ -135,13 +132,13 @@ my.Flot = Backbone.View.extend({ yLocation = item.pageY - 20; } - $('
' + content + '
').css({ + $('
' + content + '
').css({ top: yLocation, left: xLocation }).appendTo("body").fadeIn(200); } } else { - $("#recline-graph-tooltip").remove(); + $("#recline-flot-tooltip").remove(); this.previousTooltipPoint.x = null; this.previousTooltipPoint.y = null; } diff --git a/src/view.flotr2.js b/src/view.flotr2.js new file mode 100644 index 00000000..9259e046 --- /dev/null +++ b/src/view.flotr2.js @@ -0,0 +1,462 @@ +/*jshint multistr:true */ + +this.recline = this.recline || {}; +this.recline.View = this.recline.View || {}; + +(function($, my) { + +// ## Graph view for a Dataset using Flotr2 graphing library. +// +// Initialization arguments (in a hash in first parameter): +// +// * model: recline.Model.Dataset +// * state: (optional) configuration hash of form: +// +// { +// group: {column name for x-axis}, +// series: [{column name for series A}, {column name series B}, ... ], +// graphType: 'line', +// graphOptions: {custom [Flotr2 options](http://www.humblesoftware.com/flotr2/documentation#configuration)} +// } +// +// NB: should *not* provide an el argument to the view but must let the view +// generate the element itself (you can then append view.el to the DOM. +my.Flotr2 = Backbone.View.extend({ + template: ' \ +
\ +
\ +
\ +

Hey there!

\ +

There\'s no graph here yet because we don\'t know what fields you\'d like to see plotted.

\ +

Please tell us by using the menu on the right and a graph will automatically appear.

\ +
\ +
\ +
\ +', + + initialize: function(options) { + var self = this; + this.graphColors = ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"]; + + this.el = $(this.el); + _.bindAll(this, 'render', 'redraw'); + this.needToRedraw = false; + this.model.bind('change', this.render); + this.model.fields.bind('reset', this.render); + this.model.fields.bind('add', this.render); + this.model.records.bind('add', this.redraw); + this.model.records.bind('reset', this.redraw); + var stateData = _.extend({ + group: null, + // so that at least one series chooser box shows up + series: [], + graphType: 'lines-and-points' + }, + options.state + ); + this.state = new recline.Model.ObjectState(stateData); + this.editor = new my.Flotr2Controls({ + model: this.model, + state: this.state.toJSON() + }); + this.editor.state.bind('change', function() { + self.state.set(self.editor.state.toJSON()); + self.redraw(); + }); + this.elSidebar = this.editor.el; + }, + + render: function() { + var self = this; + var tmplData = this.model.toTemplateJSON(); + var htmls = Mustache.render(this.template, tmplData); + $(this.el).html(htmls); + this.$graph = this.el.find('.panel.graph'); + return this; + }, + + redraw: function() { + // There appear to be issues generating a Flotr2 graph if either: + + // * The relevant div that graph attaches to his hidden at the moment of creating the plot -- Flotr2 will complain with + // + // Uncaught Invalid dimensions for plot, width = 0, height = 0 + // * There is no data for the plot -- either same error or may have issues later with errors like 'non-existent node-value' + var areWeVisible = !jQuery.expr.filters.hidden(this.el[0]); + if ((!areWeVisible || this.model.records.length === 0)) { + this.needToRedraw = true; + return; + } + + // check we have something to plot + if (this.state.get('group') && this.state.get('series')) { + // faff around with width because flot draws axes *outside* of the element width which means graph can get push down as it hits element next to it + this.$graph.width(this.el.width() - 20); + var series = this.createSeries(); + var options = this.getGraphOptions(this.state.attributes.graphType); + this.plot = Flotr.draw(this.$graph.get(0), series, options); + } + }, + + show: function() { + // because we cannot redraw when hidden we may need to when becoming visible + if (this.needToRedraw) { + this.redraw(); + } + }, + + // ### getGraphOptions + // + // Get options for Flotr2 Graph + // + // needs to be function as can depend on state + // + // @param typeId graphType id (lines, lines-and-points etc) + getGraphOptions: function(typeId) { + var self = this; + + var tickFormatter = function (x) { + return getFormattedX(x); + }; + + // infoboxes on mouse hover on points/bars etc + var trackFormatter = function (obj) { + var x = obj.x; + var y = obj.y; + // it's horizontal so we have to flip + if (self.state.attributes.graphType === 'bars') { + var _tmp = x; + x = y; + y = _tmp; + } + + x = getFormattedX(x); + + var content = _.template('<%= group %> = <%= x %>, <%= series %> = <%= y %>', { + group: self.state.attributes.group, + x: x, + series: obj.series.label, + y: y + }); + + return content; + }; + + var getFormattedX = function (x) { + var xfield = self.model.fields.get(self.state.attributes.group); + + // time series + var xtype = xfield.get('type'); + var isDateTime = (xtype === 'date' || xtype === 'date-time' || xtype === 'time'); + + 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 = {}; + xaxis.tickFormatter = tickFormatter; + + var yaxis = {}; + yaxis.autoscale = true; + yaxis.autoscaleMargin = 0.02; + + var mouse = {}; + mouse.track = true; + mouse.relative = true; + mouse.trackFormatter = trackFormatter; + + var legend = {}; + legend.position = 'ne'; + + // mouse.lineColor is set in createSeries + var optionsPerGraphType = { + lines: { + legend: legend, + colors: this.graphColors, + lines: { show: true }, + xaxis: xaxis, + yaxis: yaxis, + mouse: mouse + }, + points: { + legend: legend, + colors: this.graphColors, + points: { show: true, hitRadius: 5 }, + xaxis: xaxis, + yaxis: yaxis, + mouse: mouse, + grid: { hoverable: true, clickable: true } + }, + 'lines-and-points': { + legend: legend, + colors: this.graphColors, + points: { show: true, hitRadius: 5 }, + lines: { show: true }, + xaxis: xaxis, + yaxis: yaxis, + mouse: mouse, + grid: { hoverable: true, clickable: true } + }, + bars: { + legend: legend, + colors: this.graphColors, + lines: { show: false }, + xaxis: yaxis, + yaxis: xaxis, + mouse: { + track: true, + relative: true, + trackFormatter: trackFormatter, + fillColor: '#FFFFFF', + fillOpacity: 0.3, + position: 'e' + }, + bars: { + show: true, + horizontal: true, + shadowSize: 0, + barWidth: 0.8 + } + }, + columns: { + legend: legend, + colors: this.graphColors, + lines: { show: false }, + xaxis: xaxis, + yaxis: yaxis, + mouse: { + track: true, + relative: true, + trackFormatter: trackFormatter, + fillColor: '#FFFFFF', + fillOpacity: 0.3, + position: 'n' + }, + bars: { + show: true, + horizontal: false, + shadowSize: 0, + barWidth: 0.8 + } + }, + grid: { hoverable: true, clickable: true } + }; + + if (self.state.get('graphOptions')){ + return _.extend(optionsPerGraphType[typeId], + self.state.get('graphOptions') + ) + }else{ + return optionsPerGraphType[typeId]; + } + }, + + createSeries: function() { + var self = this; + var series = []; + _.each(this.state.attributes.series, function(field) { + var points = []; + _.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 xtype = xfield.get('type'); + var isDateTime = (xtype === 'date' || xtype === 'date-time' || xtype === 'time'); + + if (isDateTime) { + // 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]); + } else { + points.push([x, y]); + } + }); + series.push({data: points, label: field, mouse:{lineColor: self.graphColors[series.length]}}); + }); + return series; + } +}); + +my.Flotr2Controls = Backbone.View.extend({ + className: "editor", + template: ' \ +
\ +
\ +
\ + \ +
\ + \ +
\ + \ +
\ + \ +
\ +
\ +
\ +
\ +
\ + \ +
\ + \ +
\ +
\ +', + templateSeriesEditor: ' \ +
\ + \ +
\ + \ +
\ +
\ + ', + events: { + 'change form select': 'onEditorSubmit', + 'click .editor-add': '_onAddSeries', + 'click .action-remove-series': 'removeSeries' + }, + + initialize: function(options) { + var self = this; + this.el = $(this.el); + _.bindAll(this, 'render'); + this.model.fields.bind('reset', this.render); + this.model.fields.bind('add', this.render); + this.state = new recline.Model.ObjectState(options.state); + this.render(); + }, + + render: function() { + var self = this; + var tmplData = this.model.toTemplateJSON(); + var htmls = Mustache.render(this.template, tmplData); + this.el.html(htmls); + + // set up editor from state + if (this.state.get('graphType')) { + this._selectOption('.editor-type', this.state.get('graphType')); + } + if (this.state.get('group')) { + this._selectOption('.editor-group', this.state.get('group')); + } + // ensure at least one series box shows up + var tmpSeries = [""]; + if (this.state.get('series').length > 0) { + tmpSeries = this.state.get('series'); + } + _.each(tmpSeries, function(series, idx) { + self.addSeries(idx); + self._selectOption('.editor-series.js-series-' + idx, series); + }); + return this; + }, + + // Private: Helper function to select an option from a select list + // + _selectOption: function(id,value){ + var options = this.el.find(id + ' select > option'); + if (options) { + options.each(function(opt){ + if (this.value == value) { + $(this).attr('selected','selected'); + return false; + } + }); + } + }, + + onEditorSubmit: function(e) { + var select = this.el.find('.editor-group select'); + var $editor = this; + var $series = this.el.find('.editor-series select'); + var series = $series.map(function () { + return $(this).val(); + }); + var updatedState = { + series: $.makeArray(series), + group: this.el.find('.editor-group select').val(), + graphType: this.el.find('.editor-type select').val() + }; + this.state.set(updatedState); + }, + + // Public: Adds a new empty series select box to the editor. + // + // @param [int] idx index of this series in the list of series + // + // Returns itself. + addSeries: function (idx) { + var data = _.extend({ + seriesIndex: idx, + seriesName: String.fromCharCode(idx + 64 + 1) + }, this.model.toTemplateJSON()); + + var htmls = Mustache.render(this.templateSeriesEditor, data); + this.el.find('.editor-series-group').append(htmls); + return this; + }, + + _onAddSeries: function(e) { + e.preventDefault(); + this.addSeries(this.state.get('series').length); + }, + + // Public: Removes a series list item from the editor. + // + // Also updates the labels of the remaining series elements. + removeSeries: function (e) { + e.preventDefault(); + var $el = $(e.target); + $el.parent().parent().remove(); + this.onEditorSubmit(); + } +}); + +})(jQuery, recline.View); + diff --git a/src/view.graph.js b/src/view.graph.js index dc640ca0..0517b2f8 100644 --- a/src/view.graph.js +++ b/src/view.graph.js @@ -1,462 +1,5 @@ -/*jshint multistr:true */ - this.recline = this.recline || {}; this.recline.View = this.recline.View || {}; - -(function($, my) { - -// ## Graph view for a Dataset using Flot graphing library. -// -// Initialization arguments (in a hash in first parameter): -// -// * model: recline.Model.Dataset -// * state: (optional) configuration hash of form: -// -// { -// group: {column name for x-axis}, -// series: [{column name for series A}, {column name series B}, ... ], -// graphType: 'line', -// graphOptions: {custom [Flotr2 options](http://www.humblesoftware.com/flotr2/documentation#configuration)} -// } -// -// NB: should *not* provide an el argument to the view but must let the view -// generate the element itself (you can then append view.el to the DOM. -my.Graph = Backbone.View.extend({ - template: ' \ -
\ -
\ -
\ -

Hey there!

\ -

There\'s no graph here yet because we don\'t know what fields you\'d like to see plotted.

\ -

Please tell us by using the menu on the right and a graph will automatically appear.

\ -
\ -
\ -
\ -', - - initialize: function(options) { - var self = this; - this.graphColors = ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"]; - - this.el = $(this.el); - _.bindAll(this, 'render', 'redraw'); - this.needToRedraw = false; - this.model.bind('change', this.render); - this.model.fields.bind('reset', this.render); - this.model.fields.bind('add', this.render); - this.model.records.bind('add', this.redraw); - this.model.records.bind('reset', this.redraw); - var stateData = _.extend({ - group: null, - // so that at least one series chooser box shows up - series: [], - graphType: 'lines-and-points' - }, - options.state - ); - this.state = new recline.Model.ObjectState(stateData); - this.editor = new my.GraphControls({ - model: this.model, - state: this.state.toJSON() - }); - this.editor.state.bind('change', function() { - self.state.set(self.editor.state.toJSON()); - self.redraw(); - }); - this.elSidebar = this.editor.el; - }, - - render: function() { - var self = this; - var tmplData = this.model.toTemplateJSON(); - var htmls = Mustache.render(this.template, tmplData); - $(this.el).html(htmls); - this.$graph = this.el.find('.panel.graph'); - return this; - }, - - redraw: function() { - // There appear to be issues generating a Flot graph if either: - - // * The relevant div that graph attaches to his hidden at the moment of creating the plot -- Flot will complain with - // - // Uncaught Invalid dimensions for plot, width = 0, height = 0 - // * There is no data for the plot -- either same error or may have issues later with errors like 'non-existent node-value' - var areWeVisible = !jQuery.expr.filters.hidden(this.el[0]); - if ((!areWeVisible || this.model.records.length === 0)) { - this.needToRedraw = true; - return; - } - - // check we have something to plot - if (this.state.get('group') && this.state.get('series')) { - // faff around with width because flot draws axes *outside* of the element width which means graph can get push down as it hits element next to it - this.$graph.width(this.el.width() - 20); - var series = this.createSeries(); - var options = this.getGraphOptions(this.state.attributes.graphType); - this.plot = Flotr.draw(this.$graph.get(0), series, options); - } - }, - - show: function() { - // because we cannot redraw when hidden we may need to when becoming visible - if (this.needToRedraw) { - this.redraw(); - } - }, - - // ### getGraphOptions - // - // Get options for Flot Graph - // - // needs to be function as can depend on state - // - // @param typeId graphType id (lines, lines-and-points etc) - getGraphOptions: function(typeId) { - var self = this; - - var tickFormatter = function (x) { - return getFormattedX(x); - }; - - // infoboxes on mouse hover on points/bars etc - var trackFormatter = function (obj) { - var x = obj.x; - var y = obj.y; - // it's horizontal so we have to flip - if (self.state.attributes.graphType === 'bars') { - var _tmp = x; - x = y; - y = _tmp; - } - - x = getFormattedX(x); - - var content = _.template('<%= group %> = <%= x %>, <%= series %> = <%= y %>', { - group: self.state.attributes.group, - x: x, - series: obj.series.label, - y: y - }); - - return content; - }; - - var getFormattedX = function (x) { - var xfield = self.model.fields.get(self.state.attributes.group); - - // time series - var xtype = xfield.get('type'); - var isDateTime = (xtype === 'date' || xtype === 'date-time' || xtype === 'time'); - - 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 = {}; - xaxis.tickFormatter = tickFormatter; - - var yaxis = {}; - yaxis.autoscale = true; - yaxis.autoscaleMargin = 0.02; - - var mouse = {}; - mouse.track = true; - mouse.relative = true; - mouse.trackFormatter = trackFormatter; - - var legend = {}; - legend.position = 'ne'; - - // mouse.lineColor is set in createSeries - var optionsPerGraphType = { - lines: { - legend: legend, - colors: this.graphColors, - lines: { show: true }, - xaxis: xaxis, - yaxis: yaxis, - mouse: mouse - }, - points: { - legend: legend, - colors: this.graphColors, - points: { show: true, hitRadius: 5 }, - xaxis: xaxis, - yaxis: yaxis, - mouse: mouse, - grid: { hoverable: true, clickable: true } - }, - 'lines-and-points': { - legend: legend, - colors: this.graphColors, - points: { show: true, hitRadius: 5 }, - lines: { show: true }, - xaxis: xaxis, - yaxis: yaxis, - mouse: mouse, - grid: { hoverable: true, clickable: true } - }, - bars: { - legend: legend, - colors: this.graphColors, - lines: { show: false }, - xaxis: yaxis, - yaxis: xaxis, - mouse: { - track: true, - relative: true, - trackFormatter: trackFormatter, - fillColor: '#FFFFFF', - fillOpacity: 0.3, - position: 'e' - }, - bars: { - show: true, - horizontal: true, - shadowSize: 0, - barWidth: 0.8 - } - }, - columns: { - legend: legend, - colors: this.graphColors, - lines: { show: false }, - xaxis: xaxis, - yaxis: yaxis, - mouse: { - track: true, - relative: true, - trackFormatter: trackFormatter, - fillColor: '#FFFFFF', - fillOpacity: 0.3, - position: 'n' - }, - bars: { - show: true, - horizontal: false, - shadowSize: 0, - barWidth: 0.8 - } - }, - grid: { hoverable: true, clickable: true } - }; - - if (self.state.get('graphOptions')){ - return _.extend(optionsPerGraphType[typeId], - self.state.get('graphOptions') - ) - }else{ - return optionsPerGraphType[typeId]; - } - }, - - createSeries: function() { - var self = this; - var series = []; - _.each(this.state.attributes.series, function(field) { - var points = []; - _.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 xtype = xfield.get('type'); - var isDateTime = (xtype === 'date' || xtype === 'date-time' || xtype === 'time'); - - if (isDateTime) { - // 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]); - } else { - points.push([x, y]); - } - }); - series.push({data: points, label: field, mouse:{lineColor: self.graphColors[series.length]}}); - }); - return series; - } -}); - -my.GraphControls = Backbone.View.extend({ - className: "editor", - template: ' \ -
\ -
\ -
\ - \ -
\ - \ -
\ - \ -
\ - \ -
\ -
\ -
\ -
\ -
\ - \ -
\ - \ -
\ -
\ -', - templateSeriesEditor: ' \ -
\ - \ -
\ - \ -
\ -
\ - ', - events: { - 'change form select': 'onEditorSubmit', - 'click .editor-add': '_onAddSeries', - 'click .action-remove-series': 'removeSeries' - }, - - initialize: function(options) { - var self = this; - this.el = $(this.el); - _.bindAll(this, 'render'); - this.model.fields.bind('reset', this.render); - this.model.fields.bind('add', this.render); - this.state = new recline.Model.ObjectState(options.state); - this.render(); - }, - - render: function() { - var self = this; - var tmplData = this.model.toTemplateJSON(); - var htmls = Mustache.render(this.template, tmplData); - this.el.html(htmls); - - // set up editor from state - if (this.state.get('graphType')) { - this._selectOption('.editor-type', this.state.get('graphType')); - } - if (this.state.get('group')) { - this._selectOption('.editor-group', this.state.get('group')); - } - // ensure at least one series box shows up - var tmpSeries = [""]; - if (this.state.get('series').length > 0) { - tmpSeries = this.state.get('series'); - } - _.each(tmpSeries, function(series, idx) { - self.addSeries(idx); - self._selectOption('.editor-series.js-series-' + idx, series); - }); - return this; - }, - - // Private: Helper function to select an option from a select list - // - _selectOption: function(id,value){ - var options = this.el.find(id + ' select > option'); - if (options) { - options.each(function(opt){ - if (this.value == value) { - $(this).attr('selected','selected'); - return false; - } - }); - } - }, - - onEditorSubmit: function(e) { - var select = this.el.find('.editor-group select'); - var $editor = this; - var $series = this.el.find('.editor-series select'); - var series = $series.map(function () { - return $(this).val(); - }); - var updatedState = { - series: $.makeArray(series), - group: this.el.find('.editor-group select').val(), - graphType: this.el.find('.editor-type select').val() - }; - this.state.set(updatedState); - }, - - // Public: Adds a new empty series select box to the editor. - // - // @param [int] idx index of this series in the list of series - // - // Returns itself. - addSeries: function (idx) { - var data = _.extend({ - seriesIndex: idx, - seriesName: String.fromCharCode(idx + 64 + 1) - }, this.model.toTemplateJSON()); - - var htmls = Mustache.render(this.templateSeriesEditor, data); - this.el.find('.editor-series-group').append(htmls); - return this; - }, - - _onAddSeries: function(e) { - e.preventDefault(); - this.addSeries(this.state.get('series').length); - }, - - // Public: Removes a series list item from the editor. - // - // Also updates the labels of the remaining series elements. - removeSeries: function (e) { - e.preventDefault(); - var $el = $(e.target); - $el.parent().parent().remove(); - this.onEditorSubmit(); - } -}); - -})(jQuery, recline.View); +this.recline.View.Graph = this.recline.View.Flot; +this.recline.View.GraphControls = this.recline.View.FlotControls; diff --git a/test/backend.solr.test.js b/test/backend.solr.test.js deleted file mode 100644 index 2b7ec65a..00000000 --- a/test/backend.solr.test.js +++ /dev/null @@ -1,85 +0,0 @@ -(function ($) { -module("Backend SOLR"); - -test("fetch", function() { - var dataset = new recline.Model.Dataset({ - url: 'http://openspending.org/api/search', - backend: 'solr' - }); - // stop(); - - var stub = sinon.stub($, 'ajax', function(options) { - return { - done: function(callback) { - callback(sample_data); - return this; - }, - fail: function() { - } - }; - }); - - dataset.fetch().done(function(dataset) { - var exp = [ - "_id", - "amount", - "category.label_facet", - "dataset", - "from.label_facet", - "id", - "subcategory.label_facet", - "time.label_facet", - "to.label_facet" - ]; - deepEqual( - exp, - _.pluck(dataset.fields.toJSON(), 'id') - ); - // check we've mapped types correctly - equal(dataset.fields.get('amount').get('type'), 'string'); - - // fetch does a query so we can check for records - equal(dataset.recordCount, 10342132); - equal(dataset.records.length, 2); - equal(dataset.records.at(0).get('id'), '3e3e25d7737634127b76d5ee4a7df280987013c7'); - // start(); - }); - $.ajax.restore(); -}); - -var sample_data = { - "response": { - "docs": [ - { - "_id": "south-african-national-gov-budget-2012-13::3e3e25d7737634127b76d5ee4a7df280987013c7", - "amount": 30905738200000.0, - "category.label_facet": "General public services", - "dataset": "south-african-national-gov-budget-2012-13", - "from.label_facet": "National Treasury", - "id": "3e3e25d7737634127b76d5ee4a7df280987013c7", - "subcategory.label_facet": "Transfers of a general character between different levels of government", - "time.label_facet": "01. April 2012", - "to.label_facet": "Provincial Equitable Share" - }, - { - "_id": "south-african-national-gov-budget-2012-13::738849e28e6b3c45e5b0001e142b51479b3a3e41", - "amount": 8938807300000.0, - "category.label_facet": "General public services", - "dataset": "south-african-national-gov-budget-2012-13", - "from.label_facet": "National Treasury", - "id": "738849e28e6b3c45e5b0001e142b51479b3a3e41", - "subcategory.label_facet": "Public debt transactions", - "time.label_facet": "01. April 2012", - "to.label_facet": "State Debt Costs" - } - ], - "numFound": 10342132, - "start": 0 - }, - "responseHeader": { - "QTime": 578, - "status": 0 - } -}; - -})(this.jQuery); diff --git a/test/index.html b/test/index.html index 7a102b4f..cfa1151e 100644 --- a/test/index.html +++ b/test/index.html @@ -10,7 +10,6 @@ - @@ -39,7 +38,6 @@ - @@ -48,14 +46,14 @@ - - + + @@ -66,7 +64,7 @@ - + diff --git a/test/view.graph.test.js b/test/view.flotr2.test.js similarity index 87% rename from test/view.graph.test.js rename to test/view.flotr2.test.js index 25a04d6c..89edfada 100644 --- a/test/view.graph.test.js +++ b/test/view.flotr2.test.js @@ -1,8 +1,8 @@ -module("View - Graph"); +module("View - Flotr2"); test('basics', function () { var dataset = Fixture.getDataset(); - var view = new recline.View.Graph({ + var view = new recline.View.Flotr2({ model: dataset }); $('.fixtures').append(view.el); @@ -14,7 +14,7 @@ test('basics', function () { test('initialize', function () { var dataset = Fixture.getDataset(); - var view = new recline.View.Graph({ + var view = new recline.View.Flotr2({ model: dataset, state: { 'graphType': 'lines', @@ -40,7 +40,7 @@ test('initialize', function () { test('dates in graph view', function () { expect(0); var dataset = Fixture.getDataset(); - var view = new recline.View.Graph({ + var view = new recline.View.Flotr2({ model: dataset, state: { 'graphType': 'lines', @@ -53,9 +53,9 @@ test('dates in graph view', function () { view.remove(); }); -test('GraphControls basics', function () { +test('Flotr2Controls basics', function () { var dataset = Fixture.getDataset(); - var view = new recline.View.GraphControls({ + var view = new recline.View.Flotr2Controls({ model: dataset, state: { graphType: 'bars', @@ -72,7 +72,7 @@ test('GraphControls basics', function () { test('Overriding graph options', function () { var dataset = Fixture.getDataset(); var randomWidth = Math.random(); - var view = new recline.View.Graph({ + var view = new recline.View.Flotr2({ model: dataset, state: { 'graphType': 'bars', @@ -83,4 +83,4 @@ test('Overriding graph options', function () { }); equal(view.getGraphOptions('bars').bars.barWidth, randomWidth) view.remove(); -}); \ No newline at end of file +});