Merge branch 'master' into gh-pages

This commit is contained in:
Rufus Pollock
2012-10-23 09:37:19 +01:00
14 changed files with 328 additions and 76 deletions

View File

@@ -0,0 +1,10 @@
<ul>
<li><a href="{{page.root}}/docs/src/backend.gdocs.html">gdocs: Google Docs (Spreadsheet)</a></li>
<li><a href="{{page.root}}/docs/src/backend.csv.html">csv: CSV files</a></li>
<li><a href="{{page.root}}/docs/src/backend.solr.html">solr: SOLR</a> (partial)</li>
<li><a href="{{page.root}}/docs/src/backend.elasticsearch.html">elasticsearch: ElasticSearch</a></li>
<li><a href="{{page.root}}/docs/src/backend.dataproxy.html">dataproxy: DataProxy (CSV and XLS on the Web)</a></li>
<li><a href="{{page.root}}/docs/src/backend.ckan.html">ckan: CKAN</a> &ndash; support for <a href="http://docs.ckan.org/en/latest/datastore.html">CKAN datastore</a></li>
<li><a href="{{page.root}}/docs/src/backend.couchdb.html">couchdb: CouchDB</a></li>
<li><a href="{{page.root}}/docs/src/backend.memory.html">memory: Memory (local data)</a></li>
</ul>

View File

