From a7a4e1b50cceec2dfaeb89251c6c21ee49eb73bd Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 15 Oct 2012 10:57:34 -0700 Subject: [PATCH 01/52] Replace old reference to auth branch with an XXX. --- docs/client/concepts.html | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/docs/client/concepts.html b/docs/client/concepts.html index 06d4bca77f8..5a0ae193baf 100644 --- a/docs/client/concepts.html +++ b/docs/client/concepts.html @@ -149,14 +149,8 @@

Data

an alternative API. The `mongo-livedata` is a good starting point for such a project. -{{#note}} -A pre-release version of Meteor includes a user login system and a set of tools -for securing read and write access to data based on the logged-in user. For more -information, see the -Getting -Started with Auth wiki page. -{{/note}} +XXX should we mention security/auth at all here, or just expect folks to keep +reading until the accounts section? {{/better_markdown}} From 72731ddda0464627c2d04d709f8fd0e09eec4262 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 15 Oct 2012 11:16:53 -0700 Subject: [PATCH 02/52] Use jQuery to force all non-#internal links to use target=_blank. Rewrite links to use Markdown syntax now that they don't need the to get target=_blank. --- docs/client/api.html | 49 +++++++++++++------------- docs/client/api.js | 5 ++- docs/client/concepts.html | 45 +++++++++++------------ docs/client/docs.js | 3 ++ docs/client/introduction.html | 18 +++++----- docs/client/packages/amplify.html | 3 +- docs/client/packages/backbone.html | 2 +- docs/client/packages/bootstrap.html | 2 +- docs/client/packages/coffeescript.html | 3 +- docs/client/packages/jquery.html | 2 +- docs/client/packages/less.html | 3 +- docs/client/packages/sass.html | 6 ++-- docs/client/packages/spiderable.html | 36 +++++++++---------- docs/client/packages/stylus.html | 7 ++-- docs/client/packages/underscore.html | 4 +-- 15 files changed, 90 insertions(+), 98 deletions(-) diff --git a/docs/client/api.html b/docs/client/api.html index faf6db08707..9eb1028fb1e 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -382,7 +382,7 @@

Server connections

Get the current connection status. See [Meteor.status](#meteor_status). * `reconnect` - - See
Meteor.reconnect. + See [Meteor.reconnect](#meteor_reconnect). * `onReconnect` - Set this to a function to be called as the first step of reconnecting. This function can call methods which will be executed before any other outstanding methods. For example, this can be used to re-establish @@ -589,10 +589,9 @@

Collections

{{> api_box update}} -Modify documents that match `selector` as -given by `modifier` (see modifier -documentation). By default, modify only one matching document. -If `multi` is true, modify all matching documents. +Modify documents that match `selector` as given by `modifier` (see [modifier +documentation](#modifiers)). By default, modify only one matching document. If +`multi` is true, modify all matching documents. Instead of a selector, you can pass a string, which will be interpreted as an `_id`. @@ -656,7 +655,7 @@

Collections

{{> api_box allow}} When a client calls `insert`, `update`, or `remove` on a collection, the -collection's `allow` and `deny` callbacks are called +collection's `allow` and [`deny`](#deny) callbacks are called on the server to determine if the write should be allowed. If at least one `allow` callback allows the write, and no `deny` callbacks deny the write, then the write is allowed to proceed. @@ -790,7 +789,7 @@

Collections

{{> api_box deny}} -This works just like `allow`, except it lets you +This works just like [`allow`](#allow), except it lets you make sure that certain writes are definitely denied, even if there is an `allow` rule that says that they should be permitted. @@ -943,7 +942,8 @@

Cursors

// Matches documents where fruit is one of three possibilities {fruit: {$in: ["peach", "plum", "pear"]}} -See the complete documentation. +See the [complete +documentation](http://www.mongodb.org/display/DOCS/Advanced+Queries). {{/api_box_inline}} @@ -966,7 +966,9 @@

Cursors

// Find the document with id "123", and completely replace it. Users.update({_id: "123"}, {name: "Alice", friends: ["Bob"]}); -See the full list of modifiers. +See the [full list of +modifiers](http://www.mongodb.org/display/DOCS/Updating#Updating-ModifierOperations) +full list of modifiers. {{/api_box_inline}} @@ -1693,7 +1695,7 @@

Accounts

} -Like all Meteor.Collections, you can access all +Like all [Meteor.Collection](#collections)s, you can access all documents on the server, but only those specifically published by the server are available on the client. @@ -1757,9 +1759,9 @@

Accounts

prompted to grant access to their account in the pop-up dialog. Values for the `requestPermissions` parameter differ for each login service: -- Facebook: http://developers.facebook.com/docs/authentication/permissions/ -- GitHub: http://developer.github.com/v3/oauth/#scopes -- Google: https://developers.google.com/accounts/docs/OAuth2Login#scopeparameter +- Facebook: +- GitHub: +- Google: Currently, `loginWithTwitter` and `loginWithWeibo` do not support `requestPermissions`. @@ -1800,9 +1802,9 @@

Accounts

to [`Accounts.resetPassword`](#accounts_resetpassword) to complete the password reset process. -If you are using the `accounts-ui` - package, this is handled automatically. Otherwise, it is your -responsiblity to prompt the user for the new password and call `resetPassword`. +If you are using the [`accounts-ui` package](#pkg_accounts_ui), this is handled +automatically. Otherwise, it is your responsiblity to prompt the user for the +new password and call `resetPassword`. - XXX token goes to `Accounts._resetPasswordToken`. @@ -1853,11 +1855,10 @@

Accounts

Override fields of the object by assigning to them: -- `from`: A `String` with an RFC5322 From address. By default email is from - `no-reply@meteor.com`. If you wish to receive email from users asking for help - with their account, be sure to set this to an email address that you can receive - email at. +- `from`: A `String` with an [RFC5322](http://tools.ietf.org/html/rfc5322) From + address. By default email is from `no-reply@meteor.com`. If you wish to + receive email from users asking for help with their account, be sure to set + this to an email address that you can receive email at. - `siteName`: The public name of your application. Defaults to the DNS name of the application (eg: `awesome.meteor.com`). - `resetPassword`: An `Object` with two fields: @@ -2297,9 +2298,9 @@

Email

environment variable should be of the form `smtp://USERNAME:PASSWORD@HOST:PORT/`. For apps deployed with `meteor deploy`, `MAIL_URL` defaults to an account (provided by -Mailgun) which allows -apps to send up to 200 emails per day; you may override this default by -assigning to `process.env.MAIL_URL` before your first call to `Email.send`. +[Mailgun](http://www.mailgun.com/)) which allows apps to send up to 200 emails +per day; you may override this default by assigning to `process.env.MAIL_URL` +before your first call to `Email.send`. If `MAIL_URL` is not set (eg, when running your application locally), `Email.send` outputs the message to standard output instead. diff --git a/docs/client/api.js b/docs/client/api.js index 476a85c95c8..15d896472f6 100644 --- a/docs/client/api.js +++ b/docs/client/api.js @@ -741,7 +741,7 @@ Template.api.loginWithPassword = { { name: "password", type: "String", - descr: "The user's password. This is __not__ sent in plain text over the wire — it is secured with SRP." + descr: "The user's password. This is __not__ sent in plain text over the wire — it is secured with [SRP](http://en.wikipedia.org/wiki/Secure_Remote_Password_protocol)." }, { name: "callback", @@ -1402,8 +1402,7 @@ Template.api.template_data = { }; var rfc = function (descr) { - return ('RFC5322' - + ' ' + descr); + return '[RFC5322](http://tools.ietf.org/html/rfc5322) ' + descr; }; Template.api.email_send = { diff --git a/docs/client/concepts.html b/docs/client/concepts.html index 5a0ae193baf..c38b6da2795 100644 --- a/docs/client/concepts.html +++ b/docs/client/concepts.html @@ -108,8 +108,7 @@

Data

Meteor's protocol for distributing document updates is database agnostic. By default, Meteor applications use the -familiar MongoDB API: +familiar [MongoDB API](http://www.mongodb.org/display/DOCS/Manual): servers store documents in MongoDB collections, and clients cache those documents in a client-side cache that implements the same Mongo API for queries and updates. @@ -160,11 +159,10 @@

Data

Reactivity

-Meteor embraces the concept of - -reactive programming. This means that you can write your code in a -simple imperative style, and the result will be automatically -recalculated whenever data changes that your code depends on. +Meteor embraces the concept of [reactive +programming](http://en.wikipedia.org/wiki/Reactive_programming). This means that +you can write your code in a simple imperative style, and the result will be +automatically recalculated whenever data changes that your code depends on. Meteor.autosubscribe(function () { Meteor.subscribe("messages", Session.get("currentRoomId")); @@ -208,10 +206,11 @@

Reactivity

* [`Meteor.userId`](#meteor_userid) * [`Meteor.userLoaded`](#meteor_userloaded) -Meteor's implementation -of reactivity is short and sweet, about 50 lines of code. You can -hook into it yourself to add new reactive contexts or data sources, -using the [`Meteor.deps`](#meteor_deps) module. +Meteor's +[implementation](https://github.com/meteor/meteor/blob/master/packages/deps/deps.js) +of reactivity is short and sweet, about 50 lines of code. You can hook into it +yourself to add new reactive contexts or data sources, using the +[`Meteor.deps`](#meteor_deps) module. {{/better_markdown}} @@ -261,15 +260,14 @@

Live HTML

[`Meteor.flush`](#meteor_flush) to bring the DOM up to date immediately. -When live-updating DOM elements are taken off the screen, they are -automatically cleaned up — their callbacks are torn down, any -associated database queries are stopped, and they stop updating. For -this reason, you never have to worry about -the zombie templates that plague hand-written update -logic. To protect your elements from cleanup, just make sure that they -on-screen before your code returns to the event loop, or before any -call you make to [`Meteor.flush`](#meteor_flush). +When live-updating DOM elements are taken off the screen, they are automatically +cleaned up — their callbacks are torn down, any associated database +queries are stopped, and they stop updating. For this reason, you never have to +worry about the [zombie +templates](http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/) +that plague hand-written update logic. To protect your elements from cleanup, +just make sure that they on-screen before your code returns to the event loop, +or before any call you make to [`Meteor.flush`](#meteor_flush). Another thorny problem in hand-written applications is element preservation. Suppose the user is typing text into an `` @@ -307,10 +305,9 @@

Templates

{{#note}} Today, the only templating system that has been packaged for Meteor is Handlebars. Let us know what templating systems you'd like to use with -Meteor. Meanwhile, see -the Handlebars documentation -and Meteor -Handlebars extensions. +Meteor. Meanwhile, see the [Handlebars +documentation](http://www.handlebarsjs.com/) and [Meteor Handlebars +extensions](https://github.com/meteor/meteor/wiki/Handlebars). {{/note}} A template with a `name` of `hello` is rendered by calling the diff --git a/docs/client/docs.js b/docs/client/docs.js index 72cba48598f..c406dd6fc7f 100644 --- a/docs/client/docs.js +++ b/docs/client/docs.js @@ -60,6 +60,9 @@ Meteor.startup(function () { ignore_waypoints = false; }); }); + + // Make external links open in a new tab. + $('a:not([href^="#")').attr('target', '_blank'); }); var toc = [ diff --git a/docs/client/introduction.html b/docs/client/introduction.html index a097e2be235..ce4bc72bc5c 100644 --- a/docs/client/introduction.html +++ b/docs/client/introduction.html @@ -39,7 +39,8 @@ -The following works on all supported platforms. +The following works on all [supported +platforms](https://github.com/meteor/meteor/wiki/Supported-Platforms). Install Meteor: @@ -106,25 +107,24 @@

Seven Principles of Meteor

Developer Resources

-Fork me on GitHub +Fork me on GitHub If anything in Meteor catches your interest, we hope you'll get involved with the project!
Stack Overflow
-
The best place to ask (and answer!) technical questions is - on Stack - Overflow. Be sure to add the meteor tag to your - question. +
The best place to ask (and answer!) technical questions is on [Stack + Overflow](http://stackoverflow.com/questions/tagged/meteor). Be sure to add + the meteor tag to your question.
Mailing lists
- We have two mailing lists for Meteor. meteor-talk@googlegroups.com + We have two mailing lists for Meteor. meteor-talk@googlegroups.com is for general questions, requests for help, and new project announcements. - meteor-core@googlegroups.com + meteor-core@googlegroups.com is for discussing Meteor internals and proposed changes.
@@ -134,7 +134,7 @@

Developer Resources

can.
GitHub
-
The code is on GitHub. The best way to send a patch is with a GitHub pull request, and the best way to file a bug is in the GitHub bug tracker.
+
The code is on GitHub. The best way to send a patch is with a GitHub pull request, and the best way to file a bug is in the GitHub bug tracker.
{{/markdown}} diff --git a/docs/client/packages/amplify.html b/docs/client/packages/amplify.html index 73a6d24ba36..1afe28df47e 100644 --- a/docs/client/packages/amplify.html +++ b/docs/client/packages/amplify.html @@ -10,6 +10,7 @@ Amplify defines a global namespace `amplify` on the client only. It does not run on the server. -For more information about Amplify, see http://amplifyjs.com/. +For more information about Amplify, see . + {{/better_markdown}} diff --git a/docs/client/packages/backbone.html b/docs/client/packages/backbone.html index 30675412c68..f12fc61b122 100644 --- a/docs/client/packages/backbone.html +++ b/docs/client/packages/backbone.html @@ -8,7 +8,7 @@ client-side URL routing. For more information about Backbone, see -http://documentcloud.github.com/backbone/. +. {{/better_markdown}} diff --git a/docs/client/packages/bootstrap.html b/docs/client/packages/bootstrap.html index aa2f4ccdfee..62a712298f6 100644 --- a/docs/client/packages/bootstrap.html +++ b/docs/client/packages/bootstrap.html @@ -9,7 +9,7 @@ navigation. For more information about Bootstrap, see -http://twitter.github.com/bootstrap/. +. {{/better_markdown}} diff --git a/docs/client/packages/coffeescript.html b/docs/client/packages/coffeescript.html index 6cd1cfc1654..3f24ce8c546 100644 --- a/docs/client/packages/coffeescript.html +++ b/docs/client/packages/coffeescript.html @@ -10,8 +10,7 @@ CoffeeScript is supported on both the client and the server. Files ending with `.coffee` are automatically compiled to JavaScript. -See http://jashkenas.github.com/coffee-script/ -for more information. +See for more information. {{/better_markdown}} diff --git a/docs/client/packages/jquery.html b/docs/client/packages/jquery.html index f1026687549..989160cd2d6 100644 --- a/docs/client/packages/jquery.html +++ b/docs/client/packages/jquery.html @@ -3,7 +3,7 @@ ## `jquery` -jQuery is a fast and concise JavaScript +[jQuery](http://jquery.com/) is a fast and concise JavaScript Library that simplifies HTML document traversing, event handling, animating, and Ajax interactions for rapid web development. diff --git a/docs/client/packages/less.html b/docs/client/packages/less.html index 731eeb0fc27..da85fe95bb7 100644 --- a/docs/client/packages/less.html +++ b/docs/client/packages/less.html @@ -10,8 +10,7 @@ automatically compiled to CSS and the results are included in the client CSS bundle. -See http://lesscss.org/ for -documentation of the LESS language. +See for documentation of the LESS language. {{/better_markdown}} diff --git a/docs/client/packages/sass.html b/docs/client/packages/sass.html index 7db05566a71..2b22729db13 100644 --- a/docs/client/packages/sass.html +++ b/docs/client/packages/sass.html @@ -7,10 +7,8 @@ application are automatically compiled to CSS and the results are included in the client CSS bundle. -See https://github.com/visionmedia/sass.js -for the JavaScript implementation of the Sass language -and http://sass-lang.com/ for the -original project. +See for the JavaScript implementation +of the Sass language and for the original project. {{/better_markdown}} diff --git a/docs/client/packages/spiderable.html b/docs/client/packages/spiderable.html index 08c9d619bba..3bd785c4d43 100644 --- a/docs/client/packages/spiderable.html +++ b/docs/client/packages/spiderable.html @@ -3,37 +3,33 @@ ## `spiderable` -The `spiderable` package is a temporary solution to allow web search -engines to index a Meteor application. It uses the AJAX -Crawling specification published by Google to serve HTML to -compatible spiders (Google, Bing, Yandex, and more). +The `spiderable` package is a temporary solution to allow web search engines to +index a Meteor application. It uses the [AJAX Crawling +specification](https://developers.google.com/webmasters/ajax-crawling/) +published by Google to serve HTML to compatible spiders (Google, Bing, Yandex, +and more). -When a spider requests an HTML snapshot of a page the Meteor server runs -the client half of the application inside phantomjs, a headless browser, and -returns the full HTML generated by the client code. +When a spider requests an HTML snapshot of a page the Meteor server runs the +client half of the application inside [phantomjs](http://phantomjs.org/), a +headless browser, and returns the full HTML generated by the client code. {{#warning}} This is a temporary approach to allow Meteor applications to be searchable. Expect significant changes to this package. {{/warning}} -In order to have links between multiple pages on a site visible to -spiders, apps must use real links (eg ``) rather than -simply re-rendering portions of the page when an element is -clicked. Apps should render their content based on the URL of the page -and can use -HTML5 pushState -to alter the URL on the client without triggering a page reload. See the -Todos example -for a demonstration. +In order to have links between multiple pages on a site visible to spiders, apps +must use real links (eg ``) rather than simply re-rendering +portions of the page when an element is clicked. Apps should render their +content based on the URL of the page and can use [HTML5 +pushState](https://developer.mozilla.org/en-US/docs/DOM/Manipulating_the_browser_history) +to alter the URL on the client without triggering a page reload. See the [Todos +example](http://meteor.com/examples/todos) for a demonstration. {{#warning}} If you deploy your application with `meteor bundle`, you must install -`phantomjs` (http://phantomjs.org) somewhere in your +`phantomjs` ([http://phantomjs.org](http://phantomjs.org/)) somewhere in your `$PATH`. If you use `meteor deploy` this is already taken care of. {{/warning}} diff --git a/docs/client/packages/stylus.html b/docs/client/packages/stylus.html index 0e683b19f60..4665873c378 100644 --- a/docs/client/packages/stylus.html +++ b/docs/client/packages/stylus.html @@ -14,10 +14,9 @@ your `.styl` files to enable cross-browser mixins such as `linear-gradient` and `border-radius`. -See http://learnboost.github.com/stylus -for documentation of the Stylus language, -and http://visionmedia.github.com/nib -for documentation of the nib extensions. +See for documentation of the Stylus +language, and for documentation of the nib +extensions. {{/better_markdown}} diff --git a/docs/client/packages/underscore.html b/docs/client/packages/underscore.html index 1b570d5a119..ae7f2ad88b2 100644 --- a/docs/client/packages/underscore.html +++ b/docs/client/packages/underscore.html @@ -9,8 +9,8 @@ The `underscore` package defines the `_` namespace on both the client and the server. -See http://documentcloud.github.com/underscore/ -for underscore API documentation. +See for underscore API +documentation. {{#warning}} Currently, underscore is included in all projects, as the Meteor From 1fe86fa93a8fe1835878dadccdc3856d1e696ed7 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 15 Oct 2012 11:21:21 -0700 Subject: [PATCH 03/52] Add a warning about the brokenness of the sass package to the docs (issue #143). sass.js implements a version of Sass much older than the .sass (let alone currently recommended .scss) described at sass-lang.com, and has poor error handling so it mostly just ends up confusing users. sass.js's author now uses stylus/nib. We should probably remove the sass package, but let's not add another breaking change to 0.5.0. --- docs/client/packages/sass.html | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/client/packages/sass.html b/docs/client/packages/sass.html index 2b22729db13..51c917ecf42 100644 --- a/docs/client/packages/sass.html +++ b/docs/client/packages/sass.html @@ -10,5 +10,12 @@ See for the JavaScript implementation of the Sass language and for the original project. +{{#warning}} +The Sass JavaScript implementation used by Node is unmaintained and doesn't +implement the newest language syntax documented at . It +may be removed from a future version of Meteor; consider using [Less](#less) or +[Stylus](#stylus) instead. +{{/warning}} + {{/better_markdown}} From ddf870717b5a39398511ea6ac76be38334448330 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 15 Oct 2012 11:27:01 -0700 Subject: [PATCH 04/52] Link a couple of references to Node.js. --- docs/client/api.html | 8 ++++---- docs/client/concepts.html | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/client/api.html b/docs/client/api.html index 9eb1028fb1e..f2ff4db5996 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -3,10 +3,10 @@

The Meteor API

-Your Javascript code can run in two environments: the *client* -(browser), and the *server* (a Node.js container on a server). For -each function in this API reference, we'll indicate if the function is -available just on the client, just on the server, or *Anywhere*. +Your Javascript code can run in two environments: the *client* (browser), and +the *server* (a [Node.js](http://nodejs.org/) container on a server). For each +function in this API reference, we'll indicate if the function is available just +on the client, just on the server, or *Anywhere*.

Meteor Core

diff --git a/docs/client/concepts.html b/docs/client/concepts.html index c38b6da2795..864a4025ac0 100644 --- a/docs/client/concepts.html +++ b/docs/client/concepts.html @@ -22,12 +22,12 @@

Concepts

Structuring your application

-A Meteor application is a mix of JavaScript that runs inside a -client web browser, JavaScript that runs on the Meteor server inside -a Node.js container, and all the supporting HTML fragments, CSS rules, -and static assets. Meteor automates the packaging and transmission -of these different components. And, it is quite flexible about how -you choose to structure those components in your file tree. +A Meteor application is a mix of JavaScript that runs inside a client web +browser, JavaScript that runs on the Meteor server inside a +[Node.js](http://nodejs.org/) container, and all the supporting HTML fragments, +CSS rules, and static assets. Meteor automates the packaging and transmission +of these different components. And, it is quite flexible about how you choose +to structure those components in your file tree. The only server asset is JavaScript. Meteor gathers all your JavaScript files, excluding anything under the `client` From dc632fcfa148310a84ba53948f7c8ff2817f104a Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 15 Oct 2012 11:29:19 -0700 Subject: [PATCH 05/52] Un-comment a code sample. --- docs/client/api.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/client/api.html b/docs/client/api.html index f2ff4db5996..d3056aedf7d 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -150,9 +150,9 @@

Publish and subscribe

If all of the attributes in a document are removed, Meteor will remove the (now empty) document. If you want to publish empty -documents, just use a placeholder attribute. +documents, just use a placeholder attribute: - // Clicks.insert({exists: true}); + Clicks.insert({exists: true}); {{> api_box autosubscribe}} From 4d4b504e172ba6a6d2826fdcbb5e4da0d337951d Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 15 Oct 2012 11:37:06 -0700 Subject: [PATCH 06/52] Add a use of this.userId to the first Meteor.publish example. Move the this.userId api box to the top of the "this" section, since it's relevant to both flavors of publish function (cursor and non-cursor). --- docs/client/api.html | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/docs/client/api.html b/docs/client/api.html index d3056aedf7d..ee58cd0c230 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -47,7 +47,14 @@

Publish and subscribe

// server: publish the rooms collection, minus secret info. Meteor.publish("rooms", function () { - return Rooms.find({}, {fields: {secretInfo: false}}); + return Rooms.find({}, {fields: {secretInfo: 0}}); + }); + + // ... and publish secret info for rooms where the logged-in user + // is an admin. If the client subscribes to both streams, the records + // are merged together into the same documents in the Rooms collection. + Meteor.publish("adminSecretInfo", function () { + return Rooms.find({admin: this.userId}, {fields: {secretInfo: 1}}); }); Otherwise, the publish function can [`set`](#publish_set) and @@ -108,6 +115,11 @@

Publish and subscribe

will still work. {{/warning}} +{{> api_box subscription_userId}} + +This is constant. However, if the logged-in user changes, the publish +function is rerun with the new value. + {{> api_box subscription_set}} {{> api_box subscription_unset}} {{> api_box subscription_complete}} @@ -120,11 +132,6 @@

Publish and subscribe

{{> api_box subscription_stop}} -{{> api_box subscription_userId}} - -This is constant. However, if the logged-in user changes, the publish -function is rerun with the new value. - {{> api_box subscribe}} When you subscribe to a record set, it tells the server to send records From a9af320aba335a25eb3202a562f8af029fcf042b Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 15 Oct 2012 11:38:00 -0700 Subject: [PATCH 07/52] Fix another reference to this.userId as a function. --- docs/client/api.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/client/api.html b/docs/client/api.html index ee58cd0c230..d3224eac2bf 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -213,7 +213,7 @@

Methods

* `isSimulation`: a boolean value, true if this invocation is a stub. * `unblock`: when called, allows the next method from this client to begin running. -* `userId`: a function that returns the id of the current user. +* `userId`: the id of the current user. * `setUserId`: a function that associates the current client with a user. Calling `methods` on the client defines *stub* functions associated with From 907b6ee91ab14eba4b7073e158d63bc4f94bdee9 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 15 Oct 2012 11:55:31 -0700 Subject: [PATCH 08/52] Tweak wording of Accounts.ui.config docs and show a more complete example. --- docs/client/api.html | 10 ++++++++++ docs/client/api.js | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/docs/client/api.html b/docs/client/api.html index d3224eac2bf..05b381b73fb 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -1896,6 +1896,16 @@

Accounts

{{> api_box accounts_config}} {{> api_box accounts_ui_config}} +Example: + + Accounts.ui.config({ + requestPermissions: { + facebook: ['user_likes'], + github: ['user', 'repo'] + }, + passwordSignupFields: 'USERNAME_AND_OPTIONAL_EMAIL' + }); + {{> api_box accounts_validateNewUser}} This can be called multiple times. If any of the functions return diff --git a/docs/client/api.js b/docs/client/api.js index 15d896472f6..732da9f7cf7 100644 --- a/docs/client/api.js +++ b/docs/client/api.js @@ -1008,12 +1008,12 @@ Template.api.accounts_ui_config = { id: "accounts_ui_config", name: "Accounts.ui.config(options)", locus: "Client", - descr: ["Set Accounts UI options for the `loginButtons` template."], + descr: ["Configure the behavior of `{{loginButtons}}`."], options: [ { name: "requestPermissions", type: "Object", - descr: "Which permissions to request from the user for each OAuth service. For example: `{facebook: ['user_likes'], github: ['user', 'repo']}`" + descr: "Which [permissions](#meteor_loginwithoauth) to request from the user for each OAuth service." }, { name: "passwordSignupFields", From b59ab5dfa5db3bff0a0eaeae6963f445d9425d87 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 15 Oct 2012 11:56:38 -0700 Subject: [PATCH 09/52] serializeable -> serializable --- docs/client/api.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/client/api.html b/docs/client/api.html index 05b381b73fb..29ecc8e2b43 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -275,7 +275,7 @@

Methods

If a stub is available, it will also run the stub on the client. If you include a callback function as the last argument (which can't be -an argument to the method, since functions aren't serializeable), the +an argument to the method, since functions aren't serializable), the method will run asynchronously: it will return nothing in particular and will not throw an exception. When the method is complete (which may or may not happen before `Meteor.call` returns), the callback will be From 54cc29997820555cc5a7215b80de61fc69e1acd4 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 15 Oct 2012 11:59:50 -0700 Subject: [PATCH 10/52] Meteor.apply docs: mention that it allows options (and REQUIRES array). --- docs/client/api.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/client/api.html b/docs/client/api.html index 29ecc8e2b43..648c2c6231e 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -319,8 +319,9 @@

Methods

{{> api_box meteor_apply}} -`Meteor.apply` is just like `Meteor.call`, but it allows the -arguments to be passed as an array. +`Meteor.apply` is just like `Meteor.call`, except that the method arguments are +passed as an array rather than directly as arguments, and you can specify +options about how the client executes the method.

Server connections

From 7d4d5e3a53f46f8d072566a5b2fa06e2d3a0bbec Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 15 Oct 2012 12:04:27 -0700 Subject: [PATCH 11/52] Update references to access control in "new Meteor.Collection" docs. --- docs/client/api.html | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/docs/client/api.html b/docs/client/api.html index 648c2c6231e..ff487632154 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -452,7 +452,8 @@

Collections

* On the server, a collection with that name is created on a backend Mongo server. When you call methods on that collection on the server, -they translate directly into normal Mongo operations. +they translate directly into normal Mongo operations (after checking that +they match your [access control rules](#allow)). * On the client, a Minimongo instance is created. Minimongo is essentially an in-memory, non-persistent @@ -479,13 +480,8 @@

Collections

$ meteor remove autopublish -{{#warning}} -Currently the client is given full write access to the collection. They -can execute arbitrary Mongo update commands. Once we build -authentication, you will be able to limit the client's direct access to -insert, update, and remove. We are also considering validators and -other ORM-like functionality. -{{/warning}} +and instead call [`Meteor.publish`](#meteor_publish) to specify which parts of +your collection should be published to which users. // Create a collection called Posts and put a document in it. The // document will be immediately visible in the local copy of the From 033dbee8897c3ae94ac8bb4efdf091a160b78ae4 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 15 Oct 2012 12:04:51 -0700 Subject: [PATCH 12/52] docs: add missing paren --- docs/client/api.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/client/api.html b/docs/client/api.html index ff487632154..84f672039f6 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -550,8 +550,8 @@

Collections

Cursors are a reactive data source. The first time you retrieve a cursor's documents with `fetch`, `map`, or `forEach` inside a -reactive context (eg, [`Meteor.render`](#meteor_render), -[`Meteor.autosubscribe`](#meteor_autosubscribe), Meteor will register a +reactive context (eg, [`Meteor.render`](#meteor_render) or +[`Meteor.autosubscribe`](#meteor_autosubscribe)), Meteor will register a dependency on the underlying data. Any change to the collection that changes the documents in a cursor will trigger a recomputation. To disable this behavior, pass `{reactive: false}` as an option to From 10bad07074d3e1d329aca1b8faa83778f583ec82 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 15 Oct 2012 12:08:31 -0700 Subject: [PATCH 13/52] Be explicit that the insecure package's "any allow/deny" rule is per-collection. --- docs/client/api.html | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/client/api.html b/docs/client/api.html index 84f672039f6..b7b380ee203 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -775,12 +775,12 @@

Collections

collection. Meteor also has a special "insecure mode" for quickly prototyping new -applications. In insecure mode, if you haven't set up any `allow` or -`deny` rules on a collection, then all users have full write access to -the collection. This is the only effect of insecure mode. If you call -`allow` or `deny` at all, even `allow({})`, then access is checked just -like normal. __New Meteor projects start in insecure mode by default.__ To -turn it off just type `meteor remove insecure`. +applications. In insecure mode, if you haven't set up any `allow` or `deny` +rules on a collection, then all users have full write access to the +collection. This is the only effect of insecure mode. If you call `allow` or +`deny` at all on a collection, even `Posts.allow({})`, then access is checked +just like normal on that collection. __New Meteor projects start in insecure +mode by default.__ To turn it off just type `meteor remove insecure`. {{#note}} For `update` and `remove`, documents will be affected only if they match From 28ebec8b0f9674e3ad3e03dc5a5e63fa8cf85b73 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 15 Oct 2012 12:13:53 -0700 Subject: [PATCH 14/52] Mention lack of support for validated literal doc updates. --- docs/client/api.html | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/client/api.html b/docs/client/api.html index b7b380ee203..dc883daff5d 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -963,9 +963,10 @@

Cursors

// 'supporters' array {$inc: {votes: 2}, $push: {supporters: "Traz"}} -But if a modifier doesn't contain any $-operators, then it is -instead interpreted as a literal document, and completely replaces -whatever was previously in the database. +But if a modifier doesn't contain any $-operators, then it is instead +interpreted as a literal document, and completely replaces whatever was +previously in the database. (Literal document modifiers are not currently +supported by [validated updates](#allow).) // Find the document with id "123", and completely replace it. Users.update({_id: "123"}, {name: "Alice", friends: ["Bob"]}); From 072e117122825f151dfe34800baf018efdf5359c Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 15 Oct 2012 12:18:24 -0700 Subject: [PATCH 15/52] Actually, let's put the tokens in services.email.verificationTokens. --- packages/accounts-password/email_tests.js | 4 ++-- packages/accounts-password/passwords_server.js | 17 ++++++++++------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/packages/accounts-password/email_tests.js b/packages/accounts-password/email_tests.js index c119bf0030d..4691266c47c 100644 --- a/packages/accounts-password/email_tests.js +++ b/packages/accounts-password/email_tests.js @@ -107,8 +107,8 @@ test.equal(Meteor.user().emails.length, 1); test.equal(Meteor.user().emails[0].address, email2); test.isFalse(Meteor.user().emails[0].verified); - // We should NOT be publishing verification tokens! - test.isFalse(_.has(Meteor.user(), 'emailVerificationTokens')); + // We should NOT be publishing things like verification tokens! + test.isFalse(_.has(Meteor.user(), 'services')); }, function (test, expect) { getVerifyEmailToken(email2, test, expect); diff --git a/packages/accounts-password/passwords_server.js b/packages/accounts-password/passwords_server.js index 6f0972efad8..312d82f3a00 100644 --- a/packages/accounts-password/passwords_server.js +++ b/packages/accounts-password/passwords_server.js @@ -138,13 +138,15 @@ if (!token) throw new Meteor.Error(400, "Need to pass token"); - var user = Meteor.users.findOne({'emailVerificationTokens.token': token}); + var user = Meteor.users.findOne( + {'services.email.verificationTokens.token': token}); if (!user) throw new Meteor.Error(403, "Verify email link expired"); - var tokenRecord = _.find(user.emailVerificationTokens, function (t) { - return t.token == token; - }); + var tokenRecord = _.find(user.services.email.verificationTokens, + function (t) { + return t.token == token; + }); if (!tokenRecord) throw new Meteor.Error(403, "Verify email link expired"); @@ -166,7 +168,7 @@ {_id: user._id, 'emails.address': tokenRecord.address}, {$set: {'emails.$.verified': true}, - $pull: {emailVerificationTokens: {token: token}}, + $pull: {'services.email.verificationTokens': {token: token}}, $push: {'services.resume.loginTokens': stampedLoginToken}}); this.setUserId(user._id); @@ -234,8 +236,9 @@ token: Meteor.uuid(), address: address, when: +(new Date)}; - Meteor.users.update({_id: userId}, - {$push: {emailVerificationTokens: tokenRecord}}); + Meteor.users.update( + {_id: userId}, + {$push: {'services.email.verificationTokens': tokenRecord}}); var verifyEmailUrl = Accounts.urls.verifyEmail(tokenRecord.token); Email.send({ From 2a1754f69cd089ad3b6e446c1827cca2ca4c80f4 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 15 Oct 2012 12:19:35 -0700 Subject: [PATCH 16/52] Docs: Add "field specifiers" to TOC. --- docs/client/docs.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/client/docs.js b/docs/client/docs.js index c406dd6fc7f..39b56e1f48f 100644 --- a/docs/client/docs.js +++ b/docs/client/docs.js @@ -144,7 +144,8 @@ var toc = [ {type: "spacer"}, {name: "Selectors", style: "noncode"}, {name: "Modifiers", style: "noncode"}, - {name: "Sort specifiers", style: "noncode"} + {name: "Sort specifiers", style: "noncode"}, + {name: "Field specifiers", style: "noncode"} ], "Session", [ From b6d7d7817c341cb273f42a9b2a1d19ad59781964 Mon Sep 17 00:00:00 2001 From: Nick Martin Date: Mon, 15 Oct 2012 13:52:44 -0700 Subject: [PATCH 17/52] Re-order TOC to match document. --- docs/client/docs.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/client/docs.js b/docs/client/docs.js index 39b56e1f48f..c36dcd02aad 100644 --- a/docs/client/docs.js +++ b/docs/client/docs.js @@ -92,13 +92,13 @@ var toc = [ "Publish and subscribe", [ "Meteor.publish", [ + {instance: "this", name: "userId", id: "publish_userId"}, {instance: "this", name: "set", id: "publish_set"}, {instance: "this", name: "unset", id: "publish_unset"}, {instance: "this", name: "complete", id: "publish_complete"}, {instance: "this", name: "flush", id: "publish_flush"}, {instance: "this", name: "onStop", id: "publish_onstop"}, - {instance: "this", name: "stop", id: "publish_stop"}, - {instance: "this", name: "userId", id: "publish_userId"} + {instance: "this", name: "stop", id: "publish_stop"} ], "Meteor.subscribe", "Meteor.autosubscribe" From ea992c61f62d083cb2ef5ddef4a168ac5f5b99a0 Mon Sep 17 00:00:00 2001 From: Avital Oliver Date: Mon, 15 Oct 2012 14:00:23 -0700 Subject: [PATCH 18/52] accounts-github: don't ask for the "user" scope unless explicitly requested --- packages/accounts-github/github_client.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/accounts-github/github_client.js b/packages/accounts-github/github_client.js index 047123e3a3b..c16db9680df 100644 --- a/packages/accounts-github/github_client.js +++ b/packages/accounts-github/github_client.js @@ -13,14 +13,13 @@ } var state = Meteor.uuid(); - var required_scope = ['user']; - var scope = _.union((options && options.requestPermissions) || [], required_scope); - var flat_scope = _.map(scope, encodeURIComponent).join('+'); + var scope = (options && options.requestPermissions) || []; + var flatScope = _.map(scope, encodeURIComponent).join('+'); var loginUrl = 'https://github.com/login/oauth/authorize' + '?client_id=' + config.clientId + - '&scope=' + flat_scope + + '&scope=' + flatScope + '&redirect_uri=' + Meteor.absoluteUrl('_oauth/github?close') + '&state=' + state; From 6ea9c0631f168939c115b85eefdee383f09803bf Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 15 Oct 2012 15:01:04 -0700 Subject: [PATCH 19/52] Link to accounts API section. --- docs/client/api.html | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/client/api.html b/docs/client/api.html index dc883daff5d..d46b5aaf305 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -232,9 +232,9 @@

Methods

{{> api_box method_invocation_userId}} -The user id is an arbitrary string — typically the id of the user -record in the database. You can set it with the `setUserId` function. If -you're using the Meteor accounts system then this is handled for you. +The user id is an arbitrary string — typically the id of the user record +in the database. You can set it with the `setUserId` function. If you're using +the [Meteor accounts system](#accounts_api) then this is handled for you. {{> api_box method_invocation_setUserId}} @@ -243,9 +243,9 @@

Methods

`userId` for future method calls received on this connection. Pass `null` to log out the connection. -If you are using the built-in Meteor accounts system then this should correspond -to the `_id` field of a document in the [`Meteor.users`](#meteor_users) -collection. +If you are using the [built-in Meteor accounts system](#accounts_api) then this +should correspond to the `_id` field of a document in the +[`Meteor.users`](#meteor_users) collection. `setUserId` is not retroactive. It affects the current method call and any future method calls on the connection. Any previous method calls on From b9a3f34377f2ff75afc608d20e36e477cfa01f8c Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 15 Oct 2012 15:03:25 -0700 Subject: [PATCH 20/52] Put Accounts API section before Template API. --- docs/client/api.html | 1280 +++++++++++++++++++++--------------------- docs/client/docs.js | 47 +- 2 files changed, 663 insertions(+), 664 deletions(-) diff --git a/docs/client/api.html b/docs/client/api.html index d46b5aaf305..8c25b41ac78 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -1108,851 +1108,851 @@

Session

you need to use the `underscore` package and write `_.isEqual(Session.get(key), value)`. -

Templates

-A template that you declare as `<{{! }}template name="foo"> ... ` can be accessed as the function `Template.foo`, which -returns a string of HTML when called. -The same template may occur many times on the page, and these -occurrences are called template instances. Template instances have a -life cycle of being created, put into the document, and later taken -out of the document and destroyed. Meteor manages these stages for -you, including determining when a template instance has been removed -or replaced and should be cleaned up. You can associate data with a -template instance, and you can access its DOM nodes when it is in the -document. +

Accounts

-Additionally, Meteor will maintain a template instance and its state -even if its surrounding HTML is re-rendered into new DOM nodes. As -long as the structure of template invocations is the same, Meteor will -not consider any instances to have been created or destroyed. You can -request that the same DOM nodes be retained as well using `preserve` -and `constant`. +XXX intro text -There are a number of callbacks and directives that you can specify on -a named template and that apply to all instances of the template. -They are described below. +{{> api_box user}} -{{> api_box template_call}} +Retreives the user record for the current user from +the [`Meteor.users`](#meteor_users) collection. -When called inside a template helper, the body of `Meteor.render`, or -other settings where reactive HTML is being generated, the resulting -HTML is annotated so that it renders as reactive DOM elements. -Otherwise, the HTML is unadorned and static. +On the client this will be a subset of the fields in the document, only +those that are published from the server are available on the client. By +default the server publishes `username`, `emails`, and +`profile`. See [`Meteor.users`](#meteor_users) for more on +the fields used user documents. +If the user is logged in but the user's database record is not fully +loaded yet, this returns an object with only the `_id` field set. During +this period [`userLoaded`](#meteor_userloaded) will return +`false`. -{{> api_box template_rendered}} +{{> api_box userId}} -This callback is called once when an instance of Template.*myTemplate* is -rendered into DOM nodes and put into the document for the first time, and again -each time any part of the template is re-rendered. +{{> api_box users}} -In the body of the callback, `this` is a [template -instance](#template_inst) object that is unique to this occurrence of -the template and persists across re-renderings. Use the `created` and -`destroyed` callbacks to perform initialization or clean-up on the -object. +This collection contains one document per user. Example user document: -{{> api_box template_created}} + { + _id: "bbca5d6a-2156-41c4-89da-0329e8c99a4f" // userId + username: "cool_kid_13", // unique name + emails: [ + // each email address can only belong to one user. + { address: "cool@example.com", verified: true }, + { address: "another@different.com", verified: false } + ], + profile: { + // The profile is writable by the user by default. + name: "Joe Schmoe" + }, + services: { + facebook: { + id: "709050", // facebook id + accessToken: "AAACCgdX7G2...AbV9AZDZD" + }, + resume: { + loginTokens: [ + { token: "97e8c205-c7e4-47c9-9bea-8e2ccc0694cd", + when: 1349761684048 } + ] + } + } + } -This callback is called when an invocation of *myTemplate* represents -a new occurrence of the template and not a re-rendering of an existing -template instance. Inside the callback, `this` is the new [template -instance](#template_inst) object. Properties you set on this object -will be visible from the `rendered` and `destroyed` callbacks and from -event handlers. -This callback fires once and is the first callback to fire. Every -`created` has a corresponding `destroyed`; that is, if you get a -`created` callback with a certain template instance object in `this`, -you will eventually get a `destroyed` callback for the same object. +Like all [Meteor.Collection](#collections)s, you can access all +documents on the server, but only those specifically published by the server are +available on the client. -{{> api_box template_destroyed}} +By default, the current user's `username`, `emails` and `profile` are +published to the client. You can publish additional fields for the +current user with: -This callback is called when an occurrence of a template is taken off -the page for any reason and not replaced with a re-rendering. Inside -the callback, `this` is the [template instance](#template_inst) object -being destroyed. + Meteor.publish("userData", function () { + return Meteor.users.find({_id: this.userId}, + {fields: {'other': 1, 'things': 1}}); + }); -This callback is most useful for cleaning up or undoing any external -effects of `created`. It fires once and is the last callback to fire. +If the `autopublish` package is installed, the `username` and `profile` fields +for all users are published to all clients. To publish specific fields from all +users: + Meteor.publish("allUserData", function () { + return Meteor.users.find({}, {fields: {'nested.things': 1}}); + }); -{{> api_box template_events}} +Users are by default allowed to specify their own `profile` field with +[`Accounts.createUser`](#accounts_createuser) and modify it with +`Meteor.users.update`. To allow users to edit additional fields, use +[`Meteor.users.allow`](#allow). To forbid users from making any modifications to +their user document: -Declare event handers for instances of this template. Multiple calls add -new event handlers in addition to the existing ones. + Meteor.users.deny({update: function () { return true; }}); -See [Event Maps](#eventmaps) for a detailed description of the event -map format and how event handling works in Meteor. + +{{> api_box userLoaded}} + +There are some cases when the client knows the id of the logged in user +but has not yet received the user data from the server. For example, if +the user is logged in and reloads the page the user data will be +unavailable during initial page load. + +During these periods, `userLoaded` will return false +and [`user`](#meteor_user) will return an object with only +the `_id` key. {{#note}} -This syntax replaces the previous syntax: `Template.myTemplate.events = {...}`, -but for now, the old syntax still works. +We realize this is inconvenient. It is a temporary solution. In the +future we will either make it unnecessary or fold it into a more +general mechanism. {{/note}} +{{> api_box logout}} -{{> api_box template_helpers}} +{{> api_box loginWithPassword}} -Each template has a local dictionary of helpers that are made available to it, -and this call specifies helpers to add to the template's dictionary. +{{> api_box loginWithOAuth}} -Example: +These functions initiate the login process with a third party OAuth +provider (eg: Facebook, Google, etc). When called they open a new pop-up +window that loads the provider's login page. Once the user has logged in +with the provider, the pop-up window is closed and the Meteor client +logs in to the Meteor server with the information provided by the OAuth +provider. - Template.myTemplate.helpers({ - foo: function () { - return Session.get("foo"); - } - }); +If the user has not already granted all the permissions requested they will be +prompted to grant access to their account in the pop-up dialog. Values for the +`requestPermissions` parameter differ for each login service: -In Handlebars, this helper would then be invoked as `{{dstache}}foo}}`. +- Facebook: +- GitHub: +- Google: -The following syntax is equivalent, but won't work for reserved property -names: +Currently, `loginWithTwitter` and `loginWithWeibo` do not support +`requestPermissions`. - Template.myTemplate.foo = function () { - return Session.get("foo"); - }; -{{> api_box template_preserve}} +{{> api_box accounts_createUser}} -You can "preserve" a DOM element during re-rendering, leaving the -existing element in place in the document while replacing the -surrounding HTML. This means that re-rendering a template need not -disturb text fields, iframes, and other sensitive elements it -contains. The elements to preserve must be present both as nodes in -the old DOM and as tags in the new HTML. Meteor will patch the DOM -around the preserved elements. +On the client this function logs in as the newly created user on +successful completion. On the server, it returns the newly created user +id. -{{#note}} -By default, new Meteor apps automatically include the -`preserve-inputs` package. This preserves all elements of type -`input`, `textarea`, `button`, `select`, and `option` that have unique -`id` attributes or that have `name` attributes that are unique within -an enclosing element with an `id` attribute. To turn off this default -behavior, simply remove the `preserve-inputs` package. -{{/note}} +On the client, you must pass `password` and one of `username` or `email` +— enough information for the user to be able to log in again +later. On the server, you can pass any subset of these options, but the +user will not be able to log in until it has an identifier and a +password. -Preservation is useful in a variety of cases where replacing a DOM -element with an identical or modified element would not have the same -effect as retaining the original element. These include: +To create an account without a password on the server and still let the +user pick their own password, call `createUser` with the `email` option +and then +call [`Accounts.sendEnrollmentEmail`](#accounts_sendenrollmentemail). This +will send the user an email with a link to set their initial password. -* Input text fields and other form controls -* Elements with CSS animations -* Iframes -* Nodes with references kept in JavaScript code +By default the `profile` option is added directly to the new user document. To +override this behavior, use [`Accounts.onCreateUser`](#accounts_createuser). -If you want to preserve a whole region of the DOM, an element and its -children, or nodes not rendered by Meteor, use a [constant -region](#constant) instead. +This function is only used for creating users with passwords. The OAuth +login flows do not use this function. -To preserve nodes, pass a list of selectors each of which should match -at most one element in the template. When the template is re-rendered, -the selector is run on the old DOM and the new DOM, and Meteor will -reuse the old element in place while working in any HTML changes around -it. -A second form of `preserve` takes a labeling function for each selector -and allows the selectors to match multiple nodes. The node-labeling -function takes a node and returns a label string that is unique for each -node, or `false` to exclude the node from preservation. +{{> api_box accounts_changePassword}} -For example, to preserve all `` elements with ids in template 'foo', use: +{{> api_box accounts_forgotPassword}} - Template.foo.preserve({ - 'input[id]': function (node) { return node.id; } - }); +This triggers a call +to [`Accounts.sendResetPasswordEmail`](#accounts_sendresetpasswordemail) +on the server. Pass the token the user receives in this email +to [`Accounts.resetPassword`](#accounts_resetpassword) to +complete the password reset process. -Selectors are interpreted as rooted at the top level of the template. -Each occurrence of the template operates independently, so the selectors -do not have to be unique on the entire page, only within one occurrence -of the template. Selectors will match nodes even if they are in -sub-templates. +If you are using the [`accounts-ui` package](#pkg_accounts_ui), this is handled +automatically. Otherwise, it is your responsiblity to prompt the user for the +new password and call `resetPassword`. -Preserving a node does *not* preserve its attributes or contents. They -will be updated to reflect the new HTML. Text in input fields is not -preserved unless the input field has focus, in which case the cursor and -selection are left intact. Iframes retain their navigation state and -animations continue to run as long as their parameters haven't changed. +- XXX token goes to `Accounts._resetPasswordToken`. -There are some cases where nodes can not be preserved because of -constraints inherent in the DOM API. For example, an element's tag name -can't be changed, and it can't be moved relative to its parent or other -preserved nodes. For this reason, nodes that are re-ordered or -re-parented by an update will not be preserved. +{{> api_box accounts_resetPassword}} -{{#note}} -Previous versions of Meteor had an implicit page-wide `preserve` -directive that labeled nodes by their "id" and "name" attributes. -This has been removed in favor of the explicit, opt-in mechanism. -{{/note}} - - -

Template instances

+This function accepts tokens generated +by [`Accounts.sendResetPasswordEmail`](#accounts_sendresetpasswordemail) +and +[`Accounts.sendEnrollmentEmail`](#accounts_sendenrollmentemail) -A template instance object represents an occurrence of a template in -the document. It can be used to access the DOM and it can be -assigned properties that persist across page re-renderings. +{{> api_box accounts_setPassword}} -Template instance objects are found as the value of `this` in the -`created`, `rendered`, and `destroyed` template callbacks and as an -argument to event handlers. +{{> api_box accounts_verifyEmail}} -In addition to the properties and functions described below, you can -assign additional properties of your choice to the object. Property names -starting with `_` are guaranteed to be available for your use. Use -the `created` and `destroyed` callbacks to perform initialization or -clean-up on the object. +This function accepts tokens generated +by [`Accounts.sendVerificationEmail`](#accounts_sendverificationemail). It +sets the `emails.verified` field in the user record. -You can only access `findAll`, `find`, `firstNode`, and `lastNode` -from the `rendered` callback and event handlers, not from `created` -and `destroyed`, because they require the template instance to be -in the DOM. +{{> api_box accounts_sendResetPasswordEmail}} -{{> api_box template_findAll}} +The token in this email should be passed +to [`Accounts.resetPassword`](#accounts_resetpassword). -Returns an array of DOM elements matching `selector`. +To customize the contents of the email, see +[`Accounts.emailTemplates`](#accounts_emailtemplates). -The template instance serves as the document root for the selector. Only -elements inside the template and its sub-templates can match parts of -the selector. +{{> api_box accounts_sendEnrollmentEmail}} -{{> api_box template_find}} +The token in this email should be passed +to [`Accounts.resetPassword`](#accounts_resetpassword). -Returns one DOM element matching `selector`, or `null` if there are no -such elements. +To customize the contents of the email, see +[`Accounts.emailTemplates`](#accounts_emailtemplates). -The template instance serves as the document root for the selector. Only -elements inside the template and its sub-templates can match parts of -the selector. +{{> api_box accounts_sendVerificationEmail}} -{{> api_box template_firstNode}} +The token in this email should be passed +to [`Accounts.verifyEmail`](#accounts_verifyemail). -The two nodes `firstNode` and `lastNode` indicate the extent of the -rendered template in the DOM. The rendered template includes these -nodes, their intervening siblings, and their descendents. These two -nodes are siblings (they have the same parent), and `lastNode` comes -after `firstNode`, or else they are the same node. +To customize the contents of the email, see +[`Accounts.emailTemplates`](#accounts_emailtemplates). -{{> api_box template_lastNode}} +{{> api_box accounts_emailTemplates}} -{{> api_box template_data}} +This is an `Object` with several fields that are used to generate text +for the emails by `sendResetPasswordEmail`, `sendEnrollmentEmail`, and +`sendVerificationEmail`. -This property provides access to the data context at the top level of -the template. It is updated each time the template is re-rendered. -Access is read-only and non-reactive. +Override fields of the object by assigning to them: +- `from`: A `String` with an [RFC5322](http://tools.ietf.org/html/rfc5322) From + address. By default email is from `no-reply@meteor.com`. If you wish to + receive email from users asking for help with their account, be sure to set + this to an email address that you can receive email at. +- `siteName`: The public name of your application. Defaults to the DNS name of + the application (eg: `awesome.meteor.com`). +- `resetPassword`: An `Object` with two fields: + - `resetPassword.subject`: A `Function` that takes a user object and returns + a `String` for the subject line of a reset password email. + - `resetPassword.text`: A `Function` that takes a user object and a url, and + returns the body text for a reset password email. +- `enrollAccount`: Same as `resetPassword`, but for initial password setup for + new accounts. +- `verifyEmail`: Same as `resetPassword`, but for verifying the users email + address. -{{> api_box render}} -`Meteor.render` creates a `DocumentFragment` (a sequence of DOM nodes) -that automatically updates in realtime. Most Meteor apps don't need to -call this directly, they use templates and Meteor handles the rendering. +Example: -Pass in `htmlFunc`, a function that returns an HTML -string. `Meteor.render` calls the function and turns the output into -DOM nodes. Meanwhile, it tracks the data that was used when `htmlFunc` -ran, and automatically wires up callbacks so that whenever any of the -data changes, `htmlFunc` is re-run and the DOM nodes are updated in -place. + Accounts.emailTemplates.siteName = "AwesomeSite"; + Accounts.emailTemplates.from = "AwesomeSite Admin "; + Accounts.emailTemplates.enrollAccount.subject = function (user) { + return "Welcome to Awesome Town, " + user.profile.name; + }; + Accounts.emailTemplates.enrollAccount.text = function (user, url) { + return "You have been selected to participate in building better future!" + + " To activate your account, simply click the link below:\n\n" + + url; + }; -You may insert the returned `DocumentFragment` directly into the DOM -wherever you would like it to appear. The inserted nodes will continue -to update until they are taken off the screen. Then they will be -automatically cleaned up. For more details about clean-up, see -[`Meteor.flush`](#meteor_flush). -`Meteor.render` tracks the data dependencies of `htmlFunc` by running -it in a reactive context, so it can respond to changes in any reactive -data sources used by that function. For more information, or to learn -how to make your own reactive data sources, see -[Reactivity](#reactivity). +{{> api_box accounts_config}} +{{> api_box accounts_ui_config}} Example: - // Show the number of users online. - var frag = Meteor.render(function () { - return "

There are " + Users.find({online: true}).count() + - " users online.

"; + Accounts.ui.config({ + requestPermissions: { + facebook: ['user_likes'], + github: ['user', 'repo'] + }, + passwordSignupFields: 'USERNAME_AND_OPTIONAL_EMAIL' }); - document.body.appendChild(frag); - // Find all users that have been idle for a while, and mark them as - // offline. The count on the screen will automatically update. - Users.update({idleTime: {$gt: 30}}, {online: false}); +{{> api_box accounts_validateNewUser}} -{{> api_box renderList}} +This can be called multiple times. If any of the functions return +`false` the new user creation is aborted. -Creates a `DocumentFragment` that automatically updates as the results -of a database query change. Most Meteor apps use `{{dstache}}#each}}` in -a template instead of calling this directly. +Example: -`renderList` is more efficient than using `Meteor.render` to render HTML -for a list of documents. For example, if a new document is created in -the database that matches the query, a new item will be rendered and -inserted at the appropriate place in the DOM without re-rendering the -other elements. Similarly, if a document changes position in a sorted -query, the DOM nodes will simply be moved and not re-rendered. + // All users must have a username longer than 3 characters. + Accounts.validateNewUser(function (user) { + return user.username && user.username.length >= 3; + }); -`docFunc` is called as needed to generate HTML for each document. If -you provide `elseFunc`, then whenever the query returns no results, it -will be called to render alternate content. You might use this to show -a message like "No records match your query." +{{> api_box accounts_onCreateUser}} -Each call to `docFunc` or `elseFunc` is run in its own reactive -context so that if it has other external data dependencies, it will be -individually re-run when the data changes. +Use this when you need to do more than simply accept or reject new user +creation. With this function you can programatically control the +contents of new user documents. -Example: +The function you pass will be called with two arguments: `options` and +`user`. The `options` argument comes +from [`Accounts.createUser`](#accounts_createuser) for +password-based users or from the OAuth login flow. `options` may come +from an untrusted client so make to validate any values you read from +it. The `user` argument is created on the server and contains a +proposed user object with all the automatically generated fields +required for the user to log in. - // List the titles of all of the posts that have the tag - // "frontpage". Keep the list updated as new posts are made, as tags - // change, etc. Display the selected post differently. - var frag = Meteor.renderList( - Posts.find({tags: "frontpage"}), - function(post) { - var style = Session.equals("selectedId", post._id) ? "selected" : ""; - // A real app would need to quote/sanitize post.name - return '
' + post.name + '
'; - }); - document.body.appendChild(frag); +The function should return a new user object with whatever modifications +are desired. The returned object is inserted directly into +the [`Meteor.users`](#meteor_users) collection. - // Select a post. This will cause only the selected item and the - // previously selected item to update. - var somePost = Posts.findOne({tags: "frontpage"}); - Session.set("selectedId", somePost._id); +The default create user function simply copies `options.profile` into +the new user document. Calling `onCreateUser` overrides the default +hook. This can only be called once. +Example: -{{#api_box_inline eventmaps}} + // Make members of the GitHub 'meteor' group into admins. + Accounts.onCreateUser(function(options, user) { + // http://developer.github.com/v3/orgs/members/#get-member + if (!Meteor.http.get( + "https://api.github.com/orgs/meteor/members/" + + user.services.github.username + + "?access_token=" + user.services.github.accessToken + ).error) { + user.admin = true; + } + return user; + }); -Several functions take event maps. An event map is an object where -the properties specify a set of events to handle, and the values are -the handlers for those events. The property can be in one of several -forms: +

Templates

-
-{{#dtdd "eventtype"}} -Matches a particular type of event, such as 'click'. -{{/dtdd}} +A template that you declare as `<{{! }}template name="foo"> ... ` can be accessed as the function `Template.foo`, which +returns a string of HTML when called. -{{#dtdd "eventtype selector"}} -Matches a particular type of event, but only when it appears on -an element that matches a certain CSS selector. -{{/dtdd}} +The same template may occur many times on the page, and these +occurrences are called template instances. Template instances have a +life cycle of being created, put into the document, and later taken +out of the document and destroyed. Meteor manages these stages for +you, including determining when a template instance has been removed +or replaced and should be cleaned up. You can associate data with a +template instance, and you can access its DOM nodes when it is in the +document. -{{#dtdd "event1, event2"}} -To handle more than one type of event with the same function, use a -comma-separated list. -{{/dtdd}} -
+Additionally, Meteor will maintain a template instance and its state +even if its surrounding HTML is re-rendered into new DOM nodes. As +long as the structure of template invocations is the same, Meteor will +not consider any instances to have been created or destroyed. You can +request that the same DOM nodes be retained as well using `preserve` +and `constant`. -The handler function receives two arguments: `event`, an object with -information about the event, and `template`, a [template -instance](#template_inst) for the template where the handler is -defined. The handler also receives some additional context data in -`this`, depending on the context of the current element handling the -event. In a Handlebars template, an element's context is the -Handlebars data context where that element occurs, which is set by -block helpers such as `#with` and `#each`. +There are a number of callbacks and directives that you can specify on +a named template and that apply to all instances of the template. +They are described below. -Example: +{{> api_box template_call}} - { - // Fires when any element is clicked - 'click': function (event) { ... }, +When called inside a template helper, the body of `Meteor.render`, or +other settings where reactive HTML is being generated, the resulting +HTML is annotated so that it renders as reactive DOM elements. +Otherwise, the HTML is unadorned and static. - // Fires when any element with the 'accept' class is clicked - 'click .accept': function (event) { ... }, - // Fires when 'accept' is clicked, or a key is pressed - 'keydown, click .accept': function (event) { ... } - } +{{> api_box template_rendered}} -Most events bubble up the document tree from their originating -element. For example, `'click p'` catches a click anywhere in a -paragraph, even if the click originated on a link, span, or some other -element inside the paragraph. The originating element of the event -is available as the `target` property, while the element that matched -the selector and is currently handling it is called `currentTarget`. +This callback is called once when an instance of Template.*myTemplate* is +rendered into DOM nodes and put into the document for the first time, and again +each time any part of the template is re-rendered. - { - 'click p': function (event) { - var paragraph = event.currentTarget; // always a P - var clickedElement = event.target; // could be the P or a child element - } - } +In the body of the callback, `this` is a [template +instance](#template_inst) object that is unique to this occurrence of +the template and persists across re-renderings. Use the `created` and +`destroyed` callbacks to perform initialization or clean-up on the +object. -If a selector matches multiple elements that an event bubbles to, it -will be called multiple times, for example in the case of `'click -div'` or `'click *'`. If no selector is given, the handler -will only be called once, on the original target element. +{{> api_box template_created}} -The following properties and methods are available on the event object -passed to handlers: +This callback is called when an invocation of *myTemplate* represents +a new occurrence of the template and not a re-rendering of an existing +template instance. Inside the callback, `this` is the new [template +instance](#template_inst) object. Properties you set on this object +will be visible from the `rendered` and `destroyed` callbacks and from +event handlers. -
-{{#dtdd name="type" type="String"}} -The event's type, such as "click", "blur" or "keypress". -{{/dtdd}} +This callback fires once and is the first callback to fire. Every +`created` has a corresponding `destroyed`; that is, if you get a +`created` callback with a certain template instance object in `this`, +you will eventually get a `destroyed` callback for the same object. -{{#dtdd name="target" type="DOM Element"}} -The element that originated the event. -{{/dtdd}} +{{> api_box template_destroyed}} -{{#dtdd name="currentTarget" type="DOM Element"}} -The element currently handling the event. This is the element that -matched the selector in the event map. For events that bubble, it may -be `target` or an ancestor of `target`, and its value changes as the -event bubbles. -{{/dtdd}} +This callback is called when an occurrence of a template is taken off +the page for any reason and not replaced with a re-rendering. Inside +the callback, `this` is the [template instance](#template_inst) object +being destroyed. -{{#dtdd name="which" type="Number"}} -For mouse events, the number of the mouse button (1=left, 2=middle, 3=right). -For key events, a character or key code. -{{/dtdd}} +This callback is most useful for cleaning up or undoing any external +effects of `created`. It fires once and is the last callback to fire. -{{#dtdd "stopPropagation()"}} -Prevent the event from propagating (bubbling) up to other elements. -Other event handlers matching the same element are still fired, in -this and other event maps. -{{/dtdd}} -{{#dtdd "stopImmediatePropagation()"}} -Prevent all additional event handlers from being run on this event, -including other handlers in this event map, handlers reached by -bubbling, and handlers in other event maps. -{{/dtdd}} +{{> api_box template_events}} -{{#dtdd "preventDefault()"}} -Prevents the action the browser would normally take in response to this -event, such as following a link or submitting a form. Further handlers -are still called, but cannot reverse the effect. -{{/dtdd}} +Declare event handers for instances of this template. Multiple calls add +new event handlers in addition to the existing ones. -{{#dtdd "isPropagationStopped()"}} -Returns whether `stopPropagation()` has been called for this event. -{{/dtdd}} +See [Event Maps](#eventmaps) for a detailed description of the event +map format and how event handling works in Meteor. -{{#dtdd "isImmediatePropagationStopped()"}} -Returns whether `stopImmediatePropagation()` has been called for this event. -{{/dtdd}} +{{#note}} +This syntax replaces the previous syntax: `Template.myTemplate.events = {...}`, +but for now, the old syntax still works. +{{/note}} -{{#dtdd "isDefaultPrevented()"}} -Returns whether `preventDefault()` has been called for this event. -{{/dtdd}} -
-Returning `false` from a handler is the same as calling -both `stopImmediatePropagation` and `preventDefault` on the event. +{{> api_box template_helpers}} -Event types and their uses include: +Each template has a local dictionary of helpers that are made available to it, +and this call specifies helpers to add to the template's dictionary. -
-{{#dtdd "click"}} -Mouse click on any element, including a link, button, form control, or div. -Use `preventDefault()` to prevent a clicked link from being followed. -Some ways of activating an element from the keyboard also fire `click`. -{{/dtdd}} +Example: -{{#dtdd "dblclick"}} -Double-click. -{{/dtdd}} + Template.myTemplate.helpers({ + foo: function () { + return Session.get("foo"); + } + }); -{{#dtdd "focus, blur"}} -A text input field or other form control gains or loses focus. You -can make any element focusable by giving it a `tabindex` property. -Browsers differ on whether links, checkboxes, and radio buttons are -natively focusable. These events do not bubble. -{{/dtdd}} +In Handlebars, this helper would then be invoked as `{{dstache}}foo}}`. -{{#dtdd "change"}} -A checkbox or radio button changes state. For text fields, use -`blur` or key events to respond to changes. -{{/dtdd}} +The following syntax is equivalent, but won't work for reserved property +names: -{{#dtdd "mouseenter, mouseleave"}} The pointer enters or -leaves the bounds of an element. These events do not bubble. -{{/dtdd}} + Template.myTemplate.foo = function () { + return Session.get("foo"); + }; -{{#dtdd "mousedown, mouseup"}} -The mouse button is newly down or up. -{{/dtdd}} +{{> api_box template_preserve}} -{{#dtdd "keydown, keypress, keyup"}} -The user presses a keyboard key. `keypress` is most useful for -catching typing in text fields, while `keydown` and `keyup` can be -used for arrow keys or modifier keys. -{{/dtdd}} -
+You can "preserve" a DOM element during re-rendering, leaving the +existing element in place in the document while replacing the +surrounding HTML. This means that re-rendering a template need not +disturb text fields, iframes, and other sensitive elements it +contains. The elements to preserve must be present both as nodes in +the old DOM and as tags in the new HTML. Meteor will patch the DOM +around the preserved elements. -Other DOM events are available as well, but for the events above, -Meteor has taken some care to ensure that they work uniformly in all -browsers. +{{#note}} +By default, new Meteor apps automatically include the +`preserve-inputs` package. This preserves all elements of type +`input`, `textarea`, `button`, `select`, and `option` that have unique +`id` attributes or that have `name` attributes that are unique within +an enclosing element with an `id` attribute. To turn off this default +behavior, simply remove the `preserve-inputs` package. +{{/note}} -{{/api_box_inline}} +Preservation is useful in a variety of cases where replacing a DOM +element with an identical or modified element would not have the same +effect as retaining the original element. These include: +* Input text fields and other form controls +* Elements with CSS animations +* Iframes +* Nodes with references kept in JavaScript code +If you want to preserve a whole region of the DOM, an element and its +children, or nodes not rendered by Meteor, use a [constant +region](#constant) instead. -{{#api_box_inline constant}} +To preserve nodes, pass a list of selectors each of which should match +at most one element in the template. When the template is re-rendered, +the selector is run on the old DOM and the new DOM, and Meteor will +reuse the old element in place while working in any HTML changes around +it. -You can mark a region of a template as "constant" and not subject to -re-rendering using the -`{{dstache}}#constant}}...{{dstache}}/constant}}` block helper. -Content inside the `#constant` block helper is preserved exactly as-is -even if the enclosing template is re-rendered. Changes to other parts -of the template are patched in around it in the same manner as -`preserve`. Unlike individual node preservation, a constant region -retains not only the identities of its nodes but also their attributes -and contents. The contents of the block will only be evaluated once -per occurrence of the enclosing template. +A second form of `preserve` takes a labeling function for each selector +and allows the selectors to match multiple nodes. The node-labeling +function takes a node and returns a label string that is unique for each +node, or `false` to exclude the node from preservation. -Constant regions allow non-Meteor content to be embedded in a Meteor -template. Many third-party widgets create and manage their own DOM -nodes programmatically. Typically, you put an empty element in your -template, which the widget or library will then populate with -children. Normally, when Meteor re-renders the enclosing template it -would remove the new children, since the template says it should be -empty. If the container is wrapped in a `#constant` block, however, it -is left alone; whatever content is currently in the DOM remains. +For example, to preserve all `` elements with ids in template 'foo', use: + + Template.foo.preserve({ + 'input[id]': function (node) { return node.id; } + }); + +Selectors are interpreted as rooted at the top level of the template. +Each occurrence of the template operates independently, so the selectors +do not have to be unique on the entire page, only within one occurrence +of the template. Selectors will match nodes even if they are in +sub-templates. + +Preserving a node does *not* preserve its attributes or contents. They +will be updated to reflect the new HTML. Text in input fields is not +preserved unless the input field has focus, in which case the cursor and +selection are left intact. Iframes retain their navigation state and +animations continue to run as long as their parameters haven't changed. + +There are some cases where nodes can not be preserved because of +constraints inherent in the DOM API. For example, an element's tag name +can't be changed, and it can't be moved relative to its parent or other +preserved nodes. For this reason, nodes that are re-ordered or +re-parented by an update will not be preserved. {{#note}} -Constant regions are intended for embedding non-Meteor content. -Event handlers and reactive dependencies don't currently work -correctly inside constant regions. +Previous versions of Meteor had an implicit page-wide `preserve` +directive that labeled nodes by their "id" and "name" attributes. +This has been removed in favor of the explicit, opt-in mechanism. {{/note}} -{{/api_box_inline}} - -{{#api_box_inline isolate}} +

Template instances

-Each template runs in its own reactive context. When the template -accesses a reactive data source, such as by calling `Session.get` or -making a database query, this establishes a data dependency that will -cause the whole template to be re-rendered when the data changes. -This means that the amount of re-rendering for a particular change -is affected by how you've divided your HTML into templates. +A template instance object represents an occurrence of a template in +the document. It can be used to access the DOM and it can be +assigned properties that persist across page re-renderings. -Typically, the exact extent of re-rendering is not crucial, but if you -want more control, such as for performance reasons, you can use the -`{{dstache}}#isolate}}...{{dstache}}/isolate}}` helper. Data -dependencies established inside an `#isolate` block are localized to -the block and will not in themselves cause the parent template to be -re-rendered. This block helper essentially conveys the reactivity -benefits you would get by pulling the content out into a new -sub-template. - -{{/api_box_inline}} - - - -

Accounts

+Template instance objects are found as the value of `this` in the +`created`, `rendered`, and `destroyed` template callbacks and as an +argument to event handlers. -XXX intro text +In addition to the properties and functions described below, you can +assign additional properties of your choice to the object. Property names +starting with `_` are guaranteed to be available for your use. Use +the `created` and `destroyed` callbacks to perform initialization or +clean-up on the object. -{{> api_box user}} +You can only access `findAll`, `find`, `firstNode`, and `lastNode` +from the `rendered` callback and event handlers, not from `created` +and `destroyed`, because they require the template instance to be +in the DOM. -Retreives the user record for the current user from -the [`Meteor.users`](#meteor_users) collection. +{{> api_box template_findAll}} -On the client this will be a subset of the fields in the document, only -those that are published from the server are available on the client. By -default the server publishes `username`, `emails`, and -`profile`. See [`Meteor.users`](#meteor_users) for more on -the fields used user documents. +Returns an array of DOM elements matching `selector`. -If the user is logged in but the user's database record is not fully -loaded yet, this returns an object with only the `_id` field set. During -this period [`userLoaded`](#meteor_userloaded) will return -`false`. +The template instance serves as the document root for the selector. Only +elements inside the template and its sub-templates can match parts of +the selector. -{{> api_box userId}} +{{> api_box template_find}} -{{> api_box users}} +Returns one DOM element matching `selector`, or `null` if there are no +such elements. -This collection contains one document per user. Example user document: +The template instance serves as the document root for the selector. Only +elements inside the template and its sub-templates can match parts of +the selector. - { - _id: "bbca5d6a-2156-41c4-89da-0329e8c99a4f" // userId - username: "cool_kid_13", // unique name - emails: [ - // each email address can only belong to one user. - { address: "cool@example.com", verified: true }, - { address: "another@different.com", verified: false } - ], - profile: { - // The profile is writable by the user by default. - name: "Joe Schmoe" - }, - services: { - facebook: { - id: "709050", // facebook id - accessToken: "AAACCgdX7G2...AbV9AZDZD" - }, - resume: { - loginTokens: [ - { token: "97e8c205-c7e4-47c9-9bea-8e2ccc0694cd", - when: 1349761684048 } - ] - } - } - } +{{> api_box template_firstNode}} +The two nodes `firstNode` and `lastNode` indicate the extent of the +rendered template in the DOM. The rendered template includes these +nodes, their intervening siblings, and their descendents. These two +nodes are siblings (they have the same parent), and `lastNode` comes +after `firstNode`, or else they are the same node. -Like all [Meteor.Collection](#collections)s, you can access all -documents on the server, but only those specifically published by the server are -available on the client. +{{> api_box template_lastNode}} -By default, the current user's `username`, `emails` and `profile` are -published to the client. You can publish additional fields for the -current user with: +{{> api_box template_data}} - Meteor.publish("userData", function () { - return Meteor.users.find({_id: this.userId}, - {fields: {'other': 1, 'things': 1}}); - }); +This property provides access to the data context at the top level of +the template. It is updated each time the template is re-rendered. +Access is read-only and non-reactive. -If the `autopublish` package is installed, the `username` and `profile` fields -for all users are published to all clients. To publish specific fields from all -users: - Meteor.publish("allUserData", function () { - return Meteor.users.find({}, {fields: {'nested.things': 1}}); - }); +{{> api_box render}} -Users are by default allowed to specify their own `profile` field with -[`Accounts.createUser`](#accounts_createuser) and modify it with -`Meteor.users.update`. To allow users to edit additional fields, use -[`Meteor.users.allow`](#allow). To forbid users from making any modifications to -their user document: +`Meteor.render` creates a `DocumentFragment` (a sequence of DOM nodes) +that automatically updates in realtime. Most Meteor apps don't need to +call this directly, they use templates and Meteor handles the rendering. - Meteor.users.deny({update: function () { return true; }}); +Pass in `htmlFunc`, a function that returns an HTML +string. `Meteor.render` calls the function and turns the output into +DOM nodes. Meanwhile, it tracks the data that was used when `htmlFunc` +ran, and automatically wires up callbacks so that whenever any of the +data changes, `htmlFunc` is re-run and the DOM nodes are updated in +place. +You may insert the returned `DocumentFragment` directly into the DOM +wherever you would like it to appear. The inserted nodes will continue +to update until they are taken off the screen. Then they will be +automatically cleaned up. For more details about clean-up, see +[`Meteor.flush`](#meteor_flush). -{{> api_box userLoaded}} +`Meteor.render` tracks the data dependencies of `htmlFunc` by running +it in a reactive context, so it can respond to changes in any reactive +data sources used by that function. For more information, or to learn +how to make your own reactive data sources, see +[Reactivity](#reactivity). -There are some cases when the client knows the id of the logged in user -but has not yet received the user data from the server. For example, if -the user is logged in and reloads the page the user data will be -unavailable during initial page load. +Example: -During these periods, `userLoaded` will return false -and [`user`](#meteor_user) will return an object with only -the `_id` key. + // Show the number of users online. + var frag = Meteor.render(function () { + return "

There are " + Users.find({online: true}).count() + + " users online.

"; + }); + document.body.appendChild(frag); -{{#note}} -We realize this is inconvenient. It is a temporary solution. In the -future we will either make it unnecessary or fold it into a more -general mechanism. -{{/note}} + // Find all users that have been idle for a while, and mark them as + // offline. The count on the screen will automatically update. + Users.update({idleTime: {$gt: 30}}, {online: false}); -{{> api_box logout}} +{{> api_box renderList}} -{{> api_box loginWithPassword}} +Creates a `DocumentFragment` that automatically updates as the results +of a database query change. Most Meteor apps use `{{dstache}}#each}}` in +a template instead of calling this directly. -{{> api_box loginWithOAuth}} +`renderList` is more efficient than using `Meteor.render` to render HTML +for a list of documents. For example, if a new document is created in +the database that matches the query, a new item will be rendered and +inserted at the appropriate place in the DOM without re-rendering the +other elements. Similarly, if a document changes position in a sorted +query, the DOM nodes will simply be moved and not re-rendered. -These functions initiate the login process with a third party OAuth -provider (eg: Facebook, Google, etc). When called they open a new pop-up -window that loads the provider's login page. Once the user has logged in -with the provider, the pop-up window is closed and the Meteor client -logs in to the Meteor server with the information provided by the OAuth -provider. +`docFunc` is called as needed to generate HTML for each document. If +you provide `elseFunc`, then whenever the query returns no results, it +will be called to render alternate content. You might use this to show +a message like "No records match your query." -If the user has not already granted all the permissions requested they will be -prompted to grant access to their account in the pop-up dialog. Values for the -`requestPermissions` parameter differ for each login service: +Each call to `docFunc` or `elseFunc` is run in its own reactive +context so that if it has other external data dependencies, it will be +individually re-run when the data changes. -- Facebook: -- GitHub: -- Google: +Example: -Currently, `loginWithTwitter` and `loginWithWeibo` do not support -`requestPermissions`. + // List the titles of all of the posts that have the tag + // "frontpage". Keep the list updated as new posts are made, as tags + // change, etc. Display the selected post differently. + var frag = Meteor.renderList( + Posts.find({tags: "frontpage"}), + function(post) { + var style = Session.equals("selectedId", post._id) ? "selected" : ""; + // A real app would need to quote/sanitize post.name + return '
' + post.name + '
'; + }); + document.body.appendChild(frag); + // Select a post. This will cause only the selected item and the + // previously selected item to update. + var somePost = Posts.findOne({tags: "frontpage"}); + Session.set("selectedId", somePost._id); -{{> api_box accounts_createUser}} -On the client this function logs in as the newly created user on -successful completion. On the server, it returns the newly created user -id. +{{#api_box_inline eventmaps}} -On the client, you must pass `password` and one of `username` or `email` -— enough information for the user to be able to log in again -later. On the server, you can pass any subset of these options, but the -user will not be able to log in until it has an identifier and a -password. +Several functions take event maps. An event map is an object where +the properties specify a set of events to handle, and the values are +the handlers for those events. The property can be in one of several +forms: -To create an account without a password on the server and still let the -user pick their own password, call `createUser` with the `email` option -and then -call [`Accounts.sendEnrollmentEmail`](#accounts_sendenrollmentemail). This -will send the user an email with a link to set their initial password. +
+{{#dtdd "eventtype"}} +Matches a particular type of event, such as 'click'. +{{/dtdd}} -By default the `profile` option is added directly to the new user document. To -override this behavior, use [`Accounts.onCreateUser`](#accounts_createuser). +{{#dtdd "eventtype selector"}} +Matches a particular type of event, but only when it appears on +an element that matches a certain CSS selector. +{{/dtdd}} -This function is only used for creating users with passwords. The OAuth -login flows do not use this function. +{{#dtdd "event1, event2"}} +To handle more than one type of event with the same function, use a +comma-separated list. +{{/dtdd}} +
+The handler function receives two arguments: `event`, an object with +information about the event, and `template`, a [template +instance](#template_inst) for the template where the handler is +defined. The handler also receives some additional context data in +`this`, depending on the context of the current element handling the +event. In a Handlebars template, an element's context is the +Handlebars data context where that element occurs, which is set by +block helpers such as `#with` and `#each`. -{{> api_box accounts_changePassword}} +Example: -{{> api_box accounts_forgotPassword}} + { + // Fires when any element is clicked + 'click': function (event) { ... }, -This triggers a call -to [`Accounts.sendResetPasswordEmail`](#accounts_sendresetpasswordemail) -on the server. Pass the token the user receives in this email -to [`Accounts.resetPassword`](#accounts_resetpassword) to -complete the password reset process. + // Fires when any element with the 'accept' class is clicked + 'click .accept': function (event) { ... }, -If you are using the [`accounts-ui` package](#pkg_accounts_ui), this is handled -automatically. Otherwise, it is your responsiblity to prompt the user for the -new password and call `resetPassword`. + // Fires when 'accept' is clicked, or a key is pressed + 'keydown, click .accept': function (event) { ... } + } -- XXX token goes to `Accounts._resetPasswordToken`. +Most events bubble up the document tree from their originating +element. For example, `'click p'` catches a click anywhere in a +paragraph, even if the click originated on a link, span, or some other +element inside the paragraph. The originating element of the event +is available as the `target` property, while the element that matched +the selector and is currently handling it is called `currentTarget`. -{{> api_box accounts_resetPassword}} + { + 'click p': function (event) { + var paragraph = event.currentTarget; // always a P + var clickedElement = event.target; // could be the P or a child element + } + } -This function accepts tokens generated -by [`Accounts.sendResetPasswordEmail`](#accounts_sendresetpasswordemail) -and -[`Accounts.sendEnrollmentEmail`](#accounts_sendenrollmentemail) +If a selector matches multiple elements that an event bubbles to, it +will be called multiple times, for example in the case of `'click +div'` or `'click *'`. If no selector is given, the handler +will only be called once, on the original target element. -{{> api_box accounts_setPassword}} +The following properties and methods are available on the event object +passed to handlers: -{{> api_box accounts_verifyEmail}} +
+{{#dtdd name="type" type="String"}} +The event's type, such as "click", "blur" or "keypress". +{{/dtdd}} -This function accepts tokens generated -by [`Accounts.sendVerificationEmail`](#accounts_sendverificationemail). It -sets the `emails.verified` field in the user record. +{{#dtdd name="target" type="DOM Element"}} +The element that originated the event. +{{/dtdd}} -{{> api_box accounts_sendResetPasswordEmail}} +{{#dtdd name="currentTarget" type="DOM Element"}} +The element currently handling the event. This is the element that +matched the selector in the event map. For events that bubble, it may +be `target` or an ancestor of `target`, and its value changes as the +event bubbles. +{{/dtdd}} -The token in this email should be passed -to [`Accounts.resetPassword`](#accounts_resetpassword). +{{#dtdd name="which" type="Number"}} +For mouse events, the number of the mouse button (1=left, 2=middle, 3=right). +For key events, a character or key code. +{{/dtdd}} -To customize the contents of the email, see -[`Accounts.emailTemplates`](#accounts_emailtemplates). +{{#dtdd "stopPropagation()"}} +Prevent the event from propagating (bubbling) up to other elements. +Other event handlers matching the same element are still fired, in +this and other event maps. +{{/dtdd}} -{{> api_box accounts_sendEnrollmentEmail}} +{{#dtdd "stopImmediatePropagation()"}} +Prevent all additional event handlers from being run on this event, +including other handlers in this event map, handlers reached by +bubbling, and handlers in other event maps. +{{/dtdd}} -The token in this email should be passed -to [`Accounts.resetPassword`](#accounts_resetpassword). +{{#dtdd "preventDefault()"}} +Prevents the action the browser would normally take in response to this +event, such as following a link or submitting a form. Further handlers +are still called, but cannot reverse the effect. +{{/dtdd}} -To customize the contents of the email, see -[`Accounts.emailTemplates`](#accounts_emailtemplates). +{{#dtdd "isPropagationStopped()"}} +Returns whether `stopPropagation()` has been called for this event. +{{/dtdd}} -{{> api_box accounts_sendVerificationEmail}} +{{#dtdd "isImmediatePropagationStopped()"}} +Returns whether `stopImmediatePropagation()` has been called for this event. +{{/dtdd}} -The token in this email should be passed -to [`Accounts.verifyEmail`](#accounts_verifyemail). +{{#dtdd "isDefaultPrevented()"}} +Returns whether `preventDefault()` has been called for this event. +{{/dtdd}} +
-To customize the contents of the email, see -[`Accounts.emailTemplates`](#accounts_emailtemplates). +Returning `false` from a handler is the same as calling +both `stopImmediatePropagation` and `preventDefault` on the event. -{{> api_box accounts_emailTemplates}} +Event types and their uses include: -This is an `Object` with several fields that are used to generate text -for the emails by `sendResetPasswordEmail`, `sendEnrollmentEmail`, and -`sendVerificationEmail`. +
+{{#dtdd "click"}} +Mouse click on any element, including a link, button, form control, or div. +Use `preventDefault()` to prevent a clicked link from being followed. +Some ways of activating an element from the keyboard also fire `click`. +{{/dtdd}} -Override fields of the object by assigning to them: +{{#dtdd "dblclick"}} +Double-click. +{{/dtdd}} -- `from`: A `String` with an [RFC5322](http://tools.ietf.org/html/rfc5322) From - address. By default email is from `no-reply@meteor.com`. If you wish to - receive email from users asking for help with their account, be sure to set - this to an email address that you can receive email at. -- `siteName`: The public name of your application. Defaults to the DNS name of - the application (eg: `awesome.meteor.com`). -- `resetPassword`: An `Object` with two fields: - - `resetPassword.subject`: A `Function` that takes a user object and returns - a `String` for the subject line of a reset password email. - - `resetPassword.text`: A `Function` that takes a user object and a url, and - returns the body text for a reset password email. -- `enrollAccount`: Same as `resetPassword`, but for initial password setup for - new accounts. -- `verifyEmail`: Same as `resetPassword`, but for verifying the users email - address. +{{#dtdd "focus, blur"}} +A text input field or other form control gains or loses focus. You +can make any element focusable by giving it a `tabindex` property. +Browsers differ on whether links, checkboxes, and radio buttons are +natively focusable. These events do not bubble. +{{/dtdd}} +{{#dtdd "change"}} +A checkbox or radio button changes state. For text fields, use +`blur` or key events to respond to changes. +{{/dtdd}} -Example: +{{#dtdd "mouseenter, mouseleave"}} The pointer enters or +leaves the bounds of an element. These events do not bubble. +{{/dtdd}} - Accounts.emailTemplates.siteName = "AwesomeSite"; - Accounts.emailTemplates.from = "AwesomeSite Admin "; - Accounts.emailTemplates.enrollAccount.subject = function (user) { - return "Welcome to Awesome Town, " + user.profile.name; - }; - Accounts.emailTemplates.enrollAccount.text = function (user, url) { - return "You have been selected to participate in building better future!" - + " To activate your account, simply click the link below:\n\n" - + url; - }; +{{#dtdd "mousedown, mouseup"}} +The mouse button is newly down or up. +{{/dtdd}} +{{#dtdd "keydown, keypress, keyup"}} +The user presses a keyboard key. `keypress` is most useful for +catching typing in text fields, while `keydown` and `keyup` can be +used for arrow keys or modifier keys. +{{/dtdd}} +
-{{> api_box accounts_config}} -{{> api_box accounts_ui_config}} +Other DOM events are available as well, but for the events above, +Meteor has taken some care to ensure that they work uniformly in all +browsers. -Example: +{{/api_box_inline}} - Accounts.ui.config({ - requestPermissions: { - facebook: ['user_likes'], - github: ['user', 'repo'] - }, - passwordSignupFields: 'USERNAME_AND_OPTIONAL_EMAIL' - }); -{{> api_box accounts_validateNewUser}} -This can be called multiple times. If any of the functions return -`false` the new user creation is aborted. +{{#api_box_inline constant}} -Example: +You can mark a region of a template as "constant" and not subject to +re-rendering using the +`{{dstache}}#constant}}...{{dstache}}/constant}}` block helper. +Content inside the `#constant` block helper is preserved exactly as-is +even if the enclosing template is re-rendered. Changes to other parts +of the template are patched in around it in the same manner as +`preserve`. Unlike individual node preservation, a constant region +retains not only the identities of its nodes but also their attributes +and contents. The contents of the block will only be evaluated once +per occurrence of the enclosing template. - // All users must have a username longer than 3 characters. - Accounts.validateNewUser(function (user) { - return user.username && user.username.length >= 3; - }); +Constant regions allow non-Meteor content to be embedded in a Meteor +template. Many third-party widgets create and manage their own DOM +nodes programmatically. Typically, you put an empty element in your +template, which the widget or library will then populate with +children. Normally, when Meteor re-renders the enclosing template it +would remove the new children, since the template says it should be +empty. If the container is wrapped in a `#constant` block, however, it +is left alone; whatever content is currently in the DOM remains. -{{> api_box accounts_onCreateUser}} +{{#note}} +Constant regions are intended for embedding non-Meteor content. +Event handlers and reactive dependencies don't currently work +correctly inside constant regions. +{{/note}} -Use this when you need to do more than simply accept or reject new user -creation. With this function you can programatically control the -contents of new user documents. -The function you pass will be called with two arguments: `options` and -`user`. The `options` argument comes -from [`Accounts.createUser`](#accounts_createuser) for -password-based users or from the OAuth login flow. `options` may come -from an untrusted client so make to validate any values you read from -it. The `user` argument is created on the server and contains a -proposed user object with all the automatically generated fields -required for the user to log in. +{{/api_box_inline}} -The function should return a new user object with whatever modifications -are desired. The returned object is inserted directly into -the [`Meteor.users`](#meteor_users) collection. +{{#api_box_inline isolate}} -The default create user function simply copies `options.profile` into -the new user document. Calling `onCreateUser` overrides the default -hook. This can only be called once. +Each template runs in its own reactive context. When the template +accesses a reactive data source, such as by calling `Session.get` or +making a database query, this establishes a data dependency that will +cause the whole template to be re-rendered when the data changes. +This means that the amount of re-rendering for a particular change +is affected by how you've divided your HTML into templates. -Example: +Typically, the exact extent of re-rendering is not crucial, but if you +want more control, such as for performance reasons, you can use the +`{{dstache}}#isolate}}...{{dstache}}/isolate}}` helper. Data +dependencies established inside an `#isolate` block are localized to +the block and will not in themselves cause the parent template to be +re-rendered. This block helper essentially conveys the reactivity +benefits you would get by pulling the content out into a new +sub-template. - // Make members of the GitHub 'meteor' group into admins. - Accounts.onCreateUser(function(options, user) { - // http://developer.github.com/v3/orgs/members/#get-member - if (!Meteor.http.get( - "https://api.github.com/orgs/meteor/members/" + - user.services.github.username + - "?access_token=" + user.services.github.accessToken - ).error) { - user.admin = true; - } - return user; - }); +{{/api_box_inline}}

Timers

diff --git a/docs/client/docs.js b/docs/client/docs.js index c36dcd02aad..849fc3c10e8 100644 --- a/docs/client/docs.js +++ b/docs/client/docs.js @@ -154,30 +154,6 @@ var toc = [ "Session.equals" ], - {name: "Templates", id: "templates_api"}, [ - {prefix: "Template", instance: "myTemplate", id: "template_call"}, [ - {name: "rendered", id: "template_rendered"}, - {name: "created", id: "template_created"}, - {name: "destroyed", id: "template_destroyed"}, - {name: "events", id: "template_events"}, - {name: "helpers", id: "template_helpers"}, - {name: "preserve", id: "template_preserve"} - ], - {name: "Template instances", id: "template_inst"}, [ - {instance: "this", name: "findAll", id: "template_findAll"}, - {instance: "this", name: "find", id: "template_find"}, - {instance: "this", name: "firstNode", id: "template_firstNode"}, - {instance: "this", name: "lastNode", id: "template_lastNode"}, - {instance: "this", name: "data", id: "template_data"} - ], - "Meteor.render", - "Meteor.renderList", - {type: "spacer"}, - {name: "Event maps", style: "noncode"}, - {name: "Constant regions", style: "noncode", id: "constant"}, - {name: "Reactivity isolation", style: "noncode", id: "isolate"} - ], - {name: "Accounts", id: "accounts_api"}, [ "Meteor.user", "Meteor.userId", @@ -212,6 +188,29 @@ var toc = [ "Accounts.onCreateUser" ], + {name: "Templates", id: "templates_api"}, [ + {prefix: "Template", instance: "myTemplate", id: "template_call"}, [ + {name: "rendered", id: "template_rendered"}, + {name: "created", id: "template_created"}, + {name: "destroyed", id: "template_destroyed"}, + {name: "events", id: "template_events"}, + {name: "helpers", id: "template_helpers"}, + {name: "preserve", id: "template_preserve"} + ], + {name: "Template instances", id: "template_inst"}, [ + {instance: "this", name: "findAll", id: "template_findAll"}, + {instance: "this", name: "find", id: "template_find"}, + {instance: "this", name: "firstNode", id: "template_firstNode"}, + {instance: "this", name: "lastNode", id: "template_lastNode"}, + {instance: "this", name: "data", id: "template_data"} + ], + "Meteor.render", + "Meteor.renderList", + {type: "spacer"}, + {name: "Event maps", style: "noncode"}, + {name: "Constant regions", style: "noncode", id: "constant"}, + {name: "Reactivity isolation", style: "noncode", id: "isolate"} + ], "Timers", [ "Meteor.setTimeout", From 14e164567246bb44b70a1dd663d900529774bda7 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 15 Oct 2012 15:20:52 -0700 Subject: [PATCH 21/52] Avoid horizontal scrollbars in navbar. --- docs/client/docs.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/client/docs.css b/docs/client/docs.css index b4661d17308..5d561510c08 100644 --- a/docs/client/docs.css +++ b/docs/client/docs.css @@ -437,10 +437,10 @@ pre { /* ipad landscape and desktop */ #main { width: 600px; - margin-left: 310px; /* nav width + padding */ + margin-left: 330px; /* nav width + padding */ } #nav { - width: 250px; + width: 270px; } .github-ribbon { display: block; From 9669e5f6de760c0dc8805dffa7465ae0f8f7fac7 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 15 Oct 2012 15:22:38 -0700 Subject: [PATCH 22/52] Make Accounts.changePassword header fit on one line. --- docs/client/docs.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/client/docs.css b/docs/client/docs.css index 5d561510c08..4f98beac79f 100644 --- a/docs/client/docs.css +++ b/docs/client/docs.css @@ -436,7 +436,7 @@ pre { @media (min-width: 1024px) { /* ipad landscape and desktop */ #main { - width: 600px; + width: 610px; margin-left: 330px; /* nav width + padding */ } #nav { From ac2262db726782928745b753958c4eae9df7512e Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 15 Oct 2012 15:46:09 -0700 Subject: [PATCH 23/52] Add missing word. --- docs/client/api.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/client/api.html b/docs/client/api.html index 8c25b41ac78..7eb8184d52c 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -1123,7 +1123,7 @@

Accounts

those that are published from the server are available on the client. By default the server publishes `username`, `emails`, and `profile`. See [`Meteor.users`](#meteor_users) for more on -the fields used user documents. +the fields used in user documents. If the user is logged in but the user's database record is not fully loaded yet, this returns an object with only the `_id` field set. During From b53e2dcbb9284eb5aeb970b388412b58c5e2a5e3 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 15 Oct 2012 15:46:49 -0700 Subject: [PATCH 24/52] Don't add target=_blank to internal links. --- docs/client/docs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/client/docs.js b/docs/client/docs.js index 849fc3c10e8..06b8bcd6ea6 100644 --- a/docs/client/docs.js +++ b/docs/client/docs.js @@ -62,7 +62,7 @@ Meteor.startup(function () { }); // Make external links open in a new tab. - $('a:not([href^="#")').attr('target', '_blank'); + $('a:not([href^="#"])').attr('target', '_blank'); }); var toc = [ From 6e58cf7dae8ee5b65c831341f91249eea26791ec Mon Sep 17 00:00:00 2001 From: Avital Oliver Date: Mon, 15 Oct 2012 14:42:19 -0700 Subject: [PATCH 25/52] Validate options passed to Accounts.config and Accounts.ui.config --- packages/accounts-base/accounts_common.js | 16 ++++++-- packages/accounts-base/accounts_server.js | 2 +- packages/accounts-base/accounts_tests.js | 9 +++++ packages/accounts-ui-unstyled/accounts_ui.js | 22 ++++++++--- .../accounts-ui-unstyled/accounts_ui_tests.js | 17 +++++++++ packages/accounts-ui-unstyled/package.js | 6 +++ packages/mongo-livedata/allow_tests.js | 37 +++++++++++++++++++ packages/mongo-livedata/collection.js | 19 +++++++++- 8 files changed, 117 insertions(+), 11 deletions(-) create mode 100644 packages/accounts-ui-unstyled/accounts_ui_tests.js diff --git a/packages/accounts-base/accounts_common.js b/packages/accounts-base/accounts_common.js index f83aced0148..adeb7213463 100644 --- a/packages/accounts-base/accounts_common.js +++ b/packages/accounts-base/accounts_common.js @@ -12,12 +12,22 @@ if (!Accounts._options) { // - forbidClientAccountCreation {Boolean} // Do not allow clients to create accounts directly. Accounts.config = function(options) { - _.each(["sendVerificationEmail", "forbidClientAccountCreation"], function(key) { + // validate option keys + var VALID_KEYS = ["sendVerificationEmail", "forbidClientAccountCreation"]; + _.each(_.keys(options), function (key) { + if (!_.contains(VALID_KEYS, key)) { + throw new Error("Accounts.config: Invalid key: " + key); + } + }); + + // set values in Accounts._options + _.each(VALID_KEYS, function (key) { if (key in options) { - if (key in Accounts._options) + if (key in Accounts._options) { throw new Error("Can't set `" + key + "` more than once"); - else + } else { Accounts._options[key] = options[key]; + } } }); }; diff --git a/packages/accounts-base/accounts_server.js b/packages/accounts-base/accounts_server.js index 9e681fbc886..c75ca1e87b8 100644 --- a/packages/accounts-base/accounts_server.js +++ b/packages/accounts-base/accounts_server.js @@ -316,7 +316,7 @@ return true; }, - fields: ['_id'] // we only look at _id. + fetch: ['_id'] // we only look at _id. }); /// DEFAULT INDEXES ON USERS diff --git a/packages/accounts-base/accounts_tests.js b/packages/accounts-base/accounts_tests.js index b771e17de13..a60b8c0890a 100644 --- a/packages/accounts-base/accounts_tests.js +++ b/packages/accounts-base/accounts_tests.js @@ -1,3 +1,12 @@ +// XXX it'd be cool to also test that the right thing happens if options +// *are* validated, but Accounts._options is global state which makes this hard +// (impossible?) +Tinytest.add('accounts - config validates keys', function (test) { + test.throws(function () { + Accounts.config({foo: "bar"}); + }); +}); + Tinytest.add('accounts - updateOrCreateUserFromExternalService', function (test) { var facebookId = Meteor.uuid(); var weiboId1 = Meteor.uuid(); diff --git a/packages/accounts-ui-unstyled/accounts_ui.js b/packages/accounts-ui-unstyled/accounts_ui.js index 46c823b9b0f..770a77c9de7 100644 --- a/packages/accounts-ui-unstyled/accounts_ui.js +++ b/packages/accounts-ui-unstyled/accounts_ui.js @@ -9,6 +9,14 @@ if (!Accounts.ui._options) { Accounts.ui.config = function(options) { + // validate options keys + var VALID_KEYS = ['passwordSignupFields', 'requestPermissions']; + _.each(_.keys(options), function (key) { + if (!_.contains(VALID_KEYS, key)) + throw new Error("Accounts.ui.config: Invalid key: " + key); + }); + + // deal with `passwordSignupFields` if (options.passwordSignupFields) { if (_.contains([ "USERNAME_AND_EMAIL", @@ -17,20 +25,24 @@ Accounts.ui.config = function(options) { "EMAIL_ONLY" ], options.passwordSignupFields)) { if (Accounts.ui._options.passwordSignupFields) - throw new Error("Can't set `passwordSignupFields` more than once"); + throw new Error("Accounts.ui.config: Can't set `passwordSignupFields` more than once"); else Accounts.ui._options.passwordSignupFields = options.passwordSignupFields; } else { - throw new Error("Invalid option for `passwordSignupFields`: " + options.passwordSignupFields); + throw new Error("Accounts.ui.config: Invalid option for `passwordSignupFields`: " + options.passwordSignupFields); } } + // deal with `requestPermissions` if (options.requestPermissions) { _.each(options.requestPermissions, function (scope, service) { - if (Accounts.ui._options.requestPermissions[service]) - throw new Error("Can't set `requestPermissions` more than once for " + service); - else + if (Accounts.ui._options.requestPermissions[service]) { + throw new Error("Accounts.ui.config: Can't set `requestPermissions` more than once for " + service); + } else if (!(scope instanceof Array)) { + throw new Error("Accounts.ui.config: Value for `requestPermissions` must be an array"); + } else { Accounts.ui._options.requestPermissions[service] = scope; + } }); } }; diff --git a/packages/accounts-ui-unstyled/accounts_ui_tests.js b/packages/accounts-ui-unstyled/accounts_ui_tests.js new file mode 100644 index 00000000000..e65989f5d85 --- /dev/null +++ b/packages/accounts-ui-unstyled/accounts_ui_tests.js @@ -0,0 +1,17 @@ +// XXX it'd be cool to also test that the right thing happens if options +// *are* validated, but Accouns.ui._options is global state which makes this hard +// (impossible?) +Tinytest.add('accounts-ui - config validates keys', function (test) { + test.throws(function () { + Accounts.ui.config({foo: "bar"}); + }); + + test.throws(function () { + Accounts.ui.config({passwordSignupFields: "not a valid option"}); + }); + + test.throws(function () { + Accounts.ui.config({requestPermissions: {facebook: "not an array"}}); + }); +}); + diff --git a/packages/accounts-ui-unstyled/package.js b/packages/accounts-ui-unstyled/package.js index 4defc581ad4..ef41ac3bf02 100644 --- a/packages/accounts-ui-unstyled/package.js +++ b/packages/accounts-ui-unstyled/package.js @@ -21,3 +21,9 @@ Package.on_use(function (api) { 'login_buttons_dropdown.js', 'login_buttons_dialogs.js'], 'client'); }); + +Package.on_test(function (api) { + api.use('accounts-ui-unstyled'); + api.use('tinytest'); + api.add_files('accounts_ui_tests.js', 'client'); +}); diff --git a/packages/mongo-livedata/allow_tests.js b/packages/mongo-livedata/allow_tests.js index 15f92cde815..84a6c44e391 100644 --- a/packages/mongo-livedata/allow_tests.js +++ b/packages/mongo-livedata/allow_tests.js @@ -186,6 +186,43 @@ // if (Meteor.isServer) { + Tinytest.add("collection - allow and deny validate options", function (test) { + var collection = new Meteor.Collection(null); + + test.throws(function () { + collection.allow({invalidOption: true}); + }); + test.throws(function () { + collection.deny({invalidOption: true}); + }); + + _.each(['insert', 'update', 'remove', 'fetch'], function (key) { + var options = {}; + options[key] = true; + test.throws(function () { + collection.allow(options); + }); + test.throws(function () { + collection.deny(options); + }); + }); + + _.each(['insert', 'update', 'remove'], function (key) { + var options = {}; + options[key] = ['an array']; // this should be a function, not an array + test.throws(function () { + collection.allow(options); + }); + test.throws(function () { + collection.deny(options); + }); + }); + + test.throws(function () { + collection.allow({fetch: function () {}}); // this should be an array + }); + }); + Tinytest.add("collection - calling allow restricts", function (test) { var collection = new Meteor.Collection(null); test.equal(collection._restricted, false); diff --git a/packages/mongo-livedata/collection.js b/packages/mongo-livedata/collection.js index 7ebfdc81365..a22378f4e24 100644 --- a/packages/mongo-livedata/collection.js +++ b/packages/mongo-livedata/collection.js @@ -281,19 +281,34 @@ Meteor.Collection.prototype._ensureIndex = function (index, options) { (function () { var addValidator = function(allowOrDeny, options) { + // validate keys + var VALID_KEYS = ['insert', 'update', 'remove', 'fetch']; + _.each(_.keys(options), function (key) { + if (!_.contains(VALID_KEYS, key)) + throw new Error(allowOrDeny + ": Invalid key: " + key); + }); + var self = this; self._restricted = true; _.each(['insert', 'update', 'remove'], function (name) { - if (options[name]) + if (options[name]) { + if (!(options[name] instanceof Function)) { + throw new Error(allowOrDeny + ": Value for `" + name + "` must be a function"); + } self._validators[name][allowOrDeny].push(options[name]); + } }); // Only update the fetch fields if we're passed things that affect // fetching. This way allow({}) and allow({insert: f}) don't result in // setting fetchAllFields - if (options.update || options.remove || options.fetch) + if (options.update || options.remove || options.fetch) { + if (options.fetch && !(options.fetch instanceof Array)) { + throw new Error(allowOrDeny + ": Value for `fetch` must be an array"); + } self._updateFetch(options.fetch); + } }; Meteor.Collection.prototype.allow = function(options) { From e3a1f7f4284f307cf910504f36febc010c83aa0e Mon Sep 17 00:00:00 2001 From: Avital Oliver Date: Mon, 15 Oct 2012 16:12:09 -0700 Subject: [PATCH 26/52] Refer to OAuth less in docs. Small improvements to accounts-ui docs --- docs/client/api.html | 21 ++++++++++----------- docs/client/api.js | 10 +++++----- docs/client/docs.js | 10 +++++----- docs/client/packages/accounts-ui.html | 5 ++++- 4 files changed, 24 insertions(+), 22 deletions(-) diff --git a/docs/client/api.html b/docs/client/api.html index 7eb8184d52c..d91a7a04c5b 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -1214,15 +1214,16 @@

Accounts

{{> api_box loginWithPassword}} -{{> api_box loginWithOAuth}} +{{> api_box loginWithExternalService}} -These functions initiate the login process with a third party OAuth -provider (eg: Facebook, Google, etc). When called they open a new pop-up +These functions initiate the login process with an external +service (eg: Facebook, Google, etc), using OAuth. When called they open a new pop-up window that loads the provider's login page. Once the user has logged in with the provider, the pop-up window is closed and the Meteor client -logs in to the Meteor server with the information provided by the OAuth -provider. +logs in to the Meteor server with the information provided by the external +service. + If the user has not already granted all the permissions requested they will be prompted to grant access to their account in the pop-up dialog. Values for the `requestPermissions` parameter differ for each login service: @@ -1230,9 +1231,7 @@

Accounts

- Facebook: - GitHub: - Google: - -Currently, `loginWithTwitter` and `loginWithWeibo` do not support -`requestPermissions`. +- Twitter, Weibo: currently not supported {{> api_box accounts_createUser}} @@ -1256,8 +1255,8 @@

Accounts

By default the `profile` option is added directly to the new user document. To override this behavior, use [`Accounts.onCreateUser`](#accounts_createuser). -This function is only used for creating users with passwords. The OAuth -login flows do not use this function. +This function is only used for creating users with passwords. The external +service login flows do not use this function. {{> api_box accounts_changePassword}} @@ -1388,7 +1387,7 @@

Accounts

The function you pass will be called with two arguments: `options` and `user`. The `options` argument comes from [`Accounts.createUser`](#accounts_createuser) for -password-based users or from the OAuth login flow. `options` may come +password-based users or from an extenal service login flow. `options` may come from an untrusted client so make to validate any values you read from it. The `user` argument is created on the server and contains a proposed user object with all the automatically generated fields diff --git a/docs/client/api.js b/docs/client/api.js index 732da9f7cf7..204804c5818 100644 --- a/docs/client/api.js +++ b/docs/client/api.js @@ -752,11 +752,11 @@ Template.api.loginWithPassword = { }; -Template.api.loginWithOAuth = { - id: "meteor_loginwithoauth", - name: "Meteor.loginWithOAuthProvider([options], [callback])", +Template.api.loginWithExternalService = { + id: "meteor_loginwithexternalservice", + name: "Meteor.loginWithExternalService([options], [callback])", locus: "Client", - descr: ["Log the user in using an external OAuth service."], + descr: ["Log the user in using an external service."], args: [ { name: "callback", @@ -1013,7 +1013,7 @@ Template.api.accounts_ui_config = { { name: "requestPermissions", type: "Object", - descr: "Which [permissions](#meteor_loginwithoauth) to request from the user for each OAuth service." + descr: "Which [permissions](#requestpermissions) to request from the user for each external service." }, { name: "passwordSignupFields", diff --git a/docs/client/docs.js b/docs/client/docs.js index 06b8bcd6ea6..c75932d6e9a 100644 --- a/docs/client/docs.js +++ b/docs/client/docs.js @@ -161,11 +161,11 @@ var toc = [ "Meteor.userLoaded", "Meteor.logout", "Meteor.loginWithPassword", - {name: "Meteor.loginWithFacebook", id: "meteor_loginwithoauth"}, - {name: "Meteor.loginWithGithub", id: "meteor_loginwithoauth"}, - {name: "Meteor.loginWithGoogle", id: "meteor_loginwithoauth"}, - {name: "Meteor.loginWithTwitter", id: "meteor_loginwithoauth"}, - {name: "Meteor.loginWithWeibo", id: "meteor_loginwithoauth"}, + {name: "Meteor.loginWithFacebook", id: "meteor_loginwithexternalservice"}, + {name: "Meteor.loginWithGithub", id: "meteor_loginwithexternalservice"}, + {name: "Meteor.loginWithGoogle", id: "meteor_loginwithexternalservice"}, + {name: "Meteor.loginWithTwitter", id: "meteor_loginwithexternalservice"}, + {name: "Meteor.loginWithWeibo", id: "meteor_loginwithexternalservice"}, {type: "spacer"}, "Accounts.createUser", diff --git a/docs/client/packages/accounts-ui.html b/docs/client/packages/accounts-ui.html index 7e7c4f01300..da9b7ead143 100644 --- a/docs/client/packages/accounts-ui.html +++ b/docs/client/packages/accounts-ui.html @@ -12,10 +12,13 @@ Then simply add the `{{dstache}}loginButtons}}` helper to an HTML file. This will place a login widget on the page. If there is only one OAuth provider configured, this will add one login/logout button. If you -use `accounts-password` or have more than one OAuth provider, this will +usoe `accounts-password` or have more than one external login service, this will add a 'Sign in' link which opens a dropdown menu with login options. To make the login dropdown right aligned, use `{{dstache}}loginButtons align=right}}`. +To configure the behavior of `{{dstache}}loginButtons}}`, use +[`Accounts.ui.config`](#accounts_ui_config). + `accounts-ui` also includes modal popup dialogs to handle links from [`sendResetPasswordEmail`](#accounts_sendresetpasswordemail), [`sendVerificationEmail`](#accounts_sendverificationemail), and [`sendEnrollmentEmail`](#accounts_sendenrollmentemail). These From 2e7697e9a04bb4263320fe4e56addbdca9b96dfe Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 15 Oct 2012 16:06:20 -0700 Subject: [PATCH 27/52] Fix back button in docs. --- docs/client/docs.js | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/docs/client/docs.js b/docs/client/docs.js index c75932d6e9a..97734f96a40 100644 --- a/docs/client/docs.js +++ b/docs/client/docs.js @@ -48,17 +48,25 @@ Meteor.startup(function () { } }); - $('#main, #nav').delegate("a[href^='#']", 'click', function (evt) { - evt.preventDefault(); - var sel = $(this).attr('href'); + window.onhashchange = function () { + scrollToSection(location.hash); + }; + + var scrollToSection = function (section) { ignore_waypoints = true; - Session.set("section", sel.substr(1)); + Session.set("section", section.substr(1)); scroller().animate({ - scrollTop: $(sel).offset().top + scrollTop: $(section).offset().top }, 500, 'swing', function () { - window.location.hash = sel; + window.location.hash = section; ignore_waypoints = false; }); + }; + + $('#main, #nav').delegate("a[href^='#']", 'click', function (evt) { + evt.preventDefault(); + var sel = $(this).attr('href'); + scrollToSection(sel); }); // Make external links open in a new tab. From 93090b846cb76aab8c63cfb91382950bad2dfa9a Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 15 Oct 2012 16:18:25 -0700 Subject: [PATCH 28/52] Fix doc typos. --- docs/client/api.html | 2 +- docs/client/packages/accounts-ui.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/client/api.html b/docs/client/api.html index d91a7a04c5b..f584edc3527 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -1387,7 +1387,7 @@

Accounts

The function you pass will be called with two arguments: `options` and `user`. The `options` argument comes from [`Accounts.createUser`](#accounts_createuser) for -password-based users or from an extenal service login flow. `options` may come +password-based users or from an external service login flow. `options` may come from an untrusted client so make to validate any values you read from it. The `user` argument is created on the server and contains a proposed user object with all the automatically generated fields diff --git a/docs/client/packages/accounts-ui.html b/docs/client/packages/accounts-ui.html index da9b7ead143..7fe11e9fa9c 100644 --- a/docs/client/packages/accounts-ui.html +++ b/docs/client/packages/accounts-ui.html @@ -12,7 +12,7 @@ Then simply add the `{{dstache}}loginButtons}}` helper to an HTML file. This will place a login widget on the page. If there is only one OAuth provider configured, this will add one login/logout button. If you -usoe `accounts-password` or have more than one external login service, this will +use `accounts-password` or have more than one external login service, this will add a 'Sign in' link which opens a dropdown menu with login options. To make the login dropdown right aligned, use `{{dstache}}loginButtons align=right}}`. From ef634e216e1b1c475d9a135c5f65d4da7313b4e8 Mon Sep 17 00:00:00 2001 From: Avital Oliver Date: Mon, 15 Oct 2012 16:19:02 -0700 Subject: [PATCH 29/52] docs: small fix --- docs/client/api.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/client/api.html b/docs/client/api.html index f584edc3527..4070362241a 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -1231,7 +1231,7 @@

Accounts

- Facebook: - GitHub: - Google: -- Twitter, Weibo: currently not supported +- Twitter, Weibo: `requestPermissions` currently not supported {{> api_box accounts_createUser}} From 8d1e4afc0e314e90c01865070dda1205eaa815b9 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 15 Oct 2012 16:26:07 -0700 Subject: [PATCH 30/52] Docs: clean up accounts-ui section. --- docs/client/packages/accounts-ui.html | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/client/packages/accounts-ui.html b/docs/client/packages/accounts-ui.html index 7fe11e9fa9c..2c42ccfab7f 100644 --- a/docs/client/packages/accounts-ui.html +++ b/docs/client/packages/accounts-ui.html @@ -5,16 +5,17 @@ A turn-key user interface for Meteor Accounts. To add Accounts and a set of login controls to an application add the -`accounts-ui` package and one or more login provider package: +`accounts-ui` package and at least one login provider package: `accounts-password`, `accounts-facebook`, `accounts-github`, -`accounts-google`, `accounts-twitter`, and `accounts-weibo`. +`accounts-google`, `accounts-twitter`, or `accounts-weibo`. -Then simply add the `{{dstache}}loginButtons}}` helper to an HTML -file. This will place a login widget on the page. If there is only one -OAuth provider configured, this will add one login/logout button. If you -use `accounts-password` or have more than one external login service, this will -add a 'Sign in' link which opens a dropdown menu with login options. To -make the login dropdown right aligned, use `{{dstache}}loginButtons align=right}}`. +Then simply add the `{{dstache}}loginButtons}}` helper to an HTML file. This +will place a login widget on the page. If there is only one provider configured +and it is an external service, this will add a login/logout button. If you use +`accounts-password` or use multiple external login services, this will add +a "Sign in" link which opens a dropdown menu with login options. To make the +login dropdown right aligned (useful if you position the login buttons +at the right edge of the screen), use `{{dstache}}loginButtons align=right}}`. To configure the behavior of `{{dstache}}loginButtons}}`, use [`Accounts.ui.config`](#accounts_ui_config). @@ -22,7 +23,7 @@ `accounts-ui` also includes modal popup dialogs to handle links from [`sendResetPasswordEmail`](#accounts_sendresetpasswordemail), [`sendVerificationEmail`](#accounts_sendverificationemail), and [`sendEnrollmentEmail`](#accounts_sendenrollmentemail). These -do not have be manually placed in HTML, they are automatically activated +do not have be manually placed in HTML: they are automatically activated when the URLs are loaded. From 8460c028b902801f8843036713d8b0bd347e7e19 Mon Sep 17 00:00:00 2001 From: Nick Martin Date: Mon, 15 Oct 2012 16:33:29 -0700 Subject: [PATCH 31/52] Break password-only stuff into a separate section. Reorder a bit. --- docs/client/api.html | 130 ++++++++++++++++++++-------------------- docs/client/api.js | 139 ++++++++++++++++++++++--------------------- docs/client/docs.js | 15 ++--- 3 files changed, 145 insertions(+), 139 deletions(-) diff --git a/docs/client/api.html b/docs/client/api.html index 4070362241a..abe169499db 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -1233,6 +1233,73 @@

Accounts

- Google: - Twitter, Weibo: `requestPermissions` currently not supported +{{> api_box accounts_config}} +{{> api_box accounts_ui_config}} + +Example: + + Accounts.ui.config({ + requestPermissions: { + facebook: ['user_likes'], + github: ['user', 'repo'] + }, + passwordSignupFields: 'USERNAME_AND_OPTIONAL_EMAIL' + }); + +{{> api_box accounts_validateNewUser}} + +This can be called multiple times. If any of the functions return +`false` the new user creation is aborted. + +Example: + + // All users must have a username longer than 3 characters. + Accounts.validateNewUser(function (user) { + return user.username && user.username.length >= 3; + }); + +{{> api_box accounts_onCreateUser}} + +Use this when you need to do more than simply accept or reject new user +creation. With this function you can programatically control the +contents of new user documents. + +The function you pass will be called with two arguments: `options` and +`user`. The `options` argument comes +from [`Accounts.createUser`](#accounts_createuser) for +password-based users or from an external service login flow. `options` may come +from an untrusted client so make to validate any values you read from +it. The `user` argument is created on the server and contains a +proposed user object with all the automatically generated fields +required for the user to log in. + +The function should return a new user object with whatever modifications +are desired. The returned object is inserted directly into +the [`Meteor.users`](#meteor_users) collection. + +The default create user function simply copies `options.profile` into +the new user document. Calling `onCreateUser` overrides the default +hook. This can only be called once. + +Example: + + // Make members of the GitHub 'meteor' group into admins. + Accounts.onCreateUser(function(options, user) { + // http://developer.github.com/v3/orgs/members/#get-member + if (!Meteor.http.get( + "https://api.github.com/orgs/meteor/members/" + + user.services.github.username + + "?access_token=" + user.services.github.accessToken + ).error) { + user.admin = true; + } + return user; + }); + + +

Passwords

+ +XXX intro text mentioning SRP {{> api_box accounts_createUser}} @@ -1353,69 +1420,6 @@

Accounts

}; -{{> api_box accounts_config}} -{{> api_box accounts_ui_config}} - -Example: - - Accounts.ui.config({ - requestPermissions: { - facebook: ['user_likes'], - github: ['user', 'repo'] - }, - passwordSignupFields: 'USERNAME_AND_OPTIONAL_EMAIL' - }); - -{{> api_box accounts_validateNewUser}} - -This can be called multiple times. If any of the functions return -`false` the new user creation is aborted. - -Example: - - // All users must have a username longer than 3 characters. - Accounts.validateNewUser(function (user) { - return user.username && user.username.length >= 3; - }); - -{{> api_box accounts_onCreateUser}} - -Use this when you need to do more than simply accept or reject new user -creation. With this function you can programatically control the -contents of new user documents. - -The function you pass will be called with two arguments: `options` and -`user`. The `options` argument comes -from [`Accounts.createUser`](#accounts_createuser) for -password-based users or from an external service login flow. `options` may come -from an untrusted client so make to validate any values you read from -it. The `user` argument is created on the server and contains a -proposed user object with all the automatically generated fields -required for the user to log in. - -The function should return a new user object with whatever modifications -are desired. The returned object is inserted directly into -the [`Meteor.users`](#meteor_users) collection. - -The default create user function simply copies `options.profile` into -the new user document. Calling `onCreateUser` overrides the default -hook. This can only be called once. - -Example: - - // Make members of the GitHub 'meteor' group into admins. - Accounts.onCreateUser(function(options, user) { - // http://developer.github.com/v3/orgs/members/#get-member - if (!Meteor.http.get( - "https://api.github.com/orgs/meteor/members/" + - user.services.github.username + - "?access_token=" + user.services.github.accessToken - ).error) { - user.admin = true; - } - return user; - }); -

Templates

A template that you declare as `<{{! }}template name="foo"> ... Date: Mon, 15 Oct 2012 16:33:45 -0700 Subject: [PATCH 32/52] docs: change onCreateUser example --- docs/client/api.html | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/docs/client/api.html b/docs/client/api.html index abe169499db..f6b102c3fc3 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -1283,16 +1283,9 @@

Accounts

Example: - // Make members of the GitHub 'meteor' group into admins. + // Support for playing D&D: Roll 3d6 for dexterity Accounts.onCreateUser(function(options, user) { - // http://developer.github.com/v3/orgs/members/#get-member - if (!Meteor.http.get( - "https://api.github.com/orgs/meteor/members/" + - user.services.github.username + - "?access_token=" + user.services.github.accessToken - ).error) { - user.admin = true; - } + user.dexterity = _.random(1, 6) + _.random(1, 6) + _.random(1, 6) return user; }); From fe70fbd60b9b961846b2b9d4074743df39943a64 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 15 Oct 2012 16:46:39 -0700 Subject: [PATCH 33/52] Don't use _.random that we don't have. --- docs/client/api.html | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/client/api.html b/docs/client/api.html index f6b102c3fc3..81a94b4a87f 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -1283,9 +1283,12 @@

Accounts

Example: + + // Support for playing D&D: Roll 3d6 for dexterity Accounts.onCreateUser(function(options, user) { - user.dexterity = _.random(1, 6) + _.random(1, 6) + _.random(1, 6) + var d6 = function () { return Math.floor(Math.random() * 6) + 1; }; + user.dexterity = d6() + d6() + d6(); return user; }); From ad1656b2cf516dda4f5df5a3cd8536676fb68037 Mon Sep 17 00:00:00 2001 From: Matt DeBergalis Date: Sun, 14 Oct 2012 19:59:53 -0700 Subject: [PATCH 34/52] D3.js package --- docs/client/docs.js | 1 + docs/client/packages.html | 1 + docs/client/packages/d3.html | 17 + packages/d3/d3.v2.js | 7026 ++++++++++++++++++++++++++++++++++ packages/d3/package.js | 7 + 5 files changed, 7052 insertions(+) create mode 100644 docs/client/packages/d3.html create mode 100644 packages/d3/d3.v2.js create mode 100644 packages/d3/package.js diff --git a/docs/client/docs.js b/docs/client/docs.js index 92b951f73d9..5190ee9ad16 100644 --- a/docs/client/docs.js +++ b/docs/client/docs.js @@ -265,6 +265,7 @@ var toc = [ "backbone", "bootstrap", "coffeescript", + "d3", "force-ssl", "jquery", "less", diff --git a/docs/client/packages.html b/docs/client/packages.html index 035dedc8223..863c5231ebf 100644 --- a/docs/client/packages.html +++ b/docs/client/packages.html @@ -21,6 +21,7 @@

Packages

{{> pkg_backbone}} {{> pkg_bootstrap}} {{> pkg_coffeescript}} +{{> pkg_d3}} {{> pkg_force_ssl}} {{> pkg_jquery}} {{> pkg_less}} diff --git a/docs/client/packages/d3.html b/docs/client/packages/d3.html new file mode 100644 index 00000000000..a7cf49a82bd --- /dev/null +++ b/docs/client/packages/d3.html @@ -0,0 +1,17 @@ + diff --git a/packages/d3/d3.v2.js b/packages/d3/d3.v2.js new file mode 100644 index 00000000000..44fc0b072c1 --- /dev/null +++ b/packages/d3/d3.v2.js @@ -0,0 +1,7026 @@ +(function() { + function d3_class(ctor, properties) { + try { + for (var key in properties) { + Object.defineProperty(ctor.prototype, key, { + value: properties[key], + enumerable: false + }); + } + } catch (e) { + ctor.prototype = properties; + } + } + function d3_arrayCopy(pseudoarray) { + var i = -1, n = pseudoarray.length, array = []; + while (++i < n) array.push(pseudoarray[i]); + return array; + } + function d3_arraySlice(pseudoarray) { + return Array.prototype.slice.call(pseudoarray); + } + function d3_Map() {} + function d3_identity(d) { + return d; + } + function d3_this() { + return this; + } + function d3_true() { + return true; + } + function d3_functor(v) { + return typeof v === "function" ? v : function() { + return v; + }; + } + function d3_rebind(target, source, method) { + return function() { + var value = method.apply(source, arguments); + return arguments.length ? target : value; + }; + } + function d3_number(x) { + return x != null && !isNaN(x); + } + function d3_zipLength(d) { + return d.length; + } + function d3_splitter(d) { + return d == null; + } + function d3_collapse(s) { + return s.trim().replace(/\s+/g, " "); + } + function d3_range_integerScale(x) { + var k = 1; + while (x * k % 1) k *= 10; + return k; + } + function d3_dispatch() {} + function d3_dispatch_event(dispatch) { + function event() { + var z = listeners, i = -1, n = z.length, l; + while (++i < n) if (l = z[i].on) l.apply(this, arguments); + return dispatch; + } + var listeners = [], listenerByName = new d3_Map; + event.on = function(name, listener) { + var l = listenerByName.get(name), i; + if (arguments.length < 2) return l && l.on; + if (l) { + l.on = null; + listeners = listeners.slice(0, i = listeners.indexOf(l)).concat(listeners.slice(i + 1)); + listenerByName.remove(name); + } + if (listener) listeners.push(listenerByName.set(name, { + on: listener + })); + return dispatch; + }; + return event; + } + function d3_format_precision(x, p) { + return p - (x ? 1 + Math.floor(Math.log(x + Math.pow(10, 1 + Math.floor(Math.log(x) / Math.LN10) - p)) / Math.LN10) : 1); + } + function d3_format_typeDefault(x) { + return x + ""; + } + function d3_format_group(value) { + var i = value.lastIndexOf("."), f = i >= 0 ? value.substring(i) : (i = value.length, ""), t = []; + while (i > 0) t.push(value.substring(i -= 3, i + 3)); + return t.reverse().join(",") + f; + } + function d3_formatPrefix(d, i) { + var k = Math.pow(10, Math.abs(8 - i) * 3); + return { + scale: i > 8 ? function(d) { + return d / k; + } : function(d) { + return d * k; + }, + symbol: d + }; + } + function d3_ease_clamp(f) { + return function(t) { + return t <= 0 ? 0 : t >= 1 ? 1 : f(t); + }; + } + function d3_ease_reverse(f) { + return function(t) { + return 1 - f(1 - t); + }; + } + function d3_ease_reflect(f) { + return function(t) { + return .5 * (t < .5 ? f(2 * t) : 2 - f(2 - 2 * t)); + }; + } + function d3_ease_identity(t) { + return t; + } + function d3_ease_poly(e) { + return function(t) { + return Math.pow(t, e); + }; + } + function d3_ease_sin(t) { + return 1 - Math.cos(t * Math.PI / 2); + } + function d3_ease_exp(t) { + return Math.pow(2, 10 * (t - 1)); + } + function d3_ease_circle(t) { + return 1 - Math.sqrt(1 - t * t); + } + function d3_ease_elastic(a, p) { + var s; + if (arguments.length < 2) p = .45; + if (arguments.length < 1) { + a = 1; + s = p / 4; + } else s = p / (2 * Math.PI) * Math.asin(1 / a); + return function(t) { + return 1 + a * Math.pow(2, 10 * -t) * Math.sin((t - s) * 2 * Math.PI / p); + }; + } + function d3_ease_back(s) { + if (!s) s = 1.70158; + return function(t) { + return t * t * ((s + 1) * t - s); + }; + } + function d3_ease_bounce(t) { + return t < 1 / 2.75 ? 7.5625 * t * t : t < 2 / 2.75 ? 7.5625 * (t -= 1.5 / 2.75) * t + .75 : t < 2.5 / 2.75 ? 7.5625 * (t -= 2.25 / 2.75) * t + .9375 : 7.5625 * (t -= 2.625 / 2.75) * t + .984375; + } + function d3_eventCancel() { + d3.event.stopPropagation(); + d3.event.preventDefault(); + } + function d3_eventSource() { + var e = d3.event, s; + while (s = e.sourceEvent) e = s; + return e; + } + function d3_eventDispatch(target) { + var dispatch = new d3_dispatch, i = 0, n = arguments.length; + while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch); + dispatch.of = function(thiz, argumentz) { + return function(e1) { + try { + var e0 = e1.sourceEvent = d3.event; + e1.target = target; + d3.event = e1; + dispatch[e1.type].apply(thiz, argumentz); + } finally { + d3.event = e0; + } + }; + }; + return dispatch; + } + function d3_transform(m) { + var r0 = [ m.a, m.b ], r1 = [ m.c, m.d ], kx = d3_transformNormalize(r0), kz = d3_transformDot(r0, r1), ky = d3_transformNormalize(d3_transformCombine(r1, r0, -kz)) || 0; + if (r0[0] * r1[1] < r1[0] * r0[1]) { + r0[0] *= -1; + r0[1] *= -1; + kx *= -1; + kz *= -1; + } + this.rotate = (kx ? Math.atan2(r0[1], r0[0]) : Math.atan2(-r1[0], r1[1])) * d3_transformDegrees; + this.translate = [ m.e, m.f ]; + this.scale = [ kx, ky ]; + this.skew = ky ? Math.atan2(kz, ky) * d3_transformDegrees : 0; + } + function d3_transformDot(a, b) { + return a[0] * b[0] + a[1] * b[1]; + } + function d3_transformNormalize(a) { + var k = Math.sqrt(d3_transformDot(a, a)); + if (k) { + a[0] /= k; + a[1] /= k; + } + return k; + } + function d3_transformCombine(a, b, k) { + a[0] += k * b[0]; + a[1] += k * b[1]; + return a; + } + function d3_interpolateByName(name) { + return name == "transform" ? d3.interpolateTransform : d3.interpolate; + } + function d3_uninterpolateNumber(a, b) { + b = b - (a = +a) ? 1 / (b - a) : 0; + return function(x) { + return (x - a) * b; + }; + } + function d3_uninterpolateClamp(a, b) { + b = b - (a = +a) ? 1 / (b - a) : 0; + return function(x) { + return Math.max(0, Math.min(1, (x - a) * b)); + }; + } + function d3_Color() {} + function d3_rgb(r, g, b) { + return new d3_Rgb(r, g, b); + } + function d3_Rgb(r, g, b) { + this.r = r; + this.g = g; + this.b = b; + } + function d3_rgb_hex(v) { + return v < 16 ? "0" + Math.max(0, v).toString(16) : Math.min(255, v).toString(16); + } + function d3_rgb_parse(format, rgb, hsl) { + var r = 0, g = 0, b = 0, m1, m2, name; + m1 = /([a-z]+)\((.*)\)/i.exec(format); + if (m1) { + m2 = m1[2].split(","); + switch (m1[1]) { + case "hsl": + { + return hsl(parseFloat(m2[0]), parseFloat(m2[1]) / 100, parseFloat(m2[2]) / 100); + } + case "rgb": + { + return rgb(d3_rgb_parseNumber(m2[0]), d3_rgb_parseNumber(m2[1]), d3_rgb_parseNumber(m2[2])); + } + } + } + if (name = d3_rgb_names.get(format)) return rgb(name.r, name.g, name.b); + if (format != null && format.charAt(0) === "#") { + if (format.length === 4) { + r = format.charAt(1); + r += r; + g = format.charAt(2); + g += g; + b = format.charAt(3); + b += b; + } else if (format.length === 7) { + r = format.substring(1, 3); + g = format.substring(3, 5); + b = format.substring(5, 7); + } + r = parseInt(r, 16); + g = parseInt(g, 16); + b = parseInt(b, 16); + } + return rgb(r, g, b); + } + function d3_rgb_hsl(r, g, b) { + var min = Math.min(r /= 255, g /= 255, b /= 255), max = Math.max(r, g, b), d = max - min, h, s, l = (max + min) / 2; + if (d) { + s = l < .5 ? d / (max + min) : d / (2 - max - min); + if (r == max) h = (g - b) / d + (g < b ? 6 : 0); else if (g == max) h = (b - r) / d + 2; else h = (r - g) / d + 4; + h *= 60; + } else { + s = h = 0; + } + return d3_hsl(h, s, l); + } + function d3_rgb_lab(r, g, b) { + r = d3_rgb_xyz(r); + g = d3_rgb_xyz(g); + b = d3_rgb_xyz(b); + var x = d3_xyz_lab((.4124564 * r + .3575761 * g + .1804375 * b) / d3_lab_X), y = d3_xyz_lab((.2126729 * r + .7151522 * g + .072175 * b) / d3_lab_Y), z = d3_xyz_lab((.0193339 * r + .119192 * g + .9503041 * b) / d3_lab_Z); + return d3_lab(116 * y - 16, 500 * (x - y), 200 * (y - z)); + } + function d3_rgb_xyz(r) { + return (r /= 255) <= .04045 ? r / 12.92 : Math.pow((r + .055) / 1.055, 2.4); + } + function d3_rgb_parseNumber(c) { + var f = parseFloat(c); + return c.charAt(c.length - 1) === "%" ? Math.round(f * 2.55) : f; + } + function d3_hsl(h, s, l) { + return new d3_Hsl(h, s, l); + } + function d3_Hsl(h, s, l) { + this.h = h; + this.s = s; + this.l = l; + } + function d3_hsl_rgb(h, s, l) { + function v(h) { + if (h > 360) h -= 360; else if (h < 0) h += 360; + if (h < 60) return m1 + (m2 - m1) * h / 60; + if (h < 180) return m2; + if (h < 240) return m1 + (m2 - m1) * (240 - h) / 60; + return m1; + } + function vv(h) { + return Math.round(v(h) * 255); + } + var m1, m2; + h = h % 360; + if (h < 0) h += 360; + s = s < 0 ? 0 : s > 1 ? 1 : s; + l = l < 0 ? 0 : l > 1 ? 1 : l; + m2 = l <= .5 ? l * (1 + s) : l + s - l * s; + m1 = 2 * l - m2; + return d3_rgb(vv(h + 120), vv(h), vv(h - 120)); + } + function d3_hcl(h, c, l) { + return new d3_Hcl(h, c, l); + } + function d3_Hcl(h, c, l) { + this.h = h; + this.c = c; + this.l = l; + } + function d3_hcl_lab(h, c, l) { + return d3_lab(l, Math.cos(h *= Math.PI / 180) * c, Math.sin(h) * c); + } + function d3_lab(l, a, b) { + return new d3_Lab(l, a, b); + } + function d3_Lab(l, a, b) { + this.l = l; + this.a = a; + this.b = b; + } + function d3_lab_rgb(l, a, b) { + var y = (l + 16) / 116, x = y + a / 500, z = y - b / 200; + x = d3_lab_xyz(x) * d3_lab_X; + y = d3_lab_xyz(y) * d3_lab_Y; + z = d3_lab_xyz(z) * d3_lab_Z; + return d3_rgb(d3_xyz_rgb(3.2404542 * x - 1.5371385 * y - .4985314 * z), d3_xyz_rgb(-.969266 * x + 1.8760108 * y + .041556 * z), d3_xyz_rgb(.0556434 * x - .2040259 * y + 1.0572252 * z)); + } + function d3_lab_hcl(l, a, b) { + return d3_hcl(Math.atan2(b, a) / Math.PI * 180, Math.sqrt(a * a + b * b), l); + } + function d3_lab_xyz(x) { + return x > .206893034 ? x * x * x : (x - 4 / 29) / 7.787037; + } + function d3_xyz_lab(x) { + return x > .008856 ? Math.pow(x, 1 / 3) : 7.787037 * x + 4 / 29; + } + function d3_xyz_rgb(r) { + return Math.round(255 * (r <= .00304 ? 12.92 * r : 1.055 * Math.pow(r, 1 / 2.4) - .055)); + } + function d3_selection(groups) { + d3_arraySubclass(groups, d3_selectionPrototype); + return groups; + } + function d3_selection_selector(selector) { + return function() { + return d3_select(selector, this); + }; + } + function d3_selection_selectorAll(selector) { + return function() { + return d3_selectAll(selector, this); + }; + } + function d3_selection_attr(name, value) { + function attrNull() { + this.removeAttribute(name); + } + function attrNullNS() { + this.removeAttributeNS(name.space, name.local); + } + function attrConstant() { + this.setAttribute(name, value); + } + function attrConstantNS() { + this.setAttributeNS(name.space, name.local, value); + } + function attrFunction() { + var x = value.apply(this, arguments); + if (x == null) this.removeAttribute(name); else this.setAttribute(name, x); + } + function attrFunctionNS() { + var x = value.apply(this, arguments); + if (x == null) this.removeAttributeNS(name.space, name.local); else this.setAttributeNS(name.space, name.local, x); + } + name = d3.ns.qualify(name); + return value == null ? name.local ? attrNullNS : attrNull : typeof value === "function" ? name.local ? attrFunctionNS : attrFunction : name.local ? attrConstantNS : attrConstant; + } + function d3_selection_classedRe(name) { + return new RegExp("(?:^|\\s+)" + d3.requote(name) + "(?:\\s+|$)", "g"); + } + function d3_selection_classed(name, value) { + function classedConstant() { + var i = -1; + while (++i < n) name[i](this, value); + } + function classedFunction() { + var i = -1, x = value.apply(this, arguments); + while (++i < n) name[i](this, x); + } + name = name.trim().split(/\s+/).map(d3_selection_classedName); + var n = name.length; + return typeof value === "function" ? classedFunction : classedConstant; + } + function d3_selection_classedName(name) { + var re = d3_selection_classedRe(name); + return function(node, value) { + if (c = node.classList) return value ? c.add(name) : c.remove(name); + var c = node.className, cb = c.baseVal != null, cv = cb ? c.baseVal : c; + if (value) { + re.lastIndex = 0; + if (!re.test(cv)) { + cv = d3_collapse(cv + " " + name); + if (cb) c.baseVal = cv; else node.className = cv; + } + } else if (cv) { + cv = d3_collapse(cv.replace(re, " ")); + if (cb) c.baseVal = cv; else node.className = cv; + } + }; + } + function d3_selection_style(name, value, priority) { + function styleNull() { + this.style.removeProperty(name); + } + function styleConstant() { + this.style.setProperty(name, value, priority); + } + function styleFunction() { + var x = value.apply(this, arguments); + if (x == null) this.style.removeProperty(name); else this.style.setProperty(name, x, priority); + } + return value == null ? styleNull : typeof value === "function" ? styleFunction : styleConstant; + } + function d3_selection_property(name, value) { + function propertyNull() { + delete this[name]; + } + function propertyConstant() { + this[name] = value; + } + function propertyFunction() { + var x = value.apply(this, arguments); + if (x == null) delete this[name]; else this[name] = x; + } + return value == null ? propertyNull : typeof value === "function" ? propertyFunction : propertyConstant; + } + function d3_selection_dataNode(data) { + return { + __data__: data + }; + } + function d3_selection_filter(selector) { + return function() { + return d3_selectMatches(this, selector); + }; + } + function d3_selection_sortComparator(comparator) { + if (!arguments.length) comparator = d3.ascending; + return function(a, b) { + return comparator(a && a.__data__, b && b.__data__); + }; + } + function d3_selection_on(type, listener, capture) { + function onRemove() { + var wrapper = this[name]; + if (wrapper) { + this.removeEventListener(type, wrapper, wrapper.$); + delete this[name]; + } + } + function onAdd() { + function wrapper(e) { + var o = d3.event; + d3.event = e; + args[0] = node.__data__; + try { + listener.apply(node, args); + } finally { + d3.event = o; + } + } + var node = this, args = arguments; + onRemove.call(this); + this.addEventListener(type, this[name] = wrapper, wrapper.$ = capture); + wrapper._ = listener; + } + var name = "__on" + type, i = type.indexOf("."); + if (i > 0) type = type.substring(0, i); + return listener ? onAdd : onRemove; + } + function d3_selection_each(groups, callback) { + for (var j = 0, m = groups.length; j < m; j++) { + for (var group = groups[j], i = 0, n = group.length, node; i < n; i++) { + if (node = group[i]) callback(node, i, j); + } + } + return groups; + } + function d3_selection_enter(selection) { + d3_arraySubclass(selection, d3_selection_enterPrototype); + return selection; + } + function d3_transition(groups, id, time) { + d3_arraySubclass(groups, d3_transitionPrototype); + var tweens = new d3_Map, event = d3.dispatch("start", "end"), ease = d3_transitionEase; + groups.id = id; + groups.time = time; + groups.tween = function(name, tween) { + if (arguments.length < 2) return tweens.get(name); + if (tween == null) tweens.remove(name); else tweens.set(name, tween); + return groups; + }; + groups.ease = function(value) { + if (!arguments.length) return ease; + ease = typeof value === "function" ? value : d3.ease.apply(d3, arguments); + return groups; + }; + groups.each = function(type, listener) { + if (arguments.length < 2) return d3_transition_each.call(groups, type); + event.on(type, listener); + return groups; + }; + d3.timer(function(elapsed) { + return d3_selection_each(groups, function(node, i, j) { + function start(elapsed) { + if (lock.active > id) return stop(); + lock.active = id; + tweens.forEach(function(key, value) { + if (value = value.call(node, d, i)) { + tweened.push(value); + } + }); + event.start.call(node, d, i); + if (!tick(elapsed)) d3.timer(tick, 0, time); + return 1; + } + function tick(elapsed) { + if (lock.active !== id) return stop(); + var t = (elapsed - delay) / duration, e = ease(t), n = tweened.length; + while (n > 0) { + tweened[--n].call(node, e); + } + if (t >= 1) { + stop(); + d3_transitionId = id; + event.end.call(node, d, i); + d3_transitionId = 0; + return 1; + } + } + function stop() { + if (!--lock.count) delete node.__transition__; + return 1; + } + var tweened = [], delay = node.delay, duration = node.duration, lock = (node = node.node).__transition__ || (node.__transition__ = { + active: 0, + count: 0 + }), d = node.__data__; + ++lock.count; + delay <= elapsed ? start(elapsed) : d3.timer(start, delay, time); + }); + }, 0, time); + return groups; + } + function d3_transition_each(callback) { + var id = d3_transitionId, ease = d3_transitionEase, delay = d3_transitionDelay, duration = d3_transitionDuration; + d3_transitionId = this.id; + d3_transitionEase = this.ease(); + d3_selection_each(this, function(node, i, j) { + d3_transitionDelay = node.delay; + d3_transitionDuration = node.duration; + callback.call(node = node.node, node.__data__, i, j); + }); + d3_transitionId = id; + d3_transitionEase = ease; + d3_transitionDelay = delay; + d3_transitionDuration = duration; + return this; + } + function d3_tweenNull(d, i, a) { + return a != "" && d3_tweenRemove; + } + function d3_tweenByName(b, name) { + return d3.tween(b, d3_interpolateByName(name)); + } + function d3_timer_step() { + var elapsed, now = Date.now(), t1 = d3_timer_queue; + while (t1) { + elapsed = now - t1.then; + if (elapsed >= t1.delay) t1.flush = t1.callback(elapsed); + t1 = t1.next; + } + var delay = d3_timer_flush() - now; + if (delay > 24) { + if (isFinite(delay)) { + clearTimeout(d3_timer_timeout); + d3_timer_timeout = setTimeout(d3_timer_step, delay); + } + d3_timer_interval = 0; + } else { + d3_timer_interval = 1; + d3_timer_frame(d3_timer_step); + } + } + function d3_timer_flush() { + var t0 = null, t1 = d3_timer_queue, then = Infinity; + while (t1) { + if (t1.flush) { + delete d3_timer_byId[t1.callback.id]; + t1 = t0 ? t0.next = t1.next : d3_timer_queue = t1.next; + } else { + then = Math.min(then, t1.then + t1.delay); + t1 = (t0 = t1).next; + } + } + return then; + } + function d3_mousePoint(container, e) { + var svg = container.ownerSVGElement || container; + if (svg.createSVGPoint) { + var point = svg.createSVGPoint(); + if (d3_mouse_bug44083 < 0 && (window.scrollX || window.scrollY)) { + svg = d3.select(document.body).append("svg").style("position", "absolute").style("top", 0).style("left", 0); + var ctm = svg[0][0].getScreenCTM(); + d3_mouse_bug44083 = !(ctm.f || ctm.e); + svg.remove(); + } + if (d3_mouse_bug44083) { + point.x = e.pageX; + point.y = e.pageY; + } else { + point.x = e.clientX; + point.y = e.clientY; + } + point = point.matrixTransform(container.getScreenCTM().inverse()); + return [ point.x, point.y ]; + } + var rect = container.getBoundingClientRect(); + return [ e.clientX - rect.left - container.clientLeft, e.clientY - rect.top - container.clientTop ]; + } + function d3_noop() {} + function d3_scaleExtent(domain) { + var start = domain[0], stop = domain[domain.length - 1]; + return start < stop ? [ start, stop ] : [ stop, start ]; + } + function d3_scaleRange(scale) { + return scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range()); + } + function d3_scale_nice(domain, nice) { + var i0 = 0, i1 = domain.length - 1, x0 = domain[i0], x1 = domain[i1], dx; + if (x1 < x0) { + dx = i0, i0 = i1, i1 = dx; + dx = x0, x0 = x1, x1 = dx; + } + if (nice = nice(x1 - x0)) { + domain[i0] = nice.floor(x0); + domain[i1] = nice.ceil(x1); + } + return domain; + } + function d3_scale_niceDefault() { + return Math; + } + function d3_scale_linear(domain, range, interpolate, clamp) { + function rescale() { + var linear = Math.min(domain.length, range.length) > 2 ? d3_scale_polylinear : d3_scale_bilinear, uninterpolate = clamp ? d3_uninterpolateClamp : d3_uninterpolateNumber; + output = linear(domain, range, uninterpolate, interpolate); + input = linear(range, domain, uninterpolate, d3.interpolate); + return scale; + } + function scale(x) { + return output(x); + } + var output, input; + scale.invert = function(y) { + return input(y); + }; + scale.domain = function(x) { + if (!arguments.length) return domain; + domain = x.map(Number); + return rescale(); + }; + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + return rescale(); + }; + scale.rangeRound = function(x) { + return scale.range(x).interpolate(d3.interpolateRound); + }; + scale.clamp = function(x) { + if (!arguments.length) return clamp; + clamp = x; + return rescale(); + }; + scale.interpolate = function(x) { + if (!arguments.length) return interpolate; + interpolate = x; + return rescale(); + }; + scale.ticks = function(m) { + return d3_scale_linearTicks(domain, m); + }; + scale.tickFormat = function(m) { + return d3_scale_linearTickFormat(domain, m); + }; + scale.nice = function() { + d3_scale_nice(domain, d3_scale_linearNice); + return rescale(); + }; + scale.copy = function() { + return d3_scale_linear(domain, range, interpolate, clamp); + }; + return rescale(); + } + function d3_scale_linearRebind(scale, linear) { + return d3.rebind(scale, linear, "range", "rangeRound", "interpolate", "clamp"); + } + function d3_scale_linearNice(dx) { + dx = Math.pow(10, Math.round(Math.log(dx) / Math.LN10) - 1); + return dx && { + floor: function(x) { + return Math.floor(x / dx) * dx; + }, + ceil: function(x) { + return Math.ceil(x / dx) * dx; + } + }; + } + function d3_scale_linearTickRange(domain, m) { + var extent = d3_scaleExtent(domain), span = extent[1] - extent[0], step = Math.pow(10, Math.floor(Math.log(span / m) / Math.LN10)), err = m / span * step; + if (err <= .15) step *= 10; else if (err <= .35) step *= 5; else if (err <= .75) step *= 2; + extent[0] = Math.ceil(extent[0] / step) * step; + extent[1] = Math.floor(extent[1] / step) * step + step * .5; + extent[2] = step; + return extent; + } + function d3_scale_linearTicks(domain, m) { + return d3.range.apply(d3, d3_scale_linearTickRange(domain, m)); + } + function d3_scale_linearTickFormat(domain, m) { + return d3.format(",." + Math.max(0, -Math.floor(Math.log(d3_scale_linearTickRange(domain, m)[2]) / Math.LN10 + .01)) + "f"); + } + function d3_scale_bilinear(domain, range, uninterpolate, interpolate) { + var u = uninterpolate(domain[0], domain[1]), i = interpolate(range[0], range[1]); + return function(x) { + return i(u(x)); + }; + } + function d3_scale_polylinear(domain, range, uninterpolate, interpolate) { + var u = [], i = [], j = 0, k = Math.min(domain.length, range.length) - 1; + if (domain[k] < domain[0]) { + domain = domain.slice().reverse(); + range = range.slice().reverse(); + } + while (++j <= k) { + u.push(uninterpolate(domain[j - 1], domain[j])); + i.push(interpolate(range[j - 1], range[j])); + } + return function(x) { + var j = d3.bisect(domain, x, 1, k) - 1; + return i[j](u[j](x)); + }; + } + function d3_scale_log(linear, log) { + function scale(x) { + return linear(log(x)); + } + var pow = log.pow; + scale.invert = function(x) { + return pow(linear.invert(x)); + }; + scale.domain = function(x) { + if (!arguments.length) return linear.domain().map(pow); + log = x[0] < 0 ? d3_scale_logn : d3_scale_logp; + pow = log.pow; + linear.domain(x.map(log)); + return scale; + }; + scale.nice = function() { + linear.domain(d3_scale_nice(linear.domain(), d3_scale_niceDefault)); + return scale; + }; + scale.ticks = function() { + var extent = d3_scaleExtent(linear.domain()), ticks = []; + if (extent.every(isFinite)) { + var i = Math.floor(extent[0]), j = Math.ceil(extent[1]), u = pow(extent[0]), v = pow(extent[1]); + if (log === d3_scale_logn) { + ticks.push(pow(i)); + for (; i++ < j; ) for (var k = 9; k > 0; k--) ticks.push(pow(i) * k); + } else { + for (; i < j; i++) for (var k = 1; k < 10; k++) ticks.push(pow(i) * k); + ticks.push(pow(i)); + } + for (i = 0; ticks[i] < u; i++) {} + for (j = ticks.length; ticks[j - 1] > v; j--) {} + ticks = ticks.slice(i, j); + } + return ticks; + }; + scale.tickFormat = function(n, format) { + if (arguments.length < 2) format = d3_scale_logFormat; + if (arguments.length < 1) return format; + var k = Math.max(.1, n / scale.ticks().length), f = log === d3_scale_logn ? (e = -1e-12, Math.floor) : (e = 1e-12, Math.ceil), e; + return function(d) { + return d / pow(f(log(d) + e)) <= k ? format(d) : ""; + }; + }; + scale.copy = function() { + return d3_scale_log(linear.copy(), log); + }; + return d3_scale_linearRebind(scale, linear); + } + function d3_scale_logp(x) { + return Math.log(x < 0 ? 0 : x) / Math.LN10; + } + function d3_scale_logn(x) { + return -Math.log(x > 0 ? 0 : -x) / Math.LN10; + } + function d3_scale_pow(linear, exponent) { + function scale(x) { + return linear(powp(x)); + } + var powp = d3_scale_powPow(exponent), powb = d3_scale_powPow(1 / exponent); + scale.invert = function(x) { + return powb(linear.invert(x)); + }; + scale.domain = function(x) { + if (!arguments.length) return linear.domain().map(powb); + linear.domain(x.map(powp)); + return scale; + }; + scale.ticks = function(m) { + return d3_scale_linearTicks(scale.domain(), m); + }; + scale.tickFormat = function(m) { + return d3_scale_linearTickFormat(scale.domain(), m); + }; + scale.nice = function() { + return scale.domain(d3_scale_nice(scale.domain(), d3_scale_linearNice)); + }; + scale.exponent = function(x) { + if (!arguments.length) return exponent; + var domain = scale.domain(); + powp = d3_scale_powPow(exponent = x); + powb = d3_scale_powPow(1 / exponent); + return scale.domain(domain); + }; + scale.copy = function() { + return d3_scale_pow(linear.copy(), exponent); + }; + return d3_scale_linearRebind(scale, linear); + } + function d3_scale_powPow(e) { + return function(x) { + return x < 0 ? -Math.pow(-x, e) : Math.pow(x, e); + }; + } + function d3_scale_ordinal(domain, ranger) { + function scale(x) { + return range[((index.get(x) || index.set(x, domain.push(x))) - 1) % range.length]; + } + function steps(start, step) { + return d3.range(domain.length).map(function(i) { + return start + step * i; + }); + } + var index, range, rangeBand; + scale.domain = function(x) { + if (!arguments.length) return domain; + domain = []; + index = new d3_Map; + var i = -1, n = x.length, xi; + while (++i < n) if (!index.has(xi = x[i])) index.set(xi, domain.push(xi)); + return scale[ranger.t].apply(scale, ranger.a); + }; + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + rangeBand = 0; + ranger = { + t: "range", + a: arguments + }; + return scale; + }; + scale.rangePoints = function(x, padding) { + if (arguments.length < 2) padding = 0; + var start = x[0], stop = x[1], step = (stop - start) / (Math.max(1, domain.length - 1) + padding); + range = steps(domain.length < 2 ? (start + stop) / 2 : start + step * padding / 2, step); + rangeBand = 0; + ranger = { + t: "rangePoints", + a: arguments + }; + return scale; + }; + scale.rangeBands = function(x, padding, outerPadding) { + if (arguments.length < 2) padding = 0; + if (arguments.length < 3) outerPadding = padding; + var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = (stop - start) / (domain.length - padding + 2 * outerPadding); + range = steps(start + step * outerPadding, step); + if (reverse) range.reverse(); + rangeBand = step * (1 - padding); + ranger = { + t: "rangeBands", + a: arguments + }; + return scale; + }; + scale.rangeRoundBands = function(x, padding, outerPadding) { + if (arguments.length < 2) padding = 0; + if (arguments.length < 3) outerPadding = padding; + var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = Math.floor((stop - start) / (domain.length - padding + 2 * outerPadding)), error = stop - start - (domain.length - padding) * step; + range = steps(start + Math.round(error / 2), step); + if (reverse) range.reverse(); + rangeBand = Math.round(step * (1 - padding)); + ranger = { + t: "rangeRoundBands", + a: arguments + }; + return scale; + }; + scale.rangeBand = function() { + return rangeBand; + }; + scale.rangeExtent = function() { + return d3_scaleExtent(ranger.a[0]); + }; + scale.copy = function() { + return d3_scale_ordinal(domain, ranger); + }; + return scale.domain(domain); + } + function d3_scale_quantile(domain, range) { + function rescale() { + var k = 0, n = domain.length, q = range.length; + thresholds = []; + while (++k < q) thresholds[k - 1] = d3.quantile(domain, k / q); + return scale; + } + function scale(x) { + if (isNaN(x = +x)) return NaN; + return range[d3.bisect(thresholds, x)]; + } + var thresholds; + scale.domain = function(x) { + if (!arguments.length) return domain; + domain = x.filter(function(d) { + return !isNaN(d); + }).sort(d3.ascending); + return rescale(); + }; + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + return rescale(); + }; + scale.quantiles = function() { + return thresholds; + }; + scale.copy = function() { + return d3_scale_quantile(domain, range); + }; + return rescale(); + } + function d3_scale_quantize(x0, x1, range) { + function scale(x) { + return range[Math.max(0, Math.min(i, Math.floor(kx * (x - x0))))]; + } + function rescale() { + kx = range.length / (x1 - x0); + i = range.length - 1; + return scale; + } + var kx, i; + scale.domain = function(x) { + if (!arguments.length) return [ x0, x1 ]; + x0 = +x[0]; + x1 = +x[x.length - 1]; + return rescale(); + }; + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + return rescale(); + }; + scale.copy = function() { + return d3_scale_quantize(x0, x1, range); + }; + return rescale(); + } + function d3_scale_threshold(domain, range) { + function scale(x) { + return range[d3.bisect(domain, x)]; + } + scale.domain = function(_) { + if (!arguments.length) return domain; + domain = _; + return scale; + }; + scale.range = function(_) { + if (!arguments.length) return range; + range = _; + return scale; + }; + scale.copy = function() { + return d3_scale_threshold(domain, range); + }; + return scale; + } + function d3_scale_identity(domain) { + function identity(x) { + return +x; + } + identity.invert = identity; + identity.domain = identity.range = function(x) { + if (!arguments.length) return domain; + domain = x.map(identity); + return identity; + }; + identity.ticks = function(m) { + return d3_scale_linearTicks(domain, m); + }; + identity.tickFormat = function(m) { + return d3_scale_linearTickFormat(domain, m); + }; + identity.copy = function() { + return d3_scale_identity(domain); + }; + return identity; + } + function d3_svg_arcInnerRadius(d) { + return d.innerRadius; + } + function d3_svg_arcOuterRadius(d) { + return d.outerRadius; + } + function d3_svg_arcStartAngle(d) { + return d.startAngle; + } + function d3_svg_arcEndAngle(d) { + return d.endAngle; + } + function d3_svg_line(projection) { + function line(data) { + function segment() { + segments.push("M", interpolate(projection(points), tension)); + } + var segments = [], points = [], i = -1, n = data.length, d, fx = d3_functor(x), fy = d3_functor(y); + while (++i < n) { + if (defined.call(this, d = data[i], i)) { + points.push([ +fx.call(this, d, i), +fy.call(this, d, i) ]); + } else if (points.length) { + segment(); + points = []; + } + } + if (points.length) segment(); + return segments.length ? segments.join("") : null; + } + var x = d3_svg_lineX, y = d3_svg_lineY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, tension = .7; + line.x = function(_) { + if (!arguments.length) return x; + x = _; + return line; + }; + line.y = function(_) { + if (!arguments.length) return y; + y = _; + return line; + }; + line.defined = function(_) { + if (!arguments.length) return defined; + defined = _; + return line; + }; + line.interpolate = function(_) { + if (!arguments.length) return interpolateKey; + if (typeof _ === "function") interpolateKey = interpolate = _; else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key; + return line; + }; + line.tension = function(_) { + if (!arguments.length) return tension; + tension = _; + return line; + }; + return line; + } + function d3_svg_lineX(d) { + return d[0]; + } + function d3_svg_lineY(d) { + return d[1]; + } + function d3_svg_lineLinear(points) { + return points.join("L"); + } + function d3_svg_lineLinearClosed(points) { + return d3_svg_lineLinear(points) + "Z"; + } + function d3_svg_lineStepBefore(points) { + var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ]; + while (++i < n) path.push("V", (p = points[i])[1], "H", p[0]); + return path.join(""); + } + function d3_svg_lineStepAfter(points) { + var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ]; + while (++i < n) path.push("H", (p = points[i])[0], "V", p[1]); + return path.join(""); + } + function d3_svg_lineCardinalOpen(points, tension) { + return points.length < 4 ? d3_svg_lineLinear(points) : points[1] + d3_svg_lineHermite(points.slice(1, points.length - 1), d3_svg_lineCardinalTangents(points, tension)); + } + function d3_svg_lineCardinalClosed(points, tension) { + return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite((points.push(points[0]), points), d3_svg_lineCardinalTangents([ points[points.length - 2] ].concat(points, [ points[1] ]), tension)); + } + function d3_svg_lineCardinal(points, tension, closed) { + return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineCardinalTangents(points, tension)); + } + function d3_svg_lineHermite(points, tangents) { + if (tangents.length < 1 || points.length != tangents.length && points.length != tangents.length + 2) { + return d3_svg_lineLinear(points); + } + var quad = points.length != tangents.length, path = "", p0 = points[0], p = points[1], t0 = tangents[0], t = t0, pi = 1; + if (quad) { + path += "Q" + (p[0] - t0[0] * 2 / 3) + "," + (p[1] - t0[1] * 2 / 3) + "," + p[0] + "," + p[1]; + p0 = points[1]; + pi = 2; + } + if (tangents.length > 1) { + t = tangents[1]; + p = points[pi]; + pi++; + path += "C" + (p0[0] + t0[0]) + "," + (p0[1] + t0[1]) + "," + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1]; + for (var i = 2; i < tangents.length; i++, pi++) { + p = points[pi]; + t = tangents[i]; + path += "S" + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1]; + } + } + if (quad) { + var lp = points[pi]; + path += "Q" + (p[0] + t[0] * 2 / 3) + "," + (p[1] + t[1] * 2 / 3) + "," + lp[0] + "," + lp[1]; + } + return path; + } + function d3_svg_lineCardinalTangents(points, tension) { + var tangents = [], a = (1 - tension) / 2, p0, p1 = points[0], p2 = points[1], i = 1, n = points.length; + while (++i < n) { + p0 = p1; + p1 = p2; + p2 = points[i]; + tangents.push([ a * (p2[0] - p0[0]), a * (p2[1] - p0[1]) ]); + } + return tangents; + } + function d3_svg_lineBasis(points) { + if (points.length < 3) return d3_svg_lineLinear(points); + var i = 1, n = points.length, pi = points[0], x0 = pi[0], y0 = pi[1], px = [ x0, x0, x0, (pi = points[1])[0] ], py = [ y0, y0, y0, pi[1] ], path = [ x0, ",", y0 ]; + d3_svg_lineBasisBezier(path, px, py); + while (++i < n) { + pi = points[i]; + px.shift(); + px.push(pi[0]); + py.shift(); + py.push(pi[1]); + d3_svg_lineBasisBezier(path, px, py); + } + i = -1; + while (++i < 2) { + px.shift(); + px.push(pi[0]); + py.shift(); + py.push(pi[1]); + d3_svg_lineBasisBezier(path, px, py); + } + return path.join(""); + } + function d3_svg_lineBasisOpen(points) { + if (points.length < 4) return d3_svg_lineLinear(points); + var path = [], i = -1, n = points.length, pi, px = [ 0 ], py = [ 0 ]; + while (++i < 3) { + pi = points[i]; + px.push(pi[0]); + py.push(pi[1]); + } + path.push(d3_svg_lineDot4(d3_svg_lineBasisBezier3, px) + "," + d3_svg_lineDot4(d3_svg_lineBasisBezier3, py)); + --i; + while (++i < n) { + pi = points[i]; + px.shift(); + px.push(pi[0]); + py.shift(); + py.push(pi[1]); + d3_svg_lineBasisBezier(path, px, py); + } + return path.join(""); + } + function d3_svg_lineBasisClosed(points) { + var path, i = -1, n = points.length, m = n + 4, pi, px = [], py = []; + while (++i < 4) { + pi = points[i % n]; + px.push(pi[0]); + py.push(pi[1]); + } + path = [ d3_svg_lineDot4(d3_svg_lineBasisBezier3, px), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, py) ]; + --i; + while (++i < m) { + pi = points[i % n]; + px.shift(); + px.push(pi[0]); + py.shift(); + py.push(pi[1]); + d3_svg_lineBasisBezier(path, px, py); + } + return path.join(""); + } + function d3_svg_lineBundle(points, tension) { + var n = points.length - 1; + if (n) { + var x0 = points[0][0], y0 = points[0][1], dx = points[n][0] - x0, dy = points[n][1] - y0, i = -1, p, t; + while (++i <= n) { + p = points[i]; + t = i / n; + p[0] = tension * p[0] + (1 - tension) * (x0 + t * dx); + p[1] = tension * p[1] + (1 - tension) * (y0 + t * dy); + } + } + return d3_svg_lineBasis(points); + } + function d3_svg_lineDot4(a, b) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3]; + } + function d3_svg_lineBasisBezier(path, x, y) { + path.push("C", d3_svg_lineDot4(d3_svg_lineBasisBezier1, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier1, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, y)); + } + function d3_svg_lineSlope(p0, p1) { + return (p1[1] - p0[1]) / (p1[0] - p0[0]); + } + function d3_svg_lineFiniteDifferences(points) { + var i = 0, j = points.length - 1, m = [], p0 = points[0], p1 = points[1], d = m[0] = d3_svg_lineSlope(p0, p1); + while (++i < j) { + m[i] = (d + (d = d3_svg_lineSlope(p0 = p1, p1 = points[i + 1]))) / 2; + } + m[i] = d; + return m; + } + function d3_svg_lineMonotoneTangents(points) { + var tangents = [], d, a, b, s, m = d3_svg_lineFiniteDifferences(points), i = -1, j = points.length - 1; + while (++i < j) { + d = d3_svg_lineSlope(points[i], points[i + 1]); + if (Math.abs(d) < 1e-6) { + m[i] = m[i + 1] = 0; + } else { + a = m[i] / d; + b = m[i + 1] / d; + s = a * a + b * b; + if (s > 9) { + s = d * 3 / Math.sqrt(s); + m[i] = s * a; + m[i + 1] = s * b; + } + } + } + i = -1; + while (++i <= j) { + s = (points[Math.min(j, i + 1)][0] - points[Math.max(0, i - 1)][0]) / (6 * (1 + m[i] * m[i])); + tangents.push([ s || 0, m[i] * s || 0 ]); + } + return tangents; + } + function d3_svg_lineMonotone(points) { + return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineMonotoneTangents(points)); + } + function d3_svg_lineRadial(points) { + var point, i = -1, n = points.length, r, a; + while (++i < n) { + point = points[i]; + r = point[0]; + a = point[1] + d3_svg_arcOffset; + point[0] = r * Math.cos(a); + point[1] = r * Math.sin(a); + } + return points; + } + function d3_svg_area(projection) { + function area(data) { + function segment() { + segments.push("M", interpolate(projection(points1), tension), L, interpolateReverse(projection(points0.reverse()), tension), "Z"); + } + var segments = [], points0 = [], points1 = [], i = -1, n = data.length, d, fx0 = d3_functor(x0), fy0 = d3_functor(y0), fx1 = x0 === x1 ? function() { + return x; + } : d3_functor(x1), fy1 = y0 === y1 ? function() { + return y; + } : d3_functor(y1), x, y; + while (++i < n) { + if (defined.call(this, d = data[i], i)) { + points0.push([ x = +fx0.call(this, d, i), y = +fy0.call(this, d, i) ]); + points1.push([ +fx1.call(this, d, i), +fy1.call(this, d, i) ]); + } else if (points0.length) { + segment(); + points0 = []; + points1 = []; + } + } + if (points0.length) segment(); + return segments.length ? segments.join("") : null; + } + var x0 = d3_svg_lineX, x1 = d3_svg_lineX, y0 = 0, y1 = d3_svg_lineY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, interpolateReverse = interpolate, L = "L", tension = .7; + area.x = function(_) { + if (!arguments.length) return x1; + x0 = x1 = _; + return area; + }; + area.x0 = function(_) { + if (!arguments.length) return x0; + x0 = _; + return area; + }; + area.x1 = function(_) { + if (!arguments.length) return x1; + x1 = _; + return area; + }; + area.y = function(_) { + if (!arguments.length) return y1; + y0 = y1 = _; + return area; + }; + area.y0 = function(_) { + if (!arguments.length) return y0; + y0 = _; + return area; + }; + area.y1 = function(_) { + if (!arguments.length) return y1; + y1 = _; + return area; + }; + area.defined = function(_) { + if (!arguments.length) return defined; + defined = _; + return area; + }; + area.interpolate = function(_) { + if (!arguments.length) return interpolateKey; + if (typeof _ === "function") interpolateKey = interpolate = _; else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key; + interpolateReverse = interpolate.reverse || interpolate; + L = interpolate.closed ? "M" : "L"; + return area; + }; + area.tension = function(_) { + if (!arguments.length) return tension; + tension = _; + return area; + }; + return area; + } + function d3_svg_chordSource(d) { + return d.source; + } + function d3_svg_chordTarget(d) { + return d.target; + } + function d3_svg_chordRadius(d) { + return d.radius; + } + function d3_svg_chordStartAngle(d) { + return d.startAngle; + } + function d3_svg_chordEndAngle(d) { + return d.endAngle; + } + function d3_svg_diagonalProjection(d) { + return [ d.x, d.y ]; + } + function d3_svg_diagonalRadialProjection(projection) { + return function() { + var d = projection.apply(this, arguments), r = d[0], a = d[1] + d3_svg_arcOffset; + return [ r * Math.cos(a), r * Math.sin(a) ]; + }; + } + function d3_svg_symbolSize() { + return 64; + } + function d3_svg_symbolType() { + return "circle"; + } + function d3_svg_symbolCircle(size) { + var r = Math.sqrt(size / Math.PI); + return "M0," + r + "A" + r + "," + r + " 0 1,1 0," + -r + "A" + r + "," + r + " 0 1,1 0," + r + "Z"; + } + function d3_svg_axisX(selection, x) { + selection.attr("transform", function(d) { + return "translate(" + x(d) + ",0)"; + }); + } + function d3_svg_axisY(selection, y) { + selection.attr("transform", function(d) { + return "translate(0," + y(d) + ")"; + }); + } + function d3_svg_axisSubdivide(scale, ticks, m) { + subticks = []; + if (m && ticks.length > 1) { + var extent = d3_scaleExtent(scale.domain()), subticks, i = -1, n = ticks.length, d = (ticks[1] - ticks[0]) / ++m, j, v; + while (++i < n) { + for (j = m; --j > 0; ) { + if ((v = +ticks[i] - j * d) >= extent[0]) { + subticks.push(v); + } + } + } + for (--i, j = 0; ++j < m && (v = +ticks[i] + j * d) < extent[1]; ) { + subticks.push(v); + } + } + return subticks; + } + function d3_behavior_zoomDelta() { + if (!d3_behavior_zoomDiv) { + d3_behavior_zoomDiv = d3.select("body").append("div").style("visibility", "hidden").style("top", 0).style("height", 0).style("width", 0).style("overflow-y", "scroll").append("div").style("height", "2000px").node().parentNode; + } + var e = d3.event, delta; + try { + d3_behavior_zoomDiv.scrollTop = 1e3; + d3_behavior_zoomDiv.dispatchEvent(e); + delta = 1e3 - d3_behavior_zoomDiv.scrollTop; + } catch (error) { + delta = e.wheelDelta || -e.detail * 5; + } + return delta; + } + function d3_layout_bundlePath(link) { + var start = link.source, end = link.target, lca = d3_layout_bundleLeastCommonAncestor(start, end), points = [ start ]; + while (start !== lca) { + start = start.parent; + points.push(start); + } + var k = points.length; + while (end !== lca) { + points.splice(k, 0, end); + end = end.parent; + } + return points; + } + function d3_layout_bundleAncestors(node) { + var ancestors = [], parent = node.parent; + while (parent != null) { + ancestors.push(node); + node = parent; + parent = parent.parent; + } + ancestors.push(node); + return ancestors; + } + function d3_layout_bundleLeastCommonAncestor(a, b) { + if (a === b) return a; + var aNodes = d3_layout_bundleAncestors(a), bNodes = d3_layout_bundleAncestors(b), aNode = aNodes.pop(), bNode = bNodes.pop(), sharedNode = null; + while (aNode === bNode) { + sharedNode = aNode; + aNode = aNodes.pop(); + bNode = bNodes.pop(); + } + return sharedNode; + } + function d3_layout_forceDragstart(d) { + d.fixed |= 2; + } + function d3_layout_forceDragend(d) { + d.fixed &= 1; + } + function d3_layout_forceMouseover(d) { + d.fixed |= 4; + } + function d3_layout_forceMouseout(d) { + d.fixed &= 3; + } + function d3_layout_forceAccumulate(quad, alpha, charges) { + var cx = 0, cy = 0; + quad.charge = 0; + if (!quad.leaf) { + var nodes = quad.nodes, n = nodes.length, i = -1, c; + while (++i < n) { + c = nodes[i]; + if (c == null) continue; + d3_layout_forceAccumulate(c, alpha, charges); + quad.charge += c.charge; + cx += c.charge * c.cx; + cy += c.charge * c.cy; + } + } + if (quad.point) { + if (!quad.leaf) { + quad.point.x += Math.random() - .5; + quad.point.y += Math.random() - .5; + } + var k = alpha * charges[quad.point.index]; + quad.charge += quad.pointCharge = k; + cx += k * quad.point.x; + cy += k * quad.point.y; + } + quad.cx = cx / quad.charge; + quad.cy = cy / quad.charge; + } + function d3_layout_forceLinkDistance(link) { + return 20; + } + function d3_layout_forceLinkStrength(link) { + return 1; + } + function d3_layout_stackX(d) { + return d.x; + } + function d3_layout_stackY(d) { + return d.y; + } + function d3_layout_stackOut(d, y0, y) { + d.y0 = y0; + d.y = y; + } + function d3_layout_stackOrderDefault(data) { + return d3.range(data.length); + } + function d3_layout_stackOffsetZero(data) { + var j = -1, m = data[0].length, y0 = []; + while (++j < m) y0[j] = 0; + return y0; + } + function d3_layout_stackMaxIndex(array) { + var i = 1, j = 0, v = array[0][1], k, n = array.length; + for (; i < n; ++i) { + if ((k = array[i][1]) > v) { + j = i; + v = k; + } + } + return j; + } + function d3_layout_stackReduceSum(d) { + return d.reduce(d3_layout_stackSum, 0); + } + function d3_layout_stackSum(p, d) { + return p + d[1]; + } + function d3_layout_histogramBinSturges(range, values) { + return d3_layout_histogramBinFixed(range, Math.ceil(Math.log(values.length) / Math.LN2 + 1)); + } + function d3_layout_histogramBinFixed(range, n) { + var x = -1, b = +range[0], m = (range[1] - b) / n, f = []; + while (++x <= n) f[x] = m * x + b; + return f; + } + function d3_layout_histogramRange(values) { + return [ d3.min(values), d3.max(values) ]; + } + function d3_layout_hierarchyRebind(object, hierarchy) { + d3.rebind(object, hierarchy, "sort", "children", "value"); + object.links = d3_layout_hierarchyLinks; + object.nodes = function(d) { + d3_layout_hierarchyInline = true; + return (object.nodes = object)(d); + }; + return object; + } + function d3_layout_hierarchyChildren(d) { + return d.children; + } + function d3_layout_hierarchyValue(d) { + return d.value; + } + function d3_layout_hierarchySort(a, b) { + return b.value - a.value; + } + function d3_layout_hierarchyLinks(nodes) { + return d3.merge(nodes.map(function(parent) { + return (parent.children || []).map(function(child) { + return { + source: parent, + target: child + }; + }); + })); + } + function d3_layout_packSort(a, b) { + return a.value - b.value; + } + function d3_layout_packInsert(a, b) { + var c = a._pack_next; + a._pack_next = b; + b._pack_prev = a; + b._pack_next = c; + c._pack_prev = b; + } + function d3_layout_packSplice(a, b) { + a._pack_next = b; + b._pack_prev = a; + } + function d3_layout_packIntersects(a, b) { + var dx = b.x - a.x, dy = b.y - a.y, dr = a.r + b.r; + return dr * dr - dx * dx - dy * dy > .001; + } + function d3_layout_packSiblings(node) { + function bound(node) { + xMin = Math.min(node.x - node.r, xMin); + xMax = Math.max(node.x + node.r, xMax); + yMin = Math.min(node.y - node.r, yMin); + yMax = Math.max(node.y + node.r, yMax); + } + if (!(nodes = node.children) || !(n = nodes.length)) return; + var nodes, xMin = Infinity, xMax = -Infinity, yMin = Infinity, yMax = -Infinity, a, b, c, i, j, k, n; + nodes.forEach(d3_layout_packLink); + a = nodes[0]; + a.x = -a.r; + a.y = 0; + bound(a); + if (n > 1) { + b = nodes[1]; + b.x = b.r; + b.y = 0; + bound(b); + if (n > 2) { + c = nodes[2]; + d3_layout_packPlace(a, b, c); + bound(c); + d3_layout_packInsert(a, c); + a._pack_prev = c; + d3_layout_packInsert(c, b); + b = a._pack_next; + for (i = 3; i < n; i++) { + d3_layout_packPlace(a, b, c = nodes[i]); + var isect = 0, s1 = 1, s2 = 1; + for (j = b._pack_next; j !== b; j = j._pack_next, s1++) { + if (d3_layout_packIntersects(j, c)) { + isect = 1; + break; + } + } + if (isect == 1) { + for (k = a._pack_prev; k !== j._pack_prev; k = k._pack_prev, s2++) { + if (d3_layout_packIntersects(k, c)) { + break; + } + } + } + if (isect) { + if (s1 < s2 || s1 == s2 && b.r < a.r) d3_layout_packSplice(a, b = j); else d3_layout_packSplice(a = k, b); + i--; + } else { + d3_layout_packInsert(a, c); + b = c; + bound(c); + } + } + } + } + var cx = (xMin + xMax) / 2, cy = (yMin + yMax) / 2, cr = 0; + for (i = 0; i < n; i++) { + c = nodes[i]; + c.x -= cx; + c.y -= cy; + cr = Math.max(cr, c.r + Math.sqrt(c.x * c.x + c.y * c.y)); + } + node.r = cr; + nodes.forEach(d3_layout_packUnlink); + } + function d3_layout_packLink(node) { + node._pack_next = node._pack_prev = node; + } + function d3_layout_packUnlink(node) { + delete node._pack_next; + delete node._pack_prev; + } + function d3_layout_packTransform(node, x, y, k) { + var children = node.children; + node.x = x += k * node.x; + node.y = y += k * node.y; + node.r *= k; + if (children) { + var i = -1, n = children.length; + while (++i < n) d3_layout_packTransform(children[i], x, y, k); + } + } + function d3_layout_packPlace(a, b, c) { + var db = a.r + c.r, dx = b.x - a.x, dy = b.y - a.y; + if (db && (dx || dy)) { + var da = b.r + c.r, dc = dx * dx + dy * dy; + da *= da; + db *= db; + var x = .5 + (db - da) / (2 * dc), y = Math.sqrt(Math.max(0, 2 * da * (db + dc) - (db -= dc) * db - da * da)) / (2 * dc); + c.x = a.x + x * dx + y * dy; + c.y = a.y + x * dy - y * dx; + } else { + c.x = a.x + db; + c.y = a.y; + } + } + function d3_layout_clusterY(children) { + return 1 + d3.max(children, function(child) { + return child.y; + }); + } + function d3_layout_clusterX(children) { + return children.reduce(function(x, child) { + return x + child.x; + }, 0) / children.length; + } + function d3_layout_clusterLeft(node) { + var children = node.children; + return children && children.length ? d3_layout_clusterLeft(children[0]) : node; + } + function d3_layout_clusterRight(node) { + var children = node.children, n; + return children && (n = children.length) ? d3_layout_clusterRight(children[n - 1]) : node; + } + function d3_layout_treeSeparation(a, b) { + return a.parent == b.parent ? 1 : 2; + } + function d3_layout_treeLeft(node) { + var children = node.children; + return children && children.length ? children[0] : node._tree.thread; + } + function d3_layout_treeRight(node) { + var children = node.children, n; + return children && (n = children.length) ? children[n - 1] : node._tree.thread; + } + function d3_layout_treeSearch(node, compare) { + var children = node.children; + if (children && (n = children.length)) { + var child, n, i = -1; + while (++i < n) { + if (compare(child = d3_layout_treeSearch(children[i], compare), node) > 0) { + node = child; + } + } + } + return node; + } + function d3_layout_treeRightmost(a, b) { + return a.x - b.x; + } + function d3_layout_treeLeftmost(a, b) { + return b.x - a.x; + } + function d3_layout_treeDeepest(a, b) { + return a.depth - b.depth; + } + function d3_layout_treeVisitAfter(node, callback) { + function visit(node, previousSibling) { + var children = node.children; + if (children && (n = children.length)) { + var child, previousChild = null, i = -1, n; + while (++i < n) { + child = children[i]; + visit(child, previousChild); + previousChild = child; + } + } + callback(node, previousSibling); + } + visit(node, null); + } + function d3_layout_treeShift(node) { + var shift = 0, change = 0, children = node.children, i = children.length, child; + while (--i >= 0) { + child = children[i]._tree; + child.prelim += shift; + child.mod += shift; + shift += child.shift + (change += child.change); + } + } + function d3_layout_treeMove(ancestor, node, shift) { + ancestor = ancestor._tree; + node = node._tree; + var change = shift / (node.number - ancestor.number); + ancestor.change += change; + node.change -= change; + node.shift += shift; + node.prelim += shift; + node.mod += shift; + } + function d3_layout_treeAncestor(vim, node, ancestor) { + return vim._tree.ancestor.parent == node.parent ? vim._tree.ancestor : ancestor; + } + function d3_layout_treemapPadNull(node) { + return { + x: node.x, + y: node.y, + dx: node.dx, + dy: node.dy + }; + } + function d3_layout_treemapPad(node, padding) { + var x = node.x + padding[3], y = node.y + padding[0], dx = node.dx - padding[1] - padding[3], dy = node.dy - padding[0] - padding[2]; + if (dx < 0) { + x += dx / 2; + dx = 0; + } + if (dy < 0) { + y += dy / 2; + dy = 0; + } + return { + x: x, + y: y, + dx: dx, + dy: dy + }; + } + function d3_dsv(delimiter, mimeType) { + function dsv(url, callback) { + d3.text(url, mimeType, function(text) { + callback(text && dsv.parse(text)); + }); + } + function formatRow(row) { + return row.map(formatValue).join(delimiter); + } + function formatValue(text) { + return reFormat.test(text) ? '"' + text.replace(/\"/g, '""') + '"' : text; + } + var reParse = new RegExp("\r\n|[" + delimiter + "\r\n]", "g"), reFormat = new RegExp('["' + delimiter + "\n]"), delimiterCode = delimiter.charCodeAt(0); + dsv.parse = function(text) { + var header; + return dsv.parseRows(text, function(row, i) { + if (i) { + var o = {}, j = -1, m = header.length; + while (++j < m) o[header[j]] = row[j]; + return o; + } else { + header = row; + return null; + } + }); + }; + dsv.parseRows = function(text, f) { + function token() { + if (reParse.lastIndex >= text.length) return EOF; + if (eol) { + eol = false; + return EOL; + } + var j = reParse.lastIndex; + if (text.charCodeAt(j) === 34) { + var i = j; + while (i++ < text.length) { + if (text.charCodeAt(i) === 34) { + if (text.charCodeAt(i + 1) !== 34) break; + i++; + } + } + reParse.lastIndex = i + 2; + var c = text.charCodeAt(i + 1); + if (c === 13) { + eol = true; + if (text.charCodeAt(i + 2) === 10) reParse.lastIndex++; + } else if (c === 10) { + eol = true; + } + return text.substring(j + 1, i).replace(/""/g, '"'); + } + var m = reParse.exec(text); + if (m) { + eol = m[0].charCodeAt(0) !== delimiterCode; + return text.substring(j, m.index); + } + reParse.lastIndex = text.length; + return text.substring(j); + } + var EOL = {}, EOF = {}, rows = [], n = 0, t, eol; + reParse.lastIndex = 0; + while ((t = token()) !== EOF) { + var a = []; + while (t !== EOL && t !== EOF) { + a.push(t); + t = token(); + } + if (f && !(a = f(a, n++))) continue; + rows.push(a); + } + return rows; + }; + dsv.format = function(rows) { + return rows.map(formatRow).join("\n"); + }; + return dsv; + } + function d3_geo_type(types, defaultValue) { + return function(object) { + return object && types.hasOwnProperty(object.type) ? types[object.type](object) : defaultValue; + }; + } + function d3_path_circle(radius) { + return "m0," + radius + "a" + radius + "," + radius + " 0 1,1 0," + -2 * radius + "a" + radius + "," + radius + " 0 1,1 0," + +2 * radius + "z"; + } + function d3_geo_bounds(o, f) { + if (d3_geo_boundsTypes.hasOwnProperty(o.type)) d3_geo_boundsTypes[o.type](o, f); + } + function d3_geo_boundsFeature(o, f) { + d3_geo_bounds(o.geometry, f); + } + function d3_geo_boundsFeatureCollection(o, f) { + for (var a = o.features, i = 0, n = a.length; i < n; i++) { + d3_geo_bounds(a[i].geometry, f); + } + } + function d3_geo_boundsGeometryCollection(o, f) { + for (var a = o.geometries, i = 0, n = a.length; i < n; i++) { + d3_geo_bounds(a[i], f); + } + } + function d3_geo_boundsLineString(o, f) { + for (var a = o.coordinates, i = 0, n = a.length; i < n; i++) { + f.apply(null, a[i]); + } + } + function d3_geo_boundsMultiLineString(o, f) { + for (var a = o.coordinates, i = 0, n = a.length; i < n; i++) { + for (var b = a[i], j = 0, m = b.length; j < m; j++) { + f.apply(null, b[j]); + } + } + } + function d3_geo_boundsMultiPolygon(o, f) { + for (var a = o.coordinates, i = 0, n = a.length; i < n; i++) { + for (var b = a[i][0], j = 0, m = b.length; j < m; j++) { + f.apply(null, b[j]); + } + } + } + function d3_geo_boundsPoint(o, f) { + f.apply(null, o.coordinates); + } + function d3_geo_boundsPolygon(o, f) { + for (var a = o.coordinates[0], i = 0, n = a.length; i < n; i++) { + f.apply(null, a[i]); + } + } + function d3_geo_greatArcSource(d) { + return d.source; + } + function d3_geo_greatArcTarget(d) { + return d.target; + } + function d3_geo_greatArcInterpolator() { + function interpolate(t) { + var B = Math.sin(t *= d) * k, A = Math.sin(d - t) * k, x = A * kx0 + B * kx1, y = A * ky0 + B * ky1, z = A * sy0 + B * sy1; + return [ Math.atan2(y, x) / d3_geo_radians, Math.atan2(z, Math.sqrt(x * x + y * y)) / d3_geo_radians ]; + } + var x0, y0, cy0, sy0, kx0, ky0, x1, y1, cy1, sy1, kx1, ky1, d, k; + interpolate.distance = function() { + if (d == null) k = 1 / Math.sin(d = Math.acos(Math.max(-1, Math.min(1, sy0 * sy1 + cy0 * cy1 * Math.cos(x1 - x0))))); + return d; + }; + interpolate.source = function(_) { + var cx0 = Math.cos(x0 = _[0] * d3_geo_radians), sx0 = Math.sin(x0); + cy0 = Math.cos(y0 = _[1] * d3_geo_radians); + sy0 = Math.sin(y0); + kx0 = cy0 * cx0; + ky0 = cy0 * sx0; + d = null; + return interpolate; + }; + interpolate.target = function(_) { + var cx1 = Math.cos(x1 = _[0] * d3_geo_radians), sx1 = Math.sin(x1); + cy1 = Math.cos(y1 = _[1] * d3_geo_radians); + sy1 = Math.sin(y1); + kx1 = cy1 * cx1; + ky1 = cy1 * sx1; + d = null; + return interpolate; + }; + return interpolate; + } + function d3_geo_greatArcInterpolate(a, b) { + var i = d3_geo_greatArcInterpolator().source(a).target(b); + i.distance(); + return i; + } + function d3_geom_contourStart(grid) { + var x = 0, y = 0; + while (true) { + if (grid(x, y)) { + return [ x, y ]; + } + if (x === 0) { + x = y + 1; + y = 0; + } else { + x = x - 1; + y = y + 1; + } + } + } + function d3_geom_hullCCW(i1, i2, i3, v) { + var t, a, b, c, d, e, f; + t = v[i1]; + a = t[0]; + b = t[1]; + t = v[i2]; + c = t[0]; + d = t[1]; + t = v[i3]; + e = t[0]; + f = t[1]; + return (f - b) * (c - a) - (d - b) * (e - a) > 0; + } + function d3_geom_polygonInside(p, a, b) { + return (b[0] - a[0]) * (p[1] - a[1]) < (b[1] - a[1]) * (p[0] - a[0]); + } + function d3_geom_polygonIntersect(c, d, a, b) { + var x1 = c[0], x2 = d[0], x3 = a[0], x4 = b[0], y1 = c[1], y2 = d[1], y3 = a[1], y4 = b[1], x13 = x1 - x3, x21 = x2 - x1, x43 = x4 - x3, y13 = y1 - y3, y21 = y2 - y1, y43 = y4 - y3, ua = (x43 * y13 - y43 * x13) / (y43 * x21 - x43 * y21); + return [ x1 + ua * x21, y1 + ua * y21 ]; + } + function d3_voronoi_tessellate(vertices, callback) { + var Sites = { + list: vertices.map(function(v, i) { + return { + index: i, + x: v[0], + y: v[1] + }; + }).sort(function(a, b) { + return a.y < b.y ? -1 : a.y > b.y ? 1 : a.x < b.x ? -1 : a.x > b.x ? 1 : 0; + }), + bottomSite: null + }; + var EdgeList = { + list: [], + leftEnd: null, + rightEnd: null, + init: function() { + EdgeList.leftEnd = EdgeList.createHalfEdge(null, "l"); + EdgeList.rightEnd = EdgeList.createHalfEdge(null, "l"); + EdgeList.leftEnd.r = EdgeList.rightEnd; + EdgeList.rightEnd.l = EdgeList.leftEnd; + EdgeList.list.unshift(EdgeList.leftEnd, EdgeList.rightEnd); + }, + createHalfEdge: function(edge, side) { + return { + edge: edge, + side: side, + vertex: null, + l: null, + r: null + }; + }, + insert: function(lb, he) { + he.l = lb; + he.r = lb.r; + lb.r.l = he; + lb.r = he; + }, + leftBound: function(p) { + var he = EdgeList.leftEnd; + do { + he = he.r; + } while (he != EdgeList.rightEnd && Geom.rightOf(he, p)); + he = he.l; + return he; + }, + del: function(he) { + he.l.r = he.r; + he.r.l = he.l; + he.edge = null; + }, + right: function(he) { + return he.r; + }, + left: function(he) { + return he.l; + }, + leftRegion: function(he) { + return he.edge == null ? Sites.bottomSite : he.edge.region[he.side]; + }, + rightRegion: function(he) { + return he.edge == null ? Sites.bottomSite : he.edge.region[d3_voronoi_opposite[he.side]]; + } + }; + var Geom = { + bisect: function(s1, s2) { + var newEdge = { + region: { + l: s1, + r: s2 + }, + ep: { + l: null, + r: null + } + }; + var dx = s2.x - s1.x, dy = s2.y - s1.y, adx = dx > 0 ? dx : -dx, ady = dy > 0 ? dy : -dy; + newEdge.c = s1.x * dx + s1.y * dy + (dx * dx + dy * dy) * .5; + if (adx > ady) { + newEdge.a = 1; + newEdge.b = dy / dx; + newEdge.c /= dx; + } else { + newEdge.b = 1; + newEdge.a = dx / dy; + newEdge.c /= dy; + } + return newEdge; + }, + intersect: function(el1, el2) { + var e1 = el1.edge, e2 = el2.edge; + if (!e1 || !e2 || e1.region.r == e2.region.r) { + return null; + } + var d = e1.a * e2.b - e1.b * e2.a; + if (Math.abs(d) < 1e-10) { + return null; + } + var xint = (e1.c * e2.b - e2.c * e1.b) / d, yint = (e2.c * e1.a - e1.c * e2.a) / d, e1r = e1.region.r, e2r = e2.region.r, el, e; + if (e1r.y < e2r.y || e1r.y == e2r.y && e1r.x < e2r.x) { + el = el1; + e = e1; + } else { + el = el2; + e = e2; + } + var rightOfSite = xint >= e.region.r.x; + if (rightOfSite && el.side === "l" || !rightOfSite && el.side === "r") { + return null; + } + return { + x: xint, + y: yint + }; + }, + rightOf: function(he, p) { + var e = he.edge, topsite = e.region.r, rightOfSite = p.x > topsite.x; + if (rightOfSite && he.side === "l") { + return 1; + } + if (!rightOfSite && he.side === "r") { + return 0; + } + if (e.a === 1) { + var dyp = p.y - topsite.y, dxp = p.x - topsite.x, fast = 0, above = 0; + if (!rightOfSite && e.b < 0 || rightOfSite && e.b >= 0) { + above = fast = dyp >= e.b * dxp; + } else { + above = p.x + p.y * e.b > e.c; + if (e.b < 0) { + above = !above; + } + if (!above) { + fast = 1; + } + } + if (!fast) { + var dxs = topsite.x - e.region.l.x; + above = e.b * (dxp * dxp - dyp * dyp) < dxs * dyp * (1 + 2 * dxp / dxs + e.b * e.b); + if (e.b < 0) { + above = !above; + } + } + } else { + var yl = e.c - e.a * p.x, t1 = p.y - yl, t2 = p.x - topsite.x, t3 = yl - topsite.y; + above = t1 * t1 > t2 * t2 + t3 * t3; + } + return he.side === "l" ? above : !above; + }, + endPoint: function(edge, side, site) { + edge.ep[side] = site; + if (!edge.ep[d3_voronoi_opposite[side]]) return; + callback(edge); + }, + distance: function(s, t) { + var dx = s.x - t.x, dy = s.y - t.y; + return Math.sqrt(dx * dx + dy * dy); + } + }; + var EventQueue = { + list: [], + insert: function(he, site, offset) { + he.vertex = site; + he.ystar = site.y + offset; + for (var i = 0, list = EventQueue.list, l = list.length; i < l; i++) { + var next = list[i]; + if (he.ystar > next.ystar || he.ystar == next.ystar && site.x > next.vertex.x) { + continue; + } else { + break; + } + } + list.splice(i, 0, he); + }, + del: function(he) { + for (var i = 0, ls = EventQueue.list, l = ls.length; i < l && ls[i] != he; ++i) {} + ls.splice(i, 1); + }, + empty: function() { + return EventQueue.list.length === 0; + }, + nextEvent: function(he) { + for (var i = 0, ls = EventQueue.list, l = ls.length; i < l; ++i) { + if (ls[i] == he) return ls[i + 1]; + } + return null; + }, + min: function() { + var elem = EventQueue.list[0]; + return { + x: elem.vertex.x, + y: elem.ystar + }; + }, + extractMin: function() { + return EventQueue.list.shift(); + } + }; + EdgeList.init(); + Sites.bottomSite = Sites.list.shift(); + var newSite = Sites.list.shift(), newIntStar; + var lbnd, rbnd, llbnd, rrbnd, bisector; + var bot, top, temp, p, v; + var e, pm; + while (true) { + if (!EventQueue.empty()) { + newIntStar = EventQueue.min(); + } + if (newSite && (EventQueue.empty() || newSite.y < newIntStar.y || newSite.y == newIntStar.y && newSite.x < newIntStar.x)) { + lbnd = EdgeList.leftBound(newSite); + rbnd = EdgeList.right(lbnd); + bot = EdgeList.rightRegion(lbnd); + e = Geom.bisect(bot, newSite); + bisector = EdgeList.createHalfEdge(e, "l"); + EdgeList.insert(lbnd, bisector); + p = Geom.intersect(lbnd, bisector); + if (p) { + EventQueue.del(lbnd); + EventQueue.insert(lbnd, p, Geom.distance(p, newSite)); + } + lbnd = bisector; + bisector = EdgeList.createHalfEdge(e, "r"); + EdgeList.insert(lbnd, bisector); + p = Geom.intersect(bisector, rbnd); + if (p) { + EventQueue.insert(bisector, p, Geom.distance(p, newSite)); + } + newSite = Sites.list.shift(); + } else if (!EventQueue.empty()) { + lbnd = EventQueue.extractMin(); + llbnd = EdgeList.left(lbnd); + rbnd = EdgeList.right(lbnd); + rrbnd = EdgeList.right(rbnd); + bot = EdgeList.leftRegion(lbnd); + top = EdgeList.rightRegion(rbnd); + v = lbnd.vertex; + Geom.endPoint(lbnd.edge, lbnd.side, v); + Geom.endPoint(rbnd.edge, rbnd.side, v); + EdgeList.del(lbnd); + EventQueue.del(rbnd); + EdgeList.del(rbnd); + pm = "l"; + if (bot.y > top.y) { + temp = bot; + bot = top; + top = temp; + pm = "r"; + } + e = Geom.bisect(bot, top); + bisector = EdgeList.createHalfEdge(e, pm); + EdgeList.insert(llbnd, bisector); + Geom.endPoint(e, d3_voronoi_opposite[pm], v); + p = Geom.intersect(llbnd, bisector); + if (p) { + EventQueue.del(llbnd); + EventQueue.insert(llbnd, p, Geom.distance(p, bot)); + } + p = Geom.intersect(bisector, rrbnd); + if (p) { + EventQueue.insert(bisector, p, Geom.distance(p, bot)); + } + } else { + break; + } + } + for (lbnd = EdgeList.right(EdgeList.leftEnd); lbnd != EdgeList.rightEnd; lbnd = EdgeList.right(lbnd)) { + callback(lbnd.edge); + } + } + function d3_geom_quadtreeNode() { + return { + leaf: true, + nodes: [], + point: null + }; + } + function d3_geom_quadtreeVisit(f, node, x1, y1, x2, y2) { + if (!f(node, x1, y1, x2, y2)) { + var sx = (x1 + x2) * .5, sy = (y1 + y2) * .5, children = node.nodes; + if (children[0]) d3_geom_quadtreeVisit(f, children[0], x1, y1, sx, sy); + if (children[1]) d3_geom_quadtreeVisit(f, children[1], sx, y1, x2, sy); + if (children[2]) d3_geom_quadtreeVisit(f, children[2], x1, sy, sx, y2); + if (children[3]) d3_geom_quadtreeVisit(f, children[3], sx, sy, x2, y2); + } + } + function d3_geom_quadtreePoint(p) { + return { + x: p[0], + y: p[1] + }; + } + function d3_time_utc() { + this._ = new Date(arguments.length > 1 ? Date.UTC.apply(this, arguments) : arguments[0]); + } + function d3_time_formatAbbreviate(name) { + return name.substring(0, 3); + } + function d3_time_parse(date, template, string, j) { + var c, p, i = 0, n = template.length, m = string.length; + while (i < n) { + if (j >= m) return -1; + c = template.charCodeAt(i++); + if (c == 37) { + p = d3_time_parsers[template.charAt(i++)]; + if (!p || (j = p(date, string, j)) < 0) return -1; + } else if (c != string.charCodeAt(j++)) { + return -1; + } + } + return j; + } + function d3_time_formatRe(names) { + return new RegExp("^(?:" + names.map(d3.requote).join("|") + ")", "i"); + } + function d3_time_formatLookup(names) { + var map = new d3_Map, i = -1, n = names.length; + while (++i < n) map.set(names[i].toLowerCase(), i); + return map; + } + function d3_time_parseWeekdayAbbrev(date, string, i) { + d3_time_dayAbbrevRe.lastIndex = 0; + var n = d3_time_dayAbbrevRe.exec(string.substring(i)); + return n ? i += n[0].length : -1; + } + function d3_time_parseWeekday(date, string, i) { + d3_time_dayRe.lastIndex = 0; + var n = d3_time_dayRe.exec(string.substring(i)); + return n ? i += n[0].length : -1; + } + function d3_time_parseMonthAbbrev(date, string, i) { + d3_time_monthAbbrevRe.lastIndex = 0; + var n = d3_time_monthAbbrevRe.exec(string.substring(i)); + return n ? (date.m = d3_time_monthAbbrevLookup.get(n[0].toLowerCase()), i += n[0].length) : -1; + } + function d3_time_parseMonth(date, string, i) { + d3_time_monthRe.lastIndex = 0; + var n = d3_time_monthRe.exec(string.substring(i)); + return n ? (date.m = d3_time_monthLookup.get(n[0].toLowerCase()), i += n[0].length) : -1; + } + function d3_time_parseLocaleFull(date, string, i) { + return d3_time_parse(date, d3_time_formats.c.toString(), string, i); + } + function d3_time_parseLocaleDate(date, string, i) { + return d3_time_parse(date, d3_time_formats.x.toString(), string, i); + } + function d3_time_parseLocaleTime(date, string, i) { + return d3_time_parse(date, d3_time_formats.X.toString(), string, i); + } + function d3_time_parseFullYear(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.substring(i, i + 4)); + return n ? (date.y = +n[0], i += n[0].length) : -1; + } + function d3_time_parseYear(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.substring(i, i + 2)); + return n ? (date.y = d3_time_expandYear(+n[0]), i += n[0].length) : -1; + } + function d3_time_expandYear(d) { + return d + (d > 68 ? 1900 : 2e3); + } + function d3_time_parseMonthNumber(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.substring(i, i + 2)); + return n ? (date.m = n[0] - 1, i += n[0].length) : -1; + } + function d3_time_parseDay(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.substring(i, i + 2)); + return n ? (date.d = +n[0], i += n[0].length) : -1; + } + function d3_time_parseHour24(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.substring(i, i + 2)); + return n ? (date.H = +n[0], i += n[0].length) : -1; + } + function d3_time_parseMinutes(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.substring(i, i + 2)); + return n ? (date.M = +n[0], i += n[0].length) : -1; + } + function d3_time_parseSeconds(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.substring(i, i + 2)); + return n ? (date.S = +n[0], i += n[0].length) : -1; + } + function d3_time_parseMilliseconds(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.substring(i, i + 3)); + return n ? (date.L = +n[0], i += n[0].length) : -1; + } + function d3_time_parseAmPm(date, string, i) { + var n = d3_time_amPmLookup.get(string.substring(i, i += 2).toLowerCase()); + return n == null ? -1 : (date.p = n, i); + } + function d3_time_zone(d) { + var z = d.getTimezoneOffset(), zs = z > 0 ? "-" : "+", zh = ~~(Math.abs(z) / 60), zm = Math.abs(z) % 60; + return zs + d3_time_zfill2(zh) + d3_time_zfill2(zm); + } + function d3_time_formatIsoNative(date) { + return date.toISOString(); + } + function d3_time_interval(local, step, number) { + function round(date) { + var d0 = local(date), d1 = offset(d0, 1); + return date - d0 < d1 - date ? d0 : d1; + } + function ceil(date) { + step(date = local(new d3_time(date - 1)), 1); + return date; + } + function offset(date, k) { + step(date = new d3_time(+date), k); + return date; + } + function range(t0, t1, dt) { + var time = ceil(t0), times = []; + if (dt > 1) { + while (time < t1) { + if (!(number(time) % dt)) times.push(new Date(+time)); + step(time, 1); + } + } else { + while (time < t1) times.push(new Date(+time)), step(time, 1); + } + return times; + } + function range_utc(t0, t1, dt) { + try { + d3_time = d3_time_utc; + var utc = new d3_time_utc; + utc._ = t0; + return range(utc, t1, dt); + } finally { + d3_time = Date; + } + } + local.floor = local; + local.round = round; + local.ceil = ceil; + local.offset = offset; + local.range = range; + var utc = local.utc = d3_time_interval_utc(local); + utc.floor = utc; + utc.round = d3_time_interval_utc(round); + utc.ceil = d3_time_interval_utc(ceil); + utc.offset = d3_time_interval_utc(offset); + utc.range = range_utc; + return local; + } + function d3_time_interval_utc(method) { + return function(date, k) { + try { + d3_time = d3_time_utc; + var utc = new d3_time_utc; + utc._ = date; + return method(utc, k)._; + } finally { + d3_time = Date; + } + }; + } + function d3_time_scale(linear, methods, format) { + function scale(x) { + return linear(x); + } + scale.invert = function(x) { + return d3_time_scaleDate(linear.invert(x)); + }; + scale.domain = function(x) { + if (!arguments.length) return linear.domain().map(d3_time_scaleDate); + linear.domain(x); + return scale; + }; + scale.nice = function(m) { + return scale.domain(d3_scale_nice(scale.domain(), function() { + return m; + })); + }; + scale.ticks = function(m, k) { + var extent = d3_time_scaleExtent(scale.domain()); + if (typeof m !== "function") { + var span = extent[1] - extent[0], target = span / m, i = d3.bisect(d3_time_scaleSteps, target); + if (i == d3_time_scaleSteps.length) return methods.year(extent, m); + if (!i) return linear.ticks(m).map(d3_time_scaleDate); + if (Math.log(target / d3_time_scaleSteps[i - 1]) < Math.log(d3_time_scaleSteps[i] / target)) --i; + m = methods[i]; + k = m[1]; + m = m[0].range; + } + return m(extent[0], new Date(+extent[1] + 1), k); + }; + scale.tickFormat = function() { + return format; + }; + scale.copy = function() { + return d3_time_scale(linear.copy(), methods, format); + }; + return d3.rebind(scale, linear, "range", "rangeRound", "interpolate", "clamp"); + } + function d3_time_scaleExtent(domain) { + var start = domain[0], stop = domain[domain.length - 1]; + return start < stop ? [ start, stop ] : [ stop, start ]; + } + function d3_time_scaleDate(t) { + return new Date(t); + } + function d3_time_scaleFormat(formats) { + return function(date) { + var i = formats.length - 1, f = formats[i]; + while (!f[1](date)) f = formats[--i]; + return f[0](date); + }; + } + function d3_time_scaleSetYear(y) { + var d = new Date(y, 0, 1); + d.setFullYear(y); + return d; + } + function d3_time_scaleGetYear(d) { + var y = d.getFullYear(), d0 = d3_time_scaleSetYear(y), d1 = d3_time_scaleSetYear(y + 1); + return y + (d - d0) / (d1 - d0); + } + function d3_time_scaleUTCSetYear(y) { + var d = new Date(Date.UTC(y, 0, 1)); + d.setUTCFullYear(y); + return d; + } + function d3_time_scaleUTCGetYear(d) { + var y = d.getUTCFullYear(), d0 = d3_time_scaleUTCSetYear(y), d1 = d3_time_scaleUTCSetYear(y + 1); + return y + (d - d0) / (d1 - d0); + } + if (!Date.now) Date.now = function() { + return +(new Date); + }; + try { + document.createElement("div").style.setProperty("opacity", 0, ""); + } catch (error) { + var d3_style_prototype = CSSStyleDeclaration.prototype, d3_style_setProperty = d3_style_prototype.setProperty; + d3_style_prototype.setProperty = function(name, value, priority) { + d3_style_setProperty.call(this, name, value + "", priority); + }; + } + d3 = { + version: "2.10.3" + }; + var d3_array = d3_arraySlice; + try { + d3_array(document.documentElement.childNodes)[0].nodeType; + } catch (e) { + d3_array = d3_arrayCopy; + } + var d3_arraySubclass = [].__proto__ ? function(array, prototype) { + array.__proto__ = prototype; + } : function(array, prototype) { + for (var property in prototype) array[property] = prototype[property]; + }; + d3.map = function(object) { + var map = new d3_Map; + for (var key in object) map.set(key, object[key]); + return map; + }; + d3_class(d3_Map, { + has: function(key) { + return d3_map_prefix + key in this; + }, + get: function(key) { + return this[d3_map_prefix + key]; + }, + set: function(key, value) { + return this[d3_map_prefix + key] = value; + }, + remove: function(key) { + key = d3_map_prefix + key; + return key in this && delete this[key]; + }, + keys: function() { + var keys = []; + this.forEach(function(key) { + keys.push(key); + }); + return keys; + }, + values: function() { + var values = []; + this.forEach(function(key, value) { + values.push(value); + }); + return values; + }, + entries: function() { + var entries = []; + this.forEach(function(key, value) { + entries.push({ + key: key, + value: value + }); + }); + return entries; + }, + forEach: function(f) { + for (var key in this) { + if (key.charCodeAt(0) === d3_map_prefixCode) { + f.call(this, key.substring(1), this[key]); + } + } + } + }); + var d3_map_prefix = "\0", d3_map_prefixCode = d3_map_prefix.charCodeAt(0); + d3.functor = d3_functor; + d3.rebind = function(target, source) { + var i = 1, n = arguments.length, method; + while (++i < n) target[method = arguments[i]] = d3_rebind(target, source, source[method]); + return target; + }; + d3.ascending = function(a, b) { + return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN; + }; + d3.descending = function(a, b) { + return b < a ? -1 : b > a ? 1 : b >= a ? 0 : NaN; + }; + d3.mean = function(array, f) { + var n = array.length, a, m = 0, i = -1, j = 0; + if (arguments.length === 1) { + while (++i < n) if (d3_number(a = array[i])) m += (a - m) / ++j; + } else { + while (++i < n) if (d3_number(a = f.call(array, array[i], i))) m += (a - m) / ++j; + } + return j ? m : undefined; + }; + d3.median = function(array, f) { + if (arguments.length > 1) array = array.map(f); + array = array.filter(d3_number); + return array.length ? d3.quantile(array.sort(d3.ascending), .5) : undefined; + }; + d3.min = function(array, f) { + var i = -1, n = array.length, a, b; + if (arguments.length === 1) { + while (++i < n && ((a = array[i]) == null || a != a)) a = undefined; + while (++i < n) if ((b = array[i]) != null && a > b) a = b; + } else { + while (++i < n && ((a = f.call(array, array[i], i)) == null || a != a)) a = undefined; + while (++i < n) if ((b = f.call(array, array[i], i)) != null && a > b) a = b; + } + return a; + }; + d3.max = function(array, f) { + var i = -1, n = array.length, a, b; + if (arguments.length === 1) { + while (++i < n && ((a = array[i]) == null || a != a)) a = undefined; + while (++i < n) if ((b = array[i]) != null && b > a) a = b; + } else { + while (++i < n && ((a = f.call(array, array[i], i)) == null || a != a)) a = undefined; + while (++i < n) if ((b = f.call(array, array[i], i)) != null && b > a) a = b; + } + return a; + }; + d3.extent = function(array, f) { + var i = -1, n = array.length, a, b, c; + if (arguments.length === 1) { + while (++i < n && ((a = c = array[i]) == null || a != a)) a = c = undefined; + while (++i < n) if ((b = array[i]) != null) { + if (a > b) a = b; + if (c < b) c = b; + } + } else { + while (++i < n && ((a = c = f.call(array, array[i], i)) == null || a != a)) a = undefined; + while (++i < n) if ((b = f.call(array, array[i], i)) != null) { + if (a > b) a = b; + if (c < b) c = b; + } + } + return [ a, c ]; + }; + d3.random = { + normal: function(µ, σ) { + var n = arguments.length; + if (n < 2) σ = 1; + if (n < 1) µ = 0; + return function() { + var x, y, r; + do { + x = Math.random() * 2 - 1; + y = Math.random() * 2 - 1; + r = x * x + y * y; + } while (!r || r > 1); + return µ + σ * x * Math.sqrt(-2 * Math.log(r) / r); + }; + }, + logNormal: function(µ, σ) { + var n = arguments.length; + if (n < 2) σ = 1; + if (n < 1) µ = 0; + var random = d3.random.normal(); + return function() { + return Math.exp(µ + σ * random()); + }; + }, + irwinHall: function(m) { + return function() { + for (var s = 0, j = 0; j < m; j++) s += Math.random(); + return s / m; + }; + } + }; + d3.sum = function(array, f) { + var s = 0, n = array.length, a, i = -1; + if (arguments.length === 1) { + while (++i < n) if (!isNaN(a = +array[i])) s += a; + } else { + while (++i < n) if (!isNaN(a = +f.call(array, array[i], i))) s += a; + } + return s; + }; + d3.quantile = function(values, p) { + var H = (values.length - 1) * p + 1, h = Math.floor(H), v = values[h - 1], e = H - h; + return e ? v + e * (values[h] - v) : v; + }; + d3.transpose = function(matrix) { + return d3.zip.apply(d3, matrix); + }; + d3.zip = function() { + if (!(n = arguments.length)) return []; + for (var i = -1, m = d3.min(arguments, d3_zipLength), zips = new Array(m); ++i < m; ) { + for (var j = -1, n, zip = zips[i] = new Array(n); ++j < n; ) { + zip[j] = arguments[j][i]; + } + } + return zips; + }; + d3.bisector = function(f) { + return { + left: function(a, x, lo, hi) { + if (arguments.length < 3) lo = 0; + if (arguments.length < 4) hi = a.length; + while (lo < hi) { + var mid = lo + hi >>> 1; + if (f.call(a, a[mid], mid) < x) lo = mid + 1; else hi = mid; + } + return lo; + }, + right: function(a, x, lo, hi) { + if (arguments.length < 3) lo = 0; + if (arguments.length < 4) hi = a.length; + while (lo < hi) { + var mid = lo + hi >>> 1; + if (x < f.call(a, a[mid], mid)) hi = mid; else lo = mid + 1; + } + return lo; + } + }; + }; + var d3_bisector = d3.bisector(function(d) { + return d; + }); + d3.bisectLeft = d3_bisector.left; + d3.bisect = d3.bisectRight = d3_bisector.right; + d3.first = function(array, f) { + var i = 0, n = array.length, a = array[0], b; + if (arguments.length === 1) f = d3.ascending; + while (++i < n) { + if (f.call(array, a, b = array[i]) > 0) { + a = b; + } + } + return a; + }; + d3.last = function(array, f) { + var i = 0, n = array.length, a = array[0], b; + if (arguments.length === 1) f = d3.ascending; + while (++i < n) { + if (f.call(array, a, b = array[i]) <= 0) { + a = b; + } + } + return a; + }; + d3.nest = function() { + function map(array, depth) { + if (depth >= keys.length) return rollup ? rollup.call(nest, array) : sortValues ? array.sort(sortValues) : array; + var i = -1, n = array.length, key = keys[depth++], keyValue, object, valuesByKey = new d3_Map, values, o = {}; + while (++i < n) { + if (values = valuesByKey.get(keyValue = key(object = array[i]))) { + values.push(object); + } else { + valuesByKey.set(keyValue, [ object ]); + } + } + valuesByKey.forEach(function(keyValue, values) { + o[keyValue] = map(values, depth); + }); + return o; + } + function entries(map, depth) { + if (depth >= keys.length) return map; + var a = [], sortKey = sortKeys[depth++], key; + for (key in map) { + a.push({ + key: key, + values: entries(map[key], depth) + }); + } + if (sortKey) a.sort(function(a, b) { + return sortKey(a.key, b.key); + }); + return a; + } + var nest = {}, keys = [], sortKeys = [], sortValues, rollup; + nest.map = function(array) { + return map(array, 0); + }; + nest.entries = function(array) { + return entries(map(array, 0), 0); + }; + nest.key = function(d) { + keys.push(d); + return nest; + }; + nest.sortKeys = function(order) { + sortKeys[keys.length - 1] = order; + return nest; + }; + nest.sortValues = function(order) { + sortValues = order; + return nest; + }; + nest.rollup = function(f) { + rollup = f; + return nest; + }; + return nest; + }; + d3.keys = function(map) { + var keys = []; + for (var key in map) keys.push(key); + return keys; + }; + d3.values = function(map) { + var values = []; + for (var key in map) values.push(map[key]); + return values; + }; + d3.entries = function(map) { + var entries = []; + for (var key in map) entries.push({ + key: key, + value: map[key] + }); + return entries; + }; + d3.permute = function(array, indexes) { + var permutes = [], i = -1, n = indexes.length; + while (++i < n) permutes[i] = array[indexes[i]]; + return permutes; + }; + d3.merge = function(arrays) { + return Array.prototype.concat.apply([], arrays); + }; + d3.split = function(array, f) { + var arrays = [], values = [], value, i = -1, n = array.length; + if (arguments.length < 2) f = d3_splitter; + while (++i < n) { + if (f.call(values, value = array[i], i)) { + values = []; + } else { + if (!values.length) arrays.push(values); + values.push(value); + } + } + return arrays; + }; + d3.range = function(start, stop, step) { + if (arguments.length < 3) { + step = 1; + if (arguments.length < 2) { + stop = start; + start = 0; + } + } + if ((stop - start) / step === Infinity) throw new Error("infinite range"); + var range = [], k = d3_range_integerScale(Math.abs(step)), i = -1, j; + start *= k, stop *= k, step *= k; + if (step < 0) while ((j = start + step * ++i) > stop) range.push(j / k); else while ((j = start + step * ++i) < stop) range.push(j / k); + return range; + }; + d3.requote = function(s) { + return s.replace(d3_requote_re, "\\$&"); + }; + var d3_requote_re = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g; + d3.round = function(x, n) { + return n ? Math.round(x * (n = Math.pow(10, n))) / n : Math.round(x); + }; + d3.xhr = function(url, mime, callback) { + var req = new XMLHttpRequest; + if (arguments.length < 3) callback = mime, mime = null; else if (mime && req.overrideMimeType) req.overrideMimeType(mime); + req.open("GET", url, true); + if (mime) req.setRequestHeader("Accept", mime); + req.onreadystatechange = function() { + if (req.readyState === 4) { + var s = req.status; + callback(!s && req.response || s >= 200 && s < 300 || s === 304 ? req : null); + } + }; + req.send(null); + }; + d3.text = function(url, mime, callback) { + function ready(req) { + callback(req && req.responseText); + } + if (arguments.length < 3) { + callback = mime; + mime = null; + } + d3.xhr(url, mime, ready); + }; + d3.json = function(url, callback) { + d3.text(url, "application/json", function(text) { + callback(text ? JSON.parse(text) : null); + }); + }; + d3.html = function(url, callback) { + d3.text(url, "text/html", function(text) { + if (text != null) { + var range = document.createRange(); + range.selectNode(document.body); + text = range.createContextualFragment(text); + } + callback(text); + }); + }; + d3.xml = function(url, mime, callback) { + function ready(req) { + callback(req && req.responseXML); + } + if (arguments.length < 3) { + callback = mime; + mime = null; + } + d3.xhr(url, mime, ready); + }; + var d3_nsPrefix = { + svg: "http://www.w3.org/2000/svg", + xhtml: "http://www.w3.org/1999/xhtml", + xlink: "http://www.w3.org/1999/xlink", + xml: "http://www.w3.org/XML/1998/namespace", + xmlns: "http://www.w3.org/2000/xmlns/" + }; + d3.ns = { + prefix: d3_nsPrefix, + qualify: function(name) { + var i = name.indexOf(":"), prefix = name; + if (i >= 0) { + prefix = name.substring(0, i); + name = name.substring(i + 1); + } + return d3_nsPrefix.hasOwnProperty(prefix) ? { + space: d3_nsPrefix[prefix], + local: name + } : name; + } + }; + d3.dispatch = function() { + var dispatch = new d3_dispatch, i = -1, n = arguments.length; + while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch); + return dispatch; + }; + d3_dispatch.prototype.on = function(type, listener) { + var i = type.indexOf("."), name = ""; + if (i > 0) { + name = type.substring(i + 1); + type = type.substring(0, i); + } + return arguments.length < 2 ? this[type].on(name) : this[type].on(name, listener); + }; + d3.format = function(specifier) { + var match = d3_format_re.exec(specifier), fill = match[1] || " ", sign = match[3] || "", zfill = match[5], width = +match[6], comma = match[7], precision = match[8], type = match[9], scale = 1, suffix = "", integer = false; + if (precision) precision = +precision.substring(1); + if (zfill) { + fill = "0"; + if (comma) width -= Math.floor((width - 1) / 4); + } + switch (type) { + case "n": + comma = true; + type = "g"; + break; + case "%": + scale = 100; + suffix = "%"; + type = "f"; + break; + case "p": + scale = 100; + suffix = "%"; + type = "r"; + break; + case "d": + integer = true; + precision = 0; + break; + case "s": + scale = -1; + type = "r"; + break; + } + if (type == "r" && !precision) type = "g"; + type = d3_format_types.get(type) || d3_format_typeDefault; + return function(value) { + if (integer && value % 1) return ""; + var negative = value < 0 && (value = -value) ? "-" : sign; + if (scale < 0) { + var prefix = d3.formatPrefix(value, precision); + value = prefix.scale(value); + suffix = prefix.symbol; + } else { + value *= scale; + } + value = type(value, precision); + if (zfill) { + var length = value.length + negative.length; + if (length < width) value = (new Array(width - length + 1)).join(fill) + value; + if (comma) value = d3_format_group(value); + value = negative + value; + } else { + if (comma) value = d3_format_group(value); + value = negative + value; + var length = value.length; + if (length < width) value = (new Array(width - length + 1)).join(fill) + value; + } + return value + suffix; + }; + }; + var d3_format_re = /(?:([^{])?([<>=^]))?([+\- ])?(#)?(0)?([0-9]+)?(,)?(\.[0-9]+)?([a-zA-Z%])?/; + var d3_format_types = d3.map({ + g: function(x, p) { + return x.toPrecision(p); + }, + e: function(x, p) { + return x.toExponential(p); + }, + f: function(x, p) { + return x.toFixed(p); + }, + r: function(x, p) { + return d3.round(x, p = d3_format_precision(x, p)).toFixed(Math.max(0, Math.min(20, p))); + } + }); + var d3_formatPrefixes = [ "y", "z", "a", "f", "p", "n", "μ", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y" ].map(d3_formatPrefix); + d3.formatPrefix = function(value, precision) { + var i = 0; + if (value) { + if (value < 0) value *= -1; + if (precision) value = d3.round(value, d3_format_precision(value, precision)); + i = 1 + Math.floor(1e-12 + Math.log(value) / Math.LN10); + i = Math.max(-24, Math.min(24, Math.floor((i <= 0 ? i + 1 : i - 1) / 3) * 3)); + } + return d3_formatPrefixes[8 + i / 3]; + }; + var d3_ease_quad = d3_ease_poly(2), d3_ease_cubic = d3_ease_poly(3), d3_ease_default = function() { + return d3_ease_identity; + }; + var d3_ease = d3.map({ + linear: d3_ease_default, + poly: d3_ease_poly, + quad: function() { + return d3_ease_quad; + }, + cubic: function() { + return d3_ease_cubic; + }, + sin: function() { + return d3_ease_sin; + }, + exp: function() { + return d3_ease_exp; + }, + circle: function() { + return d3_ease_circle; + }, + elastic: d3_ease_elastic, + back: d3_ease_back, + bounce: function() { + return d3_ease_bounce; + } + }); + var d3_ease_mode = d3.map({ + "in": d3_ease_identity, + out: d3_ease_reverse, + "in-out": d3_ease_reflect, + "out-in": function(f) { + return d3_ease_reflect(d3_ease_reverse(f)); + } + }); + d3.ease = function(name) { + var i = name.indexOf("-"), t = i >= 0 ? name.substring(0, i) : name, m = i >= 0 ? name.substring(i + 1) : "in"; + t = d3_ease.get(t) || d3_ease_default; + m = d3_ease_mode.get(m) || d3_ease_identity; + return d3_ease_clamp(m(t.apply(null, Array.prototype.slice.call(arguments, 1)))); + }; + d3.event = null; + d3.transform = function(string) { + var g = document.createElementNS(d3.ns.prefix.svg, "g"); + return (d3.transform = function(string) { + g.setAttribute("transform", string); + var t = g.transform.baseVal.consolidate(); + return new d3_transform(t ? t.matrix : d3_transformIdentity); + })(string); + }; + d3_transform.prototype.toString = function() { + return "translate(" + this.translate + ")rotate(" + this.rotate + ")skewX(" + this.skew + ")scale(" + this.scale + ")"; + }; + var d3_transformDegrees = 180 / Math.PI, d3_transformIdentity = { + a: 1, + b: 0, + c: 0, + d: 1, + e: 0, + f: 0 + }; + d3.interpolate = function(a, b) { + var i = d3.interpolators.length, f; + while (--i >= 0 && !(f = d3.interpolators[i](a, b))) ; + return f; + }; + d3.interpolateNumber = function(a, b) { + b -= a; + return function(t) { + return a + b * t; + }; + }; + d3.interpolateRound = function(a, b) { + b -= a; + return function(t) { + return Math.round(a + b * t); + }; + }; + d3.interpolateString = function(a, b) { + var m, i, j, s0 = 0, s1 = 0, s = [], q = [], n, o; + d3_interpolate_number.lastIndex = 0; + for (i = 0; m = d3_interpolate_number.exec(b); ++i) { + if (m.index) s.push(b.substring(s0, s1 = m.index)); + q.push({ + i: s.length, + x: m[0] + }); + s.push(null); + s0 = d3_interpolate_number.lastIndex; + } + if (s0 < b.length) s.push(b.substring(s0)); + for (i = 0, n = q.length; (m = d3_interpolate_number.exec(a)) && i < n; ++i) { + o = q[i]; + if (o.x == m[0]) { + if (o.i) { + if (s[o.i + 1] == null) { + s[o.i - 1] += o.x; + s.splice(o.i, 1); + for (j = i + 1; j < n; ++j) q[j].i--; + } else { + s[o.i - 1] += o.x + s[o.i + 1]; + s.splice(o.i, 2); + for (j = i + 1; j < n; ++j) q[j].i -= 2; + } + } else { + if (s[o.i + 1] == null) { + s[o.i] = o.x; + } else { + s[o.i] = o.x + s[o.i + 1]; + s.splice(o.i + 1, 1); + for (j = i + 1; j < n; ++j) q[j].i--; + } + } + q.splice(i, 1); + n--; + i--; + } else { + o.x = d3.interpolateNumber(parseFloat(m[0]), parseFloat(o.x)); + } + } + while (i < n) { + o = q.pop(); + if (s[o.i + 1] == null) { + s[o.i] = o.x; + } else { + s[o.i] = o.x + s[o.i + 1]; + s.splice(o.i + 1, 1); + } + n--; + } + if (s.length === 1) { + return s[0] == null ? q[0].x : function() { + return b; + }; + } + return function(t) { + for (i = 0; i < n; ++i) s[(o = q[i]).i] = o.x(t); + return s.join(""); + }; + }; + d3.interpolateTransform = function(a, b) { + var s = [], q = [], n, A = d3.transform(a), B = d3.transform(b), ta = A.translate, tb = B.translate, ra = A.rotate, rb = B.rotate, wa = A.skew, wb = B.skew, ka = A.scale, kb = B.scale; + if (ta[0] != tb[0] || ta[1] != tb[1]) { + s.push("translate(", null, ",", null, ")"); + q.push({ + i: 1, + x: d3.interpolateNumber(ta[0], tb[0]) + }, { + i: 3, + x: d3.interpolateNumber(ta[1], tb[1]) + }); + } else if (tb[0] || tb[1]) { + s.push("translate(" + tb + ")"); + } else { + s.push(""); + } + if (ra != rb) { + if (ra - rb > 180) rb += 360; else if (rb - ra > 180) ra += 360; + q.push({ + i: s.push(s.pop() + "rotate(", null, ")") - 2, + x: d3.interpolateNumber(ra, rb) + }); + } else if (rb) { + s.push(s.pop() + "rotate(" + rb + ")"); + } + if (wa != wb) { + q.push({ + i: s.push(s.pop() + "skewX(", null, ")") - 2, + x: d3.interpolateNumber(wa, wb) + }); + } else if (wb) { + s.push(s.pop() + "skewX(" + wb + ")"); + } + if (ka[0] != kb[0] || ka[1] != kb[1]) { + n = s.push(s.pop() + "scale(", null, ",", null, ")"); + q.push({ + i: n - 4, + x: d3.interpolateNumber(ka[0], kb[0]) + }, { + i: n - 2, + x: d3.interpolateNumber(ka[1], kb[1]) + }); + } else if (kb[0] != 1 || kb[1] != 1) { + s.push(s.pop() + "scale(" + kb + ")"); + } + n = q.length; + return function(t) { + var i = -1, o; + while (++i < n) s[(o = q[i]).i] = o.x(t); + return s.join(""); + }; + }; + d3.interpolateRgb = function(a, b) { + a = d3.rgb(a); + b = d3.rgb(b); + var ar = a.r, ag = a.g, ab = a.b, br = b.r - ar, bg = b.g - ag, bb = b.b - ab; + return function(t) { + return "#" + d3_rgb_hex(Math.round(ar + br * t)) + d3_rgb_hex(Math.round(ag + bg * t)) + d3_rgb_hex(Math.round(ab + bb * t)); + }; + }; + d3.interpolateHsl = function(a, b) { + a = d3.hsl(a); + b = d3.hsl(b); + var h0 = a.h, s0 = a.s, l0 = a.l, h1 = b.h - h0, s1 = b.s - s0, l1 = b.l - l0; + if (h1 > 180) h1 -= 360; else if (h1 < -180) h1 += 360; + return function(t) { + return d3_hsl_rgb(h0 + h1 * t, s0 + s1 * t, l0 + l1 * t) + ""; + }; + }; + d3.interpolateLab = function(a, b) { + a = d3.lab(a); + b = d3.lab(b); + var al = a.l, aa = a.a, ab = a.b, bl = b.l - al, ba = b.a - aa, bb = b.b - ab; + return function(t) { + return d3_lab_rgb(al + bl * t, aa + ba * t, ab + bb * t) + ""; + }; + }; + d3.interpolateHcl = function(a, b) { + a = d3.hcl(a); + b = d3.hcl(b); + var ah = a.h, ac = a.c, al = a.l, bh = b.h - ah, bc = b.c - ac, bl = b.l - al; + if (bh > 180) bh -= 360; else if (bh < -180) bh += 360; + return function(t) { + return d3_hcl_lab(ah + bh * t, ac + bc * t, al + bl * t) + ""; + }; + }; + d3.interpolateArray = function(a, b) { + var x = [], c = [], na = a.length, nb = b.length, n0 = Math.min(a.length, b.length), i; + for (i = 0; i < n0; ++i) x.push(d3.interpolate(a[i], b[i])); + for (; i < na; ++i) c[i] = a[i]; + for (; i < nb; ++i) c[i] = b[i]; + return function(t) { + for (i = 0; i < n0; ++i) c[i] = x[i](t); + return c; + }; + }; + d3.interpolateObject = function(a, b) { + var i = {}, c = {}, k; + for (k in a) { + if (k in b) { + i[k] = d3_interpolateByName(k)(a[k], b[k]); + } else { + c[k] = a[k]; + } + } + for (k in b) { + if (!(k in a)) { + c[k] = b[k]; + } + } + return function(t) { + for (k in i) c[k] = i[k](t); + return c; + }; + }; + var d3_interpolate_number = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g; + d3.interpolators = [ d3.interpolateObject, function(a, b) { + return b instanceof Array && d3.interpolateArray(a, b); + }, function(a, b) { + return (typeof a === "string" || typeof b === "string") && d3.interpolateString(a + "", b + ""); + }, function(a, b) { + return (typeof b === "string" ? d3_rgb_names.has(b) || /^(#|rgb\(|hsl\()/.test(b) : b instanceof d3_Color) && d3.interpolateRgb(a, b); + }, function(a, b) { + return !isNaN(a = +a) && !isNaN(b = +b) && d3.interpolateNumber(a, b); + } ]; + d3_Color.prototype.toString = function() { + return this.rgb() + ""; + }; + d3.rgb = function(r, g, b) { + return arguments.length === 1 ? r instanceof d3_Rgb ? d3_rgb(r.r, r.g, r.b) : d3_rgb_parse("" + r, d3_rgb, d3_hsl_rgb) : d3_rgb(~~r, ~~g, ~~b); + }; + var d3_rgbPrototype = d3_Rgb.prototype = new d3_Color; + d3_rgbPrototype.brighter = function(k) { + k = Math.pow(.7, arguments.length ? k : 1); + var r = this.r, g = this.g, b = this.b, i = 30; + if (!r && !g && !b) return d3_rgb(i, i, i); + if (r && r < i) r = i; + if (g && g < i) g = i; + if (b && b < i) b = i; + return d3_rgb(Math.min(255, Math.floor(r / k)), Math.min(255, Math.floor(g / k)), Math.min(255, Math.floor(b / k))); + }; + d3_rgbPrototype.darker = function(k) { + k = Math.pow(.7, arguments.length ? k : 1); + return d3_rgb(Math.floor(k * this.r), Math.floor(k * this.g), Math.floor(k * this.b)); + }; + d3_rgbPrototype.hsl = function() { + return d3_rgb_hsl(this.r, this.g, this.b); + }; + d3_rgbPrototype.toString = function() { + return "#" + d3_rgb_hex(this.r) + d3_rgb_hex(this.g) + d3_rgb_hex(this.b); + }; + var d3_rgb_names = d3.map({ + aliceblue: "#f0f8ff", + antiquewhite: "#faebd7", + aqua: "#00ffff", + aquamarine: "#7fffd4", + azure: "#f0ffff", + beige: "#f5f5dc", + bisque: "#ffe4c4", + black: "#000000", + blanchedalmond: "#ffebcd", + blue: "#0000ff", + blueviolet: "#8a2be2", + brown: "#a52a2a", + burlywood: "#deb887", + cadetblue: "#5f9ea0", + chartreuse: "#7fff00", + chocolate: "#d2691e", + coral: "#ff7f50", + cornflowerblue: "#6495ed", + cornsilk: "#fff8dc", + crimson: "#dc143c", + cyan: "#00ffff", + darkblue: "#00008b", + darkcyan: "#008b8b", + darkgoldenrod: "#b8860b", + darkgray: "#a9a9a9", + darkgreen: "#006400", + darkgrey: "#a9a9a9", + darkkhaki: "#bdb76b", + darkmagenta: "#8b008b", + darkolivegreen: "#556b2f", + darkorange: "#ff8c00", + darkorchid: "#9932cc", + darkred: "#8b0000", + darksalmon: "#e9967a", + darkseagreen: "#8fbc8f", + darkslateblue: "#483d8b", + darkslategray: "#2f4f4f", + darkslategrey: "#2f4f4f", + darkturquoise: "#00ced1", + darkviolet: "#9400d3", + deeppink: "#ff1493", + deepskyblue: "#00bfff", + dimgray: "#696969", + dimgrey: "#696969", + dodgerblue: "#1e90ff", + firebrick: "#b22222", + floralwhite: "#fffaf0", + forestgreen: "#228b22", + fuchsia: "#ff00ff", + gainsboro: "#dcdcdc", + ghostwhite: "#f8f8ff", + gold: "#ffd700", + goldenrod: "#daa520", + gray: "#808080", + green: "#008000", + greenyellow: "#adff2f", + grey: "#808080", + honeydew: "#f0fff0", + hotpink: "#ff69b4", + indianred: "#cd5c5c", + indigo: "#4b0082", + ivory: "#fffff0", + khaki: "#f0e68c", + lavender: "#e6e6fa", + lavenderblush: "#fff0f5", + lawngreen: "#7cfc00", + lemonchiffon: "#fffacd", + lightblue: "#add8e6", + lightcoral: "#f08080", + lightcyan: "#e0ffff", + lightgoldenrodyellow: "#fafad2", + lightgray: "#d3d3d3", + lightgreen: "#90ee90", + lightgrey: "#d3d3d3", + lightpink: "#ffb6c1", + lightsalmon: "#ffa07a", + lightseagreen: "#20b2aa", + lightskyblue: "#87cefa", + lightslategray: "#778899", + lightslategrey: "#778899", + lightsteelblue: "#b0c4de", + lightyellow: "#ffffe0", + lime: "#00ff00", + limegreen: "#32cd32", + linen: "#faf0e6", + magenta: "#ff00ff", + maroon: "#800000", + mediumaquamarine: "#66cdaa", + mediumblue: "#0000cd", + mediumorchid: "#ba55d3", + mediumpurple: "#9370db", + mediumseagreen: "#3cb371", + mediumslateblue: "#7b68ee", + mediumspringgreen: "#00fa9a", + mediumturquoise: "#48d1cc", + mediumvioletred: "#c71585", + midnightblue: "#191970", + mintcream: "#f5fffa", + mistyrose: "#ffe4e1", + moccasin: "#ffe4b5", + navajowhite: "#ffdead", + navy: "#000080", + oldlace: "#fdf5e6", + olive: "#808000", + olivedrab: "#6b8e23", + orange: "#ffa500", + orangered: "#ff4500", + orchid: "#da70d6", + palegoldenrod: "#eee8aa", + palegreen: "#98fb98", + paleturquoise: "#afeeee", + palevioletred: "#db7093", + papayawhip: "#ffefd5", + peachpuff: "#ffdab9", + peru: "#cd853f", + pink: "#ffc0cb", + plum: "#dda0dd", + powderblue: "#b0e0e6", + purple: "#800080", + red: "#ff0000", + rosybrown: "#bc8f8f", + royalblue: "#4169e1", + saddlebrown: "#8b4513", + salmon: "#fa8072", + sandybrown: "#f4a460", + seagreen: "#2e8b57", + seashell: "#fff5ee", + sienna: "#a0522d", + silver: "#c0c0c0", + skyblue: "#87ceeb", + slateblue: "#6a5acd", + slategray: "#708090", + slategrey: "#708090", + snow: "#fffafa", + springgreen: "#00ff7f", + steelblue: "#4682b4", + tan: "#d2b48c", + teal: "#008080", + thistle: "#d8bfd8", + tomato: "#ff6347", + turquoise: "#40e0d0", + violet: "#ee82ee", + wheat: "#f5deb3", + white: "#ffffff", + whitesmoke: "#f5f5f5", + yellow: "#ffff00", + yellowgreen: "#9acd32" + }); + d3_rgb_names.forEach(function(key, value) { + d3_rgb_names.set(key, d3_rgb_parse(value, d3_rgb, d3_hsl_rgb)); + }); + d3.hsl = function(h, s, l) { + return arguments.length === 1 ? h instanceof d3_Hsl ? d3_hsl(h.h, h.s, h.l) : d3_rgb_parse("" + h, d3_rgb_hsl, d3_hsl) : d3_hsl(+h, +s, +l); + }; + var d3_hslPrototype = d3_Hsl.prototype = new d3_Color; + d3_hslPrototype.brighter = function(k) { + k = Math.pow(.7, arguments.length ? k : 1); + return d3_hsl(this.h, this.s, this.l / k); + }; + d3_hslPrototype.darker = function(k) { + k = Math.pow(.7, arguments.length ? k : 1); + return d3_hsl(this.h, this.s, k * this.l); + }; + d3_hslPrototype.rgb = function() { + return d3_hsl_rgb(this.h, this.s, this.l); + }; + d3.hcl = function(h, c, l) { + return arguments.length === 1 ? h instanceof d3_Hcl ? d3_hcl(h.h, h.c, h.l) : h instanceof d3_Lab ? d3_lab_hcl(h.l, h.a, h.b) : d3_lab_hcl((h = d3_rgb_lab((h = d3.rgb(h)).r, h.g, h.b)).l, h.a, h.b) : d3_hcl(+h, +c, +l); + }; + var d3_hclPrototype = d3_Hcl.prototype = new d3_Color; + d3_hclPrototype.brighter = function(k) { + return d3_hcl(this.h, this.c, Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1))); + }; + d3_hclPrototype.darker = function(k) { + return d3_hcl(this.h, this.c, Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1))); + }; + d3_hclPrototype.rgb = function() { + return d3_hcl_lab(this.h, this.c, this.l).rgb(); + }; + d3.lab = function(l, a, b) { + return arguments.length === 1 ? l instanceof d3_Lab ? d3_lab(l.l, l.a, l.b) : l instanceof d3_Hcl ? d3_hcl_lab(l.l, l.c, l.h) : d3_rgb_lab((l = d3.rgb(l)).r, l.g, l.b) : d3_lab(+l, +a, +b); + }; + var d3_lab_K = 18; + var d3_lab_X = .95047, d3_lab_Y = 1, d3_lab_Z = 1.08883; + var d3_labPrototype = d3_Lab.prototype = new d3_Color; + d3_labPrototype.brighter = function(k) { + return d3_lab(Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1)), this.a, this.b); + }; + d3_labPrototype.darker = function(k) { + return d3_lab(Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1)), this.a, this.b); + }; + d3_labPrototype.rgb = function() { + return d3_lab_rgb(this.l, this.a, this.b); + }; + var d3_select = function(s, n) { + return n.querySelector(s); + }, d3_selectAll = function(s, n) { + return n.querySelectorAll(s); + }, d3_selectRoot = document.documentElement, d3_selectMatcher = d3_selectRoot.matchesSelector || d3_selectRoot.webkitMatchesSelector || d3_selectRoot.mozMatchesSelector || d3_selectRoot.msMatchesSelector || d3_selectRoot.oMatchesSelector, d3_selectMatches = function(n, s) { + return d3_selectMatcher.call(n, s); + }; + if (typeof Sizzle === "function") { + d3_select = function(s, n) { + return Sizzle(s, n)[0] || null; + }; + d3_selectAll = function(s, n) { + return Sizzle.uniqueSort(Sizzle(s, n)); + }; + d3_selectMatches = Sizzle.matchesSelector; + } + var d3_selectionPrototype = []; + d3.selection = function() { + return d3_selectionRoot; + }; + d3.selection.prototype = d3_selectionPrototype; + d3_selectionPrototype.select = function(selector) { + var subgroups = [], subgroup, subnode, group, node; + if (typeof selector !== "function") selector = d3_selection_selector(selector); + for (var j = -1, m = this.length; ++j < m; ) { + subgroups.push(subgroup = []); + subgroup.parentNode = (group = this[j]).parentNode; + for (var i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) { + subgroup.push(subnode = selector.call(node, node.__data__, i)); + if (subnode && "__data__" in node) subnode.__data__ = node.__data__; + } else { + subgroup.push(null); + } + } + } + return d3_selection(subgroups); + }; + d3_selectionPrototype.selectAll = function(selector) { + var subgroups = [], subgroup, node; + if (typeof selector !== "function") selector = d3_selection_selectorAll(selector); + for (var j = -1, m = this.length; ++j < m; ) { + for (var group = this[j], i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) { + subgroups.push(subgroup = d3_array(selector.call(node, node.__data__, i))); + subgroup.parentNode = node; + } + } + } + return d3_selection(subgroups); + }; + d3_selectionPrototype.attr = function(name, value) { + if (arguments.length < 2) { + if (typeof name === "string") { + var node = this.node(); + name = d3.ns.qualify(name); + return name.local ? node.getAttributeNS(name.space, name.local) : node.getAttribute(name); + } + for (value in name) this.each(d3_selection_attr(value, name[value])); + return this; + } + return this.each(d3_selection_attr(name, value)); + }; + d3_selectionPrototype.classed = function(name, value) { + if (arguments.length < 2) { + if (typeof name === "string") { + var node = this.node(), n = (name = name.trim().split(/^|\s+/g)).length, i = -1; + if (value = node.classList) { + while (++i < n) if (!value.contains(name[i])) return false; + } else { + value = node.className; + if (value.baseVal != null) value = value.baseVal; + while (++i < n) if (!d3_selection_classedRe(name[i]).test(value)) return false; + } + return true; + } + for (value in name) this.each(d3_selection_classed(value, name[value])); + return this; + } + return this.each(d3_selection_classed(name, value)); + }; + d3_selectionPrototype.style = function(name, value, priority) { + var n = arguments.length; + if (n < 3) { + if (typeof name !== "string") { + if (n < 2) value = ""; + for (priority in name) this.each(d3_selection_style(priority, name[priority], value)); + return this; + } + if (n < 2) return window.getComputedStyle(this.node(), null).getPropertyValue(name); + priority = ""; + } + return this.each(d3_selection_style(name, value, priority)); + }; + d3_selectionPrototype.property = function(name, value) { + if (arguments.length < 2) { + if (typeof name === "string") return this.node()[name]; + for (value in name) this.each(d3_selection_property(value, name[value])); + return this; + } + return this.each(d3_selection_property(name, value)); + }; + d3_selectionPrototype.text = function(value) { + return arguments.length < 1 ? this.node().textContent : this.each(typeof value === "function" ? function() { + var v = value.apply(this, arguments); + this.textContent = v == null ? "" : v; + } : value == null ? function() { + this.textContent = ""; + } : function() { + this.textContent = value; + }); + }; + d3_selectionPrototype.html = function(value) { + return arguments.length < 1 ? this.node().innerHTML : this.each(typeof value === "function" ? function() { + var v = value.apply(this, arguments); + this.innerHTML = v == null ? "" : v; + } : value == null ? function() { + this.innerHTML = ""; + } : function() { + this.innerHTML = value; + }); + }; + d3_selectionPrototype.append = function(name) { + function append() { + return this.appendChild(document.createElementNS(this.namespaceURI, name)); + } + function appendNS() { + return this.appendChild(document.createElementNS(name.space, name.local)); + } + name = d3.ns.qualify(name); + return this.select(name.local ? appendNS : append); + }; + d3_selectionPrototype.insert = function(name, before) { + function insert() { + return this.insertBefore(document.createElementNS(this.namespaceURI, name), d3_select(before, this)); + } + function insertNS() { + return this.insertBefore(document.createElementNS(name.space, name.local), d3_select(before, this)); + } + name = d3.ns.qualify(name); + return this.select(name.local ? insertNS : insert); + }; + d3_selectionPrototype.remove = function() { + return this.each(function() { + var parent = this.parentNode; + if (parent) parent.removeChild(this); + }); + }; + d3_selectionPrototype.data = function(value, key) { + function bind(group, groupData) { + var i, n = group.length, m = groupData.length, n0 = Math.min(n, m), n1 = Math.max(n, m), updateNodes = [], enterNodes = [], exitNodes = [], node, nodeData; + if (key) { + var nodeByKeyValue = new d3_Map, keyValues = [], keyValue, j = groupData.length; + for (i = -1; ++i < n; ) { + keyValue = key.call(node = group[i], node.__data__, i); + if (nodeByKeyValue.has(keyValue)) { + exitNodes[j++] = node; + } else { + nodeByKeyValue.set(keyValue, node); + } + keyValues.push(keyValue); + } + for (i = -1; ++i < m; ) { + keyValue = key.call(groupData, nodeData = groupData[i], i); + if (nodeByKeyValue.has(keyValue)) { + updateNodes[i] = node = nodeByKeyValue.get(keyValue); + node.__data__ = nodeData; + enterNodes[i] = exitNodes[i] = null; + } else { + enterNodes[i] = d3_selection_dataNode(nodeData); + updateNodes[i] = exitNodes[i] = null; + } + nodeByKeyValue.remove(keyValue); + } + for (i = -1; ++i < n; ) { + if (nodeByKeyValue.has(keyValues[i])) { + exitNodes[i] = group[i]; + } + } + } else { + for (i = -1; ++i < n0; ) { + node = group[i]; + nodeData = groupData[i]; + if (node) { + node.__data__ = nodeData; + updateNodes[i] = node; + enterNodes[i] = exitNodes[i] = null; + } else { + enterNodes[i] = d3_selection_dataNode(nodeData); + updateNodes[i] = exitNodes[i] = null; + } + } + for (; i < m; ++i) { + enterNodes[i] = d3_selection_dataNode(groupData[i]); + updateNodes[i] = exitNodes[i] = null; + } + for (; i < n1; ++i) { + exitNodes[i] = group[i]; + enterNodes[i] = updateNodes[i] = null; + } + } + enterNodes.update = updateNodes; + enterNodes.parentNode = updateNodes.parentNode = exitNodes.parentNode = group.parentNode; + enter.push(enterNodes); + update.push(updateNodes); + exit.push(exitNodes); + } + var i = -1, n = this.length, group, node; + if (!arguments.length) { + value = new Array(n = (group = this[0]).length); + while (++i < n) { + if (node = group[i]) { + value[i] = node.__data__; + } + } + return value; + } + var enter = d3_selection_enter([]), update = d3_selection([]), exit = d3_selection([]); + if (typeof value === "function") { + while (++i < n) { + bind(group = this[i], value.call(group, group.parentNode.__data__, i)); + } + } else { + while (++i < n) { + bind(group = this[i], value); + } + } + update.enter = function() { + return enter; + }; + update.exit = function() { + return exit; + }; + return update; + }; + d3_selectionPrototype.datum = d3_selectionPrototype.map = function(value) { + return arguments.length < 1 ? this.property("__data__") : this.property("__data__", value); + }; + d3_selectionPrototype.filter = function(filter) { + var subgroups = [], subgroup, group, node; + if (typeof filter !== "function") filter = d3_selection_filter(filter); + for (var j = 0, m = this.length; j < m; j++) { + subgroups.push(subgroup = []); + subgroup.parentNode = (group = this[j]).parentNode; + for (var i = 0, n = group.length; i < n; i++) { + if ((node = group[i]) && filter.call(node, node.__data__, i)) { + subgroup.push(node); + } + } + } + return d3_selection(subgroups); + }; + d3_selectionPrototype.order = function() { + for (var j = -1, m = this.length; ++j < m; ) { + for (var group = this[j], i = group.length - 1, next = group[i], node; --i >= 0; ) { + if (node = group[i]) { + if (next && next !== node.nextSibling) next.parentNode.insertBefore(node, next); + next = node; + } + } + } + return this; + }; + d3_selectionPrototype.sort = function(comparator) { + comparator = d3_selection_sortComparator.apply(this, arguments); + for (var j = -1, m = this.length; ++j < m; ) this[j].sort(comparator); + return this.order(); + }; + d3_selectionPrototype.on = function(type, listener, capture) { + var n = arguments.length; + if (n < 3) { + if (typeof type !== "string") { + if (n < 2) listener = false; + for (capture in type) this.each(d3_selection_on(capture, type[capture], listener)); + return this; + } + if (n < 2) return (n = this.node()["__on" + type]) && n._; + capture = false; + } + return this.each(d3_selection_on(type, listener, capture)); + }; + d3_selectionPrototype.each = function(callback) { + return d3_selection_each(this, function(node, i, j) { + callback.call(node, node.__data__, i, j); + }); + }; + d3_selectionPrototype.call = function(callback) { + callback.apply(this, (arguments[0] = this, arguments)); + return this; + }; + d3_selectionPrototype.empty = function() { + return !this.node(); + }; + d3_selectionPrototype.node = function(callback) { + for (var j = 0, m = this.length; j < m; j++) { + for (var group = this[j], i = 0, n = group.length; i < n; i++) { + var node = group[i]; + if (node) return node; + } + } + return null; + }; + d3_selectionPrototype.transition = function() { + var subgroups = [], subgroup, node; + for (var j = -1, m = this.length; ++j < m; ) { + subgroups.push(subgroup = []); + for (var group = this[j], i = -1, n = group.length; ++i < n; ) { + subgroup.push((node = group[i]) ? { + node: node, + delay: d3_transitionDelay, + duration: d3_transitionDuration + } : null); + } + } + return d3_transition(subgroups, d3_transitionId || ++d3_transitionNextId, Date.now()); + }; + var d3_selectionRoot = d3_selection([ [ document ] ]); + d3_selectionRoot[0].parentNode = d3_selectRoot; + d3.select = function(selector) { + return typeof selector === "string" ? d3_selectionRoot.select(selector) : d3_selection([ [ selector ] ]); + }; + d3.selectAll = function(selector) { + return typeof selector === "string" ? d3_selectionRoot.selectAll(selector) : d3_selection([ d3_array(selector) ]); + }; + var d3_selection_enterPrototype = []; + d3.selection.enter = d3_selection_enter; + d3.selection.enter.prototype = d3_selection_enterPrototype; + d3_selection_enterPrototype.append = d3_selectionPrototype.append; + d3_selection_enterPrototype.insert = d3_selectionPrototype.insert; + d3_selection_enterPrototype.empty = d3_selectionPrototype.empty; + d3_selection_enterPrototype.node = d3_selectionPrototype.node; + d3_selection_enterPrototype.select = function(selector) { + var subgroups = [], subgroup, subnode, upgroup, group, node; + for (var j = -1, m = this.length; ++j < m; ) { + upgroup = (group = this[j]).update; + subgroups.push(subgroup = []); + subgroup.parentNode = group.parentNode; + for (var i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) { + subgroup.push(upgroup[i] = subnode = selector.call(group.parentNode, node.__data__, i)); + subnode.__data__ = node.__data__; + } else { + subgroup.push(null); + } + } + } + return d3_selection(subgroups); + }; + var d3_transitionPrototype = [], d3_transitionNextId = 0, d3_transitionId = 0, d3_transitionDefaultDelay = 0, d3_transitionDefaultDuration = 250, d3_transitionDefaultEase = d3.ease("cubic-in-out"), d3_transitionDelay = d3_transitionDefaultDelay, d3_transitionDuration = d3_transitionDefaultDuration, d3_transitionEase = d3_transitionDefaultEase; + d3_transitionPrototype.call = d3_selectionPrototype.call; + d3.transition = function(selection) { + return arguments.length ? d3_transitionId ? selection.transition() : selection : d3_selectionRoot.transition(); + }; + d3.transition.prototype = d3_transitionPrototype; + d3_transitionPrototype.select = function(selector) { + var subgroups = [], subgroup, subnode, node; + if (typeof selector !== "function") selector = d3_selection_selector(selector); + for (var j = -1, m = this.length; ++j < m; ) { + subgroups.push(subgroup = []); + for (var group = this[j], i = -1, n = group.length; ++i < n; ) { + if ((node = group[i]) && (subnode = selector.call(node.node, node.node.__data__, i))) { + if ("__data__" in node.node) subnode.__data__ = node.node.__data__; + subgroup.push({ + node: subnode, + delay: node.delay, + duration: node.duration + }); + } else { + subgroup.push(null); + } + } + } + return d3_transition(subgroups, this.id, this.time).ease(this.ease()); + }; + d3_transitionPrototype.selectAll = function(selector) { + var subgroups = [], subgroup, subnodes, node; + if (typeof selector !== "function") selector = d3_selection_selectorAll(selector); + for (var j = -1, m = this.length; ++j < m; ) { + for (var group = this[j], i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) { + subnodes = selector.call(node.node, node.node.__data__, i); + subgroups.push(subgroup = []); + for (var k = -1, o = subnodes.length; ++k < o; ) { + subgroup.push({ + node: subnodes[k], + delay: node.delay, + duration: node.duration + }); + } + } + } + } + return d3_transition(subgroups, this.id, this.time).ease(this.ease()); + }; + d3_transitionPrototype.filter = function(filter) { + var subgroups = [], subgroup, group, node; + if (typeof filter !== "function") filter = d3_selection_filter(filter); + for (var j = 0, m = this.length; j < m; j++) { + subgroups.push(subgroup = []); + for (var group = this[j], i = 0, n = group.length; i < n; i++) { + if ((node = group[i]) && filter.call(node.node, node.node.__data__, i)) { + subgroup.push(node); + } + } + } + return d3_transition(subgroups, this.id, this.time).ease(this.ease()); + }; + d3_transitionPrototype.attr = function(name, value) { + if (arguments.length < 2) { + for (value in name) this.attrTween(value, d3_tweenByName(name[value], value)); + return this; + } + return this.attrTween(name, d3_tweenByName(value, name)); + }; + d3_transitionPrototype.attrTween = function(nameNS, tween) { + function attrTween(d, i) { + var f = tween.call(this, d, i, this.getAttribute(name)); + return f === d3_tweenRemove ? (this.removeAttribute(name), null) : f && function(t) { + this.setAttribute(name, f(t)); + }; + } + function attrTweenNS(d, i) { + var f = tween.call(this, d, i, this.getAttributeNS(name.space, name.local)); + return f === d3_tweenRemove ? (this.removeAttributeNS(name.space, name.local), null) : f && function(t) { + this.setAttributeNS(name.space, name.local, f(t)); + }; + } + var name = d3.ns.qualify(nameNS); + return this.tween("attr." + nameNS, name.local ? attrTweenNS : attrTween); + }; + d3_transitionPrototype.style = function(name, value, priority) { + var n = arguments.length; + if (n < 3) { + if (typeof name !== "string") { + if (n < 2) value = ""; + for (priority in name) this.styleTween(priority, d3_tweenByName(name[priority], priority), value); + return this; + } + priority = ""; + } + return this.styleTween(name, d3_tweenByName(value, name), priority); + }; + d3_transitionPrototype.styleTween = function(name, tween, priority) { + if (arguments.length < 3) priority = ""; + return this.tween("style." + name, function(d, i) { + var f = tween.call(this, d, i, window.getComputedStyle(this, null).getPropertyValue(name)); + return f === d3_tweenRemove ? (this.style.removeProperty(name), null) : f && function(t) { + this.style.setProperty(name, f(t), priority); + }; + }); + }; + d3_transitionPrototype.text = function(value) { + return this.tween("text", function(d, i) { + this.textContent = typeof value === "function" ? value.call(this, d, i) : value; + }); + }; + d3_transitionPrototype.remove = function() { + return this.each("end.transition", function() { + var p; + if (!this.__transition__ && (p = this.parentNode)) p.removeChild(this); + }); + }; + d3_transitionPrototype.delay = function(value) { + return d3_selection_each(this, typeof value === "function" ? function(node, i, j) { + node.delay = value.call(node = node.node, node.__data__, i, j) | 0; + } : (value = value | 0, function(node) { + node.delay = value; + })); + }; + d3_transitionPrototype.duration = function(value) { + return d3_selection_each(this, typeof value === "function" ? function(node, i, j) { + node.duration = Math.max(1, value.call(node = node.node, node.__data__, i, j) | 0); + } : (value = Math.max(1, value | 0), function(node) { + node.duration = value; + })); + }; + d3_transitionPrototype.transition = function() { + return this.select(d3_this); + }; + d3.tween = function(b, interpolate) { + function tweenFunction(d, i, a) { + var v = b.call(this, d, i); + return v == null ? a != "" && d3_tweenRemove : a != v && interpolate(a, v + ""); + } + function tweenString(d, i, a) { + return a != b && interpolate(a, b); + } + return typeof b === "function" ? tweenFunction : b == null ? d3_tweenNull : (b += "", tweenString); + }; + var d3_tweenRemove = {}; + var d3_timer_id = 0, d3_timer_byId = {}, d3_timer_queue = null, d3_timer_interval, d3_timer_timeout; + d3.timer = function(callback, delay, then) { + if (arguments.length < 3) { + if (arguments.length < 2) delay = 0; else if (!isFinite(delay)) return; + then = Date.now(); + } + var timer = d3_timer_byId[callback.id]; + if (timer && timer.callback === callback) { + timer.then = then; + timer.delay = delay; + } else d3_timer_byId[callback.id = ++d3_timer_id] = d3_timer_queue = { + callback: callback, + then: then, + delay: delay, + next: d3_timer_queue + }; + if (!d3_timer_interval) { + d3_timer_timeout = clearTimeout(d3_timer_timeout); + d3_timer_interval = 1; + d3_timer_frame(d3_timer_step); + } + }; + d3.timer.flush = function() { + var elapsed, now = Date.now(), t1 = d3_timer_queue; + while (t1) { + elapsed = now - t1.then; + if (!t1.delay) t1.flush = t1.callback(elapsed); + t1 = t1.next; + } + d3_timer_flush(); + }; + var d3_timer_frame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) { + setTimeout(callback, 17); + }; + d3.mouse = function(container) { + return d3_mousePoint(container, d3_eventSource()); + }; + var d3_mouse_bug44083 = /WebKit/.test(navigator.userAgent) ? -1 : 0; + d3.touches = function(container, touches) { + if (arguments.length < 2) touches = d3_eventSource().touches; + return touches ? d3_array(touches).map(function(touch) { + var point = d3_mousePoint(container, touch); + point.identifier = touch.identifier; + return point; + }) : []; + }; + d3.scale = {}; + d3.scale.linear = function() { + return d3_scale_linear([ 0, 1 ], [ 0, 1 ], d3.interpolate, false); + }; + d3.scale.log = function() { + return d3_scale_log(d3.scale.linear(), d3_scale_logp); + }; + var d3_scale_logFormat = d3.format(".0e"); + d3_scale_logp.pow = function(x) { + return Math.pow(10, x); + }; + d3_scale_logn.pow = function(x) { + return -Math.pow(10, -x); + }; + d3.scale.pow = function() { + return d3_scale_pow(d3.scale.linear(), 1); + }; + d3.scale.sqrt = function() { + return d3.scale.pow().exponent(.5); + }; + d3.scale.ordinal = function() { + return d3_scale_ordinal([], { + t: "range", + a: [ [] ] + }); + }; + d3.scale.category10 = function() { + return d3.scale.ordinal().range(d3_category10); + }; + d3.scale.category20 = function() { + return d3.scale.ordinal().range(d3_category20); + }; + d3.scale.category20b = function() { + return d3.scale.ordinal().range(d3_category20b); + }; + d3.scale.category20c = function() { + return d3.scale.ordinal().range(d3_category20c); + }; + var d3_category10 = [ "#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf" ]; + var d3_category20 = [ "#1f77b4", "#aec7e8", "#ff7f0e", "#ffbb78", "#2ca02c", "#98df8a", "#d62728", "#ff9896", "#9467bd", "#c5b0d5", "#8c564b", "#c49c94", "#e377c2", "#f7b6d2", "#7f7f7f", "#c7c7c7", "#bcbd22", "#dbdb8d", "#17becf", "#9edae5" ]; + var d3_category20b = [ "#393b79", "#5254a3", "#6b6ecf", "#9c9ede", "#637939", "#8ca252", "#b5cf6b", "#cedb9c", "#8c6d31", "#bd9e39", "#e7ba52", "#e7cb94", "#843c39", "#ad494a", "#d6616b", "#e7969c", "#7b4173", "#a55194", "#ce6dbd", "#de9ed6" ]; + var d3_category20c = [ "#3182bd", "#6baed6", "#9ecae1", "#c6dbef", "#e6550d", "#fd8d3c", "#fdae6b", "#fdd0a2", "#31a354", "#74c476", "#a1d99b", "#c7e9c0", "#756bb1", "#9e9ac8", "#bcbddc", "#dadaeb", "#636363", "#969696", "#bdbdbd", "#d9d9d9" ]; + d3.scale.quantile = function() { + return d3_scale_quantile([], []); + }; + d3.scale.quantize = function() { + return d3_scale_quantize(0, 1, [ 0, 1 ]); + }; + d3.scale.threshold = function() { + return d3_scale_threshold([ .5 ], [ 0, 1 ]); + }; + d3.scale.identity = function() { + return d3_scale_identity([ 0, 1 ]); + }; + d3.svg = {}; + d3.svg.arc = function() { + function arc() { + var r0 = innerRadius.apply(this, arguments), r1 = outerRadius.apply(this, arguments), a0 = startAngle.apply(this, arguments) + d3_svg_arcOffset, a1 = endAngle.apply(this, arguments) + d3_svg_arcOffset, da = (a1 < a0 && (da = a0, a0 = a1, a1 = da), a1 - a0), df = da < Math.PI ? "0" : "1", c0 = Math.cos(a0), s0 = Math.sin(a0), c1 = Math.cos(a1), s1 = Math.sin(a1); + return da >= d3_svg_arcMax ? r0 ? "M0," + r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + -r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + r1 + "M0," + r0 + "A" + r0 + "," + r0 + " 0 1,0 0," + -r0 + "A" + r0 + "," + r0 + " 0 1,0 0," + r0 + "Z" : "M0," + r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + -r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + r1 + "Z" : r0 ? "M" + r1 * c0 + "," + r1 * s0 + "A" + r1 + "," + r1 + " 0 " + df + ",1 " + r1 * c1 + "," + r1 * s1 + "L" + r0 * c1 + "," + r0 * s1 + "A" + r0 + "," + r0 + " 0 " + df + ",0 " + r0 * c0 + "," + r0 * s0 + "Z" : "M" + r1 * c0 + "," + r1 * s0 + "A" + r1 + "," + r1 + " 0 " + df + ",1 " + r1 * c1 + "," + r1 * s1 + "L0,0" + "Z"; + } + var innerRadius = d3_svg_arcInnerRadius, outerRadius = d3_svg_arcOuterRadius, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle; + arc.innerRadius = function(v) { + if (!arguments.length) return innerRadius; + innerRadius = d3_functor(v); + return arc; + }; + arc.outerRadius = function(v) { + if (!arguments.length) return outerRadius; + outerRadius = d3_functor(v); + return arc; + }; + arc.startAngle = function(v) { + if (!arguments.length) return startAngle; + startAngle = d3_functor(v); + return arc; + }; + arc.endAngle = function(v) { + if (!arguments.length) return endAngle; + endAngle = d3_functor(v); + return arc; + }; + arc.centroid = function() { + var r = (innerRadius.apply(this, arguments) + outerRadius.apply(this, arguments)) / 2, a = (startAngle.apply(this, arguments) + endAngle.apply(this, arguments)) / 2 + d3_svg_arcOffset; + return [ Math.cos(a) * r, Math.sin(a) * r ]; + }; + return arc; + }; + var d3_svg_arcOffset = -Math.PI / 2, d3_svg_arcMax = 2 * Math.PI - 1e-6; + d3.svg.line = function() { + return d3_svg_line(d3_identity); + }; + var d3_svg_lineInterpolators = d3.map({ + linear: d3_svg_lineLinear, + "linear-closed": d3_svg_lineLinearClosed, + "step-before": d3_svg_lineStepBefore, + "step-after": d3_svg_lineStepAfter, + basis: d3_svg_lineBasis, + "basis-open": d3_svg_lineBasisOpen, + "basis-closed": d3_svg_lineBasisClosed, + bundle: d3_svg_lineBundle, + cardinal: d3_svg_lineCardinal, + "cardinal-open": d3_svg_lineCardinalOpen, + "cardinal-closed": d3_svg_lineCardinalClosed, + monotone: d3_svg_lineMonotone + }); + d3_svg_lineInterpolators.forEach(function(key, value) { + value.key = key; + value.closed = /-closed$/.test(key); + }); + var d3_svg_lineBasisBezier1 = [ 0, 2 / 3, 1 / 3, 0 ], d3_svg_lineBasisBezier2 = [ 0, 1 / 3, 2 / 3, 0 ], d3_svg_lineBasisBezier3 = [ 0, 1 / 6, 2 / 3, 1 / 6 ]; + d3.svg.line.radial = function() { + var line = d3_svg_line(d3_svg_lineRadial); + line.radius = line.x, delete line.x; + line.angle = line.y, delete line.y; + return line; + }; + d3_svg_lineStepBefore.reverse = d3_svg_lineStepAfter; + d3_svg_lineStepAfter.reverse = d3_svg_lineStepBefore; + d3.svg.area = function() { + return d3_svg_area(d3_identity); + }; + d3.svg.area.radial = function() { + var area = d3_svg_area(d3_svg_lineRadial); + area.radius = area.x, delete area.x; + area.innerRadius = area.x0, delete area.x0; + area.outerRadius = area.x1, delete area.x1; + area.angle = area.y, delete area.y; + area.startAngle = area.y0, delete area.y0; + area.endAngle = area.y1, delete area.y1; + return area; + }; + d3.svg.chord = function() { + function chord(d, i) { + var s = subgroup(this, source, d, i), t = subgroup(this, target, d, i); + return "M" + s.p0 + arc(s.r, s.p1, s.a1 - s.a0) + (equals(s, t) ? curve(s.r, s.p1, s.r, s.p0) : curve(s.r, s.p1, t.r, t.p0) + arc(t.r, t.p1, t.a1 - t.a0) + curve(t.r, t.p1, s.r, s.p0)) + "Z"; + } + function subgroup(self, f, d, i) { + var subgroup = f.call(self, d, i), r = radius.call(self, subgroup, i), a0 = startAngle.call(self, subgroup, i) + d3_svg_arcOffset, a1 = endAngle.call(self, subgroup, i) + d3_svg_arcOffset; + return { + r: r, + a0: a0, + a1: a1, + p0: [ r * Math.cos(a0), r * Math.sin(a0) ], + p1: [ r * Math.cos(a1), r * Math.sin(a1) ] + }; + } + function equals(a, b) { + return a.a0 == b.a0 && a.a1 == b.a1; + } + function arc(r, p, a) { + return "A" + r + "," + r + " 0 " + +(a > Math.PI) + ",1 " + p; + } + function curve(r0, p0, r1, p1) { + return "Q 0,0 " + p1; + } + var source = d3_svg_chordSource, target = d3_svg_chordTarget, radius = d3_svg_chordRadius, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle; + chord.radius = function(v) { + if (!arguments.length) return radius; + radius = d3_functor(v); + return chord; + }; + chord.source = function(v) { + if (!arguments.length) return source; + source = d3_functor(v); + return chord; + }; + chord.target = function(v) { + if (!arguments.length) return target; + target = d3_functor(v); + return chord; + }; + chord.startAngle = function(v) { + if (!arguments.length) return startAngle; + startAngle = d3_functor(v); + return chord; + }; + chord.endAngle = function(v) { + if (!arguments.length) return endAngle; + endAngle = d3_functor(v); + return chord; + }; + return chord; + }; + d3.svg.diagonal = function() { + function diagonal(d, i) { + var p0 = source.call(this, d, i), p3 = target.call(this, d, i), m = (p0.y + p3.y) / 2, p = [ p0, { + x: p0.x, + y: m + }, { + x: p3.x, + y: m + }, p3 ]; + p = p.map(projection); + return "M" + p[0] + "C" + p[1] + " " + p[2] + " " + p[3]; + } + var source = d3_svg_chordSource, target = d3_svg_chordTarget, projection = d3_svg_diagonalProjection; + diagonal.source = function(x) { + if (!arguments.length) return source; + source = d3_functor(x); + return diagonal; + }; + diagonal.target = function(x) { + if (!arguments.length) return target; + target = d3_functor(x); + return diagonal; + }; + diagonal.projection = function(x) { + if (!arguments.length) return projection; + projection = x; + return diagonal; + }; + return diagonal; + }; + d3.svg.diagonal.radial = function() { + var diagonal = d3.svg.diagonal(), projection = d3_svg_diagonalProjection, projection_ = diagonal.projection; + diagonal.projection = function(x) { + return arguments.length ? projection_(d3_svg_diagonalRadialProjection(projection = x)) : projection; + }; + return diagonal; + }; + d3.svg.mouse = d3.mouse; + d3.svg.touches = d3.touches; + d3.svg.symbol = function() { + function symbol(d, i) { + return (d3_svg_symbols.get(type.call(this, d, i)) || d3_svg_symbolCircle)(size.call(this, d, i)); + } + var type = d3_svg_symbolType, size = d3_svg_symbolSize; + symbol.type = function(x) { + if (!arguments.length) return type; + type = d3_functor(x); + return symbol; + }; + symbol.size = function(x) { + if (!arguments.length) return size; + size = d3_functor(x); + return symbol; + }; + return symbol; + }; + var d3_svg_symbols = d3.map({ + circle: d3_svg_symbolCircle, + cross: function(size) { + var r = Math.sqrt(size / 5) / 2; + return "M" + -3 * r + "," + -r + "H" + -r + "V" + -3 * r + "H" + r + "V" + -r + "H" + 3 * r + "V" + r + "H" + r + "V" + 3 * r + "H" + -r + "V" + r + "H" + -3 * r + "Z"; + }, + diamond: function(size) { + var ry = Math.sqrt(size / (2 * d3_svg_symbolTan30)), rx = ry * d3_svg_symbolTan30; + return "M0," + -ry + "L" + rx + ",0" + " 0," + ry + " " + -rx + ",0" + "Z"; + }, + square: function(size) { + var r = Math.sqrt(size) / 2; + return "M" + -r + "," + -r + "L" + r + "," + -r + " " + r + "," + r + " " + -r + "," + r + "Z"; + }, + "triangle-down": function(size) { + var rx = Math.sqrt(size / d3_svg_symbolSqrt3), ry = rx * d3_svg_symbolSqrt3 / 2; + return "M0," + ry + "L" + rx + "," + -ry + " " + -rx + "," + -ry + "Z"; + }, + "triangle-up": function(size) { + var rx = Math.sqrt(size / d3_svg_symbolSqrt3), ry = rx * d3_svg_symbolSqrt3 / 2; + return "M0," + -ry + "L" + rx + "," + ry + " " + -rx + "," + ry + "Z"; + } + }); + d3.svg.symbolTypes = d3_svg_symbols.keys(); + var d3_svg_symbolSqrt3 = Math.sqrt(3), d3_svg_symbolTan30 = Math.tan(30 * Math.PI / 180); + d3.svg.axis = function() { + function axis(g) { + g.each(function() { + var g = d3.select(this); + var ticks = tickValues == null ? scale.ticks ? scale.ticks.apply(scale, tickArguments_) : scale.domain() : tickValues, tickFormat = tickFormat_ == null ? scale.tickFormat ? scale.tickFormat.apply(scale, tickArguments_) : String : tickFormat_; + var subticks = d3_svg_axisSubdivide(scale, ticks, tickSubdivide), subtick = g.selectAll(".minor").data(subticks, String), subtickEnter = subtick.enter().insert("line", "g").attr("class", "tick minor").style("opacity", 1e-6), subtickExit = d3.transition(subtick.exit()).style("opacity", 1e-6).remove(), subtickUpdate = d3.transition(subtick).style("opacity", 1); + var tick = g.selectAll("g").data(ticks, String), tickEnter = tick.enter().insert("g", "path").style("opacity", 1e-6), tickExit = d3.transition(tick.exit()).style("opacity", 1e-6).remove(), tickUpdate = d3.transition(tick).style("opacity", 1), tickTransform; + var range = d3_scaleRange(scale), path = g.selectAll(".domain").data([ 0 ]), pathEnter = path.enter().append("path").attr("class", "domain"), pathUpdate = d3.transition(path); + var scale1 = scale.copy(), scale0 = this.__chart__ || scale1; + this.__chart__ = scale1; + tickEnter.append("line").attr("class", "tick"); + tickEnter.append("text"); + var lineEnter = tickEnter.select("line"), lineUpdate = tickUpdate.select("line"), text = tick.select("text").text(tickFormat), textEnter = tickEnter.select("text"), textUpdate = tickUpdate.select("text"); + switch (orient) { + case "bottom": + { + tickTransform = d3_svg_axisX; + subtickEnter.attr("y2", tickMinorSize); + subtickUpdate.attr("x2", 0).attr("y2", tickMinorSize); + lineEnter.attr("y2", tickMajorSize); + textEnter.attr("y", Math.max(tickMajorSize, 0) + tickPadding); + lineUpdate.attr("x2", 0).attr("y2", tickMajorSize); + textUpdate.attr("x", 0).attr("y", Math.max(tickMajorSize, 0) + tickPadding); + text.attr("dy", ".71em").attr("text-anchor", "middle"); + pathUpdate.attr("d", "M" + range[0] + "," + tickEndSize + "V0H" + range[1] + "V" + tickEndSize); + break; + } + case "top": + { + tickTransform = d3_svg_axisX; + subtickEnter.attr("y2", -tickMinorSize); + subtickUpdate.attr("x2", 0).attr("y2", -tickMinorSize); + lineEnter.attr("y2", -tickMajorSize); + textEnter.attr("y", -(Math.max(tickMajorSize, 0) + tickPadding)); + lineUpdate.attr("x2", 0).attr("y2", -tickMajorSize); + textUpdate.attr("x", 0).attr("y", -(Math.max(tickMajorSize, 0) + tickPadding)); + text.attr("dy", "0em").attr("text-anchor", "middle"); + pathUpdate.attr("d", "M" + range[0] + "," + -tickEndSize + "V0H" + range[1] + "V" + -tickEndSize); + break; + } + case "left": + { + tickTransform = d3_svg_axisY; + subtickEnter.attr("x2", -tickMinorSize); + subtickUpdate.attr("x2", -tickMinorSize).attr("y2", 0); + lineEnter.attr("x2", -tickMajorSize); + textEnter.attr("x", -(Math.max(tickMajorSize, 0) + tickPadding)); + lineUpdate.attr("x2", -tickMajorSize).attr("y2", 0); + textUpdate.attr("x", -(Math.max(tickMajorSize, 0) + tickPadding)).attr("y", 0); + text.attr("dy", ".32em").attr("text-anchor", "end"); + pathUpdate.attr("d", "M" + -tickEndSize + "," + range[0] + "H0V" + range[1] + "H" + -tickEndSize); + break; + } + case "right": + { + tickTransform = d3_svg_axisY; + subtickEnter.attr("x2", tickMinorSize); + subtickUpdate.attr("x2", tickMinorSize).attr("y2", 0); + lineEnter.attr("x2", tickMajorSize); + textEnter.attr("x", Math.max(tickMajorSize, 0) + tickPadding); + lineUpdate.attr("x2", tickMajorSize).attr("y2", 0); + textUpdate.attr("x", Math.max(tickMajorSize, 0) + tickPadding).attr("y", 0); + text.attr("dy", ".32em").attr("text-anchor", "start"); + pathUpdate.attr("d", "M" + tickEndSize + "," + range[0] + "H0V" + range[1] + "H" + tickEndSize); + break; + } + } + if (scale.ticks) { + tickEnter.call(tickTransform, scale0); + tickUpdate.call(tickTransform, scale1); + tickExit.call(tickTransform, scale1); + subtickEnter.call(tickTransform, scale0); + subtickUpdate.call(tickTransform, scale1); + subtickExit.call(tickTransform, scale1); + } else { + var dx = scale1.rangeBand() / 2, x = function(d) { + return scale1(d) + dx; + }; + tickEnter.call(tickTransform, x); + tickUpdate.call(tickTransform, x); + } + }); + } + var scale = d3.scale.linear(), orient = "bottom", tickMajorSize = 6, tickMinorSize = 6, tickEndSize = 6, tickPadding = 3, tickArguments_ = [ 10 ], tickValues = null, tickFormat_, tickSubdivide = 0; + axis.scale = function(x) { + if (!arguments.length) return scale; + scale = x; + return axis; + }; + axis.orient = function(x) { + if (!arguments.length) return orient; + orient = x; + return axis; + }; + axis.ticks = function() { + if (!arguments.length) return tickArguments_; + tickArguments_ = arguments; + return axis; + }; + axis.tickValues = function(x) { + if (!arguments.length) return tickValues; + tickValues = x; + return axis; + }; + axis.tickFormat = function(x) { + if (!arguments.length) return tickFormat_; + tickFormat_ = x; + return axis; + }; + axis.tickSize = function(x, y, z) { + if (!arguments.length) return tickMajorSize; + var n = arguments.length - 1; + tickMajorSize = +x; + tickMinorSize = n > 1 ? +y : tickMajorSize; + tickEndSize = n > 0 ? +arguments[n] : tickMajorSize; + return axis; + }; + axis.tickPadding = function(x) { + if (!arguments.length) return tickPadding; + tickPadding = +x; + return axis; + }; + axis.tickSubdivide = function(x) { + if (!arguments.length) return tickSubdivide; + tickSubdivide = +x; + return axis; + }; + return axis; + }; + d3.svg.brush = function() { + function brush(g) { + g.each(function() { + var g = d3.select(this), bg = g.selectAll(".background").data([ 0 ]), fg = g.selectAll(".extent").data([ 0 ]), tz = g.selectAll(".resize").data(resizes, String), e; + g.style("pointer-events", "all").on("mousedown.brush", brushstart).on("touchstart.brush", brushstart); + bg.enter().append("rect").attr("class", "background").style("visibility", "hidden").style("cursor", "crosshair"); + fg.enter().append("rect").attr("class", "extent").style("cursor", "move"); + tz.enter().append("g").attr("class", function(d) { + return "resize " + d; + }).style("cursor", function(d) { + return d3_svg_brushCursor[d]; + }).append("rect").attr("x", function(d) { + return /[ew]$/.test(d) ? -3 : null; + }).attr("y", function(d) { + return /^[ns]/.test(d) ? -3 : null; + }).attr("width", 6).attr("height", 6).style("visibility", "hidden"); + tz.style("display", brush.empty() ? "none" : null); + tz.exit().remove(); + if (x) { + e = d3_scaleRange(x); + bg.attr("x", e[0]).attr("width", e[1] - e[0]); + redrawX(g); + } + if (y) { + e = d3_scaleRange(y); + bg.attr("y", e[0]).attr("height", e[1] - e[0]); + redrawY(g); + } + redraw(g); + }); + } + function redraw(g) { + g.selectAll(".resize").attr("transform", function(d) { + return "translate(" + extent[+/e$/.test(d)][0] + "," + extent[+/^s/.test(d)][1] + ")"; + }); + } + function redrawX(g) { + g.select(".extent").attr("x", extent[0][0]); + g.selectAll(".extent,.n>rect,.s>rect").attr("width", extent[1][0] - extent[0][0]); + } + function redrawY(g) { + g.select(".extent").attr("y", extent[0][1]); + g.selectAll(".extent,.e>rect,.w>rect").attr("height", extent[1][1] - extent[0][1]); + } + function brushstart() { + function mouse() { + var touches = d3.event.changedTouches; + return touches ? d3.touches(target, touches)[0] : d3.mouse(target); + } + function keydown() { + if (d3.event.keyCode == 32) { + if (!dragging) { + center = null; + origin[0] -= extent[1][0]; + origin[1] -= extent[1][1]; + dragging = 2; + } + d3_eventCancel(); + } + } + function keyup() { + if (d3.event.keyCode == 32 && dragging == 2) { + origin[0] += extent[1][0]; + origin[1] += extent[1][1]; + dragging = 0; + d3_eventCancel(); + } + } + function brushmove() { + var point = mouse(), moved = false; + if (offset) { + point[0] += offset[0]; + point[1] += offset[1]; + } + if (!dragging) { + if (d3.event.altKey) { + if (!center) center = [ (extent[0][0] + extent[1][0]) / 2, (extent[0][1] + extent[1][1]) / 2 ]; + origin[0] = extent[+(point[0] < center[0])][0]; + origin[1] = extent[+(point[1] < center[1])][1]; + } else center = null; + } + if (resizingX && move1(point, x, 0)) { + redrawX(g); + moved = true; + } + if (resizingY && move1(point, y, 1)) { + redrawY(g); + moved = true; + } + if (moved) { + redraw(g); + event_({ + type: "brush", + mode: dragging ? "move" : "resize" + }); + } + } + function move1(point, scale, i) { + var range = d3_scaleRange(scale), r0 = range[0], r1 = range[1], position = origin[i], size = extent[1][i] - extent[0][i], min, max; + if (dragging) { + r0 -= position; + r1 -= size + position; + } + min = Math.max(r0, Math.min(r1, point[i])); + if (dragging) { + max = (min += position) + size; + } else { + if (center) position = Math.max(r0, Math.min(r1, 2 * center[i] - min)); + if (position < min) { + max = min; + min = position; + } else { + max = position; + } + } + if (extent[0][i] !== min || extent[1][i] !== max) { + extentDomain = null; + extent[0][i] = min; + extent[1][i] = max; + return true; + } + } + function brushend() { + brushmove(); + g.style("pointer-events", "all").selectAll(".resize").style("display", brush.empty() ? "none" : null); + d3.select("body").style("cursor", null); + w.on("mousemove.brush", null).on("mouseup.brush", null).on("touchmove.brush", null).on("touchend.brush", null).on("keydown.brush", null).on("keyup.brush", null); + event_({ + type: "brushend" + }); + d3_eventCancel(); + } + var target = this, eventTarget = d3.select(d3.event.target), event_ = event.of(target, arguments), g = d3.select(target), resizing = eventTarget.datum(), resizingX = !/^(n|s)$/.test(resizing) && x, resizingY = !/^(e|w)$/.test(resizing) && y, dragging = eventTarget.classed("extent"), center, origin = mouse(), offset; + var w = d3.select(window).on("mousemove.brush", brushmove).on("mouseup.brush", brushend).on("touchmove.brush", brushmove).on("touchend.brush", brushend).on("keydown.brush", keydown).on("keyup.brush", keyup); + if (dragging) { + origin[0] = extent[0][0] - origin[0]; + origin[1] = extent[0][1] - origin[1]; + } else if (resizing) { + var ex = +/w$/.test(resizing), ey = +/^n/.test(resizing); + offset = [ extent[1 - ex][0] - origin[0], extent[1 - ey][1] - origin[1] ]; + origin[0] = extent[ex][0]; + origin[1] = extent[ey][1]; + } else if (d3.event.altKey) center = origin.slice(); + g.style("pointer-events", "none").selectAll(".resize").style("display", null); + d3.select("body").style("cursor", eventTarget.style("cursor")); + event_({ + type: "brushstart" + }); + brushmove(); + d3_eventCancel(); + } + var event = d3_eventDispatch(brush, "brushstart", "brush", "brushend"), x = null, y = null, resizes = d3_svg_brushResizes[0], extent = [ [ 0, 0 ], [ 0, 0 ] ], extentDomain; + brush.x = function(z) { + if (!arguments.length) return x; + x = z; + resizes = d3_svg_brushResizes[!x << 1 | !y]; + return brush; + }; + brush.y = function(z) { + if (!arguments.length) return y; + y = z; + resizes = d3_svg_brushResizes[!x << 1 | !y]; + return brush; + }; + brush.extent = function(z) { + var x0, x1, y0, y1, t; + if (!arguments.length) { + z = extentDomain || extent; + if (x) { + x0 = z[0][0], x1 = z[1][0]; + if (!extentDomain) { + x0 = extent[0][0], x1 = extent[1][0]; + if (x.invert) x0 = x.invert(x0), x1 = x.invert(x1); + if (x1 < x0) t = x0, x0 = x1, x1 = t; + } + } + if (y) { + y0 = z[0][1], y1 = z[1][1]; + if (!extentDomain) { + y0 = extent[0][1], y1 = extent[1][1]; + if (y.invert) y0 = y.invert(y0), y1 = y.invert(y1); + if (y1 < y0) t = y0, y0 = y1, y1 = t; + } + } + return x && y ? [ [ x0, y0 ], [ x1, y1 ] ] : x ? [ x0, x1 ] : y && [ y0, y1 ]; + } + extentDomain = [ [ 0, 0 ], [ 0, 0 ] ]; + if (x) { + x0 = z[0], x1 = z[1]; + if (y) x0 = x0[0], x1 = x1[0]; + extentDomain[0][0] = x0, extentDomain[1][0] = x1; + if (x.invert) x0 = x(x0), x1 = x(x1); + if (x1 < x0) t = x0, x0 = x1, x1 = t; + extent[0][0] = x0 | 0, extent[1][0] = x1 | 0; + } + if (y) { + y0 = z[0], y1 = z[1]; + if (x) y0 = y0[1], y1 = y1[1]; + extentDomain[0][1] = y0, extentDomain[1][1] = y1; + if (y.invert) y0 = y(y0), y1 = y(y1); + if (y1 < y0) t = y0, y0 = y1, y1 = t; + extent[0][1] = y0 | 0, extent[1][1] = y1 | 0; + } + return brush; + }; + brush.clear = function() { + extentDomain = null; + extent[0][0] = extent[0][1] = extent[1][0] = extent[1][1] = 0; + return brush; + }; + brush.empty = function() { + return x && extent[0][0] === extent[1][0] || y && extent[0][1] === extent[1][1]; + }; + return d3.rebind(brush, event, "on"); + }; + var d3_svg_brushCursor = { + n: "ns-resize", + e: "ew-resize", + s: "ns-resize", + w: "ew-resize", + nw: "nwse-resize", + ne: "nesw-resize", + se: "nwse-resize", + sw: "nesw-resize" + }; + var d3_svg_brushResizes = [ [ "n", "e", "s", "w", "nw", "ne", "se", "sw" ], [ "e", "w" ], [ "n", "s" ], [] ]; + d3.behavior = {}; + d3.behavior.drag = function() { + function drag() { + this.on("mousedown.drag", mousedown).on("touchstart.drag", mousedown); + } + function mousedown() { + function point() { + var p = target.parentNode; + return touchId ? d3.touches(p).filter(function(p) { + return p.identifier === touchId; + })[0] : d3.mouse(p); + } + function dragmove() { + if (!target.parentNode) return dragend(); + var p = point(), dx = p[0] - origin_[0], dy = p[1] - origin_[1]; + moved |= dx | dy; + origin_ = p; + d3_eventCancel(); + event_({ + type: "drag", + x: p[0] + offset[0], + y: p[1] + offset[1], + dx: dx, + dy: dy + }); + } + function dragend() { + event_({ + type: "dragend" + }); + if (moved) { + d3_eventCancel(); + if (d3.event.target === eventTarget) w.on("click.drag", click, true); + } + w.on(touchId ? "touchmove.drag-" + touchId : "mousemove.drag", null).on(touchId ? "touchend.drag-" + touchId : "mouseup.drag", null); + } + function click() { + d3_eventCancel(); + w.on("click.drag", null); + } + var target = this, event_ = event.of(target, arguments), eventTarget = d3.event.target, touchId = d3.event.touches && d3.event.changedTouches[0].identifier, offset, origin_ = point(), moved = 0; + var w = d3.select(window).on(touchId ? "touchmove.drag-" + touchId : "mousemove.drag", dragmove).on(touchId ? "touchend.drag-" + touchId : "mouseup.drag", dragend, true); + if (origin) { + offset = origin.apply(target, arguments); + offset = [ offset.x - origin_[0], offset.y - origin_[1] ]; + } else { + offset = [ 0, 0 ]; + } + if (!touchId) d3_eventCancel(); + event_({ + type: "dragstart" + }); + } + var event = d3_eventDispatch(drag, "drag", "dragstart", "dragend"), origin = null; + drag.origin = function(x) { + if (!arguments.length) return origin; + origin = x; + return drag; + }; + return d3.rebind(drag, event, "on"); + }; + d3.behavior.zoom = function() { + function zoom() { + this.on("mousedown.zoom", mousedown).on("mousewheel.zoom", mousewheel).on("mousemove.zoom", mousemove).on("DOMMouseScroll.zoom", mousewheel).on("dblclick.zoom", dblclick).on("touchstart.zoom", touchstart).on("touchmove.zoom", touchmove).on("touchend.zoom", touchstart); + } + function location(p) { + return [ (p[0] - translate[0]) / scale, (p[1] - translate[1]) / scale ]; + } + function point(l) { + return [ l[0] * scale + translate[0], l[1] * scale + translate[1] ]; + } + function scaleTo(s) { + scale = Math.max(scaleExtent[0], Math.min(scaleExtent[1], s)); + } + function translateTo(p, l) { + l = point(l); + translate[0] += p[0] - l[0]; + translate[1] += p[1] - l[1]; + } + function dispatch(event) { + if (x1) x1.domain(x0.range().map(function(x) { + return (x - translate[0]) / scale; + }).map(x0.invert)); + if (y1) y1.domain(y0.range().map(function(y) { + return (y - translate[1]) / scale; + }).map(y0.invert)); + d3.event.preventDefault(); + event({ + type: "zoom", + scale: scale, + translate: translate + }); + } + function mousedown() { + function mousemove() { + moved = 1; + translateTo(d3.mouse(target), l); + dispatch(event_); + } + function mouseup() { + if (moved) d3_eventCancel(); + w.on("mousemove.zoom", null).on("mouseup.zoom", null); + if (moved && d3.event.target === eventTarget) w.on("click.zoom", click, true); + } + function click() { + d3_eventCancel(); + w.on("click.zoom", null); + } + var target = this, event_ = event.of(target, arguments), eventTarget = d3.event.target, moved = 0, w = d3.select(window).on("mousemove.zoom", mousemove).on("mouseup.zoom", mouseup), l = location(d3.mouse(target)); + window.focus(); + d3_eventCancel(); + } + function mousewheel() { + if (!translate0) translate0 = location(d3.mouse(this)); + scaleTo(Math.pow(2, d3_behavior_zoomDelta() * .002) * scale); + translateTo(d3.mouse(this), translate0); + dispatch(event.of(this, arguments)); + } + function mousemove() { + translate0 = null; + } + function dblclick() { + var p = d3.mouse(this), l = location(p); + scaleTo(d3.event.shiftKey ? scale / 2 : scale * 2); + translateTo(p, l); + dispatch(event.of(this, arguments)); + } + function touchstart() { + var touches = d3.touches(this), now = Date.now(); + scale0 = scale; + translate0 = {}; + touches.forEach(function(t) { + translate0[t.identifier] = location(t); + }); + d3_eventCancel(); + if (touches.length === 1) { + if (now - touchtime < 500) { + var p = touches[0], l = location(touches[0]); + scaleTo(scale * 2); + translateTo(p, l); + dispatch(event.of(this, arguments)); + } + touchtime = now; + } + } + function touchmove() { + var touches = d3.touches(this), p0 = touches[0], l0 = translate0[p0.identifier]; + if (p1 = touches[1]) { + var p1, l1 = translate0[p1.identifier]; + p0 = [ (p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2 ]; + l0 = [ (l0[0] + l1[0]) / 2, (l0[1] + l1[1]) / 2 ]; + scaleTo(d3.event.scale * scale0); + } + translateTo(p0, l0); + touchtime = null; + dispatch(event.of(this, arguments)); + } + var translate = [ 0, 0 ], translate0, scale = 1, scale0, scaleExtent = d3_behavior_zoomInfinity, event = d3_eventDispatch(zoom, "zoom"), x0, x1, y0, y1, touchtime; + zoom.translate = function(x) { + if (!arguments.length) return translate; + translate = x.map(Number); + return zoom; + }; + zoom.scale = function(x) { + if (!arguments.length) return scale; + scale = +x; + return zoom; + }; + zoom.scaleExtent = function(x) { + if (!arguments.length) return scaleExtent; + scaleExtent = x == null ? d3_behavior_zoomInfinity : x.map(Number); + return zoom; + }; + zoom.x = function(z) { + if (!arguments.length) return x1; + x1 = z; + x0 = z.copy(); + return zoom; + }; + zoom.y = function(z) { + if (!arguments.length) return y1; + y1 = z; + y0 = z.copy(); + return zoom; + }; + return d3.rebind(zoom, event, "on"); + }; + var d3_behavior_zoomDiv, d3_behavior_zoomInfinity = [ 0, Infinity ]; + d3.layout = {}; + d3.layout.bundle = function() { + return function(links) { + var paths = [], i = -1, n = links.length; + while (++i < n) paths.push(d3_layout_bundlePath(links[i])); + return paths; + }; + }; + d3.layout.chord = function() { + function relayout() { + var subgroups = {}, groupSums = [], groupIndex = d3.range(n), subgroupIndex = [], k, x, x0, i, j; + chords = []; + groups = []; + k = 0, i = -1; + while (++i < n) { + x = 0, j = -1; + while (++j < n) { + x += matrix[i][j]; + } + groupSums.push(x); + subgroupIndex.push(d3.range(n)); + k += x; + } + if (sortGroups) { + groupIndex.sort(function(a, b) { + return sortGroups(groupSums[a], groupSums[b]); + }); + } + if (sortSubgroups) { + subgroupIndex.forEach(function(d, i) { + d.sort(function(a, b) { + return sortSubgroups(matrix[i][a], matrix[i][b]); + }); + }); + } + k = (2 * Math.PI - padding * n) / k; + x = 0, i = -1; + while (++i < n) { + x0 = x, j = -1; + while (++j < n) { + var di = groupIndex[i], dj = subgroupIndex[di][j], v = matrix[di][dj], a0 = x, a1 = x += v * k; + subgroups[di + "-" + dj] = { + index: di, + subindex: dj, + startAngle: a0, + endAngle: a1, + value: v + }; + } + groups[di] = { + index: di, + startAngle: x0, + endAngle: x, + value: (x - x0) / k + }; + x += padding; + } + i = -1; + while (++i < n) { + j = i - 1; + while (++j < n) { + var source = subgroups[i + "-" + j], target = subgroups[j + "-" + i]; + if (source.value || target.value) { + chords.push(source.value < target.value ? { + source: target, + target: source + } : { + source: source, + target: target + }); + } + } + } + if (sortChords) resort(); + } + function resort() { + chords.sort(function(a, b) { + return sortChords((a.source.value + a.target.value) / 2, (b.source.value + b.target.value) / 2); + }); + } + var chord = {}, chords, groups, matrix, n, padding = 0, sortGroups, sortSubgroups, sortChords; + chord.matrix = function(x) { + if (!arguments.length) return matrix; + n = (matrix = x) && matrix.length; + chords = groups = null; + return chord; + }; + chord.padding = function(x) { + if (!arguments.length) return padding; + padding = x; + chords = groups = null; + return chord; + }; + chord.sortGroups = function(x) { + if (!arguments.length) return sortGroups; + sortGroups = x; + chords = groups = null; + return chord; + }; + chord.sortSubgroups = function(x) { + if (!arguments.length) return sortSubgroups; + sortSubgroups = x; + chords = null; + return chord; + }; + chord.sortChords = function(x) { + if (!arguments.length) return sortChords; + sortChords = x; + if (chords) resort(); + return chord; + }; + chord.chords = function() { + if (!chords) relayout(); + return chords; + }; + chord.groups = function() { + if (!groups) relayout(); + return groups; + }; + return chord; + }; + d3.layout.force = function() { + function repulse(node) { + return function(quad, x1, y1, x2, y2) { + if (quad.point !== node) { + var dx = quad.cx - node.x, dy = quad.cy - node.y, dn = 1 / Math.sqrt(dx * dx + dy * dy); + if ((x2 - x1) * dn < theta) { + var k = quad.charge * dn * dn; + node.px -= dx * k; + node.py -= dy * k; + return true; + } + if (quad.point && isFinite(dn)) { + var k = quad.pointCharge * dn * dn; + node.px -= dx * k; + node.py -= dy * k; + } + } + return !quad.charge; + }; + } + function dragmove(d) { + d.px = d3.event.x; + d.py = d3.event.y; + force.resume(); + } + var force = {}, event = d3.dispatch("start", "tick", "end"), size = [ 1, 1 ], drag, alpha, friction = .9, linkDistance = d3_layout_forceLinkDistance, linkStrength = d3_layout_forceLinkStrength, charge = -30, gravity = .1, theta = .8, interval, nodes = [], links = [], distances, strengths, charges; + force.tick = function() { + if ((alpha *= .99) < .005) { + event.end({ + type: "end", + alpha: alpha = 0 + }); + return true; + } + var n = nodes.length, m = links.length, q, i, o, s, t, l, k, x, y; + for (i = 0; i < m; ++i) { + o = links[i]; + s = o.source; + t = o.target; + x = t.x - s.x; + y = t.y - s.y; + if (l = x * x + y * y) { + l = alpha * strengths[i] * ((l = Math.sqrt(l)) - distances[i]) / l; + x *= l; + y *= l; + t.x -= x * (k = s.weight / (t.weight + s.weight)); + t.y -= y * k; + s.x += x * (k = 1 - k); + s.y += y * k; + } + } + if (k = alpha * gravity) { + x = size[0] / 2; + y = size[1] / 2; + i = -1; + if (k) while (++i < n) { + o = nodes[i]; + o.x += (x - o.x) * k; + o.y += (y - o.y) * k; + } + } + if (charge) { + d3_layout_forceAccumulate(q = d3.geom.quadtree(nodes), alpha, charges); + i = -1; + while (++i < n) { + if (!(o = nodes[i]).fixed) { + q.visit(repulse(o)); + } + } + } + i = -1; + while (++i < n) { + o = nodes[i]; + if (o.fixed) { + o.x = o.px; + o.y = o.py; + } else { + o.x -= (o.px - (o.px = o.x)) * friction; + o.y -= (o.py - (o.py = o.y)) * friction; + } + } + event.tick({ + type: "tick", + alpha: alpha + }); + }; + force.nodes = function(x) { + if (!arguments.length) return nodes; + nodes = x; + return force; + }; + force.links = function(x) { + if (!arguments.length) return links; + links = x; + return force; + }; + force.size = function(x) { + if (!arguments.length) return size; + size = x; + return force; + }; + force.linkDistance = function(x) { + if (!arguments.length) return linkDistance; + linkDistance = d3_functor(x); + return force; + }; + force.distance = force.linkDistance; + force.linkStrength = function(x) { + if (!arguments.length) return linkStrength; + linkStrength = d3_functor(x); + return force; + }; + force.friction = function(x) { + if (!arguments.length) return friction; + friction = x; + return force; + }; + force.charge = function(x) { + if (!arguments.length) return charge; + charge = typeof x === "function" ? x : +x; + return force; + }; + force.gravity = function(x) { + if (!arguments.length) return gravity; + gravity = x; + return force; + }; + force.theta = function(x) { + if (!arguments.length) return theta; + theta = x; + return force; + }; + force.alpha = function(x) { + if (!arguments.length) return alpha; + if (alpha) { + if (x > 0) alpha = x; else alpha = 0; + } else if (x > 0) { + event.start({ + type: "start", + alpha: alpha = x + }); + d3.timer(force.tick); + } + return force; + }; + force.start = function() { + function position(dimension, size) { + var neighbors = neighbor(i), j = -1, m = neighbors.length, x; + while (++j < m) if (!isNaN(x = neighbors[j][dimension])) return x; + return Math.random() * size; + } + function neighbor() { + if (!neighbors) { + neighbors = []; + for (j = 0; j < n; ++j) { + neighbors[j] = []; + } + for (j = 0; j < m; ++j) { + var o = links[j]; + neighbors[o.source.index].push(o.target); + neighbors[o.target.index].push(o.source); + } + } + return neighbors[i]; + } + var i, j, n = nodes.length, m = links.length, w = size[0], h = size[1], neighbors, o; + for (i = 0; i < n; ++i) { + (o = nodes[i]).index = i; + o.weight = 0; + } + distances = []; + strengths = []; + for (i = 0; i < m; ++i) { + o = links[i]; + if (typeof o.source == "number") o.source = nodes[o.source]; + if (typeof o.target == "number") o.target = nodes[o.target]; + distances[i] = linkDistance.call(this, o, i); + strengths[i] = linkStrength.call(this, o, i); + ++o.source.weight; + ++o.target.weight; + } + for (i = 0; i < n; ++i) { + o = nodes[i]; + if (isNaN(o.x)) o.x = position("x", w); + if (isNaN(o.y)) o.y = position("y", h); + if (isNaN(o.px)) o.px = o.x; + if (isNaN(o.py)) o.py = o.y; + } + charges = []; + if (typeof charge === "function") { + for (i = 0; i < n; ++i) { + charges[i] = +charge.call(this, nodes[i], i); + } + } else { + for (i = 0; i < n; ++i) { + charges[i] = charge; + } + } + return force.resume(); + }; + force.resume = function() { + return force.alpha(.1); + }; + force.stop = function() { + return force.alpha(0); + }; + force.drag = function() { + if (!drag) drag = d3.behavior.drag().origin(d3_identity).on("dragstart", d3_layout_forceDragstart).on("drag", dragmove).on("dragend", d3_layout_forceDragend); + this.on("mouseover.force", d3_layout_forceMouseover).on("mouseout.force", d3_layout_forceMouseout).call(drag); + }; + return d3.rebind(force, event, "on"); + }; + d3.layout.partition = function() { + function position(node, x, dx, dy) { + var children = node.children; + node.x = x; + node.y = node.depth * dy; + node.dx = dx; + node.dy = dy; + if (children && (n = children.length)) { + var i = -1, n, c, d; + dx = node.value ? dx / node.value : 0; + while (++i < n) { + position(c = children[i], x, d = c.value * dx, dy); + x += d; + } + } + } + function depth(node) { + var children = node.children, d = 0; + if (children && (n = children.length)) { + var i = -1, n; + while (++i < n) d = Math.max(d, depth(children[i])); + } + return 1 + d; + } + function partition(d, i) { + var nodes = hierarchy.call(this, d, i); + position(nodes[0], 0, size[0], size[1] / depth(nodes[0])); + return nodes; + } + var hierarchy = d3.layout.hierarchy(), size = [ 1, 1 ]; + partition.size = function(x) { + if (!arguments.length) return size; + size = x; + return partition; + }; + return d3_layout_hierarchyRebind(partition, hierarchy); + }; + d3.layout.pie = function() { + function pie(data, i) { + var values = data.map(function(d, i) { + return +value.call(pie, d, i); + }); + var a = +(typeof startAngle === "function" ? startAngle.apply(this, arguments) : startAngle); + var k = ((typeof endAngle === "function" ? endAngle.apply(this, arguments) : endAngle) - startAngle) / d3.sum(values); + var index = d3.range(data.length); + if (sort != null) index.sort(sort === d3_layout_pieSortByValue ? function(i, j) { + return values[j] - values[i]; + } : function(i, j) { + return sort(data[i], data[j]); + }); + var arcs = []; + index.forEach(function(i) { + var d; + arcs[i] = { + data: data[i], + value: d = values[i], + startAngle: a, + endAngle: a += d * k + }; + }); + return arcs; + } + var value = Number, sort = d3_layout_pieSortByValue, startAngle = 0, endAngle = 2 * Math.PI; + pie.value = function(x) { + if (!arguments.length) return value; + value = x; + return pie; + }; + pie.sort = function(x) { + if (!arguments.length) return sort; + sort = x; + return pie; + }; + pie.startAngle = function(x) { + if (!arguments.length) return startAngle; + startAngle = x; + return pie; + }; + pie.endAngle = function(x) { + if (!arguments.length) return endAngle; + endAngle = x; + return pie; + }; + return pie; + }; + var d3_layout_pieSortByValue = {}; + d3.layout.stack = function() { + function stack(data, index) { + var series = data.map(function(d, i) { + return values.call(stack, d, i); + }); + var points = series.map(function(d, i) { + return d.map(function(v, i) { + return [ x.call(stack, v, i), y.call(stack, v, i) ]; + }); + }); + var orders = order.call(stack, points, index); + series = d3.permute(series, orders); + points = d3.permute(points, orders); + var offsets = offset.call(stack, points, index); + var n = series.length, m = series[0].length, i, j, o; + for (j = 0; j < m; ++j) { + out.call(stack, series[0][j], o = offsets[j], points[0][j][1]); + for (i = 1; i < n; ++i) { + out.call(stack, series[i][j], o += points[i - 1][j][1], points[i][j][1]); + } + } + return data; + } + var values = d3_identity, order = d3_layout_stackOrderDefault, offset = d3_layout_stackOffsetZero, out = d3_layout_stackOut, x = d3_layout_stackX, y = d3_layout_stackY; + stack.values = function(x) { + if (!arguments.length) return values; + values = x; + return stack; + }; + stack.order = function(x) { + if (!arguments.length) return order; + order = typeof x === "function" ? x : d3_layout_stackOrders.get(x) || d3_layout_stackOrderDefault; + return stack; + }; + stack.offset = function(x) { + if (!arguments.length) return offset; + offset = typeof x === "function" ? x : d3_layout_stackOffsets.get(x) || d3_layout_stackOffsetZero; + return stack; + }; + stack.x = function(z) { + if (!arguments.length) return x; + x = z; + return stack; + }; + stack.y = function(z) { + if (!arguments.length) return y; + y = z; + return stack; + }; + stack.out = function(z) { + if (!arguments.length) return out; + out = z; + return stack; + }; + return stack; + }; + var d3_layout_stackOrders = d3.map({ + "inside-out": function(data) { + var n = data.length, i, j, max = data.map(d3_layout_stackMaxIndex), sums = data.map(d3_layout_stackReduceSum), index = d3.range(n).sort(function(a, b) { + return max[a] - max[b]; + }), top = 0, bottom = 0, tops = [], bottoms = []; + for (i = 0; i < n; ++i) { + j = index[i]; + if (top < bottom) { + top += sums[j]; + tops.push(j); + } else { + bottom += sums[j]; + bottoms.push(j); + } + } + return bottoms.reverse().concat(tops); + }, + reverse: function(data) { + return d3.range(data.length).reverse(); + }, + "default": d3_layout_stackOrderDefault + }); + var d3_layout_stackOffsets = d3.map({ + silhouette: function(data) { + var n = data.length, m = data[0].length, sums = [], max = 0, i, j, o, y0 = []; + for (j = 0; j < m; ++j) { + for (i = 0, o = 0; i < n; i++) o += data[i][j][1]; + if (o > max) max = o; + sums.push(o); + } + for (j = 0; j < m; ++j) { + y0[j] = (max - sums[j]) / 2; + } + return y0; + }, + wiggle: function(data) { + var n = data.length, x = data[0], m = x.length, max = 0, i, j, k, s1, s2, s3, dx, o, o0, y0 = []; + y0[0] = o = o0 = 0; + for (j = 1; j < m; ++j) { + for (i = 0, s1 = 0; i < n; ++i) s1 += data[i][j][1]; + for (i = 0, s2 = 0, dx = x[j][0] - x[j - 1][0]; i < n; ++i) { + for (k = 0, s3 = (data[i][j][1] - data[i][j - 1][1]) / (2 * dx); k < i; ++k) { + s3 += (data[k][j][1] - data[k][j - 1][1]) / dx; + } + s2 += s3 * data[i][j][1]; + } + y0[j] = o -= s1 ? s2 / s1 * dx : 0; + if (o < o0) o0 = o; + } + for (j = 0; j < m; ++j) y0[j] -= o0; + return y0; + }, + expand: function(data) { + var n = data.length, m = data[0].length, k = 1 / n, i, j, o, y0 = []; + for (j = 0; j < m; ++j) { + for (i = 0, o = 0; i < n; i++) o += data[i][j][1]; + if (o) for (i = 0; i < n; i++) data[i][j][1] /= o; else for (i = 0; i < n; i++) data[i][j][1] = k; + } + for (j = 0; j < m; ++j) y0[j] = 0; + return y0; + }, + zero: d3_layout_stackOffsetZero + }); + d3.layout.histogram = function() { + function histogram(data, i) { + var bins = [], values = data.map(valuer, this), range = ranger.call(this, values, i), thresholds = binner.call(this, range, values, i), bin, i = -1, n = values.length, m = thresholds.length - 1, k = frequency ? 1 : 1 / n, x; + while (++i < m) { + bin = bins[i] = []; + bin.dx = thresholds[i + 1] - (bin.x = thresholds[i]); + bin.y = 0; + } + if (m > 0) { + i = -1; + while (++i < n) { + x = values[i]; + if (x >= range[0] && x <= range[1]) { + bin = bins[d3.bisect(thresholds, x, 1, m) - 1]; + bin.y += k; + bin.push(data[i]); + } + } + } + return bins; + } + var frequency = true, valuer = Number, ranger = d3_layout_histogramRange, binner = d3_layout_histogramBinSturges; + histogram.value = function(x) { + if (!arguments.length) return valuer; + valuer = x; + return histogram; + }; + histogram.range = function(x) { + if (!arguments.length) return ranger; + ranger = d3_functor(x); + return histogram; + }; + histogram.bins = function(x) { + if (!arguments.length) return binner; + binner = typeof x === "number" ? function(range) { + return d3_layout_histogramBinFixed(range, x); + } : d3_functor(x); + return histogram; + }; + histogram.frequency = function(x) { + if (!arguments.length) return frequency; + frequency = !!x; + return histogram; + }; + return histogram; + }; + d3.layout.hierarchy = function() { + function recurse(data, depth, nodes) { + var childs = children.call(hierarchy, data, depth), node = d3_layout_hierarchyInline ? data : { + data: data + }; + node.depth = depth; + nodes.push(node); + if (childs && (n = childs.length)) { + var i = -1, n, c = node.children = [], v = 0, j = depth + 1, d; + while (++i < n) { + d = recurse(childs[i], j, nodes); + d.parent = node; + c.push(d); + v += d.value; + } + if (sort) c.sort(sort); + if (value) node.value = v; + } else if (value) { + node.value = +value.call(hierarchy, data, depth) || 0; + } + return node; + } + function revalue(node, depth) { + var children = node.children, v = 0; + if (children && (n = children.length)) { + var i = -1, n, j = depth + 1; + while (++i < n) v += revalue(children[i], j); + } else if (value) { + v = +value.call(hierarchy, d3_layout_hierarchyInline ? node : node.data, depth) || 0; + } + if (value) node.value = v; + return v; + } + function hierarchy(d) { + var nodes = []; + recurse(d, 0, nodes); + return nodes; + } + var sort = d3_layout_hierarchySort, children = d3_layout_hierarchyChildren, value = d3_layout_hierarchyValue; + hierarchy.sort = function(x) { + if (!arguments.length) return sort; + sort = x; + return hierarchy; + }; + hierarchy.children = function(x) { + if (!arguments.length) return children; + children = x; + return hierarchy; + }; + hierarchy.value = function(x) { + if (!arguments.length) return value; + value = x; + return hierarchy; + }; + hierarchy.revalue = function(root) { + revalue(root, 0); + return root; + }; + return hierarchy; + }; + var d3_layout_hierarchyInline = false; + d3.layout.pack = function() { + function pack(d, i) { + var nodes = hierarchy.call(this, d, i), root = nodes[0]; + root.x = 0; + root.y = 0; + d3_layout_treeVisitAfter(root, function(d) { + d.r = Math.sqrt(d.value); + }); + d3_layout_treeVisitAfter(root, d3_layout_packSiblings); + var w = size[0], h = size[1], k = Math.max(2 * root.r / w, 2 * root.r / h); + if (padding > 0) { + var dr = padding * k / 2; + d3_layout_treeVisitAfter(root, function(d) { + d.r += dr; + }); + d3_layout_treeVisitAfter(root, d3_layout_packSiblings); + d3_layout_treeVisitAfter(root, function(d) { + d.r -= dr; + }); + k = Math.max(2 * root.r / w, 2 * root.r / h); + } + d3_layout_packTransform(root, w / 2, h / 2, 1 / k); + return nodes; + } + var hierarchy = d3.layout.hierarchy().sort(d3_layout_packSort), padding = 0, size = [ 1, 1 ]; + pack.size = function(x) { + if (!arguments.length) return size; + size = x; + return pack; + }; + pack.padding = function(_) { + if (!arguments.length) return padding; + padding = +_; + return pack; + }; + return d3_layout_hierarchyRebind(pack, hierarchy); + }; + d3.layout.cluster = function() { + function cluster(d, i) { + var nodes = hierarchy.call(this, d, i), root = nodes[0], previousNode, x = 0, kx, ky; + d3_layout_treeVisitAfter(root, function(node) { + var children = node.children; + if (children && children.length) { + node.x = d3_layout_clusterX(children); + node.y = d3_layout_clusterY(children); + } else { + node.x = previousNode ? x += separation(node, previousNode) : 0; + node.y = 0; + previousNode = node; + } + }); + var left = d3_layout_clusterLeft(root), right = d3_layout_clusterRight(root), x0 = left.x - separation(left, right) / 2, x1 = right.x + separation(right, left) / 2; + d3_layout_treeVisitAfter(root, function(node) { + node.x = (node.x - x0) / (x1 - x0) * size[0]; + node.y = (1 - (root.y ? node.y / root.y : 1)) * size[1]; + }); + return nodes; + } + var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ]; + cluster.separation = function(x) { + if (!arguments.length) return separation; + separation = x; + return cluster; + }; + cluster.size = function(x) { + if (!arguments.length) return size; + size = x; + return cluster; + }; + return d3_layout_hierarchyRebind(cluster, hierarchy); + }; + d3.layout.tree = function() { + function tree(d, i) { + function firstWalk(node, previousSibling) { + var children = node.children, layout = node._tree; + if (children && (n = children.length)) { + var n, firstChild = children[0], previousChild, ancestor = firstChild, child, i = -1; + while (++i < n) { + child = children[i]; + firstWalk(child, previousChild); + ancestor = apportion(child, previousChild, ancestor); + previousChild = child; + } + d3_layout_treeShift(node); + var midpoint = .5 * (firstChild._tree.prelim + child._tree.prelim); + if (previousSibling) { + layout.prelim = previousSibling._tree.prelim + separation(node, previousSibling); + layout.mod = layout.prelim - midpoint; + } else { + layout.prelim = midpoint; + } + } else { + if (previousSibling) { + layout.prelim = previousSibling._tree.prelim + separation(node, previousSibling); + } + } + } + function secondWalk(node, x) { + node.x = node._tree.prelim + x; + var children = node.children; + if (children && (n = children.length)) { + var i = -1, n; + x += node._tree.mod; + while (++i < n) { + secondWalk(children[i], x); + } + } + } + function apportion(node, previousSibling, ancestor) { + if (previousSibling) { + var vip = node, vop = node, vim = previousSibling, vom = node.parent.children[0], sip = vip._tree.mod, sop = vop._tree.mod, sim = vim._tree.mod, som = vom._tree.mod, shift; + while (vim = d3_layout_treeRight(vim), vip = d3_layout_treeLeft(vip), vim && vip) { + vom = d3_layout_treeLeft(vom); + vop = d3_layout_treeRight(vop); + vop._tree.ancestor = node; + shift = vim._tree.prelim + sim - vip._tree.prelim - sip + separation(vim, vip); + if (shift > 0) { + d3_layout_treeMove(d3_layout_treeAncestor(vim, node, ancestor), node, shift); + sip += shift; + sop += shift; + } + sim += vim._tree.mod; + sip += vip._tree.mod; + som += vom._tree.mod; + sop += vop._tree.mod; + } + if (vim && !d3_layout_treeRight(vop)) { + vop._tree.thread = vim; + vop._tree.mod += sim - sop; + } + if (vip && !d3_layout_treeLeft(vom)) { + vom._tree.thread = vip; + vom._tree.mod += sip - som; + ancestor = node; + } + } + return ancestor; + } + var nodes = hierarchy.call(this, d, i), root = nodes[0]; + d3_layout_treeVisitAfter(root, function(node, previousSibling) { + node._tree = { + ancestor: node, + prelim: 0, + mod: 0, + change: 0, + shift: 0, + number: previousSibling ? previousSibling._tree.number + 1 : 0 + }; + }); + firstWalk(root); + secondWalk(root, -root._tree.prelim); + var left = d3_layout_treeSearch(root, d3_layout_treeLeftmost), right = d3_layout_treeSearch(root, d3_layout_treeRightmost), deep = d3_layout_treeSearch(root, d3_layout_treeDeepest), x0 = left.x - separation(left, right) / 2, x1 = right.x + separation(right, left) / 2, y1 = deep.depth || 1; + d3_layout_treeVisitAfter(root, function(node) { + node.x = (node.x - x0) / (x1 - x0) * size[0]; + node.y = node.depth / y1 * size[1]; + delete node._tree; + }); + return nodes; + } + var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ]; + tree.separation = function(x) { + if (!arguments.length) return separation; + separation = x; + return tree; + }; + tree.size = function(x) { + if (!arguments.length) return size; + size = x; + return tree; + }; + return d3_layout_hierarchyRebind(tree, hierarchy); + }; + d3.layout.treemap = function() { + function scale(children, k) { + var i = -1, n = children.length, child, area; + while (++i < n) { + area = (child = children[i]).value * (k < 0 ? 0 : k); + child.area = isNaN(area) || area <= 0 ? 0 : area; + } + } + function squarify(node) { + var children = node.children; + if (children && children.length) { + var rect = pad(node), row = [], remaining = children.slice(), child, best = Infinity, score, u = Math.min(rect.dx, rect.dy), n; + scale(remaining, rect.dx * rect.dy / node.value); + row.area = 0; + while ((n = remaining.length) > 0) { + row.push(child = remaining[n - 1]); + row.area += child.area; + if ((score = worst(row, u)) <= best) { + remaining.pop(); + best = score; + } else { + row.area -= row.pop().area; + position(row, u, rect, false); + u = Math.min(rect.dx, rect.dy); + row.length = row.area = 0; + best = Infinity; + } + } + if (row.length) { + position(row, u, rect, true); + row.length = row.area = 0; + } + children.forEach(squarify); + } + } + function stickify(node) { + var children = node.children; + if (children && children.length) { + var rect = pad(node), remaining = children.slice(), child, row = []; + scale(remaining, rect.dx * rect.dy / node.value); + row.area = 0; + while (child = remaining.pop()) { + row.push(child); + row.area += child.area; + if (child.z != null) { + position(row, child.z ? rect.dx : rect.dy, rect, !remaining.length); + row.length = row.area = 0; + } + } + children.forEach(stickify); + } + } + function worst(row, u) { + var s = row.area, r, rmax = 0, rmin = Infinity, i = -1, n = row.length; + while (++i < n) { + if (!(r = row[i].area)) continue; + if (r < rmin) rmin = r; + if (r > rmax) rmax = r; + } + s *= s; + u *= u; + return s ? Math.max(u * rmax * ratio / s, s / (u * rmin * ratio)) : Infinity; + } + function position(row, u, rect, flush) { + var i = -1, n = row.length, x = rect.x, y = rect.y, v = u ? round(row.area / u) : 0, o; + if (u == rect.dx) { + if (flush || v > rect.dy) v = rect.dy; + while (++i < n) { + o = row[i]; + o.x = x; + o.y = y; + o.dy = v; + x += o.dx = Math.min(rect.x + rect.dx - x, v ? round(o.area / v) : 0); + } + o.z = true; + o.dx += rect.x + rect.dx - x; + rect.y += v; + rect.dy -= v; + } else { + if (flush || v > rect.dx) v = rect.dx; + while (++i < n) { + o = row[i]; + o.x = x; + o.y = y; + o.dx = v; + y += o.dy = Math.min(rect.y + rect.dy - y, v ? round(o.area / v) : 0); + } + o.z = false; + o.dy += rect.y + rect.dy - y; + rect.x += v; + rect.dx -= v; + } + } + function treemap(d) { + var nodes = stickies || hierarchy(d), root = nodes[0]; + root.x = 0; + root.y = 0; + root.dx = size[0]; + root.dy = size[1]; + if (stickies) hierarchy.revalue(root); + scale([ root ], root.dx * root.dy / root.value); + (stickies ? stickify : squarify)(root); + if (sticky) stickies = nodes; + return nodes; + } + var hierarchy = d3.layout.hierarchy(), round = Math.round, size = [ 1, 1 ], padding = null, pad = d3_layout_treemapPadNull, sticky = false, stickies, ratio = .5 * (1 + Math.sqrt(5)); + treemap.size = function(x) { + if (!arguments.length) return size; + size = x; + return treemap; + }; + treemap.padding = function(x) { + function padFunction(node) { + var p = x.call(treemap, node, node.depth); + return p == null ? d3_layout_treemapPadNull(node) : d3_layout_treemapPad(node, typeof p === "number" ? [ p, p, p, p ] : p); + } + function padConstant(node) { + return d3_layout_treemapPad(node, x); + } + if (!arguments.length) return padding; + var type; + pad = (padding = x) == null ? d3_layout_treemapPadNull : (type = typeof x) === "function" ? padFunction : type === "number" ? (x = [ x, x, x, x ], padConstant) : padConstant; + return treemap; + }; + treemap.round = function(x) { + if (!arguments.length) return round != Number; + round = x ? Math.round : Number; + return treemap; + }; + treemap.sticky = function(x) { + if (!arguments.length) return sticky; + sticky = x; + stickies = null; + return treemap; + }; + treemap.ratio = function(x) { + if (!arguments.length) return ratio; + ratio = x; + return treemap; + }; + return d3_layout_hierarchyRebind(treemap, hierarchy); + }; + d3.csv = d3_dsv(",", "text/csv"); + d3.tsv = d3_dsv(" ", "text/tab-separated-values"); + d3.geo = {}; + var d3_geo_radians = Math.PI / 180; + d3.geo.azimuthal = function() { + function azimuthal(coordinates) { + var x1 = coordinates[0] * d3_geo_radians - x0, y1 = coordinates[1] * d3_geo_radians, cx1 = Math.cos(x1), sx1 = Math.sin(x1), cy1 = Math.cos(y1), sy1 = Math.sin(y1), cc = mode !== "orthographic" ? sy0 * sy1 + cy0 * cy1 * cx1 : null, c, k = mode === "stereographic" ? 1 / (1 + cc) : mode === "gnomonic" ? 1 / cc : mode === "equidistant" ? (c = Math.acos(cc), c ? c / Math.sin(c) : 0) : mode === "equalarea" ? Math.sqrt(2 / (1 + cc)) : 1, x = k * cy1 * sx1, y = k * (sy0 * cy1 * cx1 - cy0 * sy1); + return [ scale * x + translate[0], scale * y + translate[1] ]; + } + var mode = "orthographic", origin, scale = 200, translate = [ 480, 250 ], x0, y0, cy0, sy0; + azimuthal.invert = function(coordinates) { + var x = (coordinates[0] - translate[0]) / scale, y = (coordinates[1] - translate[1]) / scale, p = Math.sqrt(x * x + y * y), c = mode === "stereographic" ? 2 * Math.atan(p) : mode === "gnomonic" ? Math.atan(p) : mode === "equidistant" ? p : mode === "equalarea" ? 2 * Math.asin(.5 * p) : Math.asin(p), sc = Math.sin(c), cc = Math.cos(c); + return [ (x0 + Math.atan2(x * sc, p * cy0 * cc + y * sy0 * sc)) / d3_geo_radians, Math.asin(cc * sy0 - (p ? y * sc * cy0 / p : 0)) / d3_geo_radians ]; + }; + azimuthal.mode = function(x) { + if (!arguments.length) return mode; + mode = x + ""; + return azimuthal; + }; + azimuthal.origin = function(x) { + if (!arguments.length) return origin; + origin = x; + x0 = origin[0] * d3_geo_radians; + y0 = origin[1] * d3_geo_radians; + cy0 = Math.cos(y0); + sy0 = Math.sin(y0); + return azimuthal; + }; + azimuthal.scale = function(x) { + if (!arguments.length) return scale; + scale = +x; + return azimuthal; + }; + azimuthal.translate = function(x) { + if (!arguments.length) return translate; + translate = [ +x[0], +x[1] ]; + return azimuthal; + }; + return azimuthal.origin([ 0, 0 ]); + }; + d3.geo.albers = function() { + function albers(coordinates) { + var t = n * (d3_geo_radians * coordinates[0] - lng0), p = Math.sqrt(C - 2 * n * Math.sin(d3_geo_radians * coordinates[1])) / n; + return [ scale * p * Math.sin(t) + translate[0], scale * (p * Math.cos(t) - p0) + translate[1] ]; + } + function reload() { + var phi1 = d3_geo_radians * parallels[0], phi2 = d3_geo_radians * parallels[1], lat0 = d3_geo_radians * origin[1], s = Math.sin(phi1), c = Math.cos(phi1); + lng0 = d3_geo_radians * origin[0]; + n = .5 * (s + Math.sin(phi2)); + C = c * c + 2 * n * s; + p0 = Math.sqrt(C - 2 * n * Math.sin(lat0)) / n; + return albers; + } + var origin = [ -98, 38 ], parallels = [ 29.5, 45.5 ], scale = 1e3, translate = [ 480, 250 ], lng0, n, C, p0; + albers.invert = function(coordinates) { + var x = (coordinates[0] - translate[0]) / scale, y = (coordinates[1] - translate[1]) / scale, p0y = p0 + y, t = Math.atan2(x, p0y), p = Math.sqrt(x * x + p0y * p0y); + return [ (lng0 + t / n) / d3_geo_radians, Math.asin((C - p * p * n * n) / (2 * n)) / d3_geo_radians ]; + }; + albers.origin = function(x) { + if (!arguments.length) return origin; + origin = [ +x[0], +x[1] ]; + return reload(); + }; + albers.parallels = function(x) { + if (!arguments.length) return parallels; + parallels = [ +x[0], +x[1] ]; + return reload(); + }; + albers.scale = function(x) { + if (!arguments.length) return scale; + scale = +x; + return albers; + }; + albers.translate = function(x) { + if (!arguments.length) return translate; + translate = [ +x[0], +x[1] ]; + return albers; + }; + return reload(); + }; + d3.geo.albersUsa = function() { + function albersUsa(coordinates) { + var lon = coordinates[0], lat = coordinates[1]; + return (lat > 50 ? alaska : lon < -140 ? hawaii : lat < 21 ? puertoRico : lower48)(coordinates); + } + var lower48 = d3.geo.albers(); + var alaska = d3.geo.albers().origin([ -160, 60 ]).parallels([ 55, 65 ]); + var hawaii = d3.geo.albers().origin([ -160, 20 ]).parallels([ 8, 18 ]); + var puertoRico = d3.geo.albers().origin([ -60, 10 ]).parallels([ 8, 18 ]); + albersUsa.scale = function(x) { + if (!arguments.length) return lower48.scale(); + lower48.scale(x); + alaska.scale(x * .6); + hawaii.scale(x); + puertoRico.scale(x * 1.5); + return albersUsa.translate(lower48.translate()); + }; + albersUsa.translate = function(x) { + if (!arguments.length) return lower48.translate(); + var dz = lower48.scale() / 1e3, dx = x[0], dy = x[1]; + lower48.translate(x); + alaska.translate([ dx - 400 * dz, dy + 170 * dz ]); + hawaii.translate([ dx - 190 * dz, dy + 200 * dz ]); + puertoRico.translate([ dx + 580 * dz, dy + 430 * dz ]); + return albersUsa; + }; + return albersUsa.scale(lower48.scale()); + }; + d3.geo.bonne = function() { + function bonne(coordinates) { + var x = coordinates[0] * d3_geo_radians - x0, y = coordinates[1] * d3_geo_radians - y0; + if (y1) { + var p = c1 + y1 - y, E = x * Math.cos(y) / p; + x = p * Math.sin(E); + y = p * Math.cos(E) - c1; + } else { + x *= Math.cos(y); + y *= -1; + } + return [ scale * x + translate[0], scale * y + translate[1] ]; + } + var scale = 200, translate = [ 480, 250 ], x0, y0, y1, c1; + bonne.invert = function(coordinates) { + var x = (coordinates[0] - translate[0]) / scale, y = (coordinates[1] - translate[1]) / scale; + if (y1) { + var c = c1 + y, p = Math.sqrt(x * x + c * c); + y = c1 + y1 - p; + x = x0 + p * Math.atan2(x, c) / Math.cos(y); + } else { + y *= -1; + x /= Math.cos(y); + } + return [ x / d3_geo_radians, y / d3_geo_radians ]; + }; + bonne.parallel = function(x) { + if (!arguments.length) return y1 / d3_geo_radians; + c1 = 1 / Math.tan(y1 = x * d3_geo_radians); + return bonne; + }; + bonne.origin = function(x) { + if (!arguments.length) return [ x0 / d3_geo_radians, y0 / d3_geo_radians ]; + x0 = x[0] * d3_geo_radians; + y0 = x[1] * d3_geo_radians; + return bonne; + }; + bonne.scale = function(x) { + if (!arguments.length) return scale; + scale = +x; + return bonne; + }; + bonne.translate = function(x) { + if (!arguments.length) return translate; + translate = [ +x[0], +x[1] ]; + return bonne; + }; + return bonne.origin([ 0, 0 ]).parallel(45); + }; + d3.geo.equirectangular = function() { + function equirectangular(coordinates) { + var x = coordinates[0] / 360, y = -coordinates[1] / 360; + return [ scale * x + translate[0], scale * y + translate[1] ]; + } + var scale = 500, translate = [ 480, 250 ]; + equirectangular.invert = function(coordinates) { + var x = (coordinates[0] - translate[0]) / scale, y = (coordinates[1] - translate[1]) / scale; + return [ 360 * x, -360 * y ]; + }; + equirectangular.scale = function(x) { + if (!arguments.length) return scale; + scale = +x; + return equirectangular; + }; + equirectangular.translate = function(x) { + if (!arguments.length) return translate; + translate = [ +x[0], +x[1] ]; + return equirectangular; + }; + return equirectangular; + }; + d3.geo.mercator = function() { + function mercator(coordinates) { + var x = coordinates[0] / 360, y = -(Math.log(Math.tan(Math.PI / 4 + coordinates[1] * d3_geo_radians / 2)) / d3_geo_radians) / 360; + return [ scale * x + translate[0], scale * Math.max(-.5, Math.min(.5, y)) + translate[1] ]; + } + var scale = 500, translate = [ 480, 250 ]; + mercator.invert = function(coordinates) { + var x = (coordinates[0] - translate[0]) / scale, y = (coordinates[1] - translate[1]) / scale; + return [ 360 * x, 2 * Math.atan(Math.exp(-360 * y * d3_geo_radians)) / d3_geo_radians - 90 ]; + }; + mercator.scale = function(x) { + if (!arguments.length) return scale; + scale = +x; + return mercator; + }; + mercator.translate = function(x) { + if (!arguments.length) return translate; + translate = [ +x[0], +x[1] ]; + return mercator; + }; + return mercator; + }; + d3.geo.path = function() { + function path(d, i) { + if (typeof pointRadius === "function") pointCircle = d3_path_circle(pointRadius.apply(this, arguments)); + pathType(d); + var result = buffer.length ? buffer.join("") : null; + buffer = []; + return result; + } + function project(coordinates) { + return projection(coordinates).join(","); + } + function polygonArea(coordinates) { + var sum = area(coordinates[0]), i = 0, n = coordinates.length; + while (++i < n) sum -= area(coordinates[i]); + return sum; + } + function polygonCentroid(coordinates) { + var polygon = d3.geom.polygon(coordinates[0].map(projection)), area = polygon.area(), centroid = polygon.centroid(area < 0 ? (area *= -1, 1) : -1), x = centroid[0], y = centroid[1], z = area, i = 0, n = coordinates.length; + while (++i < n) { + polygon = d3.geom.polygon(coordinates[i].map(projection)); + area = polygon.area(); + centroid = polygon.centroid(area < 0 ? (area *= -1, 1) : -1); + x -= centroid[0]; + y -= centroid[1]; + z -= area; + } + return [ x, y, 6 * z ]; + } + function area(coordinates) { + return Math.abs(d3.geom.polygon(coordinates.map(projection)).area()); + } + var pointRadius = 4.5, pointCircle = d3_path_circle(pointRadius), projection = d3.geo.albersUsa(), buffer = []; + var pathType = d3_geo_type({ + FeatureCollection: function(o) { + var features = o.features, i = -1, n = features.length; + while (++i < n) buffer.push(pathType(features[i].geometry)); + }, + Feature: function(o) { + pathType(o.geometry); + }, + Point: function(o) { + buffer.push("M", project(o.coordinates), pointCircle); + }, + MultiPoint: function(o) { + var coordinates = o.coordinates, i = -1, n = coordinates.length; + while (++i < n) buffer.push("M", project(coordinates[i]), pointCircle); + }, + LineString: function(o) { + var coordinates = o.coordinates, i = -1, n = coordinates.length; + buffer.push("M"); + while (++i < n) buffer.push(project(coordinates[i]), "L"); + buffer.pop(); + }, + MultiLineString: function(o) { + var coordinates = o.coordinates, i = -1, n = coordinates.length, subcoordinates, j, m; + while (++i < n) { + subcoordinates = coordinates[i]; + j = -1; + m = subcoordinates.length; + buffer.push("M"); + while (++j < m) buffer.push(project(subcoordinates[j]), "L"); + buffer.pop(); + } + }, + Polygon: function(o) { + var coordinates = o.coordinates, i = -1, n = coordinates.length, subcoordinates, j, m; + while (++i < n) { + subcoordinates = coordinates[i]; + j = -1; + if ((m = subcoordinates.length - 1) > 0) { + buffer.push("M"); + while (++j < m) buffer.push(project(subcoordinates[j]), "L"); + buffer[buffer.length - 1] = "Z"; + } + } + }, + MultiPolygon: function(o) { + var coordinates = o.coordinates, i = -1, n = coordinates.length, subcoordinates, j, m, subsubcoordinates, k, p; + while (++i < n) { + subcoordinates = coordinates[i]; + j = -1; + m = subcoordinates.length; + while (++j < m) { + subsubcoordinates = subcoordinates[j]; + k = -1; + if ((p = subsubcoordinates.length - 1) > 0) { + buffer.push("M"); + while (++k < p) buffer.push(project(subsubcoordinates[k]), "L"); + buffer[buffer.length - 1] = "Z"; + } + } + } + }, + GeometryCollection: function(o) { + var geometries = o.geometries, i = -1, n = geometries.length; + while (++i < n) buffer.push(pathType(geometries[i])); + } + }); + var areaType = path.area = d3_geo_type({ + FeatureCollection: function(o) { + var area = 0, features = o.features, i = -1, n = features.length; + while (++i < n) area += areaType(features[i]); + return area; + }, + Feature: function(o) { + return areaType(o.geometry); + }, + Polygon: function(o) { + return polygonArea(o.coordinates); + }, + MultiPolygon: function(o) { + var sum = 0, coordinates = o.coordinates, i = -1, n = coordinates.length; + while (++i < n) sum += polygonArea(coordinates[i]); + return sum; + }, + GeometryCollection: function(o) { + var sum = 0, geometries = o.geometries, i = -1, n = geometries.length; + while (++i < n) sum += areaType(geometries[i]); + return sum; + } + }, 0); + var centroidType = path.centroid = d3_geo_type({ + Feature: function(o) { + return centroidType(o.geometry); + }, + Polygon: function(o) { + var centroid = polygonCentroid(o.coordinates); + return [ centroid[0] / centroid[2], centroid[1] / centroid[2] ]; + }, + MultiPolygon: function(o) { + var area = 0, coordinates = o.coordinates, centroid, x = 0, y = 0, z = 0, i = -1, n = coordinates.length; + while (++i < n) { + centroid = polygonCentroid(coordinates[i]); + x += centroid[0]; + y += centroid[1]; + z += centroid[2]; + } + return [ x / z, y / z ]; + } + }); + path.projection = function(x) { + projection = x; + return path; + }; + path.pointRadius = function(x) { + if (typeof x === "function") pointRadius = x; else { + pointRadius = +x; + pointCircle = d3_path_circle(pointRadius); + } + return path; + }; + return path; + }; + d3.geo.bounds = function(feature) { + var left = Infinity, bottom = Infinity, right = -Infinity, top = -Infinity; + d3_geo_bounds(feature, function(x, y) { + if (x < left) left = x; + if (x > right) right = x; + if (y < bottom) bottom = y; + if (y > top) top = y; + }); + return [ [ left, bottom ], [ right, top ] ]; + }; + var d3_geo_boundsTypes = { + Feature: d3_geo_boundsFeature, + FeatureCollection: d3_geo_boundsFeatureCollection, + GeometryCollection: d3_geo_boundsGeometryCollection, + LineString: d3_geo_boundsLineString, + MultiLineString: d3_geo_boundsMultiLineString, + MultiPoint: d3_geo_boundsLineString, + MultiPolygon: d3_geo_boundsMultiPolygon, + Point: d3_geo_boundsPoint, + Polygon: d3_geo_boundsPolygon + }; + d3.geo.circle = function() { + function circle() {} + function visible(point) { + return arc.distance(point) < radians; + } + function clip(coordinates) { + var i = -1, n = coordinates.length, clipped = [], p0, p1, p2, d0, d1; + while (++i < n) { + d1 = arc.distance(p2 = coordinates[i]); + if (d1 < radians) { + if (p1) clipped.push(d3_geo_greatArcInterpolate(p1, p2)((d0 - radians) / (d0 - d1))); + clipped.push(p2); + p0 = p1 = null; + } else { + p1 = p2; + if (!p0 && clipped.length) { + clipped.push(d3_geo_greatArcInterpolate(clipped[clipped.length - 1], p1)((radians - d0) / (d1 - d0))); + p0 = p1; + } + } + d0 = d1; + } + p0 = coordinates[0]; + p1 = clipped[0]; + if (p1 && p2[0] === p0[0] && p2[1] === p0[1] && !(p2[0] === p1[0] && p2[1] === p1[1])) { + clipped.push(p1); + } + return resample(clipped); + } + function resample(coordinates) { + var i = 0, n = coordinates.length, j, m, resampled = n ? [ coordinates[0] ] : coordinates, resamples, origin = arc.source(); + while (++i < n) { + resamples = arc.source(coordinates[i - 1])(coordinates[i]).coordinates; + for (j = 0, m = resamples.length; ++j < m; ) resampled.push(resamples[j]); + } + arc.source(origin); + return resampled; + } + var origin = [ 0, 0 ], degrees = 90 - .01, radians = degrees * d3_geo_radians, arc = d3.geo.greatArc().source(origin).target(d3_identity); + circle.clip = function(d) { + if (typeof origin === "function") arc.source(origin.apply(this, arguments)); + return clipType(d) || null; + }; + var clipType = d3_geo_type({ + FeatureCollection: function(o) { + var features = o.features.map(clipType).filter(d3_identity); + return features && (o = Object.create(o), o.features = features, o); + }, + Feature: function(o) { + var geometry = clipType(o.geometry); + return geometry && (o = Object.create(o), o.geometry = geometry, o); + }, + Point: function(o) { + return visible(o.coordinates) && o; + }, + MultiPoint: function(o) { + var coordinates = o.coordinates.filter(visible); + return coordinates.length && { + type: o.type, + coordinates: coordinates + }; + }, + LineString: function(o) { + var coordinates = clip(o.coordinates); + return coordinates.length && (o = Object.create(o), o.coordinates = coordinates, o); + }, + MultiLineString: function(o) { + var coordinates = o.coordinates.map(clip).filter(function(d) { + return d.length; + }); + return coordinates.length && (o = Object.create(o), o.coordinates = coordinates, o); + }, + Polygon: function(o) { + var coordinates = o.coordinates.map(clip); + return coordinates[0].length && (o = Object.create(o), o.coordinates = coordinates, o); + }, + MultiPolygon: function(o) { + var coordinates = o.coordinates.map(function(d) { + return d.map(clip); + }).filter(function(d) { + return d[0].length; + }); + return coordinates.length && (o = Object.create(o), o.coordinates = coordinates, o); + }, + GeometryCollection: function(o) { + var geometries = o.geometries.map(clipType).filter(d3_identity); + return geometries.length && (o = Object.create(o), o.geometries = geometries, o); + } + }); + circle.origin = function(x) { + if (!arguments.length) return origin; + origin = x; + if (typeof origin !== "function") arc.source(origin); + return circle; + }; + circle.angle = function(x) { + if (!arguments.length) return degrees; + radians = (degrees = +x) * d3_geo_radians; + return circle; + }; + return d3.rebind(circle, arc, "precision"); + }; + d3.geo.greatArc = function() { + function greatArc() { + var d = greatArc.distance.apply(this, arguments), t = 0, dt = precision / d, coordinates = [ p0 ]; + while ((t += dt) < 1) coordinates.push(interpolate(t)); + coordinates.push(p1); + return { + type: "LineString", + coordinates: coordinates + }; + } + var source = d3_geo_greatArcSource, p0, target = d3_geo_greatArcTarget, p1, precision = 6 * d3_geo_radians, interpolate = d3_geo_greatArcInterpolator(); + greatArc.distance = function() { + if (typeof source === "function") interpolate.source(p0 = source.apply(this, arguments)); + if (typeof target === "function") interpolate.target(p1 = target.apply(this, arguments)); + return interpolate.distance(); + }; + greatArc.source = function(_) { + if (!arguments.length) return source; + source = _; + if (typeof source !== "function") interpolate.source(p0 = source); + return greatArc; + }; + greatArc.target = function(_) { + if (!arguments.length) return target; + target = _; + if (typeof target !== "function") interpolate.target(p1 = target); + return greatArc; + }; + greatArc.precision = function(_) { + if (!arguments.length) return precision / d3_geo_radians; + precision = _ * d3_geo_radians; + return greatArc; + }; + return greatArc; + }; + d3.geo.greatCircle = d3.geo.circle; + d3.geom = {}; + d3.geom.contour = function(grid, start) { + var s = start || d3_geom_contourStart(grid), c = [], x = s[0], y = s[1], dx = 0, dy = 0, pdx = NaN, pdy = NaN, i = 0; + do { + i = 0; + if (grid(x - 1, y - 1)) i += 1; + if (grid(x, y - 1)) i += 2; + if (grid(x - 1, y)) i += 4; + if (grid(x, y)) i += 8; + if (i === 6) { + dx = pdy === -1 ? -1 : 1; + dy = 0; + } else if (i === 9) { + dx = 0; + dy = pdx === 1 ? -1 : 1; + } else { + dx = d3_geom_contourDx[i]; + dy = d3_geom_contourDy[i]; + } + if (dx != pdx && dy != pdy) { + c.push([ x, y ]); + pdx = dx; + pdy = dy; + } + x += dx; + y += dy; + } while (s[0] != x || s[1] != y); + return c; + }; + var d3_geom_contourDx = [ 1, 0, 1, 1, -1, 0, -1, 1, 0, 0, 0, 0, -1, 0, -1, NaN ], d3_geom_contourDy = [ 0, -1, 0, 0, 0, -1, 0, 0, 1, -1, 1, 1, 0, -1, 0, NaN ]; + d3.geom.hull = function(vertices) { + if (vertices.length < 3) return []; + var len = vertices.length, plen = len - 1, points = [], stack = [], i, j, h = 0, x1, y1, x2, y2, u, v, a, sp; + for (i = 1; i < len; ++i) { + if (vertices[i][1] < vertices[h][1]) { + h = i; + } else if (vertices[i][1] == vertices[h][1]) { + h = vertices[i][0] < vertices[h][0] ? i : h; + } + } + for (i = 0; i < len; ++i) { + if (i === h) continue; + y1 = vertices[i][1] - vertices[h][1]; + x1 = vertices[i][0] - vertices[h][0]; + points.push({ + angle: Math.atan2(y1, x1), + index: i + }); + } + points.sort(function(a, b) { + return a.angle - b.angle; + }); + a = points[0].angle; + v = points[0].index; + u = 0; + for (i = 1; i < plen; ++i) { + j = points[i].index; + if (a == points[i].angle) { + x1 = vertices[v][0] - vertices[h][0]; + y1 = vertices[v][1] - vertices[h][1]; + x2 = vertices[j][0] - vertices[h][0]; + y2 = vertices[j][1] - vertices[h][1]; + if (x1 * x1 + y1 * y1 >= x2 * x2 + y2 * y2) { + points[i].index = -1; + } else { + points[u].index = -1; + a = points[i].angle; + u = i; + v = j; + } + } else { + a = points[i].angle; + u = i; + v = j; + } + } + stack.push(h); + for (i = 0, j = 0; i < 2; ++j) { + if (points[j].index !== -1) { + stack.push(points[j].index); + i++; + } + } + sp = stack.length; + for (; j < plen; ++j) { + if (points[j].index === -1) continue; + while (!d3_geom_hullCCW(stack[sp - 2], stack[sp - 1], points[j].index, vertices)) { + --sp; + } + stack[sp++] = points[j].index; + } + var poly = []; + for (i = 0; i < sp; ++i) { + poly.push(vertices[stack[i]]); + } + return poly; + }; + d3.geom.polygon = function(coordinates) { + coordinates.area = function() { + var i = 0, n = coordinates.length, a = coordinates[n - 1][0] * coordinates[0][1], b = coordinates[n - 1][1] * coordinates[0][0]; + while (++i < n) { + a += coordinates[i - 1][0] * coordinates[i][1]; + b += coordinates[i - 1][1] * coordinates[i][0]; + } + return (b - a) * .5; + }; + coordinates.centroid = function(k) { + var i = -1, n = coordinates.length, x = 0, y = 0, a, b = coordinates[n - 1], c; + if (!arguments.length) k = -1 / (6 * coordinates.area()); + while (++i < n) { + a = b; + b = coordinates[i]; + c = a[0] * b[1] - b[0] * a[1]; + x += (a[0] + b[0]) * c; + y += (a[1] + b[1]) * c; + } + return [ x * k, y * k ]; + }; + coordinates.clip = function(subject) { + var input, i = -1, n = coordinates.length, j, m, a = coordinates[n - 1], b, c, d; + while (++i < n) { + input = subject.slice(); + subject.length = 0; + b = coordinates[i]; + c = input[(m = input.length) - 1]; + j = -1; + while (++j < m) { + d = input[j]; + if (d3_geom_polygonInside(d, a, b)) { + if (!d3_geom_polygonInside(c, a, b)) { + subject.push(d3_geom_polygonIntersect(c, d, a, b)); + } + subject.push(d); + } else if (d3_geom_polygonInside(c, a, b)) { + subject.push(d3_geom_polygonIntersect(c, d, a, b)); + } + c = d; + } + a = b; + } + return subject; + }; + return coordinates; + }; + d3.geom.voronoi = function(vertices) { + var polygons = vertices.map(function() { + return []; + }); + d3_voronoi_tessellate(vertices, function(e) { + var s1, s2, x1, x2, y1, y2; + if (e.a === 1 && e.b >= 0) { + s1 = e.ep.r; + s2 = e.ep.l; + } else { + s1 = e.ep.l; + s2 = e.ep.r; + } + if (e.a === 1) { + y1 = s1 ? s1.y : -1e6; + x1 = e.c - e.b * y1; + y2 = s2 ? s2.y : 1e6; + x2 = e.c - e.b * y2; + } else { + x1 = s1 ? s1.x : -1e6; + y1 = e.c - e.a * x1; + x2 = s2 ? s2.x : 1e6; + y2 = e.c - e.a * x2; + } + var v1 = [ x1, y1 ], v2 = [ x2, y2 ]; + polygons[e.region.l.index].push(v1, v2); + polygons[e.region.r.index].push(v1, v2); + }); + return polygons.map(function(polygon, i) { + var cx = vertices[i][0], cy = vertices[i][1]; + polygon.forEach(function(v) { + v.angle = Math.atan2(v[0] - cx, v[1] - cy); + }); + return polygon.sort(function(a, b) { + return a.angle - b.angle; + }).filter(function(d, i) { + return !i || d.angle - polygon[i - 1].angle > 1e-10; + }); + }); + }; + var d3_voronoi_opposite = { + l: "r", + r: "l" + }; + d3.geom.delaunay = function(vertices) { + var edges = vertices.map(function() { + return []; + }), triangles = []; + d3_voronoi_tessellate(vertices, function(e) { + edges[e.region.l.index].push(vertices[e.region.r.index]); + }); + edges.forEach(function(edge, i) { + var v = vertices[i], cx = v[0], cy = v[1]; + edge.forEach(function(v) { + v.angle = Math.atan2(v[0] - cx, v[1] - cy); + }); + edge.sort(function(a, b) { + return a.angle - b.angle; + }); + for (var j = 0, m = edge.length - 1; j < m; j++) { + triangles.push([ v, edge[j], edge[j + 1] ]); + } + }); + return triangles; + }; + d3.geom.quadtree = function(points, x1, y1, x2, y2) { + function insert(n, p, x1, y1, x2, y2) { + if (isNaN(p.x) || isNaN(p.y)) return; + if (n.leaf) { + var v = n.point; + if (v) { + if (Math.abs(v.x - p.x) + Math.abs(v.y - p.y) < .01) { + insertChild(n, p, x1, y1, x2, y2); + } else { + n.point = null; + insertChild(n, v, x1, y1, x2, y2); + insertChild(n, p, x1, y1, x2, y2); + } + } else { + n.point = p; + } + } else { + insertChild(n, p, x1, y1, x2, y2); + } + } + function insertChild(n, p, x1, y1, x2, y2) { + var sx = (x1 + x2) * .5, sy = (y1 + y2) * .5, right = p.x >= sx, bottom = p.y >= sy, i = (bottom << 1) + right; + n.leaf = false; + n = n.nodes[i] || (n.nodes[i] = d3_geom_quadtreeNode()); + if (right) x1 = sx; else x2 = sx; + if (bottom) y1 = sy; else y2 = sy; + insert(n, p, x1, y1, x2, y2); + } + var p, i = -1, n = points.length; + if (n && isNaN(points[0].x)) points = points.map(d3_geom_quadtreePoint); + if (arguments.length < 5) { + if (arguments.length === 3) { + y2 = x2 = y1; + y1 = x1; + } else { + x1 = y1 = Infinity; + x2 = y2 = -Infinity; + while (++i < n) { + p = points[i]; + if (p.x < x1) x1 = p.x; + if (p.y < y1) y1 = p.y; + if (p.x > x2) x2 = p.x; + if (p.y > y2) y2 = p.y; + } + var dx = x2 - x1, dy = y2 - y1; + if (dx > dy) y2 = y1 + dx; else x2 = x1 + dy; + } + } + var root = d3_geom_quadtreeNode(); + root.add = function(p) { + insert(root, p, x1, y1, x2, y2); + }; + root.visit = function(f) { + d3_geom_quadtreeVisit(f, root, x1, y1, x2, y2); + }; + points.forEach(root.add); + return root; + }; + d3.time = {}; + var d3_time = Date, d3_time_daySymbols = [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ]; + d3_time_utc.prototype = { + getDate: function() { + return this._.getUTCDate(); + }, + getDay: function() { + return this._.getUTCDay(); + }, + getFullYear: function() { + return this._.getUTCFullYear(); + }, + getHours: function() { + return this._.getUTCHours(); + }, + getMilliseconds: function() { + return this._.getUTCMilliseconds(); + }, + getMinutes: function() { + return this._.getUTCMinutes(); + }, + getMonth: function() { + return this._.getUTCMonth(); + }, + getSeconds: function() { + return this._.getUTCSeconds(); + }, + getTime: function() { + return this._.getTime(); + }, + getTimezoneOffset: function() { + return 0; + }, + valueOf: function() { + return this._.valueOf(); + }, + setDate: function() { + d3_time_prototype.setUTCDate.apply(this._, arguments); + }, + setDay: function() { + d3_time_prototype.setUTCDay.apply(this._, arguments); + }, + setFullYear: function() { + d3_time_prototype.setUTCFullYear.apply(this._, arguments); + }, + setHours: function() { + d3_time_prototype.setUTCHours.apply(this._, arguments); + }, + setMilliseconds: function() { + d3_time_prototype.setUTCMilliseconds.apply(this._, arguments); + }, + setMinutes: function() { + d3_time_prototype.setUTCMinutes.apply(this._, arguments); + }, + setMonth: function() { + d3_time_prototype.setUTCMonth.apply(this._, arguments); + }, + setSeconds: function() { + d3_time_prototype.setUTCSeconds.apply(this._, arguments); + }, + setTime: function() { + d3_time_prototype.setTime.apply(this._, arguments); + } + }; + var d3_time_prototype = Date.prototype; + var d3_time_formatDateTime = "%a %b %e %H:%M:%S %Y", d3_time_formatDate = "%m/%d/%y", d3_time_formatTime = "%H:%M:%S"; + var d3_time_days = d3_time_daySymbols, d3_time_dayAbbreviations = d3_time_days.map(d3_time_formatAbbreviate), d3_time_months = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ], d3_time_monthAbbreviations = d3_time_months.map(d3_time_formatAbbreviate); + d3.time.format = function(template) { + function format(date) { + var string = [], i = -1, j = 0, c, f; + while (++i < n) { + if (template.charCodeAt(i) == 37) { + string.push(template.substring(j, i), (f = d3_time_formats[c = template.charAt(++i)]) ? f(date) : c); + j = i + 1; + } + } + string.push(template.substring(j, i)); + return string.join(""); + } + var n = template.length; + format.parse = function(string) { + var d = { + y: 1900, + m: 0, + d: 1, + H: 0, + M: 0, + S: 0, + L: 0 + }, i = d3_time_parse(d, template, string, 0); + if (i != string.length) return null; + if ("p" in d) d.H = d.H % 12 + d.p * 12; + var date = new d3_time; + date.setFullYear(d.y, d.m, d.d); + date.setHours(d.H, d.M, d.S, d.L); + return date; + }; + format.toString = function() { + return template; + }; + return format; + }; + var d3_time_zfill2 = d3.format("02d"), d3_time_zfill3 = d3.format("03d"), d3_time_zfill4 = d3.format("04d"), d3_time_sfill2 = d3.format("2d"); + var d3_time_dayRe = d3_time_formatRe(d3_time_days), d3_time_dayAbbrevRe = d3_time_formatRe(d3_time_dayAbbreviations), d3_time_monthRe = d3_time_formatRe(d3_time_months), d3_time_monthLookup = d3_time_formatLookup(d3_time_months), d3_time_monthAbbrevRe = d3_time_formatRe(d3_time_monthAbbreviations), d3_time_monthAbbrevLookup = d3_time_formatLookup(d3_time_monthAbbreviations); + var d3_time_formats = { + a: function(d) { + return d3_time_dayAbbreviations[d.getDay()]; + }, + A: function(d) { + return d3_time_days[d.getDay()]; + }, + b: function(d) { + return d3_time_monthAbbreviations[d.getMonth()]; + }, + B: function(d) { + return d3_time_months[d.getMonth()]; + }, + c: d3.time.format(d3_time_formatDateTime), + d: function(d) { + return d3_time_zfill2(d.getDate()); + }, + e: function(d) { + return d3_time_sfill2(d.getDate()); + }, + H: function(d) { + return d3_time_zfill2(d.getHours()); + }, + I: function(d) { + return d3_time_zfill2(d.getHours() % 12 || 12); + }, + j: function(d) { + return d3_time_zfill3(1 + d3.time.dayOfYear(d)); + }, + L: function(d) { + return d3_time_zfill3(d.getMilliseconds()); + }, + m: function(d) { + return d3_time_zfill2(d.getMonth() + 1); + }, + M: function(d) { + return d3_time_zfill2(d.getMinutes()); + }, + p: function(d) { + return d.getHours() >= 12 ? "PM" : "AM"; + }, + S: function(d) { + return d3_time_zfill2(d.getSeconds()); + }, + U: function(d) { + return d3_time_zfill2(d3.time.sundayOfYear(d)); + }, + w: function(d) { + return d.getDay(); + }, + W: function(d) { + return d3_time_zfill2(d3.time.mondayOfYear(d)); + }, + x: d3.time.format(d3_time_formatDate), + X: d3.time.format(d3_time_formatTime), + y: function(d) { + return d3_time_zfill2(d.getFullYear() % 100); + }, + Y: function(d) { + return d3_time_zfill4(d.getFullYear() % 1e4); + }, + Z: d3_time_zone, + "%": function(d) { + return "%"; + } + }; + var d3_time_parsers = { + a: d3_time_parseWeekdayAbbrev, + A: d3_time_parseWeekday, + b: d3_time_parseMonthAbbrev, + B: d3_time_parseMonth, + c: d3_time_parseLocaleFull, + d: d3_time_parseDay, + e: d3_time_parseDay, + H: d3_time_parseHour24, + I: d3_time_parseHour24, + L: d3_time_parseMilliseconds, + m: d3_time_parseMonthNumber, + M: d3_time_parseMinutes, + p: d3_time_parseAmPm, + S: d3_time_parseSeconds, + x: d3_time_parseLocaleDate, + X: d3_time_parseLocaleTime, + y: d3_time_parseYear, + Y: d3_time_parseFullYear + }; + var d3_time_numberRe = /^\s*\d+/; + var d3_time_amPmLookup = d3.map({ + am: 0, + pm: 1 + }); + d3.time.format.utc = function(template) { + function format(date) { + try { + d3_time = d3_time_utc; + var utc = new d3_time; + utc._ = date; + return local(utc); + } finally { + d3_time = Date; + } + } + var local = d3.time.format(template); + format.parse = function(string) { + try { + d3_time = d3_time_utc; + var date = local.parse(string); + return date && date._; + } finally { + d3_time = Date; + } + }; + format.toString = local.toString; + return format; + }; + var d3_time_formatIso = d3.time.format.utc("%Y-%m-%dT%H:%M:%S.%LZ"); + d3.time.format.iso = Date.prototype.toISOString ? d3_time_formatIsoNative : d3_time_formatIso; + d3_time_formatIsoNative.parse = function(string) { + var date = new Date(string); + return isNaN(date) ? null : date; + }; + d3_time_formatIsoNative.toString = d3_time_formatIso.toString; + d3.time.second = d3_time_interval(function(date) { + return new d3_time(Math.floor(date / 1e3) * 1e3); + }, function(date, offset) { + date.setTime(date.getTime() + Math.floor(offset) * 1e3); + }, function(date) { + return date.getSeconds(); + }); + d3.time.seconds = d3.time.second.range; + d3.time.seconds.utc = d3.time.second.utc.range; + d3.time.minute = d3_time_interval(function(date) { + return new d3_time(Math.floor(date / 6e4) * 6e4); + }, function(date, offset) { + date.setTime(date.getTime() + Math.floor(offset) * 6e4); + }, function(date) { + return date.getMinutes(); + }); + d3.time.minutes = d3.time.minute.range; + d3.time.minutes.utc = d3.time.minute.utc.range; + d3.time.hour = d3_time_interval(function(date) { + var timezone = date.getTimezoneOffset() / 60; + return new d3_time((Math.floor(date / 36e5 - timezone) + timezone) * 36e5); + }, function(date, offset) { + date.setTime(date.getTime() + Math.floor(offset) * 36e5); + }, function(date) { + return date.getHours(); + }); + d3.time.hours = d3.time.hour.range; + d3.time.hours.utc = d3.time.hour.utc.range; + d3.time.day = d3_time_interval(function(date) { + var day = new d3_time(1970, 0); + day.setFullYear(date.getFullYear(), date.getMonth(), date.getDate()); + return day; + }, function(date, offset) { + date.setDate(date.getDate() + offset); + }, function(date) { + return date.getDate() - 1; + }); + d3.time.days = d3.time.day.range; + d3.time.days.utc = d3.time.day.utc.range; + d3.time.dayOfYear = function(date) { + var year = d3.time.year(date); + return Math.floor((date - year - (date.getTimezoneOffset() - year.getTimezoneOffset()) * 6e4) / 864e5); + }; + d3_time_daySymbols.forEach(function(day, i) { + day = day.toLowerCase(); + i = 7 - i; + var interval = d3.time[day] = d3_time_interval(function(date) { + (date = d3.time.day(date)).setDate(date.getDate() - (date.getDay() + i) % 7); + return date; + }, function(date, offset) { + date.setDate(date.getDate() + Math.floor(offset) * 7); + }, function(date) { + var day = d3.time.year(date).getDay(); + return Math.floor((d3.time.dayOfYear(date) + (day + i) % 7) / 7) - (day !== i); + }); + d3.time[day + "s"] = interval.range; + d3.time[day + "s"].utc = interval.utc.range; + d3.time[day + "OfYear"] = function(date) { + var day = d3.time.year(date).getDay(); + return Math.floor((d3.time.dayOfYear(date) + (day + i) % 7) / 7); + }; + }); + d3.time.week = d3.time.sunday; + d3.time.weeks = d3.time.sunday.range; + d3.time.weeks.utc = d3.time.sunday.utc.range; + d3.time.weekOfYear = d3.time.sundayOfYear; + d3.time.month = d3_time_interval(function(date) { + date = d3.time.day(date); + date.setDate(1); + return date; + }, function(date, offset) { + date.setMonth(date.getMonth() + offset); + }, function(date) { + return date.getMonth(); + }); + d3.time.months = d3.time.month.range; + d3.time.months.utc = d3.time.month.utc.range; + d3.time.year = d3_time_interval(function(date) { + date = d3.time.day(date); + date.setMonth(0, 1); + return date; + }, function(date, offset) { + date.setFullYear(date.getFullYear() + offset); + }, function(date) { + return date.getFullYear(); + }); + d3.time.years = d3.time.year.range; + d3.time.years.utc = d3.time.year.utc.range; + var d3_time_scaleSteps = [ 1e3, 5e3, 15e3, 3e4, 6e4, 3e5, 9e5, 18e5, 36e5, 108e5, 216e5, 432e5, 864e5, 1728e5, 6048e5, 2592e6, 7776e6, 31536e6 ]; + var d3_time_scaleLocalMethods = [ [ d3.time.second, 1 ], [ d3.time.second, 5 ], [ d3.time.second, 15 ], [ d3.time.second, 30 ], [ d3.time.minute, 1 ], [ d3.time.minute, 5 ], [ d3.time.minute, 15 ], [ d3.time.minute, 30 ], [ d3.time.hour, 1 ], [ d3.time.hour, 3 ], [ d3.time.hour, 6 ], [ d3.time.hour, 12 ], [ d3.time.day, 1 ], [ d3.time.day, 2 ], [ d3.time.week, 1 ], [ d3.time.month, 1 ], [ d3.time.month, 3 ], [ d3.time.year, 1 ] ]; + var d3_time_scaleLocalFormats = [ [ d3.time.format("%Y"), function(d) { + return true; + } ], [ d3.time.format("%B"), function(d) { + return d.getMonth(); + } ], [ d3.time.format("%b %d"), function(d) { + return d.getDate() != 1; + } ], [ d3.time.format("%a %d"), function(d) { + return d.getDay() && d.getDate() != 1; + } ], [ d3.time.format("%I %p"), function(d) { + return d.getHours(); + } ], [ d3.time.format("%I:%M"), function(d) { + return d.getMinutes(); + } ], [ d3.time.format(":%S"), function(d) { + return d.getSeconds(); + } ], [ d3.time.format(".%L"), function(d) { + return d.getMilliseconds(); + } ] ]; + var d3_time_scaleLinear = d3.scale.linear(), d3_time_scaleLocalFormat = d3_time_scaleFormat(d3_time_scaleLocalFormats); + d3_time_scaleLocalMethods.year = function(extent, m) { + return d3_time_scaleLinear.domain(extent.map(d3_time_scaleGetYear)).ticks(m).map(d3_time_scaleSetYear); + }; + d3.time.scale = function() { + return d3_time_scale(d3.scale.linear(), d3_time_scaleLocalMethods, d3_time_scaleLocalFormat); + }; + var d3_time_scaleUTCMethods = d3_time_scaleLocalMethods.map(function(m) { + return [ m[0].utc, m[1] ]; + }); + var d3_time_scaleUTCFormats = [ [ d3.time.format.utc("%Y"), function(d) { + return true; + } ], [ d3.time.format.utc("%B"), function(d) { + return d.getUTCMonth(); + } ], [ d3.time.format.utc("%b %d"), function(d) { + return d.getUTCDate() != 1; + } ], [ d3.time.format.utc("%a %d"), function(d) { + return d.getUTCDay() && d.getUTCDate() != 1; + } ], [ d3.time.format.utc("%I %p"), function(d) { + return d.getUTCHours(); + } ], [ d3.time.format.utc("%I:%M"), function(d) { + return d.getUTCMinutes(); + } ], [ d3.time.format.utc(":%S"), function(d) { + return d.getUTCSeconds(); + } ], [ d3.time.format.utc(".%L"), function(d) { + return d.getUTCMilliseconds(); + } ] ]; + var d3_time_scaleUTCFormat = d3_time_scaleFormat(d3_time_scaleUTCFormats); + d3_time_scaleUTCMethods.year = function(extent, m) { + return d3_time_scaleLinear.domain(extent.map(d3_time_scaleUTCGetYear)).ticks(m).map(d3_time_scaleUTCSetYear); + }; + d3.time.scale.utc = function() { + return d3_time_scale(d3.scale.linear(), d3_time_scaleUTCMethods, d3_time_scaleUTCFormat); + }; +})(); \ No newline at end of file diff --git a/packages/d3/package.js b/packages/d3/package.js new file mode 100644 index 00000000000..a4868a8ea24 --- /dev/null +++ b/packages/d3/package.js @@ -0,0 +1,7 @@ +Package.describe({ + summary: "Library for manipulating documents based on data." +}); + +Package.on_use(function (api) { + api.add_files('d3.v2.js', 'client'); +}); From 0bfbbc13c2a6ffe096b76140a417021bad8bff05 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 15 Oct 2012 16:51:49 -0700 Subject: [PATCH 35/52] Fix D3 link. --- docs/client/packages/d3.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/client/packages/d3.html b/docs/client/packages/d3.html index a7cf49a82bd..f361fa27c8e 100644 --- a/docs/client/packages/d3.html +++ b/docs/client/packages/d3.html @@ -3,7 +3,7 @@ ## `d3` -[D3.js](http://d3js.org/D3.js) is a JavaScript library for manipulating +[D3.js](http://d3js.org/) is a JavaScript library for manipulating documents based on data. D3 helps you bring data to life using HTML, SVG and CSS. D3's emphasis on web standards gives you the full capabilities of modern browsers without tying yourself to a proprietary framework, From 957c2860402ff2b82f5ebcb5f578cfcdc93c06df Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 15 Oct 2012 16:56:35 -0700 Subject: [PATCH 36/52] Add D3 to LICENSE.txt. --- LICENSE.txt | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/LICENSE.txt b/LICENSE.txt index 5f5319ca246..c0fede68211 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -752,6 +752,38 @@ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISE OF THE POSSIBILITY OF SUCH DAMAGE. +---------- +D3: http://d3js.org/ +---------- + +Copyright (c) 2012, Michael Bostock +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* The name Michael Bostock may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + ============= Public Domain From b1852a376e01842ac39b68638d9b8ab59e7d0efd Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 15 Oct 2012 17:19:42 -0700 Subject: [PATCH 37/52] History.md: d3 package. --- History.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/History.md b/History.md index 92e2b93f06c..0378f1b54dd 100644 --- a/History.md +++ b/History.md @@ -40,6 +40,9 @@ * Meteor now provides a compatible replacement for the DOM `localStorage` facility that works in IE7, in the `localstorage-polyfill` smart package. +* Meteor now packages the D3 library for manipulating documents based on data in + a smart package called `d3`. + * `Meteor.Collection` now takes its optional `manager` argument (used to associate a collection with a server you've connected to with `Meteor.connect`) as a named option. (The old call syntax continues to work From 02f7512da6b0a965c5d0b6845003fb5b1fcb3fc0 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 15 Oct 2012 17:29:47 -0700 Subject: [PATCH 38/52] Accounts.ui.config should link to accounts-ui --- docs/client/api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/client/api.js b/docs/client/api.js index 92a4ac8d1d8..0f339c1b9e7 100644 --- a/docs/client/api.js +++ b/docs/client/api.js @@ -798,7 +798,7 @@ Template.api.accounts_ui_config = { id: "accounts_ui_config", name: "Accounts.ui.config(options)", locus: "Client", - descr: ["Configure the behavior of `{{loginButtons}}`."], + descr: ["Configure the behavior of [`{{loginButtons}}`](#accountsui)."], options: [ { name: "requestPermissions", From 2bd3cb727b5e6b2dc0b88e48979cf3f08f72f3db Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 15 Oct 2012 17:29:56 -0700 Subject: [PATCH 39/52] Fix references to Meteor.createUser --- packages/accounts-password/passwords_server.js | 2 +- packages/accounts-ui-unstyled/login_buttons_dropdown.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/accounts-password/passwords_server.js b/packages/accounts-password/passwords_server.js index 312d82f3a00..5d907de04d1 100644 --- a/packages/accounts-password/passwords_server.js +++ b/packages/accounts-password/passwords_server.js @@ -445,7 +445,7 @@ // XXX allow an optional callback? if (callback) { - throw new Error("Meteor.createUser with callback not supported on the server yet."); + throw new Error("Accounts.createUser with callback not supported on the server yet."); } var userId = createUser(options).id; diff --git a/packages/accounts-ui-unstyled/login_buttons_dropdown.js b/packages/accounts-ui-unstyled/login_buttons_dropdown.js index 2d4e5508912..121c4aaeeab 100644 --- a/packages/accounts-ui-unstyled/login_buttons_dropdown.js +++ b/packages/accounts-ui-unstyled/login_buttons_dropdown.js @@ -385,7 +385,7 @@ var signup = function () { loginButtonsSession.resetMessages(); - var options = {}; // to be passed to Meteor.createUser + var options = {}; // to be passed to Accounts.createUser var username = trimmedElementValueById('login-username'); if (username !== null) { From b0f4db8d0f616e2d31af8cb2ce19c2adbd28028e Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 15 Oct 2012 17:35:07 -0700 Subject: [PATCH 40/52] Docs for validateNewUser describe what happens on throw. --- docs/client/api.html | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/docs/client/api.html b/docs/client/api.html index 81a94b4a87f..3104a614f60 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -1248,14 +1248,22 @@

Accounts

{{> api_box accounts_validateNewUser}} -This can be called multiple times. If any of the functions return -`false` the new user creation is aborted. +This can be called multiple times. If any of the functions return `false` or +throw an error, the new user creation is aborted. To set a specific error +message (which will be displayed by [`accounts-ui`](#accountsui)), throw a new +[`Meteor.Error`](#meteor_error). Example: - // All users must have a username longer than 3 characters. + // Validate username, sending a specific error message on failure. Accounts.validateNewUser(function (user) { - return user.username && user.username.length >= 3; + if (user.username && user.username.length >= 3) + return true; + throw new Meteor.Error(403, "Username must have at least 3 characters"); + }); + // Validate username, without a specific error message. + Accounts.validateNewUser(function (user) { + return user.username !== "root"; }); {{> api_box accounts_onCreateUser}} From 18324749cb7906abfea221b5822741bdd8eab0f1 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 15 Oct 2012 17:36:00 -0700 Subject: [PATCH 41/52] Insert missing word. --- docs/client/api.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/client/api.html b/docs/client/api.html index 3104a614f60..4f2ab7a1c5a 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -1276,7 +1276,7 @@

Accounts

`user`. The `options` argument comes from [`Accounts.createUser`](#accounts_createuser) for password-based users or from an external service login flow. `options` may come -from an untrusted client so make to validate any values you read from +from an untrusted client so make sure to validate any values you read from it. The `user` argument is created on the server and contains a proposed user object with all the automatically generated fields required for the user to log in. From 2533e2beca9400889ecdb90a2e9e9e7c94663fc3 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 15 Oct 2012 17:38:19 -0700 Subject: [PATCH 42/52] Be explicit that onCreateUser can mutate or return new. --- docs/client/api.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/client/api.html b/docs/client/api.html index 4f2ab7a1c5a..38ed646a91a 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -1281,9 +1281,9 @@

Accounts

proposed user object with all the automatically generated fields required for the user to log in. -The function should return a new user object with whatever modifications -are desired. The returned object is inserted directly into -the [`Meteor.users`](#meteor_users) collection. +The function should return the user document (either the one passed in or a +newly-created object) with whatever modifications are desired. The returned +document is inserted directly into the [`Meteor.users`](#meteor_users) collection. The default create user function simply copies `options.profile` into the new user document. Calling `onCreateUser` overrides the default From 85403c26d20d4478f89877fba8c903cf42d041b3 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 15 Oct 2012 17:43:31 -0700 Subject: [PATCH 43/52] Make the example onCreateUser hook copy profile too. --- docs/client/api.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/client/api.html b/docs/client/api.html index 38ed646a91a..cabb531b564 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -1297,6 +1297,9 @@

Accounts

Accounts.onCreateUser(function(options, user) { var d6 = function () { return Math.floor(Math.random() * 6) + 1; }; user.dexterity = d6() + d6() + d6(); + // We still want the default hook's 'profile' behavior. + if (options.profile) + user.profile = options.profile; return user; }); From c5af2098dac708989361b2ef7196d8182446d3ca Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 15 Oct 2012 17:44:52 -0700 Subject: [PATCH 44/52] Add missing period. --- docs/client/api.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/client/api.html b/docs/client/api.html index cabb531b564..46b49efc0f3 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -1354,7 +1354,7 @@

Passwords

This function accepts tokens generated by [`Accounts.sendResetPasswordEmail`](#accounts_sendresetpasswordemail) and -[`Accounts.sendEnrollmentEmail`](#accounts_sendenrollmentemail) +[`Accounts.sendEnrollmentEmail`](#accounts_sendenrollmentemail). {{> api_box accounts_setPassword}} From f00836846294c38e1716454ace4e8d92166719b9 Mon Sep 17 00:00:00 2001 From: Nick Martin Date: Mon, 15 Oct 2012 17:40:29 -0700 Subject: [PATCH 45/52] More license updates. --- LICENSE.txt | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/LICENSE.txt b/LICENSE.txt index c0fede68211..201b3c924cf 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -356,6 +356,11 @@ Copyright (c) 2011: Tim Koschützki (tim@debuggable.com) Felix Geisendörfer (felix@debuggable.com) +---------- +node-form-data: https://github.com/felixge/node-form-data +---------- + +Copyright (c) 2012 Felix Geisendörfer (felix@debuggable.com) and contributors ============== @@ -622,6 +627,11 @@ npmlog: https://github.com/isaacs/npmlog once: https://github.com/isaacs/once osenv: https://github.com/isaacs/osenv mute-stream: https://github.com/isaacs/mute-stream +couch-login: https://github.com/isaacs/couch-login +npmconf: https://github.com/isaacs/npmconf +read-installed: https://github.com/isaacs/read-installed +read-package-json: https://github.com/isaacs/read-package-json +promzard: https://github.com/isaacs/promzard ---------- Copyright (c) Isaac Z. Schlueter ("Author") @@ -873,6 +883,36 @@ By Isaac Z. Schlueter (http://blog.izs.me/) 0. You just DO WHAT THE FUCK YOU WANT TO. +---------- +node-stream-buffer: https://github.com/samcday/node-stream-buffer +---------- + +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to + + ---------- mongodb: http://www.mongodb.org/ ---------- From cd486910f323eeefc2584bdcae608bcc14d37619 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 15 Oct 2012 17:49:46 -0700 Subject: [PATCH 46/52] Add missing article to example. --- docs/client/api.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/client/api.html b/docs/client/api.html index 46b49efc0f3..3af53ee19a9 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -1421,7 +1421,7 @@

Passwords

return "Welcome to Awesome Town, " + user.profile.name; }; Accounts.emailTemplates.enrollAccount.text = function (user, url) { - return "You have been selected to participate in building better future!" + return "You have been selected to participate in building a better future!" + " To activate your account, simply click the link below:\n\n" + url; }; From 523e7e9f98ae15f13c7ed7957fdd93b093ec9e85 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 15 Oct 2012 17:55:21 -0700 Subject: [PATCH 47/52] Not documenting where the reset tokens go. --- docs/client/api.html | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/client/api.html b/docs/client/api.html index 3af53ee19a9..e2f9b276627 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -1347,8 +1347,6 @@

Passwords

automatically. Otherwise, it is your responsiblity to prompt the user for the new password and call `resetPassword`. -- XXX token goes to `Accounts._resetPasswordToken`. - {{> api_box accounts_resetPassword}} This function accepts tokens generated From a3385ba9e08224168d7f3f0c610778613655d3bb Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 15 Oct 2012 18:19:07 -0700 Subject: [PATCH 48/52] Describe user fields in more detail. --- docs/client/api.html | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/docs/client/api.html b/docs/client/api.html index e2f9b276627..a4902630143 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -1134,10 +1134,11 @@

Accounts

{{> api_box users}} -This collection contains one document per user. Example user document: +This collection contains one document per registered user. Here's an example +user document: { - _id: "bbca5d6a-2156-41c4-89da-0329e8c99a4f" // userId + _id: "bbca5d6a-2156-41c4-89da-0329e8c99a4f", // Meteor.userId() username: "cool_kid_13", // unique name emails: [ // each email address can only belong to one user. @@ -1162,6 +1163,21 @@

Accounts

} } +A user document can contain any data you want to store about a user. Meteor +treats the following fields specially: + +- `username`: a unique String identifying the user. +- `emails`: an Array of Objects with keys `address` and `verified`; + an email address may belong to at most one user. `verified` is + a Boolean which is true if the user has [verified the + address](#accounts_verifyemail) with a token sent over email. +- `profile`: an Object which (by default) the user can create + and update with any data. +- `services`: an Object containing data used by particular + login services. For example, its `reset` field contains + tokens used by [forgot password](#accounts_forgotpassword) links, + and its `resume` field contains tokens used to keep you + logged in between sessions. Like all [Meteor.Collection](#collections)s, you can access all documents on the server, but only those specifically published by the server are From bdd5c556662bcb3c13b5baf025b5d710209e7949 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 15 Oct 2012 18:39:29 -0700 Subject: [PATCH 49/52] Start of "Passwords" intro. --- docs/client/api.html | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/docs/client/api.html b/docs/client/api.html index a4902630143..433f31e5cdf 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -1230,6 +1230,8 @@

Accounts

{{> api_box loginWithPassword}} +XXX needs link to passwords section and mention the package + {{> api_box loginWithExternalService}} These functions initiate the login process with an external @@ -1249,6 +1251,8 @@

Accounts

- Google: - Twitter, Weibo: `requestPermissions` currently not supported +XXX mention provider packages + {{> api_box accounts_config}} {{> api_box accounts_ui_config}} @@ -1322,7 +1326,27 @@

Accounts

Passwords

-XXX intro text mentioning SRP +The `accounts-password` package implements a complete system for password-based +authentication. In addition to the basic password-based signing process, it also +includes support for email-based password recovery. + +Instead of sending user passwords over the wire and storing them directly in the +database, Meteor uses the [Secure Remote Password +protocol](http://en.wikipedia.org/wiki/Secure_Remote_Password_protocol). XXX + +XXX package needed + +XXX SRP / secure / wire + +XXX gives you all these cool features like emails + +XXX username/email + +To add password support to your application, run `$ meteor add +accounts-password`. Then, either build a user interface using the functions +documented in this section, or use the [`accounts-ui` package](#accountsui) to +include a turn-key user interface for password login including user creation, +password change, and "forgot password" links. {{> api_box accounts_createUser}} From 630ba60a5d432544a41410b346145144a287e004 Mon Sep 17 00:00:00 2001 From: Nick Martin Date: Mon, 15 Oct 2012 20:46:26 -0700 Subject: [PATCH 50/52] Accounts passwords header section. --- docs/client/api.html | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/docs/client/api.html b/docs/client/api.html index 433f31e5cdf..2c2330d904b 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -1326,27 +1326,23 @@

Accounts

Passwords

-The `accounts-password` package implements a complete system for password-based -authentication. In addition to the basic password-based signing process, it also -includes support for email-based password recovery. - -Instead of sending user passwords over the wire and storing them directly in the -database, Meteor uses the [Secure Remote Password -protocol](http://en.wikipedia.org/wiki/Secure_Remote_Password_protocol). XXX - -XXX package needed - -XXX SRP / secure / wire - -XXX gives you all these cool features like emails - -XXX username/email +The `accounts-password` package implements a complete system for +password based authentication. In addition to the basic username and +password based sign-in process it also supports email based sign-in +including address verification and password recovery emails. + +Unlike most web applications, the Meteor client does not send the user's +password directly to the server. It uses the [Secure Remote Password +protocol](http://en.wikipedia.org/wiki/Secure_Remote_Password_protocol) +to ensure the server never sees the user's plain-text password. This +helps protect against embarrassing password leaks if the server's +database is compromised. To add password support to your application, run `$ meteor add -accounts-password`. Then, either build a user interface using the functions -documented in this section, or use the [`accounts-ui` package](#accountsui) to -include a turn-key user interface for password login including user creation, -password change, and "forgot password" links. +accounts-password`. You can construct your own user interface using the +functions below, or use the [`accounts-ui` package](#accountsui) to +include a turn-key user interface for password-based sign-in. + {{> api_box accounts_createUser}} From bcab93c9d14b8886b26bed32d67dcf384dd746b3 Mon Sep 17 00:00:00 2001 From: Nick Martin Date: Mon, 15 Oct 2012 20:58:43 -0700 Subject: [PATCH 51/52] Tested in an app. --- docs/client/api.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/client/api.html b/docs/client/api.html index 2c2330d904b..712767f7c76 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -721,7 +721,7 @@

Collections

option. Set `fetch` to an array of the field names that should be retrieved. -Example: XXX test me! +Example: // Create a collection where users can only modify documents that // they own. Ownership is tracked by an 'owner' field on each From b1216c460ab929465220736ec31f4376f33239e5 Mon Sep 17 00:00:00 2001 From: Nick Martin Date: Mon, 15 Oct 2012 22:12:49 -0700 Subject: [PATCH 52/52] Tweak error message on collection method fail. Looks better in the screencast. --- packages/mongo-livedata/collection.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mongo-livedata/collection.js b/packages/mongo-livedata/collection.js index a22378f4e24..4bb3ec4bc73 100644 --- a/packages/mongo-livedata/collection.js +++ b/packages/mongo-livedata/collection.js @@ -184,7 +184,7 @@ _.each(["insert", "update", "remove"], function (name) { // down. callback = function (err) { if (err) - Meteor._debug(name + " failed: " + err.error + " -- " + err.reason); + Meteor._debug(name + " failed: " + (err.reason || err.stack)); }; }