diff --git a/README.md b/README.md
index aaaf1e6..8e09707 100644
--- a/README.md
+++ b/README.md
@@ -14,15 +14,15 @@ With Universe, you can be there in just a few lines of code. You've got better t
- Real-time updates to query results as you filter
- Flexible filtering system
- Automatic and invisible management of data indexing and memory
-
-## Coming Very Soon
- Post Aggregation
+
+## Features in the Pipeline
- Query Joins
- Query Macros
- Sub Queries
+- To help contribute, join us at [](https://gitter.im/crossfilter/universe?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
## Demos
-
- [Basic Usage](http://codepen.io/tannerlinsley/pen/oxjyvg?editors=0010) (Codepen)
## [API](#api)
@@ -36,6 +36,8 @@ With Universe, you can be there in just a few lines of code. You've got better t
- [.remove()](#api-remove)
+- [Post Aggregation](#post-aggregation)
+ - [.post()](#post-aggregation-post)
- [Pro Tips](#pro-tips)
## Getting Started
@@ -68,7 +70,6 @@ var myUniverse = universe([
```
### Query your data
-
```javascript
.then(function(myUniverse){
@@ -87,6 +88,17 @@ var myUniverse = universe([
}
},
})
+
+ // Optionally post-aggregate your data
+ // Reduce all results after 5 to a single result using sums
+ myUniverse.squash(5, null, {
+ count: '$sum',
+ quantity: {
+ sum: '$sum'
+ }
+ })
+
+ // See Post-Aggregations for more information
})
```
@@ -177,9 +189,20 @@ As you filter your data on the universe level, every query's result is updated i
.then(function(myUniverse){
return myUniverse.clear()
})
-
```
+
+
+
+
+
+
+
+
+
+
+
+
API #
universe( [data] , {config} ) #
@@ -249,14 +272,18 @@ As you filter your data on the universe level, every query's result is updated i
- Parameters
- `columnKey` - the column property or array index you would like to pre-compile eg.
```javascript
- .column('total')
+ .then(function(universe){
+ return universe.column('total')
+ })
```
- `columnObject` allows you to override the column type, otherwise it is calculated automatically:
```javascript
- .column({
+ .then(function(universe){
+ return universe.column({
key: 'total',
type: 'number'
})
+ })
```
- Returns
- `promise` resolved with
@@ -271,14 +298,16 @@ As you filter your data on the universe level, every query's result is updated i
- Parameters
- `columnKey` - the column property or array of columns you would like to clear eg.
```javascript
- // Single Key
- .clear('total')
- // Complex Key
- .clear({key: ['complex', 'key']})
- // Multiple Single Keys
- .clear(['total', 'quantity'])
- // Multiple Complex Keys
- .clear([{key: ['complex', 'key']}, {key: ['another', 'one']}])
+ .then(function(universe){
+ // Single Key
+ return universe.clear('total')
+ // Complex Key
+ return universe.clear({key: ['complex', 'key']})
+ // Multiple Single Keys
+ return universe.clear(['total', 'quantity'])
+ // Multiple Complex Keys
+ return universe.clear([{key: ['complex', 'key']}, {key: ['another', 'one']}])
+ })
```
- Returns
- `promise` resolved with
@@ -297,47 +326,302 @@ As you filter your data on the universe level, every query's result is updated i
- **universe instance**
+
+
+
+
+
+
+
+
+
+
+
+Post Aggregation #
+
+Post aggregation methods can be run on query results to further modify your data. Just like queries, the results magically and instantly respond to filtering.
+- Each post aggregation is very powerful, but not all post aggregations can be chained together.
+
+### Locking a query
+A majority of the time, you're probably only interested in the end result of a query chain. For this reason, Post Aggregations default to mutating the data of their direct parent (unless the parent is the original query), thereby avoiding unnecessary copying of data.
+On the other hand, if you plan on accessing data at any point in the middle of a query chain, you will need to `lock()` that query's results. This ensure's it won't be overwritten or mutated by any further post aggregation.
+
+*Note:* Running more than 1 post aggregation on a query will automatically lock the parent query.
+
+```javascript
+
+.then(function(universe){
+ return universe.query({
+ groupBy: 'tag'
+ })
+})
+.then(function(query){
+ query.lock()
+ var all = query.data
+ return query.limit(5)
+})
+.then(function(query){
+ var only5 = query.data
+
+ all.length === 10
+ only5.length === 5
+})
+```
+Without locking the above query before using `.limit(5)`, the `all` data array would have been mutated by `.limit(5)`
+
+.sortByKey(descending) #
+
+- Description
+ - Sort results by key (ascending or descending)
+- Parameters
+ - `descending` - Pass true to sortKeys in descending order
+ ```javascript
+ .then(function(query){
+ return query.sortByKey(true)
+ })
+ ```
+- Returns
+ - `promise` resolved with
+ - **query instance**
+
+
+.limit(n, n2) #
+
+- Description
+ - Limit results to those between`n` and `n2`. If `n2` is not passed, will limit to the first `n` records
+- Parameters
+ - `n` - Start index. Defaults to 0 if `null` or `undefined`,
+ - `n2` - End index. Defaults to `query.data.length` if `null`. If `undefined`, will limit to the first `n` records instead.
+ ```javascript
+ .then(function(query){
+ // limits results to the first 5 records
+ return query.limit(5)
+ // limits results to records 5 through 10
+ return query.limit(4, 10)
+ })
+ ```
+- Returns
+ - `promise` resolved with
+ - **query instance**
+
+
+.squash(n, n2, aggregationMap, keyName) #
+
+- Description
+ - Takes records from `n` to `n2` and reduces them to a single record using the aggregationMap
+- Parameters
+ - `n` - Start index. Defaults to `0` if `false`-y
+ - `n2` - End index. Defaults to `query.data.length` if `false`-y
+ - `aggregationMap` - A 1:1 map of property to the aggregation to be used when combining the records
+ - `keyName` (optional) - The key to be used for the new record. Defaults to `Other`
+
+ ```javascript
+ .then(function(universe){
+ universe.query({
+ groupBy: 'type',
+ select: {
+ $sum: 'total',
+ otherColumn: {
+ $avg: 'tip'
+ }
+ })
+ })
+ .then(function(query){
+ // Will squash all records after the 5 record
+ query.squash(5, null, {
+ // Sum the sum column
+ sum: '$sum',
+ othercolumn: {
+ // Average the avg column
+ avg: '$avg'
+ }
+ }, 'Everything after 5')
+ // Give the squashed record a new key
+ })
+ ```
+- Returns
+ - `promise` resolved with
+ - **query instance**
+
+
+.change(n, n2, changeFields) #
+
+- Description
+ - Determines the change from the `n` to `n2` using the keys in `changeFields`
+- Parameters
+ - `n` - Start index. Defaults to `0` if `false`-y
+ - `n2` - End index. Defaults to `query.data.length` if `false`-y
+ - `changeFields` - An object or array, referencing the fields to measure for change
+
+ ```javascript
+ .then(function(universe){
+ universe.query({
+ groupBy: 'type',
+ select: {
+ $sum: 'total',
+ otherColumn: {
+ $avg: 'tip'
+ }
+ }
+ })
+ })
+ .then(function(query){
+ // Measure the change for sum and avg from result 0 to 10
+ query.change(0, 10, {
+ sum: true
+ otherColumn: {
+ avg: true
+ }
+ })
+ })
+ ```
+- Returns
+ - `promise` resolved with
+ - **query instance**
+ - `query.data` is now an object:
+ ```javascript
+ {
+ key: ['nKey', 'n2Key'],
+ value: {
+ sumChange: 7,
+ otherColumn: {
+ avgChange: 4
+ }
+ }
+ }
+ ```
+
+
+.changeMap(changeMapObj) #
+
+- Description
+ - Determines incremental change for each record across the fields defined in `changeMapObj`
+- Parameters
+ - `changeMapObj` - An object or array, referencing the fields to measure for change
+
+ ```javascript
+ .then(function(universe){
+ universe.query({
+ groupBy: 'type',
+ select: {
+ $sum: 'total',
+ otherColumn: {
+ $avg: 'tip'
+ }
+ }
+ })
+ })
+ .then(function(query){
+ // Measure the change for sum and avg from result 0 to 10
+ query.change({
+ sum: true
+ otherColumn: {
+ avg: true
+ }
+ })
+ })
+ ```
+- Returns
+ - `promise` resolved with
+ - **query instance**
+ - `query.data` records are now decorated with incremental change data:
+ ```javascript
+ [...{
+ key: 'tag5'
+ value: {
+ sum: 5
+ sumChange: 7,
+ sumChangeFromStart: 0,
+ sumChangeFromEnd: 30,
+ otherColumn: {
+ avgChange: 4
+ avgChangeFromStart: -4
+ avgChangeFromEnd: -20
+ }
+ }
+ }...]
+ ```
+
+
+.post(callback) #
+
+- Description
+ - Use a custom callback function to perform your own post aggregations.
+- Parameters
+ - `callback` - the callback function to execute. It accepts the following parameters:
+ - `query` - the new query object. A fresh reference (or copy, if the parent is locked) is located at `query.data`. It is highly discouraged to change any other property on this object
+ - `parentQuery` - the parent query.
+ - You may optionally return a promise-like value for asynchronous processing
+ ```javascript
+ .post(function(query, parentQuery){
+ query.data[0].key = 'newKeyName'
+ return Promise.resolve(doSomethingSpecial(query.data))
+ })
+ ```
+- Returns
+ - `promise` resolved with
+ - **query instance**
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Pro Tips #
#### No Arrays Necessary
Don’t want to use arrays in your aggregations? No problem, because this:
```javascript
-u.query({
- select: {
- $sum: {
- $sum: [
- {$max: ['tip', 'total’]},
- {$min: ['quantity', 'total’]}
- ]
- },
- }
- })
+.then(function(universe){
+ universe.query({
+ select: {
+ $sum: {
+ $sum: [
+ {$max: ['tip', 'total’]},
+ {$min: ['quantity', 'total’]}
+ ]
+ },
+ }
+ })
+})
```
… is now easier written like this:
```javascript
-u.query({
- select: {
- $sum: {
- $sum: {
- $max: ['tip', 'total'],
- $min: ['quantity', 'total']
- }
- },
+.then(function(universe){
+ universe.query({
+ select: {
+ $sum: {
+ $sum: {
+ $max: ['tip', 'total'],
+ $min: ['quantity', 'total']
}
- })
+ },
+ }
+ })
+})
```
#### No Objects Necessary, either!
What’s that? Don’t like the verbosity of objects or arrays? Use the new string syntax!
```javascript
-universe.query({
- select: {
- $sum: '$sum($max(tip,total), $min(quantity,total))'
- }
- })
+.then(function(universe){
+ universe.query({
+ select: {
+ $sum: '$sum($max(tip,total), $min(quantity,total))'
+ }
+ })
+})
```
#### Pre-compile Columns