@@ -3,8 +3,8 @@
var dataset = new recline.Model.Dataset({ var dataset = new recline.Model.Dataset({
url: '{{page.root}}/demos/data/sample.csv', url: '{{page.root}}/demos/data/sample.csv',
backend: 'csv', backend: 'csv',
// separator: ',', // delimiter: ',',
// delimiter: '"', // quotechar: '"',
// encoding: 'utf8' // encoding: 'utf8'
}); });
@@ -13,7 +13,7 @@ var dataset = new recline.Model.Dataset({
dataset.fetch(); dataset.fetch();
// show the data for illustrations sake // show the data for illustrations sake
var grid = new recline.View.Grid({ var grid = new recline.View.SlickGrid({
model: dataset model: dataset
}); });
$('#my-online-csv').append(grid.el); $('#my-online-csv').append(grid.el);

View File

@@ -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();

View File

@@ -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 = '<h3>' + record.get('country') + ' &ndash; ' + record.get('date') + '</h3>';
html += 'id: ' + record.get('id');
return html;
}
view.render();

View File

@@ -98,21 +98,18 @@ section {
background: -ms-linear-gradient(top, #2d2d2d 0%,#040404 100%); /* IE10+ */ background: -ms-linear-gradient(top, #2d2d2d 0%,#040404 100%); /* IE10+ */
background: linear-gradient(top, #2d2d2d 0%,#040404 100%); /* W3C */ background: linear-gradient(top, #2d2d2d 0%,#040404 100%); /* W3C */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#2d2d2d', endColorstr='#040404',GradientType=0 ); /* IE6-9 */ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#2d2d2d', endColorstr='#040404',GradientType=0 ); /* IE6-9 */
color:#FFF;
padding:0px; padding:0px;
margin-bottom:0px; margin-bottom:0px;
border:none; border:none;
font-family:'PT Sans', Helvetica, Arial, sans-serif;
padding: 60px; padding: 60px;
padding-bottom: 200px; padding-bottom: 200px;
/* hide crocodile top to footer on front page */ /* hide crocodile top to footer on front page */
margin-bottom: -30px; margin-bottom: -30px;
} }
.home-page.page-header a { .home-page.page-header p {
color:#FFF; color:#FFF;
} }
.home-page.page-header a.dotted { .home-page.page-header a {
border-color:#FFF;
} }
.home-page.page-header .container { .home-page.page-header .container {
background-image: url(images/header-screen.png); background-image: url(images/header-screen.png);
@@ -120,7 +117,7 @@ section {
background-position: -3px 0px; background-position: -3px 0px;
} }
.home-page.page-header .inner { .home-page.page-header .inner {
padding:0px 0px 30px 40px; padding:0px 0px 0px 40px;
font-size:16px; font-size:16px;
line-height: 23px; line-height: 23px;
width: 400px; width: 400px;
@@ -130,14 +127,23 @@ section {
margin-top:-14px; margin-top:-14px;
} }
section.grey { .home-page.page-header .links {
background-color:#f5f5f5; margin-top: 30px;
margin-bottom: 0;
} }
section.grey:after { .home-page.page-header .links a {
background-position: center -50px; margin-left: 25px;
} }
.home-page.page-header .links a:first-child {
margin-left: 0px;
}
/* ---------------------------------------------------
Footer
--------------------------------------------------- */
.footer { .footer {
background-color:#040404; background-color:#040404;
color:#CCC; color:#CCC;

View File

@@ -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 Documents as well as for bulk loading via a query API and doing bulk transforms
on the backend. on the backend.
<div class="alert alert-info">Looking for quickstart tutorial rather than reference documentation? See the <a href="tutorial-backends.html">Backends Tutorial</a>.</div>
Backends come in 2 flavours: 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. 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. 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 # Backend API

View File

@@ -54,14 +54,7 @@ root: ../
</div> </div>
<div class="span4"> <div class="span4">
<h4>Backends</h4> <h4>Backends</h4>
<ul> {% include backend-list.html %}
<li><a href="src/backend.elasticsearch.html">elasticsearch: ElasticSearch</a></li>
<li><a href="src/backend.gdocs.html">gdocs: Google Docs (Spreadsheet)</a></li>
<li><a href="src/backend.csv.html">csv: CSV files</a></li>
<li><a href="src/backend.couchdb.html">couchdb: CouchDB</a></li>
<li><a href="src/backend.dataproxy.html">dataproxy: DataProxy (CSV and XLS on the Web)</a></li>
<li><a href="src/backend.memory.html">memory: Memory (local data)</a></li>
</ul>
</div> </div>
<div class="span4"> <div class="span4">
<h4>Dataset Views and Widgets</h4> <h4>Dataset Views and Widgets</h4>

View File

@@ -118,20 +118,43 @@ The type list is as follows (brackets indicate
possible aliases for specific types - these types will be recognized and possible aliases for specific types - these types will be recognized and
normalized to the default type name for that type): normalized to the default type name for that type):
* string (text) - a string * **string (text)**: a string
* number (double, float, numeric) - a number including floating point numbers. * **number (double, float, numeric)**: a number including floating point numbers.
* integer (int) - an integer. * **integer (int)**: an integer.
* date - a date. The preferred format is YYYY-MM-DD. * **date**: a date. The preferred format is YYYY-MM-DD.
* time - a time without a date * **time**: a time without a date
* date-time (datetime, timestamp) a date-time. It is recommended this be in ISO 8601 * **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. format of YYYY-MM- DDThh:mm:ssZ in UTC time.
* boolean (bool) * **boolean (bool)**
* binary - base64 representation of binary data. * **binary**: base64 representation of binary data.
* geo_point * **geo_point**: as per
* geojson <http://www.elasticsearch.org/guide/reference/mapping/geo-point-type.html>.
* array That is a field (in these examples named location) that has one of the
* object (json) following structures:
* any - value of field may be any type
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 <http://geojson.org/>
* **array**: an array
* **object (json)**: an object
* **any**: value of field may be any type
<div class="alert">NB: types are not validated so you can set the type to <div class="alert">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, whatever value you like (it does not have to be in the above list). However,

View File

@@ -1,6 +1,6 @@
--- ---
layout: container layout: container
title: Tutorial - Backends - Loading data from different sources using Backends title: Loading data from different sources using Backends - Tutorial
recline-deps: true recline-deps: true
root: ../ root: ../
--- ---
@@ -53,23 +53,18 @@ var dataset = recline.Model.Dataset({
{% endhighlight %} {% endhighlight %}
<div class="alert alert-info"> <div class="alert alert-info">
<strong>Backend identifiers</strong> <p><strong>Backend identifiers</strong>
How do you know the backend identifier for a given Backend? It's just the name 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. of the 'class' in recline.Backend module (but case-insensitive). E.g.
recline.Backend.ElasticSearch can be identified as 'ElasticSearch' or recline.Backend.ElasticSearch can be identified as 'ElasticSearch' or
'elasticsearch'. 'elasticsearch'.</p>
<p><strong>What Backends are available from Recline?</strong>
{% include backend-list.html %}
</p>
<p><strong>Backend you'd like to see not available?</strong> It's easy to write your own &ndash; see the <a href="backends.html">Backend reference docs</a> for details of the required API.
</p>
</div> </div>
### 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 ## Preparing your app
@@ -82,7 +77,7 @@ much more limited if you are just using a Backend. Specifically:
<script type="text/javascript" src="vendor/underscore/1.1.6/underscore.js"></script> <script type="text/javascript" src="vendor/underscore/1.1.6/underscore.js"></script>
<script type="text/javascript" src="vendor/backbone/0.5.1/backbone.js"></script> <script type="text/javascript" src="vendor/backbone/0.5.1/backbone.js"></script>
<!-- include the backend code you need e.g. here for gdocs --> <!-- include the backend code you need e.g. here for gdocs -->
<script type="text/javascript" src="src/backend/gdocs.js"></script> <script type="text/javascript" src="src/backend.gdocs.js"></script>
<!-- Or you can just include all of recline. --> <!-- Or you can just include all of recline. -->
<script type="text/javascript" src="dist/recline.js"></script> <script type="text/javascript" src="dist/recline.js"></script>
@@ -98,7 +93,7 @@ For Recline to be able to access a Google Spreadsheet it **must** have been
<div class="alert alert-info"> <div class="alert alert-info">
<strong>Want a real world example?</strong> This <a <strong>Want a real world example?</strong> This <a
href="http://okfnlabs.org/opendatacensus">Open Data Census micro-app</a> loads href="http://dashboard.opengovernmentdata.org/census/">Open Data Census micro-app</a> loads
data from Google Docs and then displays it on a specialist interface combining data from Google Docs and then displays it on a specialist interface combining
a bespoke chooser and a Kartograph (svg-only) map. a bespoke chooser and a Kartograph (svg-only) map.
</div> </div>
@@ -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: 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 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) 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; recline.Backend.DataProxy.timeout = 10000;
{% endhighlight %} {% 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).

106
docs/tutorial-maps.markdown Normal file
View File

@@ -0,0 +1,106 @@
---
layout: container
title: Maps - Customizing Maps in Recline - Tutorial
recline-deps: true
root: ../
---
<div class="page-header">
<h1>
Doing More with the Map View
<br />
<small>This tutorial goes beyond the <a href="tutorial-views.html">basic
views tutorial</a> and shows you how to do more with maps</small>
</h1>
</div>
### 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 %}
<script type="text/javascript">
{% include data.js %}
var dataset = new recline.Model.Dataset({
records: data
});
</script>
### General Pointers
Check out the <a href="{{page.root}}/docs/src/view.map.html">Map reference
(source) docs</a>. 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 %}
<div id="map-infobox">&nbsp;</div>
<script type="text/javascript">
{% include tutorial-maps-infobox.js %}
</script>
### 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 %}
<div id="map-customize">&nbsp;</div>
<script type="text/javascript">
{% include tutorial-maps-customize.js %}
</script>
### 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 %}

View File

@@ -10,6 +10,8 @@ root: ../
</h1> </h1>
</div> </div>
<h3>Basics &ndash; Using the Dataset and its Friends</h3>
<hr />
<div id="tutorials" class="tutorials"> <div id="tutorials" class="tutorials">
<div class="row"> <div class="row">
<div class="span4"> <div class="span4">
@@ -30,17 +32,32 @@ root: ../
</div> </div>
</div> </div>
<div id="tutorials" class="tutorials"> <h3>Backends &ndash; Loading and Storing Data from Remote Sources</h3>
<hr />
<div class="tutorials">
<div class="row"> <div class="row">
<div class="span4"> <div class="span4">
<div class="well"> <div class="well">
<h4><a href="tutorial-backends.html">Backends: loading data from Google Docs, Local CSV, DataHub &amp; more ...</a></h4> <h4><a href="tutorial-backends.html">Backends: loading data from Google Docs, Local CSV, DataHub &amp; more ...</a></h4>
</div> </div>
</div> </div>
</div>
</div>
<h3>Views &ndash; Grids, Maps, Graphs and More!</h3>
<hr />
<div class="tutorials">
<div class="row">
<div class="span4"> <div class="span4">
<div class="well"> <div class="well">
<h4><a href="tutorial-views.html">Views Quickstart - Grids, Graphs and Maps</a></h4> <h4><a href="tutorial-views.html">Views Quickstart - Grids, Graphs and Maps</a></h4>
</div> </div>
</div> </div>
<div class="span4">
<div class="well">
<h4><a href="tutorial-maps.html">Doing more with maps</a></h4>
</div>
</div>
</div> </div>
</div> </div>

View File

@@ -11,8 +11,13 @@ title: Home
<a href="docs/"><img src="images/logo.png" width="455" height="190" alt="Recline Data Explorer and Library"></a> <a href="docs/"><img src="images/logo.png" width="455" height="190" alt="Recline Data Explorer and Library"></a>
</h1> </h1>
<div class="inner"> <div class="inner">
A simple but powerful library for building data applications in <p>A simple but powerful library for building data applications in
pure Javascript and HTML. pure Javascript and HTML.</p>
<p class="links">
<a href="docs/" class="btn btn-large">Documentation &raquo;</a>
<a href="docs/tutorials.html" class="btn btn-large">Tutorials &raquo;</a>
<a href="demos/" class="btn btn-large">Demos &raquo;</a>
</p>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -8,9 +8,14 @@ this.recline.View = this.recline.View || {};
// ## Map view for a Dataset using Leaflet mapping library. // ## Map view for a Dataset using Leaflet mapping library.
// //
// This view allows to plot gereferenced records on a map. The location // This view allows to plot gereferenced records on a map. The location
// information can be provided either via a field with // information can be provided in 2 ways:
// [GeoJSON](http://geojson.org) objects or two fields with latitude and //
// longitude coordinates. // 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 // Initialization arguments are as standard for Dataset Views. State object may
// have the following (optional) configuration options: // 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} // geomField: {id of field containing geometry in the dataset}
// lonField: {id of field containing longitude in the dataset} // lonField: {id of field containing longitude in the dataset}
// latField: {id of field containing latitude in the dataset} // latField: {id of field containing latitude in the dataset}
// autoZoom: true,
// // use cluster support
// cluster: false
// } // }
// </pre> // </pre>
// //
@@ -123,6 +131,39 @@ my.Map = Backbone.View.extend({
return html; return html;
}, },
// Options to use for the [Leaflet GeoJSON layer](http://leaflet.cloudmade.com/reference.html#geojson)
// See also <http://leaflet.cloudmade.com/examples/geojson.html>
//
// 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 // END: Customization section
// ---- // ----
@@ -187,6 +228,15 @@ my.Map = Backbone.View.extend({
return; 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.state.get('autoZoom')){
if (this.visible){ if (this.visible){
this._zoomToFeatures(); this._zoomToFeatures();
@@ -194,11 +244,6 @@ my.Map = Backbone.View.extend({
this._zoomPending = true; this._zoomPending = true;
} }
} }
if (this.state.get('cluster')) {
this.map.addLayer(this.markers);
} else {
this.map.addLayer(this.features);
}
} }
}, },
@@ -315,7 +360,7 @@ my.Map = Backbone.View.extend({
} else { } else {
return null; return null;
} }
} else if (value && value.slice) { } else if (value && _.isArray(value)) {
// [ lon, lat ] // [ lon, lat ]
return { return {
"type": "Point", "type": "Point",
@@ -404,14 +449,11 @@ my.Map = Backbone.View.extend({
this.markers = new L.MarkerClusterGroup(this._clusterOptions); this.markers = new L.MarkerClusterGroup(this._clusterOptions);
this.features = new L.GeoJSON(null,{ // rebind this (as needed in e.g. default case above)
pointToLayer: function (feature, latlng) { this.geoJsonLayerOptions.pointToLayer = _.bind(
var marker = new L.marker(latlng); this.geoJsonLayerOptions.pointToLayer,
marker.bindPopup(feature.properties.popupContent); this);
self.markers.addLayer(marker); this.features = new L.GeoJSON(null, this.geoJsonLayerOptions);
return marker;
}
});
this.map.setView([0, 0], 2); this.map.setView([0, 0], 2);

View File

@@ -207,6 +207,26 @@ test('Popup - Custom', function () {
view.remove(); 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 () { test('MapMenu', function () {
var dataset = Fixture.getDataset(); var dataset = Fixture.getDataset();
var controls = new recline.View.MapMenu({ var controls = new recline.View.MapMenu({