From c9208f1a082b356c63a72923ec8591d5b3e593a9 Mon Sep 17 00:00:00 2001 From: Rufus Pollock Date: Mon, 22 Oct 2012 16:04:33 +0100 Subject: [PATCH 1/7] [content][s]: front page button links to main docs. --- css-site/style.css | 26 ++++++++++++++++---------- index.html | 9 +++++++-- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/css-site/style.css b/css-site/style.css index 0739c949..b1c5b023 100644 --- a/css-site/style.css +++ b/css-site/style.css @@ -98,21 +98,18 @@ section { background: -ms-linear-gradient(top, #2d2d2d 0%,#040404 100%); /* IE10+ */ background: linear-gradient(top, #2d2d2d 0%,#040404 100%); /* W3C */ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#2d2d2d', endColorstr='#040404',GradientType=0 ); /* IE6-9 */ - color:#FFF; padding:0px; margin-bottom:0px; border:none; - font-family:'PT Sans', Helvetica, Arial, sans-serif; padding: 60px; padding-bottom: 200px; /* hide crocodile top to footer on front page */ margin-bottom: -30px; } -.home-page.page-header a { +.home-page.page-header p { color:#FFF; } -.home-page.page-header a.dotted { - border-color:#FFF; +.home-page.page-header a { } .home-page.page-header .container { background-image: url(images/header-screen.png); @@ -120,7 +117,7 @@ section { background-position: -3px 0px; } .home-page.page-header .inner { - padding:0px 0px 30px 40px; + padding:0px 0px 0px 40px; font-size:16px; line-height: 23px; width: 400px; @@ -130,14 +127,23 @@ section { margin-top:-14px; } -section.grey { - background-color:#f5f5f5; +.home-page.page-header .links { + margin-top: 30px; + margin-bottom: 0; } -section.grey:after { - background-position: center -50px; +.home-page.page-header .links a { + margin-left: 25px; } +.home-page.page-header .links a:first-child { + margin-left: 0px; +} + +/* --------------------------------------------------- + Footer +--------------------------------------------------- */ + .footer { background-color:#040404; color:#CCC; diff --git a/index.html b/index.html index ed709413..63db771d 100644 --- a/index.html +++ b/index.html @@ -11,8 +11,13 @@ title: Home Recline Data Explorer and Library
- A simple but powerful library for building data applications in - pure Javascript and HTML. +

A simple but powerful library for building data applications in + pure Javascript and HTML.

+
From 68d2f66f66ca09d5e3ba99958b7f4bb5e378dc3c Mon Sep 17 00:00:00 2001 From: Rufus Pollock Date: Mon, 22 Oct 2012 16:30:15 +0100 Subject: [PATCH 2/7] [docs/backend][s]: tidy up and factor out backend list to include file. --- _includes/backend-list.html | 8 ++++++ _includes/example-backends-online-csv.js | 6 ++--- docs/backends.markdown | 10 +++++++ docs/index.html | 9 +------ docs/tutorial-backends.markdown | 33 ++++++++---------------- 5 files changed, 33 insertions(+), 33 deletions(-) create mode 100644 _includes/backend-list.html diff --git a/_includes/backend-list.html b/_includes/backend-list.html new file mode 100644 index 00000000..8e5eaddb --- /dev/null +++ b/_includes/backend-list.html @@ -0,0 +1,8 @@ + diff --git a/_includes/example-backends-online-csv.js b/_includes/example-backends-online-csv.js index 60a57d93..284998c9 100644 --- a/_includes/example-backends-online-csv.js +++ b/_includes/example-backends-online-csv.js @@ -3,8 +3,8 @@ var dataset = new recline.Model.Dataset({ url: '{{page.root}}/demos/data/sample.csv', backend: 'csv', - // separator: ',', - // delimiter: '"', + // delimiter: ',', + // quotechar: '"', // encoding: 'utf8' }); @@ -13,7 +13,7 @@ var dataset = new recline.Model.Dataset({ dataset.fetch(); // show the data for illustrations sake -var grid = new recline.View.Grid({ +var grid = new recline.View.SlickGrid({ model: dataset }); $('#my-online-csv').append(grid.el); diff --git a/docs/backends.markdown b/docs/backends.markdown index 72e22069..527f9014 100644 --- a/docs/backends.markdown +++ b/docs/backends.markdown @@ -16,11 +16,21 @@ source. They provide methods for loading and saving Datasets and individuals Documents as well as for bulk loading via a query API and doing bulk transforms on the backend. +
Looking for quickstart tutorial rather than reference documentation? See the Backends Tutorial.
+ Backends come in 2 flavours: 1. Loader backends - only implement fetch method. The data is then cached in a Memory.Store on the Dataset and interacted with there. This is best for sources which just allow you to load data or where you want to load the data once and work with it locally. 2. Store backends - these support fetch, query and, if write-enabled, save. These are suitable where the backend contains a lot of data (infeasible to load locally - for examples a million rows) or where the backend has capabilities you want to take advantage of. +# List of Backends Shipped with Recline + +{% include backend-list.html %} + +NB: examples of the 2 types of backends are provided by the Google docs backend (a "Loader" backend) and the ElasticSearch backend (a Store backend). + +It's easy to write your own backend - you just need to implement the API as described below. + # Backend API diff --git a/docs/index.html b/docs/index.html index 37f90f55..a76db5e8 100644 --- a/docs/index.html +++ b/docs/index.html @@ -54,14 +54,7 @@ root: ../

Dataset Views and Widgets

diff --git a/docs/tutorial-backends.markdown b/docs/tutorial-backends.markdown index fad3b026..2f8c3594 100644 --- a/docs/tutorial-backends.markdown +++ b/docs/tutorial-backends.markdown @@ -1,6 +1,6 @@ --- layout: container -title: Tutorial - Backends - Loading data from different sources using Backends +title: Loading data from different sources using Backends - Tutorial recline-deps: true root: ../ --- @@ -53,23 +53,18 @@ var dataset = recline.Model.Dataset({ {% endhighlight %}
-Backend identifiers +

Backend identifiers How do you know the backend identifier for a given Backend? It's just the name of the 'class' in recline.Backend module (but case-insensitive). E.g. recline.Backend.ElasticSearch can be identified as 'ElasticSearch' or -'elasticsearch'. +'elasticsearch'.

+

What Backends are available from Recline? +{% include backend-list.html %} +

+

Backend you'd like to see not available? It's easy to write your own – see the Backend reference docs for details of the required API. +

-### Included Backends - -* [gdocs: Google Docs (Spreadsheet)](src/backend.gdocs.html) -* [csv: CSV files](src/backend.csv.html) -* [elasticsearch: ElasticSearch](src/backend.elasticsearch.html) - this also covers the DataHub as it has an ElasticSearch compatible API -* [dataproxy: DataProxy (CSV and XLS on the Web)](src/backend.dataproxy.html) -* [couchdb: CouchDB](src/backend.couchdb.html) - -Backend not on this list that you would like to see? It's very easy to write a -new backend -- see below for more details. ## Preparing your app @@ -82,7 +77,7 @@ much more limited if you are just using a Backend. Specifically: - + @@ -98,7 +93,7 @@ For Recline to be able to access a Google Spreadsheet it **must** have been
Want a real world example? This Open Data Census micro-app loads +href="http://dashboard.opengovernmentdata.org/census/">Open Data Census micro-app loads data from Google Docs and then displays it on a specialist interface combining a bespoke chooser and a Kartograph (svg-only) map.
@@ -137,7 +132,7 @@ Recline supports ElasticSearch as a full read/write/query backend. It also means For loading data from CSV files there are 3 cases: -1. CSV is online but on same domain -- we can then load using AJAX (as no problems with same origin policy) +1. CSV is online but on same domain or supporting CORS (S3 and Google Storage support CORS!) -- we can then load using AJAX (as no problems with same origin policy) 2. CSV is on local disk -- if your browser supports HTML5 File API we can load the CSV file off disk 3. CSV is online but not on same domain -- use DataProxy (see below) @@ -211,9 +206,3 @@ You can customize the length of this timeout by setting the following constant: recline.Backend.DataProxy.timeout = 10000; {% endhighlight %} - -## Writing your own backend - -Writing your own backend is easy to do. Details of the required API are in the -[Backend documentation](backends.html). - From 80626485cea44f62109b855b54a410f943c6524f Mon Sep 17 00:00:00 2001 From: Rufus Pollock Date: Mon, 22 Oct 2012 21:39:18 +0100 Subject: [PATCH 3/7] [#223,map][xs]: small improvements to in file docs for Map view. --- src/view.map.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/view.map.js b/src/view.map.js index b34066a1..8199dae2 100644 --- a/src/view.map.js +++ b/src/view.map.js @@ -8,9 +8,14 @@ this.recline.View = this.recline.View || {}; // ## Map view for a Dataset using Leaflet mapping library. // // This view allows to plot gereferenced records on a map. The location -// information can be provided either via a field with -// [GeoJSON](http://geojson.org) objects or two fields with latitude and -// longitude coordinates. +// information can be provided in 2 ways: +// +// 1. Via a single field. This field must be either a geo_point or +// [GeoJSON](http://geojson.org) object +// 2. Via two fields with latitude and longitude coordinates. +// +// Which fields in the data these correspond to can be configured via the state +// (and are guessed if no info is provided). // // Initialization arguments are as standard for Dataset Views. State object may // have the following (optional) configuration options: @@ -21,6 +26,9 @@ this.recline.View = this.recline.View || {}; // geomField: {id of field containing geometry in the dataset} // lonField: {id of field containing longitude in the dataset} // latField: {id of field containing latitude in the dataset} +// autoZoom: true, +// // use cluster support +// cluster: false // } // // From b70b41ec5c6451bb18439460a508382aded478ec Mon Sep 17 00:00:00 2001 From: Rufus Pollock Date: Mon, 22 Oct 2012 21:59:20 +0100 Subject: [PATCH 4/7] [#249,docs][s]: document geo_point type throughly. --- docs/models.markdown | 49 ++++++++++++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/docs/models.markdown b/docs/models.markdown index 593979ba..0b5702b4 100644 --- a/docs/models.markdown +++ b/docs/models.markdown @@ -118,20 +118,43 @@ The type list is as follows (brackets indicate possible aliases for specific types - these types will be recognized and normalized to the default type name for that type): -* string (text) - a string -* number (double, float, numeric) - a number including floating point numbers. -* integer (int) - an integer. -* date - a date. The preferred format is YYYY-MM-DD. -* time - a time without a date -* date-time (datetime, timestamp) a date-time. It is recommended this be in ISO 8601 +* **string (text)**: a string +* **number (double, float, numeric)**: a number including floating point numbers. +* **integer (int)**: an integer. +* **date**: a date. The preferred format is YYYY-MM-DD. +* **time**: a time without a date +* **date-time (datetime, timestamp)**: a date-time. It is recommended this be in ISO 8601 format of YYYY-MM- DDThh:mm:ssZ in UTC time. -* boolean (bool) -* binary - base64 representation of binary data. -* geo_point -* geojson -* array -* object (json) -* any - value of field may be any type +* **boolean (bool)** +* **binary**: base64 representation of binary data. +* **geo_point**: as per + . + That is a field (in these examples named location) that has one of the + following structures: + + location: { + lon: ... + lat: ... + } + + location: [lon,lat] + + location: "lat, lng" + + As bonus there is also support for (beyond the ES style geo_point): + + // geonames style + location: { + lng: ... + lat: ... + } + // found on the web + location: "(lat, lon)" + +* **geojson**: as per +* **array**: an array +* **object (json)**: an object +* **any**: value of field may be any type
NB: types are not validated so you can set the type to whatever value you like (it does not have to be in the above list). However, From 84edf764a08ccb638357f9feac82202a25e7826f Mon Sep 17 00:00:00 2001 From: Rufus Pollock Date: Tue, 23 Oct 2012 00:19:33 +0100 Subject: [PATCH 5/7] [docs/backend][s]: add solr and ckan to backend list. --- _includes/backend-list.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/_includes/backend-list.html b/_includes/backend-list.html index 8e5eaddb..60848916 100644 --- a/_includes/backend-list.html +++ b/_includes/backend-list.html @@ -1,8 +1,10 @@ From df935091fdcb163e8e04301a5225aec084f5a01a Mon Sep 17 00:00:00 2001 From: Rufus Pollock Date: Tue, 23 Oct 2012 00:19:59 +0100 Subject: [PATCH 6/7] [#244,view/map][m]: support for customizing rendering of map features in all ways supported by Leaflet - fixes #244. * This automatically provides support for things like custom markers via pointToLayer etc * Also a significant bugfix for a bug that surfaced when using different marker (like the CircleMarker) - and which took ~30m to track down. Bug was that the call to zoom (or, more specifically, call to getBounds) occurred before features were added to the map and getBounds for some objects (such as circles) requires map to exit (so you can do a projection!) --- src/view.map.js | 62 +++++++++++++++++++++++++++++++++---------- test/view.map.test.js | 20 ++++++++++++++ 2 files changed, 68 insertions(+), 14 deletions(-) diff --git a/src/view.map.js b/src/view.map.js index 8199dae2..d6de52fc 100644 --- a/src/view.map.js +++ b/src/view.map.js @@ -131,6 +131,39 @@ my.Map = Backbone.View.extend({ return html; }, + // Options to use for the [Leaflet GeoJSON layer](http://leaflet.cloudmade.com/reference.html#geojson) + // See also + // + // e.g. + // + // pointToLayer: function(feature, latLng) + // onEachFeature: function(feature, layer) + // + // See defaults for examples + geoJsonLayerOptions: { + // pointToLayer function to use when creating points + // + // Default behaviour shown here is to create a marker using the + // popupContent set on the feature properties (created via infobox function + // during feature generation) + // + // NB: inside pointToLayer `this` will be set to point to this map view + // instance (which allows e.g. this.markers to work in this default case) + pointToLayer: function (feature, latlng) { + var marker = new L.Marker(latlng); + marker.bindPopup(feature.properties.popupContent); + // this is for cluster case + this.markers.addLayer(marker); + return marker; + }, + // onEachFeature default which adds popup in + onEachFeature: function(feature, layer) { + if (feature.properties && feature.properties.popupContent) { + layer.bindPopup(feature.properties.popupContent); + } + } + }, + // END: Customization section // ---- @@ -195,6 +228,15 @@ my.Map = Backbone.View.extend({ return; } + // this must come before zooming! + // if not: errors when using e.g. circle markers like + // "Cannot call method 'project' of undefined" + if (this.state.get('cluster')) { + this.map.addLayer(this.markers); + } else { + this.map.addLayer(this.features); + } + if (this.state.get('autoZoom')){ if (this.visible){ this._zoomToFeatures(); @@ -202,11 +244,6 @@ my.Map = Backbone.View.extend({ this._zoomPending = true; } } - if (this.state.get('cluster')) { - this.map.addLayer(this.markers); - } else { - this.map.addLayer(this.features); - } } }, @@ -323,7 +360,7 @@ my.Map = Backbone.View.extend({ } else { return null; } - } else if (value && value.slice) { + } else if (value && _.isArray(value)) { // [ lon, lat ] return { "type": "Point", @@ -412,14 +449,11 @@ my.Map = Backbone.View.extend({ this.markers = new L.MarkerClusterGroup(this._clusterOptions); - this.features = new L.GeoJSON(null,{ - pointToLayer: function (feature, latlng) { - var marker = new L.marker(latlng); - marker.bindPopup(feature.properties.popupContent); - self.markers.addLayer(marker); - return marker; - } - }); + // rebind this (as needed in e.g. default case above) + this.geoJsonLayerOptions.pointToLayer = _.bind( + this.geoJsonLayerOptions.pointToLayer, + this); + this.features = new L.GeoJSON(null, this.geoJsonLayerOptions); this.map.setView([0, 0], 2); diff --git a/test/view.map.test.js b/test/view.map.test.js index 3d8f038f..e429f646 100644 --- a/test/view.map.test.js +++ b/test/view.map.test.js @@ -207,6 +207,26 @@ test('Popup - Custom', function () { view.remove(); }); +test('geoJsonLayerOptions', function () { + var dataset = GeoJSONFixture.getDataset(); + var view = new recline.View.Map({ + model: dataset + }); + $('.fixtures').append(view.el); + view.geoJsonLayerOptions.point + view.geoJsonLayerOptions.pointToLayer = function(feature, latlng) { + var marker = new L.CircleMarker(latlng, { radius: 8 } ); + marker.bindPopup(feature.properties.popupContent); + return marker; + } + view.render(); + + // TODO: test it somehow? + expect(0); + + view.remove(); +}); + test('MapMenu', function () { var dataset = Fixture.getDataset(); var controls = new recline.View.MapMenu({ From 2684bfed0eb657ee93c32c2169418cfa130bc55f Mon Sep 17 00:00:00 2001 From: Rufus Pollock Date: Tue, 23 Oct 2012 00:27:25 +0100 Subject: [PATCH 7/7] [#223,tutorial/maps][m]: start of a in-depth map tutorial with instructions on customizing infoboxes and markers. * Also add to tutorials.html (and refactor that page a bit) --- _includes/tutorial-maps-customize.js | 17 +++++ _includes/tutorial-maps-infobox.js | 14 ++++ docs/tutorial-maps.markdown | 106 +++++++++++++++++++++++++++ docs/tutorials.html | 19 ++++- 4 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 _includes/tutorial-maps-customize.js create mode 100644 _includes/tutorial-maps-infobox.js create mode 100644 docs/tutorial-maps.markdown diff --git a/_includes/tutorial-maps-customize.js b/_includes/tutorial-maps-customize.js new file mode 100644 index 00000000..3433e3a7 --- /dev/null +++ b/_includes/tutorial-maps-customize.js @@ -0,0 +1,17 @@ +var $el = $('#map-customize'); +var view = new recline.View.Map({ + el: $el, + model: dataset +}); + +view.geoJsonLayerOptions.pointToLayer = function(feature, latlng) { + // Look up Record so we can use it to customize size of marker + // note that 'this' is specially bound for us to parent view + that feature + // stores record cid + var record = this.model.records.getByCid(feature.properties.cid); + var marker = new L.CircleMarker(latlng, { radius: record.get('x') * 3 }); + marker.bindPopup(feature.properties.popupContent); + return marker; +} +view.render(); + diff --git a/_includes/tutorial-maps-infobox.js b/_includes/tutorial-maps-infobox.js new file mode 100644 index 00000000..1baf7729 --- /dev/null +++ b/_includes/tutorial-maps-infobox.js @@ -0,0 +1,14 @@ +// this element will need to exist! +var $el = $('#map-infobox'); +var view = new recline.View.Map({ + el: $el, + model: dataset +}); +// record is the recline.Model.Record object +view.infobox = function(record) { + var html = '

' + record.get('country') + ' – ' + record.get('date') + '

'; + html += 'id: ' + record.get('id'); + return html; +} +view.render(); + diff --git a/docs/tutorial-maps.markdown b/docs/tutorial-maps.markdown new file mode 100644 index 00000000..6d67f62d --- /dev/null +++ b/docs/tutorial-maps.markdown @@ -0,0 +1,106 @@ +--- +layout: container +title: Maps - Customizing Maps in Recline - Tutorial +recline-deps: true +root: ../ +--- + + + +### Preparing your page + +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: + +{% highlight javascript %} +{% include data.js %} +var dataset = new recline.Model.Dataset({ + records: data +}); +{% endhighlight %} + + + +### General Pointers + +Check out the Map reference +(source) docs. In particular this has details of the various state options. + +In addition, remember that Recline's map view is just a relatively lightweight +wrapper around Leaflet. This means that pretty much anything you can do with +Leaflet you can do with Recline's map. Specifically a `recline.View.Map` +instance has the following attributes exposed: + + map: the Leaflet map (L.Map) + features: Leaflet GeoJSON layer (L.GeoJSON) containing all the features + +(Recline converts all records in a dataset that yield geospatial info to a +GeoJSON feature and adds it to the features layer). + +### Customizing the infobox + +The default infobox just shows all of the dataset attributes. Usually you'll +want something a bit nicer. All you need to do is override the infobox +function. For example, in our case let's make a nicer title and only show some +data. + +{% highlight javascript %} +{% include tutorial-maps-infobox.js %} +{% endhighlight %} + +
 
+ + + +### Customizing the marker + +We're going to show how to replace the default marker with a circular marker. +Even more exciting, we'll show how to have the marker size vary with an +attribute of our data. We do the customization by via over-riding the +pointToLayer function: + +{% highlight javascript %} +{% include tutorial-maps-customize.js %} +{% endhighlight %} + +
 
+ + + +### Customing features (which aren't points) + +Leaflet treats points and features differently. To customize features that +aren't point we will need to bind to the feature layers featureparse event. As +the feature layer can get re-rendered you don't do this directly but rather set +the featureparse function on the recline view. For example, for classic popup +behaviour: + +{% highlight javascript %} +view.featureparse = function (e) { + if (e.properties && e.properties.popupContent) { + e.layer.bindPopup(e.properties.popupContent); + } +}; +{% endhighlight %} + diff --git a/docs/tutorials.html b/docs/tutorials.html index 476a6f8a..cdd8f846 100644 --- a/docs/tutorials.html +++ b/docs/tutorials.html @@ -10,6 +10,8 @@ root: ../
+

Basics – Using the Dataset and its Friends

+
@@ -30,17 +32,32 @@ root: ../
-
+

Backends – Loading and Storing Data from Remote Sources

+
+ + +

Views – Grids, Maps, Graphs and More!

+
+ +