Update Dart dependencies

Add new dependencies that have been introduced since
update_dart_dependencies was functional

Change-Id: I5b8b5f7a10d9c4c8cb4506cc7c5ddd432716b034
diff --git a/pub/ansicolor/.gitignore b/pub/ansicolor/.gitignore
new file mode 100644
index 0000000..1a90bb8
--- /dev/null
+++ b/pub/ansicolor/.gitignore
@@ -0,0 +1,5 @@
+packages
+pubspec.lock
+.project
+.children
+*~
\ No newline at end of file
diff --git a/pub/ansicolor/AUTHORS b/pub/ansicolor/AUTHORS
new file mode 100644
index 0000000..c3c748b
--- /dev/null
+++ b/pub/ansicolor/AUTHORS
@@ -0,0 +1,4 @@
+Authors:
+
+John Thomas McDole <codefu@google.com>
+
diff --git a/pub/ansicolor/BUILD.gn b/pub/ansicolor/BUILD.gn
new file mode 100644
index 0000000..418f65b
--- /dev/null
+++ b/pub/ansicolor/BUILD.gn
@@ -0,0 +1,14 @@
+# This file is generated by importer.py for ansicolor-0.0.9
+
+import("//build/dart/dart_package.gni")
+
+dart_package("ansicolor") {
+  package_name = "ansicolor"
+
+  source_dir = "lib"
+
+  disable_analysis = true
+
+  deps = [
+  ]
+}
diff --git a/pub/ansicolor/LICENSE b/pub/ansicolor/LICENSE
new file mode 100644
index 0000000..80bb203
--- /dev/null
+++ b/pub/ansicolor/LICENSE
@@ -0,0 +1,13 @@
+Copyright 2013 Google Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
\ No newline at end of file
diff --git a/pub/ansicolor/README.md b/pub/ansicolor/README.md
new file mode 100644
index 0000000..270a12d
--- /dev/null
+++ b/pub/ansicolor/README.md
@@ -0,0 +1,29 @@
+ANSI / Xterm 256 color library for Dart
+------
+
+Feel like you're missing some color in your terminal programs? Use AnsiPen to add ANSI color codes to your log messages.  
+
+Easy to disable for production, just set `color_disabled = false` and all codes will be empty - no re-writing debug messages.  
+
+Example
+------
+Note: Be mindful of contrasting colors.  If you set "bright white" foreground and don't adjust the background, you'll have a bad time with lighter terminals.  
+
+Foreground to bright white with default background:
+```dart
+AnsiPen pen = new AnsiPen()..white(bold: true);
+print(pen("Bright white foreground") + " this text is default fg/bg");
+```
+
+Background as a peach, foreground as white:
+```dart
+AnsiPen pen = new AnsiPen()..white()..rgb(r: 1.0, g: 0.8, b: 0.2);
+print(pen("White foreground with a peach background"));
+```
+
+Rainbow Demo
+------
+
+If you want a specific color, you can call the `xterm()` with the index listed in the rainbow below. To show the rainbow on your own terminal, just call `print(ansi_demo());` or run src/demo.dart
+
+![alt tag](https://raw.github.com/google/ansicolor-dart/master/ansicolor-dart.png)
diff --git a/pub/ansicolor/lib/ansicolor.dart b/pub/ansicolor/lib/ansicolor.dart
new file mode 100644
index 0000000..772eba7
--- /dev/null
+++ b/pub/ansicolor/lib/ansicolor.dart
@@ -0,0 +1,200 @@
+///
+/// Copyright 2013 Google Inc. All Rights Reserved.
+///
+/// ANSI/XTERM SGR (Select Graphics Rendering) support for 256 colors.
+/// Note: if you're using the dart editor, these won't look right in the
+/// terminal; disable via [color_disabled] or use Eclipse with the Dart and
+/// AnsiConsol plugins!
+///
+library ansicolor;
+
+/**
+ *  Globally enable or disable [AnsiPen] settings
+ *  Handy for turning on and off embedded colors without commenting out code.
+ */
+bool color_disabled = false;
+
+
+/**
+ * Pen attributes for foreground and background colors.
+ * Use the pen in string interpolation to output ansi codes.
+ * Use [up] in string interpolation to globally reset colors.
+ */
+class AnsiPen {
+
+  /**
+   * Treat a pen instance as a function such that pen("msg") is the same as
+   * pen.write("msg").
+   */
+  call(String msg) => write(msg);
+
+  /**
+   * Allow pen colors to be used in a string.
+   * Note: Once the pen is down, its attributes remain in effect till they are
+   * changed by another pen or [up].
+   */
+  String toString() {
+    if (color_disabled) return "";
+    if (_pen != null) return _pen;
+
+    StringBuffer sb = new StringBuffer();
+    if (_fcolor != null) {
+      sb.write("${ANSI_ESC}38;5;${_fcolor}m");
+    }
+
+    if (_bcolor != null) {
+      sb.write("${ANSI_ESC}48;5;${_bcolor}m");
+    }
+
+    _pen = sb.toString();
+    return _pen;
+  }
+
+  /**
+   * Return control codes to change the terminal colors.
+   */
+  String get down => this.toString();
+
+  /**
+   * Reset all pen attributes in the terminal.
+   */
+  String get up => color_disabled ? "" : ANSI_DEFAULT;
+
+  /**
+   * Write the [msg] with the pen's current settings and then reset all
+   * attributes.
+   */
+  String write(String msg) => "${this}$msg$up";
+
+  black({bool bg: false, bool bold: false})    => _std(0, bold, bg);
+  red({bool bg: false, bool bold: false})      => _std(1, bold, bg);
+  green({bool bg: false, bool bold: false})    => _std(2, bold, bg);
+  yellow({bool bg: false, bool bold: false})   => _std(3, bold, bg);
+  blue({bool bg: false, bool bold: false})     => _std(4, bold, bg);
+  magenta({bool bg: false, bool bold: false})  => _std(5, bold, bg);
+  cyan({bool bg: false, bool bold: false})     => _std(6, bold, bg);
+  white({bool bg: false, bool bold: false})    => _std(7, bold, bg);
+
+  /**
+   * Set the pen color to the rgb value between 0.0..1.0
+   */
+  rgb({r: 1.0, g: 1.0, b: 1.0, bool bg: false}) =>
+    xterm(
+        (r.clamp(0.0, 1.0) * 5).toInt() * 36 +
+        (g.clamp(0.0, 1.0) * 5).toInt() * 6 +
+        (b.clamp(0.0, 1.0) * 5).toInt() + 16,
+        bg: bg);
+
+  /**
+   * See the pen color to a grey scale value between 0.0 and 1.0
+   */
+  gray({level: 1.0, bool bg: false}) =>
+      xterm(232 + (level.clamp(0.0, 1.0) * 23).round(), bg: bg);
+
+  _std(int color, bool bold, bool bg) => xterm(color + (bold ? 8 : 0), bg: bg);
+
+  /**
+   * Directly index the xterm 256 color palette.
+   */
+  xterm(int color, {bool bg: false}) {
+    _pen = null;
+    var c = color.toInt().clamp(0, 256);
+    if (bg) {
+      _bcolor = c;
+    } else {
+      _fcolor = c;
+    }
+  }
+
+  /**
+   * Reset the pen's attributes.
+   */
+  reset() {
+    _pen = null;
+    _bcolor = _fcolor = null;
+  }
+
+  int _fcolor;
+  int _bcolor;
+  String _pen;
+}
+
+
+/**
+ *  ANSI Control Sequence Introducer, signals the terminal for new settings.
+ */
+String get ANSI_ESC => color_disabled ? "" : '\x1B[';
+
+
+/**
+ *  Reset all colors and options for current SGRs to terminal defaults.
+ */
+String get ANSI_DEFAULT => color_disabled ? "" : "${ANSI_ESC}0m";
+
+
+/**
+ * Defaults the terminal's foreground color without altering the background.
+ * Does not modify [AnsiPen]!
+ */
+String resetForeground() => "${ANSI_ESC}39m";
+
+
+/**
+ * Defaults the terminal's background color without altering the foreground.
+ * Does not modify [AnsiPen]!
+ */
+String resetBackground() => "${ANSI_ESC}49m";
+
+
+/**
+ * Due to missing sprintf(), this is my cheap "%03d".
+ */
+String _toSpace(int i, [int width = 3]) {
+  if (width <= 0 && i == 0) return "";
+  return "${_toSpace(i ~/ 10, --width)}${i % 10}";
+}
+
+/**
+ * Return a reference table for foreground and background colors.
+ */
+String ansi_demo() {
+  StringBuffer sb = new StringBuffer();
+
+  AnsiPen pen = new AnsiPen();
+
+  for (int c = 0; c < 16; c++) {
+    pen..reset()..white(bold: true)..xterm(c, bg: true);
+    sb.write(pen("${_toSpace(c)} "));
+    pen..reset()..xterm(c);
+    sb.write(pen(" ${_toSpace(c)} "));
+    if (c == 7 || c == 15) {
+      sb.write("\n");
+    }
+  }
+
+  for (int r = 0; r < 6; r++) {
+    sb.write("\n");
+    for (int g = 0; g < 6; g++) {
+      for (int b = 0; b < 6; b++) {
+        var c = r*36 + g*6 + b + 16;
+        pen..reset()..rgb(r: r / 5, g: g / 5, b: b / 5, bg: true)..
+            white(bold: true);
+        sb.write(pen(" ${_toSpace(c)} "));
+        pen..reset()..rgb(r: r / 5, g: g / 5, b: b / 5);
+        sb.write(pen(" ${_toSpace(c)} "));
+      }
+      sb.write("\n");
+    }
+  }
+
+  for (int c = 0; c < 24; c++) {
+    if (0 == c % 8) {
+      sb.write("\n");
+    }
+    pen..reset()..gray(level: c / 23, bg: true)..white(bold:true);
+    sb.write(pen(" ${_toSpace(c + 232)} "));
+    pen..reset()..gray(level: c / 23);
+    sb.write(pen(" ${_toSpace(c + 232)} "));
+  }
+  return sb.toString();
+}
diff --git a/pub/ansicolor/pubspec.yaml b/pub/ansicolor/pubspec.yaml
new file mode 100644
index 0000000..b2b4193
--- /dev/null
+++ b/pub/ansicolor/pubspec.yaml
@@ -0,0 +1,7 @@
+name: ansicolor
+version: 0.0.9
+author: John Thomas McDole <codefu@google.com>
+description: An xterm 256 color support library
+homepage: https://github.com/google/ansicolor-dart
+dev_dependencies:
+  unittest: ">=0.6.19"
diff --git a/pub/ansicolor/src/demo.dart b/pub/ansicolor/src/demo.dart
new file mode 100644
index 0000000..c562b2d
--- /dev/null
+++ b/pub/ansicolor/src/demo.dart
@@ -0,0 +1,5 @@
+import "../lib/ansicolor.dart";
+
+void main() {
+  print(ansi_demo());
+}
\ No newline at end of file
diff --git a/pub/bazel_worker/BUILD.gn b/pub/bazel_worker/BUILD.gn
index ba8a0b7..3724e96 100644
--- a/pub/bazel_worker/BUILD.gn
+++ b/pub/bazel_worker/BUILD.gn
@@ -1,4 +1,4 @@
-# This file is generated by importer.py for bazel_worker-0.1.3
+# This file is generated by importer.py for bazel_worker-0.1.4
 
 import("//build/dart/dart_package.gni")
 
diff --git a/pub/bazel_worker/CHANGELOG.md b/pub/bazel_worker/CHANGELOG.md
index e42904a..7878fb7 100644
--- a/pub/bazel_worker/CHANGELOG.md
+++ b/pub/bazel_worker/CHANGELOG.md
@@ -1,3 +1,11 @@
+## 0.1.4
+
+* Added `BazelWorkerDriver` class, which can be used to implement the bazel side
+  of the protocol. This allows you to speak to any process which knows the bazel
+  protocol from your own process.
+* Changed `WorkerConnection#readRequest` to return a `FutureOr<WorkRequest>`
+  instead of dynamic.
+
 ## 0.1.3
 
 * Add automatic intercepting of print calls and append them to
diff --git a/pub/bazel_worker/e2e_test/lib/async_worker.dart b/pub/bazel_worker/e2e_test/lib/async_worker.dart
new file mode 100644
index 0000000..b8239d3
--- /dev/null
+++ b/pub/bazel_worker/e2e_test/lib/async_worker.dart
@@ -0,0 +1,17 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:bazel_worker/bazel_worker.dart';
+
+/// Example worker that just returns in its response all the arguments passed
+/// separated by newlines.
+class ExampleAsyncWorker extends AsyncWorkerLoop {
+  Future<WorkResponse> performRequest(WorkRequest request) async {
+    return new WorkResponse()
+      ..exitCode = 0
+      ..output = request.arguments.join('\n');
+  }
+}
diff --git a/pub/bazel_worker/e2e_test/lib/sync_worker.dart b/pub/bazel_worker/e2e_test/lib/sync_worker.dart
new file mode 100644
index 0000000..7c98dbe
--- /dev/null
+++ b/pub/bazel_worker/e2e_test/lib/sync_worker.dart
@@ -0,0 +1,15 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:bazel_worker/bazel_worker.dart';
+
+/// Example worker that just returns in its response all the arguments passed
+/// separated by newlines.
+class ExampleSyncWorker extends SyncWorkerLoop {
+  WorkResponse performRequest(WorkRequest request) {
+    return new WorkResponse()
+      ..exitCode = 0
+      ..output = request.arguments.join('\n');
+  }
+}
diff --git a/pub/bazel_worker/e2e_test/pubspec.yaml b/pub/bazel_worker/e2e_test/pubspec.yaml
new file mode 100644
index 0000000..9ecc48c
--- /dev/null
+++ b/pub/bazel_worker/e2e_test/pubspec.yaml
@@ -0,0 +1,8 @@
+name: e2e_test
+dependencies:
+  bazel_worker:
+    path: ../
+dev_dependencies:
+  cli_util: ^0.0.1
+  path: ^1.4.1
+  test: ^0.12.0
diff --git a/pub/bazel_worker/e2e_test/test/e2e_test.dart b/pub/bazel_worker/e2e_test/test/e2e_test.dart
new file mode 100644
index 0000000..2879219
--- /dev/null
+++ b/pub/bazel_worker/e2e_test/test/e2e_test.dart
@@ -0,0 +1,60 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:io';
+
+import 'package:bazel_worker/driver.dart';
+import 'package:cli_util/cli_util.dart';
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+
+void main() {
+  var sdkDir = getSdkDir();
+  var dart = p.join(sdkDir.path, 'bin', 'dart');
+  runE2eTestForWorker('sync worker',
+      () => Process.start(dart, [p.join('bin', 'sync_worker.dart')]));
+  runE2eTestForWorker('async worker',
+      () => Process.start(dart, [p.join('bin', 'async_worker.dart')]));
+}
+
+void runE2eTestForWorker(String groupName, SpawnWorker spawnWorker) {
+  BazelWorkerDriver driver;
+  group(groupName, () {
+    setUp(() {
+      driver = new BazelWorkerDriver(spawnWorker);
+    });
+
+    tearDown(() async {
+      await driver.terminateWorkers();
+    });
+
+    test('single work request', () async {
+      await _doRequests(driver, count: 1);
+    });
+
+    test('lots of requests', () async {
+      await _doRequests(driver, count: 1000);
+    });
+  });
+}
+
+/// Runs [count] work requests through [driver], and asserts that they all
+/// completed with the correct response.
+Future _doRequests(BazelWorkerDriver driver, {int count}) async {
+  count ??= 100;
+  var requests = new List.generate(count, (requestNum) {
+    var request = new WorkRequest();
+    request.arguments
+        .addAll(new List.generate(requestNum, (argNum) => '$argNum'));
+    return request;
+  });
+  var responses = await Future.wait(requests.map(driver.doWork));
+  for (int i = 0; i < responses.length; i++) {
+    var request = requests[i];
+    var response = responses[i];
+    expect(response.exitCode, EXIT_CODE_OK);
+    expect(response.output, request.arguments.join('\n'));
+  }
+}
diff --git a/pub/bazel_worker/lib/bazel_worker.dart b/pub/bazel_worker/lib/bazel_worker.dart
index 8d42fb6..0b00408 100644
--- a/pub/bazel_worker/lib/bazel_worker.dart
+++ b/pub/bazel_worker/lib/bazel_worker.dart
@@ -2,10 +2,10 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-export 'src/async_worker_loop.dart';
+export 'src/worker/async_worker_loop.dart';
 export 'src/constants.dart';
 export 'src/message_grouper.dart';
-export 'src/sync_worker_loop.dart';
-export 'src/worker_connection.dart';
-export 'src/worker_loop.dart';
+export 'src/worker/sync_worker_loop.dart';
+export 'src/worker/worker_connection.dart';
+export 'src/worker/worker_loop.dart';
 export 'src/worker_protocol.pb.dart';
diff --git a/pub/bazel_worker/lib/driver.dart b/pub/bazel_worker/lib/driver.dart
new file mode 100644
index 0000000..7453491
--- /dev/null
+++ b/pub/bazel_worker/lib/driver.dart
@@ -0,0 +1,9 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+export 'src/driver/driver.dart';
+export 'src/driver/driver_connection.dart';
+export 'src/constants.dart';
+export 'src/message_grouper.dart';
+export 'src/worker_protocol.pb.dart';
diff --git a/pub/bazel_worker/lib/src/async_worker_loop.dart b/pub/bazel_worker/lib/src/async_worker_loop.dart
deleted file mode 100644
index bf6c893..0000000
--- a/pub/bazel_worker/lib/src/async_worker_loop.dart
+++ /dev/null
@@ -1,90 +0,0 @@
-// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-import 'dart:async';
-import 'dart:io';
-
-import 'constants.dart';
-import 'async_message_grouper.dart';
-import 'utils.dart';
-import 'worker_connection.dart';
-import 'worker_loop.dart';
-import 'worker_protocol.pb.dart';
-
-/// Connection between a worker and input / output.
-abstract class AsyncWorkerConnection implements WorkerConnection {
-  /// Read a new [WorkRequest]. Returns [null] when there are no more requests.
-  Future<WorkRequest> readRequest();
-
-  /// Write the given [response] as bytes to the output.
-  void writeResponse(WorkResponse response);
-}
-
-/// Persistent Bazel worker loop.
-///
-/// Extend this class and implement the `performRequest` method.
-abstract class AsyncWorkerLoop implements WorkerLoop {
-  final AsyncWorkerConnection connection;
-
-  AsyncWorkerLoop({AsyncWorkerConnection connection})
-      : this.connection = connection ?? new StdAsyncWorkerConnection();
-
-  /// Perform a single [WorkRequest], and return a [WorkResponse].
-  Future<WorkResponse> performRequest(WorkRequest request);
-
-  /// Run the worker loop. The returned [Future] doesn't complete until
-  /// [connection#readRequest] returns `null`.
-  Future run() async {
-    while (true) {
-      WorkResponse response;
-      try {
-        var request = await connection.readRequest();
-        if (request == null) break;
-        var printMessages = new StringBuffer();
-        response = await runZoned(() => performRequest(request),
-            zoneSpecification:
-                new ZoneSpecification(print: (self, parent, zone, message) {
-          printMessages.writeln();
-          printMessages.write(message);
-        }));
-        if (printMessages.isNotEmpty) {
-          response.output = '${response.output}$printMessages';
-        }
-        // In case they forget to set this.
-        response.exitCode ??= EXIT_CODE_OK;
-      } catch (e, s) {
-        response = new WorkResponse()
-          ..exitCode = EXIT_CODE_ERROR
-          ..output = '$e\n$s';
-      }
-
-      connection.writeResponse(response);
-    }
-  }
-}
-
-/// Default implementation of [AsyncWorkerConnection] that works with [Stdin]
-/// and [Stdout].
-class StdAsyncWorkerConnection implements AsyncWorkerConnection {
-  final AsyncMessageGrouper _messageGrouper;
-  final StreamSink<List<int>> _outputStream;
-
-  StdAsyncWorkerConnection(
-      {Stream<List<int>> inputStream, StreamSink<List<int>> outputStream})
-      : _messageGrouper = new AsyncMessageGrouper(inputStream ?? stdin),
-        _outputStream = outputStream ?? stdout;
-
-  @override
-  Future<WorkRequest> readRequest() async {
-    var buffer = await _messageGrouper.next;
-    if (buffer == null) return null;
-
-    return new WorkRequest.fromBuffer(buffer);
-  }
-
-  @override
-  void writeResponse(WorkResponse response) {
-    _outputStream.add(protoToDelimitedBuffer(response));
-  }
-}
diff --git a/pub/bazel_worker/lib/src/driver/driver.dart b/pub/bazel_worker/lib/src/driver/driver.dart
new file mode 100644
index 0000000..9d0bb85
--- /dev/null
+++ b/pub/bazel_worker/lib/src/driver/driver.dart
@@ -0,0 +1,153 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:collection';
+import 'dart:io';
+
+import '../constants.dart';
+import '../worker_protocol.pb.dart';
+import 'driver_connection.dart';
+
+typedef Future<Process> SpawnWorker();
+
+/// A driver for talking to a bazel worker.
+///
+/// This allows you to use any binary that supports the bazel worker protocol in
+/// the same way that bazel would, but from another dart process instead.
+class BazelWorkerDriver {
+  /// The maximum number of idle workers at any given time.
+  final int _maxIdleWorkers;
+
+  /// The maximum number of concurrent workers to run at any given time.
+  final int _maxWorkers;
+
+  /// The number of currently active workers.
+  int get _numWorkers => _readyWorkers.length + _spawningWorkers.length;
+
+  /// Idle worker processes.
+  final _idleWorkers = <Process>[];
+
+  /// All workers that are fully spawned and ready to handle work.
+  final _readyWorkers = <Process>[];
+
+  /// All workers that are in the process of being spawned.
+  final _spawningWorkers = <Future<Process>>[];
+
+  /// Work requests that haven't been started yet.
+  final _workQueue = new Queue<WorkRequest>();
+
+  /// Factory method that spawns a worker process.
+  final SpawnWorker _spawnWorker;
+
+  BazelWorkerDriver(this._spawnWorker, {int maxIdleWorkers, int maxWorkers})
+      : this._maxIdleWorkers = maxIdleWorkers ?? 4,
+        this._maxWorkers = maxWorkers ?? 4;
+
+  Future<WorkResponse> doWork(WorkRequest request) {
+    var responseCompleter = new Completer<WorkResponse>();
+    _responseCompleters[request] = responseCompleter;
+    _workQueue.add(request);
+    _runWorkQueue();
+    return responseCompleter.future;
+  }
+
+  /// Calls `kill` on all worker processes.
+  Future terminateWorkers() async {
+    for (var worker in _readyWorkers) {
+      worker.kill();
+    }
+    await Future.wait(_spawningWorkers.map((worker) async {
+      (await worker).kill();
+    }));
+  }
+
+  /// Runs as many items in [_workQueue] as possible given the number of
+  /// available workers.
+  ///
+  /// Will spawn additional workers until [_maxWorkers] has been reached.
+  ///
+  /// This method synchronously drains the [_workQueue] and [_idleWorkers], but
+  /// some tasks may not actually start right away if they need to wait for a
+  /// worker to spin up.
+  void _runWorkQueue() {
+    // Bail out conditions, we will continue to call ourselves indefinitely
+    // until one of these is met.
+    if (_workQueue.isEmpty) return;
+    if (_numWorkers == _maxWorkers && _idleWorkers.isEmpty) return;
+    if (_numWorkers > _maxWorkers) {
+      throw new StateError('Internal error, created to many workers. Please '
+          'file a bug at https://github.com/dart-lang/bazel_worker/issues/new');
+    }
+
+    // At this point we definitely want to run a task, we just need to decide
+    // whether or not we need to start up a new worker.
+    var request = _workQueue.removeFirst();
+    if (_idleWorkers.isNotEmpty) {
+      _runWorker(_idleWorkers.removeLast(), request);
+    } else {
+      // No need to block here, we want to continue to synchronously drain the
+      // work queue.
+      var futureWorker = _spawnWorker();
+      _spawningWorkers.add(futureWorker);
+      futureWorker.then((worker) {
+        _spawningWorkers.remove(futureWorker);
+        _readyWorkers.add(worker);
+
+        _workerConnections[worker] = new StdDriverConnection.forWorker(worker);
+        _runWorker(worker, request);
+
+        // When the worker exits we should retry running the work queue in case
+        // there is more work to be done. This is primarily just a defensive
+        // thing but is cheap to do.
+        worker.exitCode.then((_) {
+          _readyWorkers.remove(worker);
+          _runWorkQueue();
+        });
+      });
+    }
+    // Recursively calls itself until one of the bail out conditions are met.
+    _runWorkQueue();
+  }
+
+  /// Sends [request] to [worker].
+  ///
+  /// Once the worker responds then it will be added back to the pool of idle
+  /// workers.
+  Future _runWorker(Process worker, WorkRequest request) async {
+    try {
+      var connection = _workerConnections[worker];
+      connection.writeRequest(request);
+      var response = await connection.readResponse();
+      _responseCompleters[request].complete(response);
+
+      // Do additional work if available.
+      _idleWorkers.add(worker);
+      _runWorkQueue();
+
+      // If the worker wasn't immediately used we might have to many idle
+      // workers now, kill one if necessary.
+      if (_idleWorkers.length > _maxIdleWorkers) {
+        // Note that whenever we spawn a worker we listen for its exit code
+        // and clean it up so we don't need to do that here.
+        var worker = _idleWorkers.removeLast();
+        _readyWorkers.remove(worker);
+        worker.kill();
+      }
+    } catch (e, s) {
+      // Note that we don't need to do additional cleanup here on failures. If
+      // the worker dies that is already handled in a generic fashion, we just
+      // need to make sure we complete with a valid response.
+      if (!_responseCompleters[request].isCompleted) {
+        var response = new WorkResponse()
+          ..exitCode = EXIT_CODE_ERROR
+          ..output = 'Error running worker:\n$e\n$s';
+        _responseCompleters[request].complete(response);
+      }
+    }
+  }
+}
+
+final _responseCompleters = new Expando<Completer<WorkResponse>>('response');
+final _workerConnections = new Expando<DriverConnection>('connectin');
diff --git a/pub/bazel_worker/lib/src/driver/driver_connection.dart b/pub/bazel_worker/lib/src/driver/driver_connection.dart
new file mode 100644
index 0000000..b170586
--- /dev/null
+++ b/pub/bazel_worker/lib/src/driver/driver_connection.dart
@@ -0,0 +1,75 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import '../async_message_grouper.dart';
+import '../worker_protocol.pb.dart';
+import '../constants.dart';
+import '../utils.dart';
+
+/// A connection from a `BazelWorkerDriver` to a worker.
+///
+/// Unlike `WorkerConnection` there is no synchronous version of this class.
+/// This is because drivers talk to multiple workers, so they should never block
+/// when waiting for the response of any individual worker.
+abstract class DriverConnection {
+  Future<WorkResponse> readResponse();
+
+  void writeRequest(WorkRequest request);
+}
+
+/// Default implementation of [DriverConnection] that works with [Stdin]
+/// and [Stdout].
+class StdDriverConnection implements DriverConnection {
+  final AsyncMessageGrouper _messageGrouper;
+  final StreamSink<List<int>> _outputStream;
+
+  StdDriverConnection(
+      {Stream<List<int>> inputStream, StreamSink<List<int>> outputStream})
+      : _messageGrouper = new AsyncMessageGrouper(inputStream ?? stdin),
+        _outputStream = outputStream ?? stdout;
+
+  factory StdDriverConnection.forWorker(Process worker) =>
+      new StdDriverConnection(
+          inputStream: worker.stdout, outputStream: worker.stdin);
+
+  /// Note: This will attempts to recover from invalid proto messages by parsing
+  /// them as strings. This is a common error case for workers (they print a
+  /// message to stdout on accident). This isn't perfect however as it only
+  /// happens if the parsing throws, you can still hang indefinitely if the
+  /// [MessageGrouper] doesn't find what it thinks is the end of a proto
+  /// message.
+  @override
+  Future<WorkResponse> readResponse() async {
+    var buffer = await _messageGrouper.next;
+    if (buffer == null) return null;
+
+    WorkResponse response;
+    try {
+      response = new WorkResponse.fromBuffer(buffer);
+    } catch (_) {
+      try {
+        // Try parsing the message as a string and set that as the output.
+        var output = UTF8.decode(buffer);
+        var response = new WorkResponse()
+          ..exitCode = EXIT_CODE_ERROR
+          ..output = 'Worker sent an invalid response:\n$output';
+        return response;
+      } catch (_) {
+        // Fall back to original exception and rethrow if we fail to parse as
+        // a string.
+      }
+      rethrow;
+    }
+    return response;
+  }
+
+  @override
+  void writeRequest(WorkRequest request) {
+    _outputStream.add(protoToDelimitedBuffer(request));
+  }
+}
diff --git a/pub/bazel_worker/lib/src/sync_worker_loop.dart b/pub/bazel_worker/lib/src/sync_worker_loop.dart
deleted file mode 100644
index a3528a5..0000000
--- a/pub/bazel_worker/lib/src/sync_worker_loop.dart
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-import 'dart:async';
-import 'dart:io';
-
-import 'constants.dart';
-import 'sync_message_grouper.dart';
-import 'utils.dart';
-import 'worker_connection.dart';
-import 'worker_loop.dart';
-import 'worker_protocol.pb.dart';
-
-/// Connection between a worker and input / output.
-abstract class SyncWorkerConnection implements WorkerConnection {
-  /// Read a new [WorkRequest]. Returns [null] when there are no more requests.
-  WorkRequest readRequest();
-
-  /// Write the given [response] as bytes to the output.
-  void writeResponse(WorkResponse response);
-}
-
-/// Persistent Bazel worker loop.
-///
-/// Extend this class and implement the `performRequest` method.
-abstract class SyncWorkerLoop implements WorkerLoop {
-  final SyncWorkerConnection connection;
-
-  SyncWorkerLoop({SyncWorkerConnection connection})
-      : this.connection = connection ?? new StdSyncWorkerConnection();
-
-  /// Perform a single [WorkRequest], and return a [WorkResponse].
-  WorkResponse performRequest(WorkRequest request);
-
-  /// Run the worker loop. Blocks until [connection#readRequest] returns `null`.
-  void run() {
-    while (true) {
-      WorkResponse response;
-      try {
-        var request = connection.readRequest();
-        if (request == null) break;
-        var printMessages = new StringBuffer();
-        response = runZoned(() => performRequest(request), zoneSpecification:
-            new ZoneSpecification(print: (self, parent, zone, message) {
-          printMessages.writeln();
-          printMessages.write(message);
-        }));
-        if (printMessages.isNotEmpty) {
-          response.output = '${response.output}$printMessages';
-        }
-        // In case they forget to set this.
-        response.exitCode ??= EXIT_CODE_OK;
-      } catch (e, s) {
-        response = new WorkResponse()
-          ..exitCode = EXIT_CODE_ERROR
-          ..output = '$e\n$s';
-      }
-
-      connection.writeResponse(response);
-    }
-  }
-}
-
-/// Default implementation of [SyncWorkerConnection] that works with [Stdin] and
-/// [Stdout].
-class StdSyncWorkerConnection implements SyncWorkerConnection {
-  final SyncMessageGrouper _messageGrouper;
-  final Stdout _stdoutStream;
-
-  StdSyncWorkerConnection({Stdin stdinStream, Stdout stdoutStream})
-      : _messageGrouper = new SyncMessageGrouper(stdinStream ?? stdin),
-        _stdoutStream = stdoutStream ?? stdout;
-
-  @override
-  WorkRequest readRequest() {
-    var buffer = _messageGrouper.next;
-    if (buffer == null) return null;
-
-    return new WorkRequest.fromBuffer(buffer);
-  }
-
-  @override
-  void writeResponse(WorkResponse response) {
-    _stdoutStream.add(protoToDelimitedBuffer(response));
-  }
-}
diff --git a/pub/bazel_worker/lib/src/worker/async_worker_loop.dart b/pub/bazel_worker/lib/src/worker/async_worker_loop.dart
new file mode 100644
index 0000000..29f6ff8
--- /dev/null
+++ b/pub/bazel_worker/lib/src/worker/async_worker_loop.dart
@@ -0,0 +1,53 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import '../constants.dart';
+import '../worker_protocol.pb.dart';
+import 'worker_connection.dart';
+import 'worker_loop.dart';
+
+/// Persistent Bazel worker loop.
+///
+/// Extend this class and implement the `performRequest` method.
+abstract class AsyncWorkerLoop implements WorkerLoop {
+  final AsyncWorkerConnection connection;
+
+  AsyncWorkerLoop({AsyncWorkerConnection connection})
+      : this.connection = connection ?? new StdAsyncWorkerConnection();
+
+  /// Perform a single [WorkRequest], and return a [WorkResponse].
+  Future<WorkResponse> performRequest(WorkRequest request);
+
+  /// Run the worker loop. The returned [Future] doesn't complete until
+  /// [connection#readRequest] returns `null`.
+  Future run() async {
+    while (true) {
+      WorkResponse response;
+      try {
+        var request = await connection.readRequest();
+        if (request == null) break;
+        var printMessages = new StringBuffer();
+        response = await runZoned(() => performRequest(request),
+            zoneSpecification:
+                new ZoneSpecification(print: (self, parent, zone, message) {
+          printMessages.writeln();
+          printMessages.write(message);
+        }));
+        if (printMessages.isNotEmpty) {
+          response.output = '${response.output}$printMessages';
+        }
+        // In case they forget to set this.
+        response.exitCode ??= EXIT_CODE_OK;
+      } catch (e, s) {
+        response = new WorkResponse()
+          ..exitCode = EXIT_CODE_ERROR
+          ..output = '$e\n$s';
+      }
+
+      connection.writeResponse(response);
+    }
+  }
+}
diff --git a/pub/bazel_worker/lib/src/worker/sync_worker_loop.dart b/pub/bazel_worker/lib/src/worker/sync_worker_loop.dart
new file mode 100644
index 0000000..e1b9aaf
--- /dev/null
+++ b/pub/bazel_worker/lib/src/worker/sync_worker_loop.dart
@@ -0,0 +1,50 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+import 'dart:async';
+
+import '../constants.dart';
+import '../worker_protocol.pb.dart';
+import 'worker_connection.dart';
+import 'worker_loop.dart';
+
+/// Persistent Bazel worker loop.
+///
+/// Extend this class and implement the `performRequest` method.
+abstract class SyncWorkerLoop implements WorkerLoop {
+  final SyncWorkerConnection connection;
+
+  SyncWorkerLoop({SyncWorkerConnection connection})
+      : this.connection = connection ?? new StdSyncWorkerConnection();
+
+  /// Perform a single [WorkRequest], and return a [WorkResponse].
+  WorkResponse performRequest(WorkRequest request);
+
+  /// Run the worker loop. Blocks until [connection#readRequest] returns `null`.
+  void run() {
+    while (true) {
+      WorkResponse response;
+      try {
+        var request = connection.readRequest();
+        if (request == null) break;
+        var printMessages = new StringBuffer();
+        response = runZoned(() => performRequest(request), zoneSpecification:
+            new ZoneSpecification(print: (self, parent, zone, message) {
+          printMessages.writeln();
+          printMessages.write(message);
+        }));
+        if (printMessages.isNotEmpty) {
+          response.output = '${response.output}$printMessages';
+        }
+        // In case they forget to set this.
+        response.exitCode ??= EXIT_CODE_OK;
+      } catch (e, s) {
+        response = new WorkResponse()
+          ..exitCode = EXIT_CODE_ERROR
+          ..output = '$e\n$s';
+      }
+
+      connection.writeResponse(response);
+    }
+  }
+}
diff --git a/pub/bazel_worker/lib/src/worker/worker_connection.dart b/pub/bazel_worker/lib/src/worker/worker_connection.dart
new file mode 100644
index 0000000..7fdd4e7
--- /dev/null
+++ b/pub/bazel_worker/lib/src/worker/worker_connection.dart
@@ -0,0 +1,82 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:io';
+
+import '../async_message_grouper.dart';
+import '../sync_message_grouper.dart';
+import '../utils.dart';
+import '../worker_protocol.pb.dart';
+
+/// A connection from a worker to a driver (driver could be bazel, a dart
+/// program using `BazelWorkerDriver`, or any other process that speaks the
+/// protocol).
+abstract class WorkerConnection {
+  /// Reads a [WorkRequest] or returns [null] if there are none left.
+  ///
+  /// See [AsyncWorkerConnection] and [SyncWorkerConnection] for more narrow
+  /// interfaces.
+  FutureOr<WorkRequest> readRequest();
+
+  void writeResponse(WorkResponse response);
+}
+
+abstract class AsyncWorkerConnection implements WorkerConnection {
+  @override
+  Future<WorkRequest> readRequest();
+}
+
+abstract class SyncWorkerConnection implements WorkerConnection {
+  WorkRequest readRequest();
+}
+
+/// Default implementation of [AsyncWorkerConnection] that works with [Stdin]
+/// and [Stdout].
+class StdAsyncWorkerConnection implements AsyncWorkerConnection {
+  final AsyncMessageGrouper _messageGrouper;
+  final StreamSink<List<int>> _outputStream;
+
+  StdAsyncWorkerConnection(
+      {Stream<List<int>> inputStream, StreamSink<List<int>> outputStream})
+      : _messageGrouper = new AsyncMessageGrouper(inputStream ?? stdin),
+        _outputStream = outputStream ?? stdout;
+
+  @override
+  Future<WorkRequest> readRequest() async {
+    var buffer = await _messageGrouper.next;
+    if (buffer == null) return null;
+
+    return new WorkRequest.fromBuffer(buffer);
+  }
+
+  @override
+  void writeResponse(WorkResponse response) {
+    _outputStream.add(protoToDelimitedBuffer(response));
+  }
+}
+
+/// Default implementation of [SyncWorkerConnection] that works with [Stdin] and
+/// [Stdout].
+class StdSyncWorkerConnection implements SyncWorkerConnection {
+  final SyncMessageGrouper _messageGrouper;
+  final Stdout _stdoutStream;
+
+  StdSyncWorkerConnection({Stdin stdinStream, Stdout stdoutStream})
+      : _messageGrouper = new SyncMessageGrouper(stdinStream ?? stdin),
+        _stdoutStream = stdoutStream ?? stdout;
+
+  @override
+  WorkRequest readRequest() {
+    var buffer = _messageGrouper.next;
+    if (buffer == null) return null;
+
+    return new WorkRequest.fromBuffer(buffer);
+  }
+
+  @override
+  void writeResponse(WorkResponse response) {
+    _stdoutStream.add(protoToDelimitedBuffer(response));
+  }
+}
diff --git a/pub/bazel_worker/lib/src/worker_loop.dart b/pub/bazel_worker/lib/src/worker/worker_loop.dart
similarity index 94%
rename from pub/bazel_worker/lib/src/worker_loop.dart
rename to pub/bazel_worker/lib/src/worker/worker_loop.dart
index 8eb7008..2e4a023 100644
--- a/pub/bazel_worker/lib/src/worker_loop.dart
+++ b/pub/bazel_worker/lib/src/worker/worker_loop.dart
@@ -2,7 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-import 'worker_protocol.pb.dart';
+import '../worker_protocol.pb.dart';
 
 /// Interface for a [WorkerLoop].
 ///
diff --git a/pub/bazel_worker/lib/src/worker_connection.dart b/pub/bazel_worker/lib/src/worker_connection.dart
deleted file mode 100644
index c39bc3f..0000000
--- a/pub/bazel_worker/lib/src/worker_connection.dart
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-import 'worker_protocol.pb.dart';
-
-/// Interface for a [WorkerConnection].
-///
-/// Use [SyncWorkerConnection] or [AsyncWorkerConnection] implementations.
-abstract class WorkerConnection {
-  /// Read a [WorkRequest]. Returns either [Future<WorkRequest>] or
-  /// [WorkRequest].
-  dynamic readRequest();
-
-  /// Writes a [WorkResponse].
-  void writeResponse(WorkResponse response);
-}
diff --git a/pub/bazel_worker/pubspec.yaml b/pub/bazel_worker/pubspec.yaml
index e6ca01b..9f88507 100644
--- a/pub/bazel_worker/pubspec.yaml
+++ b/pub/bazel_worker/pubspec.yaml
@@ -1,11 +1,11 @@
 name: bazel_worker
-version: 0.1.3
+version: 0.1.4
 description: Tools for creating a bazel persistent worker.
 author: Dart Team <misc@dartlang.org>
 homepage: https://github.com/dart-lang/bazel_worker
 
 environment:
-  sdk: '>=1.8.0 <2.0.0'
+  sdk: '>=1.22.1 <2.0.0'
 
 dependencies:
   async: ^1.9.0
diff --git a/pub/bazel_worker/tool/travis.sh b/pub/bazel_worker/tool/travis.sh
old mode 100644
new mode 100755
index 6b5e340..a011957
--- a/pub/bazel_worker/tool/travis.sh
+++ b/pub/bazel_worker/tool/travis.sh
@@ -10,7 +10,15 @@
 # Verify that the libraries are error free.
 dartanalyzer --fatal-warnings \
   lib/bazel_worker.dart \
+  lib/driver.dart \
+  lib/testing.dart \
   test/test_all.dart
 
 # Run the tests.
 pub run test
+
+pushd e2e_test
+pub get
+dartanalyzer --fatal-warnings test/e2e_test.dart
+pub run test
+popd
diff --git a/pub/browser/BUILD.gn b/pub/browser/BUILD.gn
new file mode 100644
index 0000000..b0e8833
--- /dev/null
+++ b/pub/browser/BUILD.gn
@@ -0,0 +1,14 @@
+# This file is generated by importer.py for browser-0.10.0+2
+
+import("//build/dart/dart_package.gni")
+
+dart_package("browser") {
+  package_name = "browser"
+
+  source_dir = "lib"
+
+  disable_analysis = true
+
+  deps = [
+  ]
+}
diff --git a/pub/browser/LICENSE b/pub/browser/LICENSE
new file mode 100644
index 0000000..5c60afe
--- /dev/null
+++ b/pub/browser/LICENSE
@@ -0,0 +1,26 @@
+Copyright 2014, the Dart project authors. 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.
+    * Neither the name of Google Inc. nor the names of its
+      contributors may 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 THE COPYRIGHT
+OWNER OR CONTRIBUTORS 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.
diff --git a/pub/browser/README.md b/pub/browser/README.md
new file mode 100644
index 0000000..fa96851
--- /dev/null
+++ b/pub/browser/README.md
@@ -0,0 +1,39 @@
+This package contains dart.js, and previously contained interop.js
+
+dart.js
+=======
+
+The dart.js file is used in Dart browser apps to check for native Dart support
+and either (a) bootstrap Dartium or (b) load compiled JS instead.  Previously,
+we've recommended that you add a script tag pointing the version of dart.js in
+our repository.  This doesn't work offline and also results in slower startup
+(see [dartbug.com/6723](http://dartbug.com/6723)).
+
+Instead, we now recommend that you install dart.js via the following steps:
+
+1. Add the following to your pubspec.yaml:
+  dependencies:
+    browser: any
+
+2. Run pub install.
+
+3. Use a relative script tag in your html to the installed version:
+
+    `<script src="packages/browser/dart.js"></script>`
+
+If you do not wish to use pub, you may host a copy of this file locally instead.
+In this case, you will need to update it yourself as necessary.  We reserve the
+right to move the old file in the repository, so we no longer recommend linking
+to it directly.
+
+interop.js
+==========
+
+This script was required for dart:js interop to work, but it is no longer
+needed. The functionality is now supported by dart:js directly.
+
+If you previously had a script such as this, please remove it:
+
+```html
+<script src="packages/browser/interop.js"></script>
+```
diff --git a/pub/browser/pubspec.yaml b/pub/browser/pubspec.yaml
new file mode 100644
index 0000000..3bf88f1
--- /dev/null
+++ b/pub/browser/pubspec.yaml
@@ -0,0 +1,8 @@
+name: browser
+version: 0.10.0+2
+author: "Dart Team <misc@dartlang.org>"
+homepage: http://www.dartlang.org
+description: >
+ The bootstrap dart.js script for Dart apps running in the browser.
+environment:
+  sdk: ">=1.3.0-dev.4.1 <2.0.0"
diff --git a/pub/charted/.gitignore b/pub/charted/.gitignore
new file mode 100644
index 0000000..9134752
--- /dev/null
+++ b/pub/charted/.gitignore
@@ -0,0 +1,6 @@
+build/
+*.swp
+packages
+.pub
+pubspec.lock
+.idea
diff --git a/pub/charted/BUILD.gn b/pub/charted/BUILD.gn
new file mode 100644
index 0000000..688ef12
--- /dev/null
+++ b/pub/charted/BUILD.gn
@@ -0,0 +1,19 @@
+# This file is generated by importer.py for charted-0.0.10
+
+import("//build/dart/dart_package.gni")
+
+dart_package("charted") {
+  package_name = "charted"
+
+  source_dir = "lib"
+
+  disable_analysis = true
+
+  deps = [
+    "//third_party/dart-pkg/pub/intl",
+    "//third_party/dart-pkg/pub/observe",
+    "//third_party/dart-pkg/pub/csslib",
+    "//third_party/dart-pkg/pub/logging",
+    "//third_party/dart-pkg/pub/browser",
+  ]
+}
diff --git a/pub/charted/LICENSE b/pub/charted/LICENSE
new file mode 100644
index 0000000..4b585f7
--- /dev/null
+++ b/pub/charted/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2014, Michael Bostock and Google Inc.
+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.
+
+* Neither the name of Google Inc. nor the names of its
+  contributors may 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.
diff --git a/pub/charted/README.md b/pub/charted/README.md
new file mode 100644
index 0000000..5493d50
--- /dev/null
+++ b/pub/charted/README.md
@@ -0,0 +1,8 @@
+Visualization toolkit for Dart
+==============================
+[![Build Status](https://drone.io/github.com/google/charted/status.png)](https://drone.io/github.com/google/charted/latest)
+
+Charted provides
+* A selection API similar to D3.js
+* Visualization utilities ported from D3.js
+* An easy-to-use API to create charts including an SVG implementation based on the Selection API.
diff --git a/pub/charted/lib/charted.dart b/pub/charted/lib/charted.dart
new file mode 100644
index 0000000..c706efb
--- /dev/null
+++ b/pub/charted/lib/charted.dart
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+/**
+ * A library to create visualizations - implementation/port of D3.js in Dart.
+ */
+library charted;
+
+export 'charts/charts.dart';
+export 'core/core.dart';
+export 'event/event.dart';
+export 'interpolators/interpolators.dart';
+export 'layout/layout.dart';
+export 'locale/locale.dart';
+export 'scale/scale.dart';
+export 'selection/selection.dart';
+export 'transition/transition.dart';
+export 'svg/svg.dart';
diff --git a/pub/charted/lib/charts/behaviors/axis_marker.dart b/pub/charted/lib/charts/behaviors/axis_marker.dart
new file mode 100644
index 0000000..0ea0df6
--- /dev/null
+++ b/pub/charted/lib/charts/behaviors/axis_marker.dart
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+part of charted.charts;
+
+class AxisMarker implements ChartBehavior {
+  ChartArea _area;
+  Rect _rect;
+
+  Element _markerX;
+  Element _markerY;
+
+  bool _showMarkerX = true;
+  bool _showMarkerY = true;
+  bool _showing;
+
+  Element _lower;
+  Element _upper;
+
+  StreamSubscription _mouseMoveSubscription;
+  StreamSubscription _mouseInSubscription;
+  StreamSubscription _mouseOutSubscription;
+
+  void init(ChartArea area, Element upper, Element lower) {
+    _area = area;
+    _lower = lower;
+    _upper = upper;
+
+    if (_area.dimensionAxesCount != 0) {
+      _mouseInSubscription = _area.onMouseOver.listen(_show);
+      _mouseOutSubscription = _area.onMouseOut.listen(_hide);
+    }
+  }
+
+  void dispose() {
+    if (_mouseInSubscription != null) _mouseInSubscription.cancel();
+    if (_mouseOutSubscription != null) _mouseOutSubscription.cancel();
+    if (_mouseMoveSubscription != null) _mouseOutSubscription.cancel();
+    if (_markerX != null) _markerX.remove();
+    if (_markerY != null) _markerY.remove();
+  }
+
+  void _show(ChartEvent e) {
+    if (_mouseMoveSubscription != null) return;
+    _create();
+    _visibility(true);
+    _mouseMoveSubscription = _area.onMouseMove.listen(_update);
+  }
+
+  void _hide(ChartEvent e) {
+    if (_showing != true) return;
+    _visibility(false);
+    _mouseMoveSubscription.cancel();
+    _mouseMoveSubscription = null;
+  }
+
+  void _visibility(bool show) {
+    if (_showing == show) return;
+    var value = show ? 'visible' : 'hidden';
+    if (_markerX != null) {
+      _markerX.style.visibility = value;
+    }
+    if (_markerY != null) {
+      _markerY.style.visibility = value;
+    }
+  }
+
+  bool _isRenderArea(ChartEvent e) =>
+      _rect != null && _rect.contains(e.chartX, e.chartY);
+
+  void _create() {
+    if (_rect == null) {
+      var renderArea = _area.layout.renderArea;
+      _rect = new Rect(
+          renderArea.x, renderArea.y, renderArea.width, renderArea.height);
+    }
+    if (_showMarkerX && _markerX == null) {
+      _markerX = new LineElement();
+      _markerX.attributes
+          ..['x1'] = '0'
+          ..['y1'] = _rect.y.toString()
+          ..['x2'] = '0'
+          ..['y2'] = (_rect.y + _rect.height).toString()
+          ..['class'] = 'axis-marker axis-marker-x';
+      _lower.append(_markerX);
+    }
+    if (_showMarkerY && _markerY == null) {
+      _markerY = new LineElement();
+      _markerY.attributes
+          ..['x1'] = _rect.x.toString()
+          ..['y1'] = '0'
+          ..['x2'] = (_rect.x + _rect.width).toString()
+          ..['y2'] = '0'
+          ..['class'] = 'axis-marker axis-marker-y';
+      _lower.append(_markerY);
+    }
+    _visibility(false);
+  }
+
+  void _update(ChartEvent e) {
+    if (!_isRenderArea(e)) {
+      _visibility(false);
+    } else {
+      _visibility(true);
+      window.requestAnimationFrame((_) {
+        if (_showMarkerX) {
+          _markerX.attributes['transform'] = 'translate(${e.chartX},0)';
+        }
+        if (_showMarkerY) {
+          _markerY.attributes['transform'] = 'translate(0,${e.chartY})';
+        }
+      });
+    }
+  }
+}
diff --git a/pub/charted/lib/charts/behaviors/chart_tooltip.dart b/pub/charted/lib/charts/behaviors/chart_tooltip.dart
new file mode 100644
index 0000000..bb2c2b7
--- /dev/null
+++ b/pub/charted/lib/charts/behaviors/chart_tooltip.dart
@@ -0,0 +1,173 @@
+part of charted.charts;
+
+/**
+ * The ChartTooltip displays tooltip for the values being interacted with in the
+ * chart.  It displays all the active values in the data row and use the value
+ * in the dimension as the title.
+ */
+class ChartTooltip implements ChartBehavior {
+  static const _TOOLTIP_OFFSET = 26;
+  final String orientation;
+  final bool showDimensionValue;
+  final bool showMeasureTotal;
+
+  ChartArea _area;
+  Selection _tooltipSelection;
+  SubscriptionsDisposer _disposer = new SubscriptionsDisposer();
+
+  /**
+   * Constructs the tooltip, display extra fields base on [config] and position
+   * the tooltip base on [orientation] specified in the constructor.
+   */
+  ChartTooltip({this.showDimensionValue: false,
+      this.showMeasureTotal: false, this.orientation: ORIENTATION_RIGHT});
+
+  /** Sets up listeners for triggering tooltip. */
+  void init(ChartArea area, Element upperRenderPane, Element lowerRenderPane) {
+    _area = area;
+    _disposer.addAll([
+        area.onValueMouseOver.listen(show),
+        area.onValueMouseOut.listen(hide)
+    ]);
+
+    // Tooltip requires host to be position: relative.
+    area.host.style.position = 'relative';
+
+    var _scope = new SelectionScope.element(_area.host);
+    _scope.append('div')..classed('tooltip');
+    _tooltipSelection = _scope.select('.tooltip');
+  }
+
+  void dispose() {
+    _disposer.dispose();
+    if (_tooltipSelection != null) _tooltipSelection.remove();
+  }
+
+  /**
+   * Displays the tooltip upon receiving a hover event on an element in the
+   * chart.
+   */
+  show(ChartEvent e) {
+    // Clear
+    _tooltipSelection.first.children.clear();
+
+    // Display dimension value if set in config.
+    if (showDimensionValue) {
+      var column = _area.config.dimensions.elementAt(0),
+          value =
+              _area.data.rows.elementAt(e.row).elementAt(column),
+          formatter = _getFormatterForColumn(column);
+
+      _tooltipSelection.append('div')
+        ..classed('tooltip-title')
+        ..text((formatter != null) ? formatter(value) : value.toString());
+    }
+
+    // Display sum of the values in active row if set in config.
+    if (showMeasureTotal) {
+      var formatter =
+          _getFormatterForColumn(e.series.measures.elementAt(0));
+      var total = 0;
+      for (var i = 0; i < e.series.measures.length; i++) {
+        total += _area.data.rows.elementAt(e.row).
+            elementAt(e.series.measures.elementAt(i));
+      }
+      _tooltipSelection.append('div')
+          ..classed('tooltip-total')
+          ..text((formatter != null) ? formatter(total) : total.toString());
+    }
+
+    // Create the tooltip items base on the number of measures in the series.
+    var items = _tooltipSelection.selectAll('.tooltip-item').
+        data(e.series.measures);
+    items.enter.append('div')
+        ..classed('tooltip-item')
+        ..classedWithCallback('active', (d, i, c) => (i == e.column));
+
+    // Display the label for the currently active series.
+    var tooltipItems = _tooltipSelection.selectAll('.tooltip-item');
+    tooltipItems.append('div')
+        ..classed('tooltip-item-label')
+        ..textWithCallback((d, i, c) => _area.data.columns.
+            elementAt(e.series.measures.elementAt(i)).label);
+
+    // Display the value of the currently active series
+    tooltipItems.append('div')
+        ..classed('tooltip-item-value')
+        ..styleWithCallback('color', (d, i, c) =>
+            _area.theme.getColorForKey(d))
+        ..textWithCallback((d, i, c) {
+      var formatter = _getFormatterForColumn(d),
+          value = _area.data.rows.elementAt(e.row).elementAt(d);
+      return (formatter != null) ? formatter(value) : value.toString();
+    });
+
+    math.Point position = computeTooltipPosition(
+        new math.Point(e.chartX + _ChartArea.MARGIN,
+            e.chartY + _ChartArea.MARGIN),
+            _tooltipSelection.first.getBoundingClientRect());
+
+    // Set position of the tooltip and display it.
+    _tooltipSelection
+        ..style('left', '${position.x}px')
+        ..style('top', '${position.y}px')
+        ..style('opacity', '1');
+  }
+
+  /** Computes the ideal tooltip position based on orientation. */
+  math.Point computeTooltipPosition(math.Point coord,
+      math.Rectangle rect) {
+    var x, y;
+    if (orientation == ORIENTATION_TOP) {
+      x = coord.x - rect.width / 2;
+      y = coord.y - rect.height - _TOOLTIP_OFFSET;
+    } else if (orientation == ORIENTATION_RIGHT) {
+      x = coord.x + _TOOLTIP_OFFSET;
+      y = coord.y - rect.height / 2;
+    } else if (orientation == ORIENTATION_BOTTOM) {
+      x = coord.x - rect.width / 2;
+      y = coord.y + _TOOLTIP_OFFSET;
+    } else { // left
+      x = coord.x - rect.width - _TOOLTIP_OFFSET;
+      y = coord.y - rect.height / 2;
+    }
+
+    return boundTooltipPosition(
+        new math.Rectangle(x, y, rect.width, rect.height));
+  }
+
+  /** Positions the tooltip to be inside of the window boundary. */
+  math.Point boundTooltipPosition(math.Rectangle rect) {
+    var hostRect = _area.host.getBoundingClientRect();
+    var windowWidth = window.innerWidth;
+    var windowHeight = window.innerHeight;
+
+    var top = rect.top;
+    var left = rect.left;
+
+    // Checks top and bottom.
+    if (rect.top + hostRect.top < 0) {
+      top = -hostRect.top;
+    } else if (rect.top + rect.height + hostRect.top > windowHeight) {
+      top = windowHeight - rect.height - hostRect.top;
+    }
+
+    // Checks left and right.
+    if (rect.left < 0) {
+      left = -hostRect.left;
+    } else if (rect.left + rect.width + hostRect.left > windowWidth) {
+      left = windowWidth - rect.width - hostRect.left;
+    }
+
+    return new math.Point(left, top);
+  }
+
+  FormatFunction _getFormatterForColumn(int column) =>
+      _area.data.columns.elementAt(column).formatter;
+
+  hide(ChartEvent e) {
+    if (_tooltipSelection == null) return;
+    _tooltipSelection.style('opacity', '0');
+  }
+}
+
diff --git a/pub/charted/lib/charts/chart_area.dart b/pub/charted/lib/charts/chart_area.dart
new file mode 100644
index 0000000..8d3939f
--- /dev/null
+++ b/pub/charted/lib/charts/chart_area.dart
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+part of charted.charts;
+
+/**
+ * Given a DOM element, [ChartArea] takes care of rendering the axis and
+ * passing relevant parameters to chart renderers that draw the actual
+ * data visualizations.
+ */
+abstract class ChartArea implements ChartBehaviorSource {
+  /**
+   * Data used by the chart. Chart isn't updated till the next call to
+   * draw function if [autoUpdate] is set to false.
+   *
+   * Setting new value to [data] will update the chart.
+   */
+  ChartData data;
+
+  /**
+   * Configuration for this chart.  ChartBase subscribes to changes on
+   * [config] and calls draw upon any changes.
+   *
+   * Refer to [ChartConfig] for further documentation about which changes
+   * are added to the stream, which in turn trigger an update on the chart.
+   */
+  ChartConfig config;
+
+  /**
+   * Theme for this chart. Any changes to [theme] are not applied to the chart
+   * until it is redrawn. Changes can be forced by calling [draw] function.
+   */
+  ChartTheme theme;
+
+  /**
+   * When set to true, the chart subscribes to changes on data and updates the
+   * chart when [data] or [config] changes. Defaults to false.
+   */
+  bool autoUpdate;
+
+  /**
+   * Number of dimension axes that this area contains.
+   * Examples:
+   *   - A bar-chart has one dimension axis (typically the 'x' axis)
+   *   - A bubble-chart has two dimension axis (both 'x' and 'y')
+   *   - A pie-chart does not have any axis
+   *
+   * Currently, the only valid values are 0, 1 and 2.
+   */
+  int dimensionAxesCount;
+
+  /**
+   * Geometry of components in this [ChartArea]
+   */
+  ChartAreaLayout get layout;
+
+  /**
+   * Scales used to render the measure axis of the given [ChartSeries]
+   */
+  Iterable<Scale> measureScales(ChartSeries s);
+
+  /**
+   * Scales used to render the dimension axes
+   */
+  Iterable<Scale> get dimensionScales;
+
+  /**
+   * Host of the ChartArea
+   */
+  Element get host;
+
+  /**
+   * Draw the chart with current data and configuration.
+   */
+  void draw();
+
+  /*
+   * Force destroy the ChartArea.
+   *   - Clear references to all passed objects and subscriptions.
+   *   - Call dispose on all renderers and behaviors.
+   */
+  void dispose();
+
+  /**
+   * Factory method to create an instance of the default implementation
+   *   - [host] is the hosting element for the chart. The default
+   *       implementation uses a HTML Element that has [Element.clientHeight]
+   *       and [Element.clientWidth] defined.
+   *   - [data] is an implementation of [ChartData] that is rendered.
+   *   - [config] is an implementation of [ChartData]
+   *   - [autoUpdate] indicates if the charts must be updated upon changes
+   *       to data and config.  If set to false, the chart isn't updated
+   *       until [draw] is called.
+   *   - [dimensionAxesCount] indicates the number of dimension axis
+   *       displayed in the chart - currently, only 0, 1 and 2 are supported.
+   */
+  factory ChartArea(host, ChartData data, ChartConfig config,
+      {bool autoUpdate: false, int dimensionAxesCount: 1}) =>
+          new _ChartArea(host, data, config, autoUpdate, dimensionAxesCount);
+}
+
+/**
+ * Class representing geometry of the [ChartArea] and various components
+ * that are created by the ChartArea.
+ */
+abstract class ChartAreaLayout {
+  /** Sizes of the axes by orientation */
+  UnmodifiableMapView<String, Rect> get axes;
+
+  /** Size of render area */
+  Rect get renderArea => new Rect();
+
+  /** Size of chart area */
+  Rect get chartArea => new Rect();
+}
diff --git a/pub/charted/lib/charts/chart_config.dart b/pub/charted/lib/charts/chart_config.dart
new file mode 100644
index 0000000..42f7f2a
--- /dev/null
+++ b/pub/charted/lib/charts/chart_config.dart
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+part of charted.charts;
+
+/**
+ * Configuration of the chart.
+ */
+abstract class ChartConfig {
+  /**
+   * List of series to visualize on this chart.
+   *
+   * If the implementation is observable, setting a new list must broadcast
+   * a change.  Additionally, if [series] is set to an [ObservableList],
+   * changes to the list are broadcast too.
+   */
+  Iterable<ChartSeries> series;
+
+  /**
+   * List of columns that form the dimensions on the chart.
+   *
+   * If the implementation is observable, setting a new list must broadcast
+   * a change. Additionally, if [dimensions] is set to an [ObservableList],
+   * changes to the list are broadcast too.
+   */
+  Iterable<int> dimensions;
+
+  /** Instance of [ChartLegend] implementation used to render legend */
+  ChartLegend legend;
+
+  /** Recommended minimum size for the chart */
+  Rect minimumSize;
+
+  /** Indicates if the chart has primary dimension on the left axis */
+  bool leftAxisIsPrimary = false;
+
+  /** Registers axis configuration for the axis represented by [id]. */
+  void registerMeasureAxis(String id, ChartAxisConfig axis);
+
+  /** Return the user-set axis configuration for [id] */
+  ChartAxisConfig getMeasureAxis(String id);
+
+  /** Register axis configuration of the axis used for dimension [column]. */
+  void registerDimensionAxis(int column, ChartAxisConfig axis);
+
+  /**
+   * Return the user set axis configuration for [column].  If a custom scale
+   * was not set, returns null.
+   */
+  ChartAxisConfig getDimensionAxis(int column);
+
+  /**
+   * List of measure axes ids that are displayed. If not specified, the first
+   * two measure axes are displayed.  If the list is empty, none of the
+   * measure axes are displayed.
+   */
+  Iterable<String> displayedMeasureAxes;
+
+  /**
+   * Indicates if the dimension axes should be drawn on this chart. Unless set
+   * to "false", the axes are rendered.
+   */
+  bool renderDimensionAxes;
+
+  /** Factory method to create an instance of the default implementation */
+  factory ChartConfig(Iterable<ChartSeries> series, Iterable<int> dimensions)
+      => new _ChartConfig(series, dimensions);
+}
+
+/**
+ * Implementation of [ChangeRecord] that is used to notify changes to
+ * [ChartConfig].  Currently, changes to list of dimensions and list of series
+ * are monitored.
+ */
+class ChartConfigChangeRecord implements ChangeRecord {
+  const ChartConfigChangeRecord();
+}
+
+/*
+ * Configuration for an axis
+ */
+class ChartAxisConfig {
+  /** Title for the axis */
+  String title;
+
+  /** Scale to be used with the axis */
+  Scale scale;
+
+  /**
+   * For a quantitative scale, values at which ticks should be displayed.
+   * When not specified, the ticks are intepolated evenly over the output
+   * range.
+   */
+  Iterable tickValues;
+}
+
diff --git a/pub/charted/lib/charts/chart_data.dart b/pub/charted/lib/charts/chart_data.dart
new file mode 100644
index 0000000..3b91021
--- /dev/null
+++ b/pub/charted/lib/charts/chart_data.dart
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+part of charted.charts;
+
+/**
+ * Interface to be implemented by data providers to give tabular access to
+ * data for chart renderers.
+ */
+abstract class ChartData {
+  /**
+   * Create a new instance of [ChartData]'s internal implementation
+   */
+  factory ChartData(Iterable<ChartColumnSpec> columns, Iterable<Iterable> rows)
+      => new _ChartData(columns, rows);
+
+  /** Read-only access to column specs */
+  Iterable<ChartColumnSpec> get columns;
+
+  /** Read-only access to rows */
+  Iterable<Iterable> get rows;
+
+  @override
+  String toString();
+}
+
+/**
+ * Interface implemented by [ChartData] transformers.
+ * Examples:
+ *   AggregationTranformer to aggregations rows/columns
+ *   FilterTransformer to filter data
+ *   TransposeTransformer to convert rows to columns and vice-versa
+ */
+abstract class ChartDataTransform {
+  /**
+   * Create a new instance of [ChartData] by selecting a subset
+   * of rows and columns from the current one
+   */
+  ChartData transform(ChartData source);
+}
+
+/**
+ * Implementation of [ChangeRecord], that is used to notify when rows get added
+ * or removed to ChartData
+ */
+class ChartRowChangeRecord implements ChangeRecord {
+  /**
+   * Changes to the rows - contains all updates to rows since last notification.
+   */
+  final List<ListChangeRecord> changes;
+
+  const ChartRowChangeRecord(this.changes);
+}
+
+/**
+ * Implementation of [ChangeRecord], that is used to notify changes to
+ * values in [ChartData].
+ */
+class ChartValueChangeRecord implements ChangeRecord {
+  /**
+   * Row that changes.
+   */
+  final int row;
+
+  /**
+   * List of changes to data on the row - includes all updates since the
+   * last change notification.
+   */
+  final List<ListChangeRecord> changes;
+
+  const ChartValueChangeRecord(this.row, this.changes);
+}
+
+/**
+ * Meta information for each column in ChartData
+ */
+class ChartColumnSpec {
+  static const String TYPE_BOOLEAN = 'boolean';
+  static const String TYPE_DATE = 'date';
+  static const String TYPE_NUMBER = 'number';
+  static const String TYPE_STRING = 'string';
+  static const String TYPE_TIMESTAMP = 'timestamp';
+
+  static const List ORDINAL_SCALES = const [ TYPE_STRING ];
+  static const List LINEAR_SCALES = const [ TYPE_NUMBER ];
+  static const List TIME_SCALES = const [ TYPE_DATE, TYPE_TIMESTAMP ];
+
+  /** Formatter for values that belong to this column */
+  final FormatFunction formatter;
+
+  /**
+   * Label for the column.  Used in legend, tooltips etc;
+   * When not specified, defaults to empty string.
+   */
+  final String label;
+
+  /**
+   * Type of data in this column. Used for interpolations, computing
+   * scales and ranges. When not specified, it is assumed to be "number"
+   * for measures and "string" for dimensions.
+   */
+  final String type;
+
+  /**
+   * Indicates if this column requires an ordinal scale.  Ordinal scales
+   * directly map the input value to one output value (i.e they do not
+   * use interpolations to compute the values.  Eg: City names)
+   *
+   * If not specified, an ordinal scale is used for string columns and
+   * interpolated scales are used for others.
+   */
+  final bool useOrdinalScale;
+
+  /**
+   * Initialize axis scale according to [ChartColumnSpec] type.
+   * This logic is extracted from [ChartArea] implementation for conveniently
+   * adding more scale types.
+   */
+  Scale createDefaultScale() {
+    if (useOrdinalScale == true) return new OrdinalScale();
+    if (LINEAR_SCALES.contains(type)) return new LinearScale();
+    if (TIME_SCALES.contains(type)) return new TimeScale();
+    return null;
+  }
+
+  ChartColumnSpec({this.label, String type : TYPE_NUMBER,
+      this.formatter, bool useOrdinalScale})
+      : useOrdinalScale = useOrdinalScale == true ||
+            useOrdinalScale == null && ORDINAL_SCALES.contains(type),
+        type = type;
+}
diff --git a/pub/charted/lib/charts/chart_data_waterfall.dart b/pub/charted/lib/charts/chart_data_waterfall.dart
new file mode 100644
index 0000000..bb5fab6
--- /dev/null
+++ b/pub/charted/lib/charts/chart_data_waterfall.dart
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2015 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+part of charted.charts;
+
+/**
+ * Interface to be implemented by data providers to give tabular access to
+ * data for waterfall chart renderer.
+ */
+abstract class WaterfallChartData extends ChartData {
+  /**
+   * Create a new instance of [ChartData]'s internal implementation
+   */
+  factory WaterfallChartData(Iterable<ChartColumnSpec> columns,
+      Iterable<Iterable> rows, [Iterable<int> baseRows]) =>
+    new _WaterfallChartData(columns, rows, baseRows);
+
+  /**
+   * Set of row indices that are drawn as base (no shifting on y-axis).
+   */
+  Iterable<int> baseRows;
+}
diff --git a/pub/charted/lib/charts/chart_events.dart b/pub/charted/lib/charts/chart_events.dart
new file mode 100644
index 0000000..a715606
--- /dev/null
+++ b/pub/charted/lib/charts/chart_events.dart
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+part of charted.charts;
+
+abstract class ChartBehaviorSource {
+  /**
+   * Stream of events that notify when a mouse button was pressed anywhere
+   * on the [ChartArea]
+   */
+  Stream<ChartEvent> get onMouseUp;
+
+  /**
+   * Stream of events that notify when an already pressed mouse button is
+   * released on the [ChartArea]
+   */
+  Stream<ChartEvent> get onMouseDown;
+
+  /**
+   * Stream of events that notify when mouse pointer enters [ChartArea]
+   */
+  Stream<ChartEvent> get onMouseOver;
+
+  /**
+   * Stream of events that notify when mouse pointer exits [ChartArea]
+   */
+  Stream<ChartEvent> get onMouseOut;
+
+  /**
+   * Stream of events that notify when mouse is moved on [ChartArea]
+   */
+  Stream<ChartEvent> get onMouseMove;
+
+  /**
+   * Stream of events that notify when a rendered value is clicked.
+   */
+  Stream<ChartEvent> get onValueClick;
+
+  /**
+   * Stream of events that notify when user moves mouse over a rendered value
+   */
+  Stream<ChartEvent> get onValueMouseOver;
+
+  /**
+   * Stream of events that notify when user moves mouse out of rendered value
+   */
+  Stream<ChartEvent> get onValueMouseOut;
+
+  /**
+   * A pane that is rendered below all the chart elements - for use with
+   * behaviors that add elements to chart.
+   */
+  Element get lowerBehaviorPane;
+
+  /**
+   * A pane that is rendered above all the chart elements - for use with
+   * behaviors that add elements to chart.
+   */
+  Element get upperBehaviorPane;
+
+  /**
+   * Add a behavior of ChartArea
+   */
+  void addChartBehavior(ChartBehavior behavior);
+
+  /**
+   * Remove a behavior of ChartArea
+   */
+  void removeChartBehavior(ChartBehavior behavior);
+}
+
+/**
+ * Class representing an event emitted by ChartEventSource
+ */
+abstract class ChartEvent {
+  /** DOM source event that caused this event */
+  Event get source;
+
+  /** ChartSeries if any on which this event occurred */
+  ChartSeries get series;
+
+  /** Column in ChartData on which this event occurred */
+  int get column;
+
+  /** Row in ChartData on which this event occurred */
+  int get row;
+
+  /** Value from ChartData on which the event occurred */
+  num get value;
+
+  /** X position relative to the rendered chart */
+  num get chartX;
+
+  /** Y position relative to the rendered chart */
+  num get chartY;
+}
+
+/**
+ * Interface implemented by chart behaviors.
+ * During initialization, the behaviors subscribe to any necessary events and
+ * handle them appropriately.
+ */
+abstract class ChartBehavior {
+  /**
+   * Called while ChartArea is being initialized.
+   *  - [area] is the ChartArea on which this behavior is installed
+   *  - [upperRenderPane] is an Element that is rendered on top of the
+   *      chart.  Behaviors can use it to draw any visualization in response
+   *      to user actions.
+   *  - [lowerRenderPane] is an Element that is rendered below the chart.
+   */
+  void init(ChartArea area, Element upperRenderPane, Element lowerRenderPane);
+
+  /**
+   * Clears all DOM created by this behavior, unsubscribes to event listeners
+   * and clears any state.
+   */
+  void dispose();
+}
diff --git a/pub/charted/lib/charts/chart_legend.dart b/pub/charted/lib/charts/chart_legend.dart
new file mode 100644
index 0000000..177d68c
--- /dev/null
+++ b/pub/charted/lib/charts/chart_legend.dart
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+part of charted.charts;
+
+/**
+ * Interface that is implemented by classes that support
+ * displaying legend.
+ */
+abstract class ChartLegend {
+
+  /** Title of the legend, dynamically updates the legend title when set. */
+  String title;
+
+  /**
+   * Called by [ChartArea] to notify changes to legend.
+   */
+  update(Iterable<ChartLegendItem> legend, ChartArea chart);
+
+  /*
+   * Factory to create the default implementation of legend.
+   */
+  factory ChartLegend(Element host, {maxItems: 0, title: ''}) =>
+      new _ChartLegend(host, maxItems, title);
+}
+
+/**
+ * Class representing an item in the legend.
+ */
+class ChartLegendItem {
+  /** Index of the column in [ChartData] */
+  int column;
+
+  /** HTML color used for this column in the chart */
+  String color;
+
+  /** The label of the Legend Item. */
+  String label;
+
+  /** List of series that this column is part of */
+  Iterable<ChartSeries> series;
+
+  /** Utility constructors */
+  ChartLegendItem({this.column, this.color, this.label, this.series});
+}
diff --git a/pub/charted/lib/charts/chart_renderer.dart b/pub/charted/lib/charts/chart_renderer.dart
new file mode 100644
index 0000000..9f1af55
--- /dev/null
+++ b/pub/charted/lib/charts/chart_renderer.dart
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+part of charted.charts;
+
+/**
+ * Renders the chart on a compatible [ChartArea].
+ */
+abstract class ChartRenderer {
+  /**
+   * Returns extent of the series. This extent is used by [ChartArea] to
+   * set the output range of the corresponding scale/axis of the series.
+   *
+   * Extent has valid values only if [prepare] was already called.
+   */
+  Extent get extent;
+
+  /**
+   * Indicates if this renderer uses range "band" on any of the dimension
+   * axis. Band is space taken on the dimension axis (if more than a point).
+   *
+   * Examples:
+   *   A bar chart takes up space (width of the bar) on the dimension axis.
+   *   A line chart does not take any space
+   */
+  Iterable<int> get dimensionsUsingBand;
+
+  /**
+   * Hint for padding between two bands that the ChartArea could use. This
+   * getter is called only for renderers that have [dimensionsUsingBand]
+   * set to non-empty list.
+   */
+  double get bandInnerPadding;
+
+  /**
+   * Hint for padding before the first and after the last bands that the
+   * ChartArea could use.  This getter is called only for renderers that have
+   * [dimensionsUsingBand] set to non-empty list.
+   */
+  double get bandOuterPadding;
+
+  /**
+   * Stream of events that indicate that user clicked on a displayed value
+   */
+  Stream<ChartEvent> get onValueMouseClick;
+
+  /**
+   * Stream of events that indicate that user moved the mouse pointer out
+   * over a displayed value
+   */
+  Stream<ChartEvent> get onValueMouseOver;
+
+  /**
+   * Stream of events that indicate that user moved the mouse pointer out
+   * of a displayed value
+   */
+  Stream<ChartEvent> get onValueMouseOut;
+
+  /**
+   * Prepare the chart for rendering.
+   * - [area] represents the [ChartArea] on which the chart is rendered.
+   * - [series] represents the [ChartSeries] that is rendered
+   */
+  bool prepare(ChartArea area, ChartSeries series);
+
+  /**
+   * Render series data on the passed [host].
+   * Draw will not be successful if [prepare] was not already called.
+   */
+  void draw(Element host);
+
+  /**
+   * Clears DOM created by this renderer and releases
+   * references to passed objects.
+   */
+  void dispose();
+}
diff --git a/pub/charted/lib/charts/chart_series.dart b/pub/charted/lib/charts/chart_series.dart
new file mode 100644
index 0000000..0d4a76f
--- /dev/null
+++ b/pub/charted/lib/charts/chart_series.dart
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+part of charted.charts;
+
+/**
+ * A [ChartSeries] represents one or more columns in ChartData that are
+ * rendered together.
+ *
+ * Examples:
+ * 1. For bar-chart or line-chart, a series consists of one column
+ * 2. For stacked chart or grouped bar chart, a series has more than columns
+ */
+class ChartSeries {
+  /** Name of the series */
+  final String name;
+
+  /**
+   * Optional Ids of measure axes.
+   *
+   * When specified renderers scale the column values against the ranges
+   * of the given axes. If an axis with a matching Id does not exist in
+   * [ChartArea] a new axis is created.
+   *
+   * When not specified, renderers may use [ChartArea.defaultMeasureAxis]
+   * where ever necessary.  Refer to the implementation of [ChartRenderer] for
+   * more information on defaults and how the measure axes are used.
+   *
+   * If the implementation is [Observable] and [measureAxisIds] is set to an
+   * [ObservableList], changes to the list must be broadcasted.
+   */
+  Iterable<String> measureAxisIds;
+
+  /**
+   * List of columns in ChartData that are measures of this series.
+   *
+   * A series may include more than one measure if the renderer supports it.
+   * When there are more measures than what the renderer can handle, a renderer
+   * only renders the first "supported number" of columns. If the number of
+   * columns is less than the minimum that the renderer supports, the remaining
+   * measures are assumed to have zeros.
+   *
+   * If the implementation is [Observable] and [measures] is set to an
+   * [ObservableList], changes to the list must be broadcasted.
+   */
+  Iterable<int> measures;
+
+  /**
+   * Instance of the renderer used to render the series.
+   *
+   * [ChartArea] creates a renderer using [ChartRender.create] and uses it
+   * to compute range of the measure axis and to render the chart.
+   */
+  ChartRenderer renderer;
+
+  /**
+   * Factory function to create an instance of internal implementation of
+   * [ChartSeries].
+   */
+  factory ChartSeries(String name, Iterable<int> measures,
+      ChartRenderer renderer, { Iterable<String> measureAxisIds : null })
+          => new _ChartSeries(name, measures, renderer, measureAxisIds);
+}
+
+/**
+ * Implementation of [ChangeRecord] that is used to notify changes to
+ * [ChartSeries].  Currently, only changes to measures and measureAxisIds
+ * are supported.
+ */
+class ChartSeriesChangeRecord implements ChangeRecord {
+  /**
+   * Reference to series that changed
+   */
+  final ChartSeries series;
+
+  const ChartSeriesChangeRecord(this.series);
+}
\ No newline at end of file
diff --git a/pub/charted/lib/charts/chart_theme.dart b/pub/charted/lib/charts/chart_theme.dart
new file mode 100644
index 0000000..68cece9
--- /dev/null
+++ b/pub/charted/lib/charts/chart_theme.dart
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+part of charted.charts;
+
+/**
+ * Theme used to render the chart area, specifically colors and axes.
+ *
+ * Typical implementations of ChartTheme also implement theme interfaces
+ * used by the renderers, tooltips, legends and any other behaviors.
+ */
+
+abstract class ChartTheme {
+  static ChartTheme current = new QuantumChartTheme();
+
+  /** Column/series when it is disabled, possibly because another is active */
+  static const int STATE_DISABLED = 0;
+
+  /** Column/Series that is normal */
+  static const int STATE_NORMAL = 1;
+
+  /** Column/series that is active, possibly by a click */
+  static const int STATE_ACTIVE = 2;
+
+  /**
+   * Color that can be used for key.
+   * For a given input key, the output is always the same.
+   */
+  String getColorForKey(key, [int state]);
+
+  /**
+   * Width of the separator between two chart elements.
+   * Used to separate pies in pie-chart, bars in grouped and stacked charts.
+   */
+  int get defaultSeparatorWidth => 1;
+
+  /**
+   * Stroke width used by all shapes.
+   * It also used when computing width of the by the renderers.
+   */
+  int get defaultStrokeWidth => 2;
+
+  /** Easing function for the transition */
+  EasingFn  get transitionEasingType => Transition.defaultEasingType;
+
+  /** Easing mode for the transition */
+  EasingMode get transitionEasingMode => Transition.defaultEasingMode;
+
+  /** Total duration of the transision in milli-seconds */
+  int get transitionDuration => 250;
+
+  /** Theme passed to the measure axes */
+  ChartAxisTheme get measureAxisTheme;
+
+  /** Theme passed to the dimension axes */
+  ChartAxisTheme get dimensionAxisTheme;
+}
+
+abstract class ChartAxisTheme {
+  /**
+   * Treshold for tick length.  Setting [axisTickSize] <= [FILL_RENDER_AREA]
+   * will make the axis span the entire height/width of the rendering area.
+   */
+  static const int FILL_RENDER_AREA = SMALL_INT_MIN;
+
+  /**
+   * Number of ticks displayed on the axis - only used when an axis is
+   * using a quantitative scale.
+   */
+  int get axisTickCount;
+
+  /**
+   * Size of ticks on the axis. When [measureTickSize] <= [FILL_RENDER_AREA],
+   * the painted tick will span complete height/width of the rendering area.
+   */
+  int get axisTickSize;
+
+  /** Space between axis and label for dimension axes */
+  int get axisTickPadding;
+
+  /**
+   * Space between the first tick and the measure axes.
+   * Only used on charts that don't have renderers that use "bands" of space
+   * on the dimension axes
+   *
+   * Represented as a percentage of space between two consecutive ticks. The
+   * space between two consecutive ticks is also known as the segment size.
+   */
+  double get axisOuterPadding;
+
+  /**
+   * Space between the two bands in the chart.
+   * Only used on charts that have renderers that use "bands" of space on the
+   * dimension axes.
+   *
+   * Represented as a percentage of space between two consecutive ticks. The
+   * space between two consecutive ticks is also known as the segment size.
+   */
+  double get axisBandInnerPadding;
+
+  /**
+   * Space between the first band and the measure axis.
+   * Only used on charts that have renderers that use "bands" of space on the
+   * dimension axes.
+   *
+   * Represented as a percentage of space between two consecutive ticks. The
+   * space between two consecutive ticks is also known as the segment size.
+   */
+  double get axisBandOuterPadding;
+
+  /** When set to true, the axes resize to fit the labels. */
+  bool get axisAutoResize => true;
+
+  /**
+   * Width of vertical axis when it is not resizing automatically. If
+   * [autoResizeAxis] is set to true, [verticalAxisWidth] will be used as the
+   * maximum width of the vertical axis.
+   *
+   * Height of vertical axis is automatically computed based on height of the
+   * visualization.
+   */
+  int get verticalAxisWidth => 200;
+
+  /**
+   * Height of horizontal axis when it is not resizing automatically. If
+   * [autoResizeAxis] is set to true [horizontalAxisHeight] is used as the
+   * maximum height of the horizontal axis.
+   *
+   * Width of horizontal axis is automatically computed based on width of the
+   * visualization.
+   */
+  int get horizontalAxisHeight => 200;
+}
diff --git a/pub/charted/lib/charts/charts.dart b/pub/charted/lib/charts/charts.dart
new file mode 100644
index 0000000..738a02c
--- /dev/null
+++ b/pub/charted/lib/charts/charts.dart
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+library charted.charts;
+
+import 'dart:async';
+import 'dart:collection';
+import 'dart:html' show Element, document, window, Event, MouseEvent;
+import 'dart:math' as math;
+import 'dart:svg' hide Rect;
+import 'dart:typed_data';
+
+import 'package:charted/core/core.dart';
+import 'package:charted/layout/layout.dart';
+import 'package:charted/locale/locale.dart';
+import 'package:charted/scale/scale.dart';
+import 'package:charted/selection/selection.dart';
+import 'package:charted/svg/svg.dart';
+import 'package:charted/transition/transition.dart';
+import 'package:collection/equality.dart';
+import 'package:logging/logging.dart';
+import 'package:observe/observe.dart';
+import 'package:charted/interpolators/interpolators.dart';
+
+part 'chart_area.dart';
+part 'chart_config.dart';
+part 'chart_data.dart';
+part 'chart_data_waterfall.dart';
+part 'chart_events.dart';
+part 'chart_legend.dart';
+part 'chart_renderer.dart';
+part 'chart_series.dart';
+part 'chart_theme.dart';
+
+part 'behaviors/axis_marker.dart';
+part 'behaviors/chart_tooltip.dart';
+
+part 'renderers/bar_chart_renderer.dart';
+part 'renderers/base_renderer.dart';
+part 'renderers/bubble_chart_renderer.dart';
+part 'renderers/line_chart_renderer.dart';
+part 'renderers/pie_chart_renderer.dart';
+part 'renderers/stackedbar_chart_renderer.dart';
+part 'renderers/waterfall_chart_renderer.dart';
+
+part 'src/chart_area_impl.dart';
+part 'src/chart_axis_impl.dart';
+part 'src/chart_config_impl.dart';
+part 'src/chart_data_impl.dart';
+part 'src/chart_data_waterfall_impl.dart';
+part 'src/chart_events_impl.dart';
+part 'src/chart_legend_impl.dart';
+part 'src/chart_series_impl.dart';
+
+part 'themes/quantum_theme.dart';
+
+part 'transformers/aggregation.dart';
+part 'transformers/aggregation_item.dart';
+part 'transformers/aggregation_transformer.dart';
+part 'transformers/filter_transformer.dart';
+part 'transformers/transpose_transformer.dart';
+
+final Logger logger = new Logger('charted.charts');
+
+class SubscriptionsDisposer {
+  List<StreamSubscription> _subscriptions = [];
+  Expando<StreamSubscription> _byObject = new Expando();
+
+  void add(StreamSubscription value, [Object handle]) {
+    if (handle != null) _byObject[handle] = value;
+    _subscriptions.add(value);
+  }
+
+  void addAll(List<StreamSubscription> values, [Object handle]) {
+    for (var subscription in values) {
+      add(subscription, handle);
+    }
+  }
+
+  void unsubscribe(Object handle) {
+    StreamSubscription s = _byObject[handle];
+    if (s != null) {
+      _subscriptions.remove(s);
+      s.cancel();
+    }
+  }
+
+  void dispose() {
+    _subscriptions.forEach((StreamSubscription val) {
+      if (val != null) val.cancel();
+    });
+    _subscriptions.clear();
+  }
+}
diff --git a/pub/charted/lib/charts/renderers/bar_chart_renderer.dart b/pub/charted/lib/charts/renderers/bar_chart_renderer.dart
new file mode 100644
index 0000000..76a611d
--- /dev/null
+++ b/pub/charted/lib/charts/renderers/bar_chart_renderer.dart
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+part of charted.charts;
+
+class BarChartRenderer extends BaseRenderer {
+  final Iterable<int> dimensionsUsingBand = const[0];
+
+  /*
+   * Returns false if the number of dimension axes on the area is 0.
+   * Otherwise, the first dimension scale is used to render the chart.
+   */
+  @override
+  bool prepare(ChartArea area, ChartSeries series) {
+    _ensureAreaAndSeries(area, series);
+    return area.dimensionAxesCount != 0;
+  }
+
+  @override
+  void draw(Element element) {
+    _ensureReadyToDraw(element);
+
+    var measuresCount = series.measures.length,
+        measureScale = area.measureScales(series).first,
+        dimensionScale = area.dimensionScales.first;
+
+    var rows = new List()..addAll(area.data.rows.map((e) {
+      var row = [];
+      for (var measure in series.measures) {
+        row.add(e[measure]);
+      }
+      return row;
+    }));
+
+    var x = area.data.rows.map(
+        (row) => row.elementAt(area.config.dimensions.first)).toList();
+
+    var bars = new OrdinalScale()
+        ..domain = new Range(series.measures.length).toList()
+        ..rangeRoundBands([0, dimensionScale.rangeBand]);
+
+    var groups = root.selectAll('.row-group').data(rows);
+
+    groups.enter.append('g')
+        ..classed('row-group')
+        ..attrWithCallback('transform', (d, i, c) =>
+            'translate(${dimensionScale.apply(x[i])}, 0)');
+    groups.exit.remove();
+
+    // TODO(psunkari): Try not to set an attribute with row index on the gorup.
+    groups.transition()
+        ..attrWithCallback('transform', (d, i, c) =>
+            'translate(${dimensionScale.apply(x[i])}, 0)')
+        ..attrWithCallback('data-row', (d, i, e) => i)
+        ..duration(theme.transitionDuration);
+
+    int barWidth = bars.rangeBand -
+        theme.defaultSeparatorWidth - theme.defaultStrokeWidth;
+
+    var bar = groups.selectAll('.bar').dataWithCallback((d, i, c) => rows[i]);
+    var enter = bar.enter.append('rect')
+        ..classed('bar')
+        ..attr('y', rect.height)
+        ..attr('height', 0)
+        ..styleWithCallback('fill', (d, i, c) => colorForKey(i))
+        ..attrWithCallback(
+            'x', (d, i, e) => bars.apply(i) + theme.defaultStrokeWidth)
+        ..attr('width', barWidth)
+        ..on('click', (d, i, e) => _event(mouseClickController, d, i, e))
+        ..on('mouseover', (d, i, e) => _event(mouseOverController, d, i, e))
+        ..on('mouseout', (d, i, e) => _event(mouseOutController, d, i, e));
+
+    bar.transition()
+        ..attrWithCallback(
+            'x', (d, i, c) => bars.apply(i) + theme.defaultStrokeWidth)
+        ..styleWithCallback('fill', (d, i, c) => colorForKey(i))
+        ..attr('width', barWidth)
+        ..duration(theme.transitionDuration);
+
+    int delay = 0;
+    bar.transition()
+        ..attrWithCallback('y', (d, i, c) => measureScale.apply(d).round())
+        // height -1 so bar does not overlap x axis.
+        ..attrWithCallback('height', (d, i, c) {
+            var height = rect.height - measureScale.apply(d).round() - 1;
+            return (height < 0) ? 0 : height;
+        })
+        ..delayWithCallback((d, i, c) =>
+            delay += theme.transitionDuration ~/
+              (series.measures.length * rows.length));
+
+    if (theme.defaultStrokeWidth > 0) {
+      enter.attr('stroke-width', '${theme.defaultStrokeWidth}px');
+      enter.styleWithCallback('stroke', (d, i, c) => colorForKey(i));
+      bar.transition()
+          ..styleWithCallback('stroke', (d, i, c) => colorForKey(i));
+    }
+
+    bar.exit.remove();
+  }
+
+  @override
+  double get bandInnerPadding {
+    assert(series != null && area != null);
+    var measuresCount = series.measures.length;
+    return measuresCount > 2 ? 1 - (measuresCount / (measuresCount + 1)) :
+        area.theme.dimensionAxisTheme.axisBandInnerPadding;
+  }
+
+  @override
+  double get bandOuterPadding {
+    assert(series != null && area != null);
+    return area.theme.dimensionAxisTheme.axisBandOuterPadding;
+  }
+
+  void _event(StreamController controller, data, int index, Element e) {
+    if (controller == null) return;
+    var rowStr = e.parent.dataset['row'];
+    var row = rowStr != null ? int.parse(rowStr) : null;
+    controller.add(
+        new _ChartEvent(scope.event, area, series, row, index, data));
+  }
+}
diff --git a/pub/charted/lib/charts/renderers/base_renderer.dart b/pub/charted/lib/charts/renderers/base_renderer.dart
new file mode 100644
index 0000000..263c474
--- /dev/null
+++ b/pub/charted/lib/charts/renderers/base_renderer.dart
@@ -0,0 +1,103 @@
+/**
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+part of charted.charts;
+
+class BaseRenderer implements ChartRenderer {
+  ChartArea area;
+  ChartSeries series;
+  ChartTheme theme;
+  Rect rect;
+
+  Element host;
+  Selection root;
+  SelectionScope scope;
+
+  StreamController<ChartEvent> mouseOverController;
+  StreamController<ChartEvent> mouseOutController;
+  StreamController<ChartEvent> mouseClickController;
+
+  void _ensureAreaAndSeries(ChartArea area, ChartSeries series) {
+    assert(area != null && series != null);
+    this.area = area;
+    this.series = series;
+  }
+
+  void _ensureReadyToDraw(Element element) {
+    assert(series != null && area != null);
+    assert(element != null && element is GElement);
+
+    if (scope == null) {
+      host = element;
+      scope = new SelectionScope.element(element);
+      root = scope.selectElements([host]);
+    }
+
+    theme = area.theme;
+    rect = area.layout.renderArea;
+  }
+
+  @override
+  void dispose() {
+    if (root == null) return;
+    root.selectAll('.row-group').remove();
+  }
+
+  @override
+  Extent get extent {
+    assert(series != null && area != null);
+    var rows = area.data.rows,
+    max = rows[0][series.measures.first],
+    min = max;
+
+    rows.forEach((row) {
+      series.measures.forEach((idx) {
+        if (row[idx] > max) max = row[idx];
+        if (row[idx] < min) min = row[idx];
+      });
+    });
+    return new Extent(min, max);
+  }
+
+  @override
+  Stream<ChartEvent> get onValueMouseOver {
+    if (mouseOverController == null) {
+      mouseOverController = new StreamController.broadcast(sync: true);
+    }
+    return mouseOverController.stream;
+  }
+
+  @override
+  Stream<ChartEvent> get onValueMouseOut {
+    if (mouseOutController == null) {
+      mouseOutController = new StreamController.broadcast(sync: true);
+    }
+    return mouseOutController.stream;
+  }
+
+  @override
+  Stream<ChartEvent> get onValueMouseClick {
+    if (mouseClickController == null) {
+      mouseClickController = new StreamController.broadcast(sync: true);
+    }
+    return mouseClickController.stream;
+  }
+
+  double get bandInnerPadding => 1.0;
+  double get bandOuterPadding => area.theme.dimensionAxisTheme.axisOuterPadding;
+
+  /** Get a color using the theme's ordinal scale of colors */
+  String colorForKey(i) =>
+      area.theme.getColorForKey(series.measures.elementAt(i));
+
+  /** List of measure values as rows containing only measure columns */
+  Iterable<Iterable> get asRowValues => [];
+
+  /** List of measure values as columns */
+  Iterable<Iterable> get asColumnValues => [];
+}
\ No newline at end of file
diff --git a/pub/charted/lib/charts/renderers/bubble_chart_renderer.dart b/pub/charted/lib/charts/renderers/bubble_chart_renderer.dart
new file mode 100644
index 0000000..85e6eef
--- /dev/null
+++ b/pub/charted/lib/charts/renderers/bubble_chart_renderer.dart
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+part of charted.charts;
+
+class BubbleChartRenderer implements ChartRenderer {
+  final Iterable<int> dimensionsUsingBand = const[];
+
+  ChartArea area;
+  ChartSeries series;
+
+  final double maxBubbleRadius;
+
+  Element _host;
+  Selection _group;
+  SelectionScope _scope;
+
+  StreamController<ChartEvent> _mouseOverController;
+  StreamController<ChartEvent> _mouseOutController;
+  StreamController<ChartEvent> _mouseClickController;
+
+  BubbleChartRenderer([this.maxBubbleRadius = 20.0]);
+
+  /*
+   * BubbleChart needs two dimension axes.
+   */
+  @override
+  bool prepare(ChartArea area, ChartSeries series) {
+    assert(area != null && series != null);
+    if (area.dimensionAxesCount != 2) return false;
+    this.area = area;
+    this.series = series;
+    return true;
+  }
+
+  @override
+  void draw(Element element) {
+    assert(series != null && area != null);
+    assert(element != null && element is GElement);
+
+    if (_scope == null) {
+      _host = element;
+      _scope = new SelectionScope.element(element);
+      _group = _scope.selectElements([_host]);
+    }
+
+    var geometry = area.layout.renderArea,
+        measuresCount = series.measures.length,
+        bubbleRadiusScale = area.measureScales(series).first,
+        xDimensionScale = area.dimensionScales.first,
+        yDimensionScale = area.dimensionScales.last,
+        theme = area.theme,
+        bubbleRadiusFactor =
+            maxBubbleRadius / min([geometry.width, geometry.height]);
+
+    String color(i) => theme.getColorForKey(series.measures.elementAt(i));
+
+    // Measure values used to set size of the bubble.
+    var columns = [];
+    for (int m in series.measures) {
+      columns.add(new List.from(
+          area.data.rows.map((Iterable row) => row.elementAt(m))));
+    }
+
+    // Dimension values used to position the bubble.
+    var xDimensionIndex = area.config.dimensions.first,
+        yDimensionIndex = area.config.dimensions.last,
+        xDimensionVals = [],
+        yDimensionVals = [];
+    for (var row in area.data.rows) {
+      xDimensionVals.add(row.elementAt(xDimensionIndex));
+      yDimensionVals.add(row.elementAt(yDimensionIndex));
+    }
+
+    var group = _group.selectAll('.measure-group').data(columns);
+    group.enter.append('g')
+        ..classed('measure-group')
+        ..styleWithCallback('fill', (d, i, e) => color(i));
+    group.exit.remove();
+
+    var measures = group.selectAll('.bubble').dataWithCallback(
+        (d, i, e) => columns[i]);
+    var enter = measures.enter.append('circle')
+        ..classed('bubble')
+        ..attrWithCallback('transform',
+            (d, i, e) => 'translate('
+                '${xDimensionScale.apply(xDimensionVals[i])},'
+                '${yDimensionScale.apply(yDimensionVals[i])})')
+        ..attrWithCallback('r',
+            (d, i, e) => '${bubbleRadiusScale.apply(d) * bubbleRadiusFactor}');
+    measures.exit.remove();
+  }
+
+  @override
+  void dispose() {
+    if (_group == null) return;
+    _group.selectAll('.row-group').remove();
+  }
+
+  @override
+  double get bandInnerPadding => 1.0;
+
+  @override
+  double get bandOuterPadding => area.theme.dimensionAxisTheme.axisOuterPadding;
+
+  @override
+  Extent get extent {
+    assert(series != null && area != null);
+    var rows = area.data.rows,
+        max = rows[0][series.measures.first],
+        min = max;
+
+    rows.forEach((row) {
+      series.measures.forEach((idx) {
+        if (row[idx] > max) max = row[idx];
+        if (row[idx] < min) min = row[idx];
+      });
+    });
+    return new Extent(min, max);
+  }
+
+  void _event(StreamController controller, data, int index, Element e) {
+    if (controller == null) return;
+    var rowStr = e.parent.dataset['row'];
+    var row = rowStr != null ? int.parse(rowStr) : null;
+    controller.add(
+        new _ChartEvent(_scope.event, area, series, row, index, data));
+  }
+
+  @override
+  Stream<ChartEvent> get onValueMouseOver {
+    if (_mouseOverController == null) {
+      _mouseOverController = new StreamController.broadcast(sync: true);
+    }
+    return _mouseOverController.stream;
+  }
+
+  @override
+  Stream<ChartEvent> get onValueMouseOut {
+    if (_mouseOutController == null) {
+      _mouseOutController = new StreamController.broadcast(sync: true);
+    }
+    return _mouseOutController.stream;
+  }
+
+  @override
+  Stream<ChartEvent> get onValueMouseClick {
+    if (_mouseClickController == null) {
+      _mouseClickController = new StreamController.broadcast(sync: true);
+    }
+    return _mouseClickController.stream;
+  }
+}
diff --git a/pub/charted/lib/charts/renderers/line_chart_renderer.dart b/pub/charted/lib/charts/renderers/line_chart_renderer.dart
new file mode 100644
index 0000000..b651a0c
--- /dev/null
+++ b/pub/charted/lib/charts/renderers/line_chart_renderer.dart
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+part of charted.charts;
+
+class LineChartRenderer extends BaseRenderer {
+  final Iterable<int> dimensionsUsingBand = const[];
+
+  /*
+   * Returns false if the number of dimension axes on the area is 0.
+   * Otherwise, the first dimension scale is used to render the chart.
+   */
+  @override
+  bool prepare(ChartArea area, ChartSeries series) {
+    _ensureAreaAndSeries(area, series);
+    return area.dimensionAxesCount != 0;
+  }
+
+  @override
+  void draw(Element element) {
+    _ensureReadyToDraw(element);
+
+    var measuresCount = series.measures.length,
+        measureScale = area.measureScales(series).first,
+        dimensionScale = area.dimensionScales.first;
+
+    // Create initial values for transitiion
+    var initialValues = series.measures.map((column) {
+      return area.data.rows.map((values) => 0).toList();
+    }).toList();
+
+    // Create lists of values in measure columns.
+    var lines = series.measures.map((column) {
+      return area.data.rows.map((values) => values[column]).toList();
+    }).toList();
+
+    // We only support one dimension axes, so we always use the
+    // first dimension.
+    var x = area.data.rows.map(
+        (row) => row.elementAt(area.config.dimensions.first)).toList();
+
+    var rangeBandOffset = dimensionScale.rangeBand / 2;
+    var _xAccessor = (d, i) => dimensionScale.apply(x[i]) + rangeBandOffset;
+    var _yAccessor = (d, i) => measureScale.apply(d);
+
+    var line = new SvgLine();
+    line.xAccessor = _xAccessor;
+    line.yAccessor = _yAccessor;
+
+    // Draw the lines.
+    // TODO (midoringo): Right now the default stroke-width is 2px, which is
+    // hard for user to hover over. Maybe add an larger, invisible capture area?
+    var svgLines = root.selectAll('.line').data(initialValues);
+    svgLines.enter.append('path')
+        ..classed('line', true)
+        ..attrWithCallback('d', (d, i, e) => line.path(d, i, e))
+        ..styleWithCallback('stroke', (d, i, e) => colorForKey(i))
+        ..on('mouseover', (d, i, e) {
+          // Thickens the line on hover and show the points.
+          root.selectAll('.line-point-${i}')..style('opacity', '1');
+          e.classes.add('active');
+        })
+        ..on('mouseout', (d, i, e) {
+          // Thins the line on mouse out and hide the points.
+          root.selectAll('.line-point-${i}')..style('opacity', '0');
+          e.classes.remove('active');
+        })
+        ..style('fill', 'none');
+
+    int delay = 0;
+    svgLines = root.selectAll('.line').data(lines);
+    svgLines.transition()
+        ..attrWithCallback('d', (d, i, e) => line.path(d, i, e))
+        ..styleWithCallback('stroke', (d, i, e) => colorForKey(i))
+        ..duration(theme.transitionDuration)
+        ..delayWithCallback((d, i, c) => delay += 50 ~/ series.measures.length);
+    svgLines.exit.remove();
+
+    // Draw the circle for each point in line for events.
+    for (var columnIndex = 0; columnIndex < lines.length; columnIndex++) {
+      root.selectAll('.line-point-${columnIndex}').remove();
+      var points = root.selectAll('point').data(lines[columnIndex]);
+      points.enter.append('circle')
+        ..classed('line-point line-point-${columnIndex}', true)
+        ..attr('r', 4)
+        ..attrWithCallback('data-row', (d, i, e) => i)
+        ..attrWithCallback('cx', (d, i, e) => _xAccessor(d, i))
+        ..attrWithCallback('cy', (d, i, e) => _yAccessor(d, i))
+        ..styleWithCallback('stroke', (d, i, e) => colorForKey(columnIndex))
+        ..styleWithCallback('fill', (d, i, e) => colorForKey(columnIndex))
+        ..style('opacity', '0')
+        ..on('click',
+            (d, i, e) => _event(mouseClickController, d, columnIndex, e))
+        ..on('mouseover', (d, i, e) {
+          e.style.opacity = '1';
+          _event(mouseOverController, d, columnIndex, e);
+        })
+        ..on('mouseout', (d, i, e) {
+          e.style.opacity = '0';
+          _event(mouseOutController, d, columnIndex, e);
+        });
+    }
+  }
+
+  @override
+  void dispose() {
+    if (root == null) return;
+    root.selectAll('.line').remove();
+  }
+
+  void _event(StreamController controller, data, int index, Element e) {
+    if (controller == null) return;
+    var rowStr = e.dataset['row'];
+    var row = rowStr != null ? int.parse(rowStr) : null;
+    controller.add(
+        new _ChartEvent(scope.event, area, series, row, index, data));
+  }
+}
diff --git a/pub/charted/lib/charts/renderers/pie_chart_renderer.dart b/pub/charted/lib/charts/renderers/pie_chart_renderer.dart
new file mode 100644
index 0000000..14525e2
--- /dev/null
+++ b/pub/charted/lib/charts/renderers/pie_chart_renderer.dart
@@ -0,0 +1,212 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+part of charted.charts;
+
+class PieChartRenderer extends BaseRenderer {
+  static const STATS_PERCENTAGE = 'percentage-only';
+  static const STATS_VALUE = 'value-only';
+  static const STATS_VALUE_PERCENTAGE = 'value-percentage';
+
+  final Iterable<int> dimensionsUsingBand = const[];
+  final statsMode;
+  final num innerRadius;
+
+  SelectionScope _scope;
+  List<List> _prevRows = null;
+  double _prevSlice;
+
+  PieChartRenderer({this.innerRadius: 0, this.statsMode: STATS_PERCENTAGE});
+
+  /*
+   * Returns false if the number of dimension axes != 0. Pie chart can only
+   * be rendered on areas with no axes.
+   */
+  @override
+  bool prepare(ChartArea area, ChartSeries series) {
+    _ensureAreaAndSeries(area, series);
+    return area.dimensionAxesCount == 0;
+  }
+
+  @override
+  void draw(GElement element) {
+    _ensureReadyToDraw(element);
+
+    var rows = new List()..addAll(area.data.rows.map((e) {
+      var row = [];
+      for (var measure in series.measures) {
+        row.add(e[measure]);
+      }
+      return row;
+    }));
+
+    var radius = math.min(rect.width, rect.height) / 2;
+    var outerRadius = radius - 10;
+    var sliceRadius = (radius - 10 - innerRadius) / rows.length;
+
+    if (_prevRows == null) {
+      _prevRows = new List<List>();
+      _prevSlice = sliceRadius;
+    }
+
+    while (_prevRows.length < rows.length)
+      _prevRows.add(new List());
+    while (_prevRows.length > rows.length)
+      _prevRows.removeLast();
+    for (int i = 0; i < _prevRows.length; i++) {
+      while (_prevRows[i].length > rows[i].length)
+        _prevRows[i].removeLast();
+      while (_prevRows[i].length < rows[i].length)
+        _prevRows[i].add(0);
+    }
+
+    var group = root.selectAll('.row-group').data(rows);
+    group.enter.append('g')
+        ..classed('row-group')
+        ..attrWithCallback('data-row', (d, i, e) => i)
+        ..attrWithCallback('transform', (d, i, c) =>
+            'translate(${rect.width / 2}, ${rect.height / 2})');
+    group.exit.remove();
+
+    var layout = new PieLayout();
+    var arc = new SvgArc();
+
+    List<List> prevArcData = new List<List>();
+    for (int i = 0; i < rows.length; i++) {
+      prevArcData.add(layout.layout(_prevRows[i]));
+      for (int j = 0; j < rows[i].length; j++) {
+        prevArcData[i][j].innerRadius =
+            outerRadius - _prevSlice * (i + 1);
+        if (prevArcData[i][j].innerRadius < 0)
+          prevArcData[i][j].innerRadius = 0;
+        prevArcData[i][j].outerRadius = outerRadius - _prevSlice * i;
+        if (prevArcData[i][j].outerRadius < prevArcData[i][j].innerRadius)
+          prevArcData[i][j].outerRadius = prevArcData[i][j].innerRadius;
+      }
+    }
+
+    List<List> arcData = new List<List>();
+    for (int i = 0; i < rows.length; i++) {
+      arcData.add(layout.layout(rows[i]));
+      for (int j = 0; j < rows[i].length; j++) {
+        arcData[i][j].innerRadius = outerRadius - sliceRadius * (i + 1) + 0.5;
+        arcData[i][j].outerRadius = outerRadius - sliceRadius * i;
+      }
+    }
+
+    var pie = group.selectAll('.pie-path')
+        .dataWithCallback((d, i, c) => prevArcData[i]);
+    pie.enter.append('path')
+        ..classed('pie-path')
+        ..attrWithCallback('fill', (d, i, e) => colorForKey(i))
+        ..attrWithCallback('d', (d, i, e) {
+          return arc.path(d, i, host);
+        })
+        ..attr('stroke-width', '1px')
+        ..style('stroke', "#ffffff");
+
+    pie.dataWithCallback((d, i, c) => arcData[i]);
+    pie.transition()
+      ..attrWithCallback('fill', (d, i, e) => colorForKey(i))
+      ..attrTween('d', (d, i, e) {
+          int o = ((outerRadius - d.outerRadius) / sliceRadius).round();
+          return (t) => arc.path(interpolateSvgArcData(
+            prevArcData[o][i], arcData[o][i])(t), i, host);
+        })
+      ..duration(theme.transitionDuration);
+    pie
+      ..on('click', (d, i, e) => _event(mouseClickController, d, i, e))
+      ..on('mouseover', (d, i, e) => _event(mouseOverController, d, i, e))
+      ..on('mouseout', (d, i, e) => _event(mouseOutController, d, i, e));
+    pie.exit.remove();
+
+    for (int i = 0; i < rows.length; i++) {
+      for (int j = 0; j < rows[i].length; j++)
+        _prevRows[i][j] = rows[i][j];
+    }
+
+    _prevSlice = sliceRadius;
+
+    List total = new List();
+    rows.forEach((d) {
+      var sum = 0;
+      d.forEach((e) => sum += e);
+      total.add(sum);
+    });
+
+    var ic = -1,
+        order = 0;
+    var statistic = group.selectAll('.statistic')
+        .dataWithCallback((d, i, c) => arcData[i]);
+
+    statistic.enter.append('text')
+        ..classed('statistic')
+        ..style('fill', 'white')
+        ..attrWithCallback('transform', (d, i, c) {
+          var offsets = arc.centroid(d, i, c);
+          return 'translate(${offsets[0]}, ${offsets[1]})';
+        })
+        ..attr('dy', '.35em')
+        ..style('text-anchor', 'middle');
+
+    statistic
+        ..textWithCallback((d, i, e) {
+          if (i <= ic) order++;
+          ic = i;
+          return _processSliceText(d.data, total[order]);
+        })
+        ..attr('opacity', '0')
+        ..style('pointer-events', 'none')
+        ..attrWithCallback('transform', (d, i, c) {
+          var offsets = arc.centroid(d, i, c);
+          return 'translate(${offsets[0]}, ${offsets[1]})';
+        });
+
+    statistic.transition()
+        ..attr('opacity', '1')
+        ..delay(theme.transitionDuration)
+        ..duration(theme.transitionDuration);
+
+    statistic.exit.remove();
+  }
+
+  @override
+  void dispose() {
+    if (root == null) return;
+    root.selectAll('.row-group').remove();
+  }
+
+  String _processSliceText(value, total) {
+    var significant = value * 100 / total >= 5;
+    if (statsMode == STATS_PERCENTAGE) {
+      return (significant) ? '${(value * 100 / total).toStringAsFixed(0)}%': '';
+    } else if (statsMode == STATS_VALUE) {
+      return (significant) ? value.toString() : '';
+    } else {
+      return (significant) ?
+          '${value} (${(value * 100 / total).toStringAsFixed(0)}%)': '';
+    }
+  }
+
+  @override
+  double get bandInnerPadding => 0.0;
+
+  @override
+  double get bandOuterPadding => 0.0;
+
+  @override
+  Extent get extent => const Extent(0, 100);
+
+  void _event(StreamController controller, data, int index, Element e) {
+     if (controller == null) return;
+     var rowStr = e.parent.dataset['row'];
+     var row = rowStr != null ? int.parse(rowStr) : null;
+     controller.add(
+         new _ChartEvent(scope.event, area, series, row, index, data.value));
+   }
+}
diff --git a/pub/charted/lib/charts/renderers/stackedbar_chart_renderer.dart b/pub/charted/lib/charts/renderers/stackedbar_chart_renderer.dart
new file mode 100644
index 0000000..0edd82d
--- /dev/null
+++ b/pub/charted/lib/charts/renderers/stackedbar_chart_renderer.dart
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+part of charted.charts;
+
+class StackedBarChartRenderer extends BaseRenderer {
+  final Iterable<int> dimensionsUsingBand = const[0];
+
+  /*
+   * Returns false if the number of dimension axes on the area is 0.
+   * Otherwise, the first dimension scale is used to render the chart.
+   */
+  @override
+  bool prepare(ChartArea area, ChartSeries series) {
+    _ensureAreaAndSeries(area, series);
+    return area.dimensionAxesCount != 0;
+  }
+
+  @override
+  void draw(Element element) {
+    _ensureReadyToDraw(element);
+
+    var measuresCount = series.measures.length,
+    measureScale = area.measureScales(series).first,
+    dimensionScale = area.dimensionScales.first;
+
+    var rows = new List()
+      ..addAll(area.data.rows.map((e) {
+      var row = [];
+      for (var i = series.measures.length - 1; i >= 0; i--) {
+        row.add(e[series.measures.elementAt(i)]);
+      }
+      return row;
+    }));
+
+    // We support only one dimension, so always use the first one.
+    var x = area.data.rows.map(
+            (row) => row.elementAt(area.config.dimensions.first)).toList();
+
+    var group = root.selectAll('.row-group').data(rows);
+    group.enter.append('g')
+      ..classed('row-group')
+      ..attrWithCallback('transform', (d, i, c) =>
+    'translate(${dimensionScale.apply(x[i])}, 0)');
+    group.exit.remove();
+
+    group.transition()
+      ..attrWithCallback('transform', (d, i, c) =>
+    'translate(${dimensionScale.apply(x[i])}, 0)')
+      ..duration(theme.transitionDuration)
+      ..attrWithCallback('data-row', (d, i, e) => i);
+
+    /* TODO(prsd): Handle cases where x and y axes are swapped */
+    var bar = group.selectAll('.bar').dataWithCallback((d, i, c) => rows[i]);
+
+    var ic = -1,
+    order = 0,
+    prevY = new List();
+
+    prevY.add(0);
+    bar.each((d, i, e) {
+      if (i > ic) {
+        prevY[prevY.length - 1] = e.attributes['y'];
+      } else {
+        prevY.add(e.attributes['y']);
+      }
+      ic = i;
+    });
+
+    ic = 1000000000;
+    var enter = bar.enter.append('rect')
+      ..classed('bar')
+      ..styleWithCallback('fill', (d, i, c) => colorForKey(_reverseIdx(i)))
+      ..attr('width', dimensionScale.rangeBand - theme.defaultStrokeWidth)
+      ..attrWithCallback('y', (d, i, c) {
+      var tempY;
+      if (i <= ic && i > 0) {
+        tempY = prevY[order];
+        order++;
+      } else {
+        tempY = rect.height;
+      }
+      ic = i;
+      return tempY;
+    })
+      ..attr('height', 0)
+      ..on('click', (d, i, e) => _event(mouseClickController, d, i, e))
+      ..on('mouseover', (d, i, e) => _event(mouseOverController, d, i, e))
+      ..on('mouseout', (d, i, e) => _event(mouseOutController, d, i, e));
+
+    bar.transition()
+      ..styleWithCallback('fill', (d, i, c) => colorForKey(_reverseIdx(i)))
+      ..attr('width', dimensionScale.rangeBand - theme.defaultStrokeWidth)
+      ..duration(theme.transitionDuration);
+
+    var y = 0,
+    length = bar.length,
+    // Keeps track of heights of previously graphed bars. If all bars before
+    // current one have 0 height, the current bar doesn't need offset.
+    prevAllZeroHeight = true,
+    // Keeps track of the offset already exist in the previous bar, when the
+    // computed bar height is less than (theme.defaultSeparatorWidth +
+    // theme.defaultStrokeWidth), this height is already discounted, so the
+    // next bar's offset in height can be this much less than normal.
+    prevOffset = 0;
+
+    bar.transition()
+      ..attrWithCallback('y', (d, i, c) {
+      if (i == 0) y = measureScale.apply(0).round();
+      return (y -= (rect.height - measureScale.apply(d).round()));
+    })
+      ..attrWithCallback('height', (d, i, c) {
+      var ht = rect.height - measureScale.apply(d).round();
+      if (i != 0) {
+        // If previous bars has 0 height, don't offset for spacing
+        // If any of the previous bar has non 0 height, do the offset.
+        ht -= prevAllZeroHeight ? 1 :
+        (theme.defaultSeparatorWidth + theme.defaultStrokeWidth);
+        ht += prevOffset;
+      } else {
+        // When rendering next group of bars, reset prevZeroHeight.
+        prevOffset = 0;
+        prevAllZeroHeight = true;
+        ht -= 1;
+        // -1 so bar does not overlap x axis.
+      }
+      if (ht <= 0) {
+        prevOffset = prevAllZeroHeight ? 0 :
+        (theme.defaultSeparatorWidth + theme.defaultStrokeWidth) + ht;
+        ht = 0;
+      }
+      prevAllZeroHeight = (ht == 0) && prevAllZeroHeight;
+      return ht;
+    })
+      ..duration(theme.transitionDuration)
+      ..delay(50);
+
+    if (theme.defaultStrokeWidth > 0) {
+      enter.attr('stroke-width', '${theme.defaultStrokeWidth}px');
+      enter.styleWithCallback('stroke', (d, i, c) =>
+          colorForKey(_reverseIdx(i)));
+      bar.transition()
+        ..styleWithCallback('stroke', (d, i, c) => colorForKey(_reverseIdx(i)));
+    }
+
+    bar.exit.remove();
+  }
+
+  @override
+  double get bandInnerPadding =>
+      area.theme.dimensionAxisTheme.axisBandInnerPadding;
+
+  @override
+  Extent get extent {
+    assert(area != null && series != null);
+    var rows = area.data.rows,
+    max = rows[0][series.measures.first],
+    min = max;
+
+    rows.forEach((row) {
+      if (row[series.measures.first] < min)
+        min = row[series.measures.first];
+
+      var bar = 0;
+      series.measures.forEach((idx) {
+        bar += row[idx];
+      });
+      if (bar > max) max = bar;
+    });
+
+    return new Extent(min, max);
+  }
+
+  void _event(StreamController controller, data, int index, Element e) {
+    if (controller == null) return;
+    var rowStr = e.parent.dataset['row'];
+    var row = rowStr != null ? int.parse(rowStr) : null;
+    controller.add(new _ChartEvent(
+        scope.event, area, series, row, _reverseIdx(index), data));
+  }
+
+  // Because waterfall bar chart render the measures in reverse order to match
+  // the legend, we need to reverse the index for color and event.
+  int _reverseIdx(int index) => series.measures.length - 1 - index;
+}
diff --git a/pub/charted/lib/charts/renderers/waterfall_chart_renderer.dart b/pub/charted/lib/charts/renderers/waterfall_chart_renderer.dart
new file mode 100644
index 0000000..d169bc6
--- /dev/null
+++ b/pub/charted/lib/charts/renderers/waterfall_chart_renderer.dart
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2015 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+part of charted.charts;
+
+class WaterfallChartRenderer extends BaseRenderer {
+  final Iterable<int> dimensionsUsingBand = const[0];
+
+  /*
+   * Returns false if the number of dimension axes on the area is 0.
+   * Otherwise, the first dimension scale is used to render the chart.
+   */
+  @override
+  bool prepare(ChartArea area, ChartSeries series) {
+    _ensureAreaAndSeries(area, series);
+    return area.dimensionAxesCount != 0 && area.data is WaterfallChartData;
+  }
+
+  @override
+  void draw(Element element) {
+    _ensureReadyToDraw(element);
+
+    var measuresCount = series.measures.length,
+    measureScale = area.measureScales(series).first,
+    dimensionScale = area.dimensionScales.first;
+
+    // We support only one dimension, so always use the first one.
+    var x = area.data.rows.map(
+            (row) => row.elementAt(area.config.dimensions.first)).toList();
+
+    List<Iterable> rows = new List()
+      ..addAll(area.data.rows.map((e) {
+      var row = [];
+      for (var i = measuresCount - 1; i >= 0; i--) {
+        row.add(e[series.measures.elementAt(i)]);
+      }
+      return row;
+    }));
+
+    // Pre-compute shift value on y-axis for non-base rows
+    var yShift = new List(),
+        runningTotal = 0;
+    for (int i = 0; i < rows.length; i++) {
+      var row = rows[i];
+      if (_isBaseRow(i)) {
+        runningTotal = 0;
+      }
+      yShift.add(runningTotal);
+      var bar = 0;
+      row.forEach((value) => bar += value);
+      runningTotal += bar;
+
+      // Handle Nagative incremental values:
+      if (row.any((value) => value < 0)) {
+        assert(row.every((value) => value <= 0));
+        for (int j = 0; j < row.length; j++) {
+          row[j] = 0 - row[j];
+        }
+        yShift[yShift.length - 1] += bar;
+      }
+    }
+
+    var group = root.selectAll('.row-group').data(rows);
+    group.enter.append('g')
+      ..classed('row-group')
+      ..attrWithCallback('transform', (d, i, c) =>
+          'translate(${dimensionScale.apply(x[i])},'
+          '${measureScale.apply(yShift[i]).round() - rect.height})');
+    group.exit.remove();
+
+    group.transition()
+      ..attrWithCallback('transform', (d, i, c) =>
+          'translate(${dimensionScale.apply(x[i])},'
+          '${measureScale.apply(yShift[i]).round() - rect.height})')
+      ..duration(theme.transitionDuration)
+      ..attrWithCallback('data-row', (d, i, e) => i);
+
+    /* TODO(prsd): Handle cases where x and y axes are swapped */
+    var bar = group.selectAll('.bar').dataWithCallback((d, i, c) => rows[i]);
+
+    var ic = -1,
+    order = 0,
+    prevY = new List()..add(0);
+
+    bar.each((d, i, e) {
+      if (i > ic) {
+        prevY[prevY.length - 1] = e.attributes['y'];
+      } else {
+        prevY.add(e.attributes['y']);
+      }
+      ic = i;
+    });
+
+    ic = 1000000000;
+    var enter = bar.enter.append('rect')
+      ..classed('bar')
+      ..styleWithCallback('fill', (d, i, c) => colorForKey(_reverseIdx(i)))
+      ..attr('width', dimensionScale.rangeBand - theme.defaultStrokeWidth)
+      ..attrWithCallback('y', (d, i, c) {
+      var tempY;
+      if (i <= ic && i > 0) {
+        tempY = prevY[order];
+        order++;
+      } else {
+        tempY = rect.height;
+      }
+      ic = i;
+      return tempY;
+    })
+      ..attr('height', 0)
+      ..on('click', (d, i, e) => _event(mouseClickController, d, i, e))
+      ..on('mouseover', (d, i, e) => _event(mouseOverController, d, i, e))
+      ..on('mouseout', (d, i, e) => _event(mouseOutController, d, i, e));
+
+    bar.transition()
+      ..styleWithCallback('fill', (d, i, c) => colorForKey(_reverseIdx(i)))
+      ..attr('width', dimensionScale.rangeBand - theme.defaultStrokeWidth)
+      ..duration(theme.transitionDuration);
+
+    var y = 0,
+    length = bar.length,
+    // Keeps track of heights of previously graphed bars. If all bars before
+    // current one have 0 height, the current bar doesn't need offset.
+    prevAllZeroHeight = true,
+    // Keeps track of the offset already exist in the previous bar, when the
+    // computed bar height is less than (theme.defaultSeparatorWidth +
+    // theme.defaultStrokeWidth), this height is already discounted, so the
+    // next bar's offset in height can be this much less than normal.
+    prevOffset = 0;
+
+    bar.transition()
+      ..attrWithCallback('y', (d, i, c) {
+      if (i == 0) y = measureScale.apply(0).round();
+      return (y -= (rect.height - measureScale.apply(d).round()));
+    })
+      ..attrWithCallback('height', (d, i, c) {
+      var ht = rect.height - measureScale.apply(d).round();
+      if (i != 0) {
+        // If previous bars has 0 height, don't offset for spacing
+        // If any of the previous bar has non 0 height, do the offset.
+        ht -= prevAllZeroHeight ? 1 :
+        (theme.defaultSeparatorWidth + theme.defaultStrokeWidth);
+        ht += prevOffset;
+      } else {
+        // When rendering next group of bars, reset prevZeroHeight.
+        prevOffset = 0;
+        prevAllZeroHeight = true;
+        ht -= 1;
+        // -1 so bar does not overlap x axis.
+      }
+      if (ht <= 0) {
+        prevOffset = prevAllZeroHeight ? 0 :
+        (theme.defaultSeparatorWidth + theme.defaultStrokeWidth) + ht;
+        ht = 0;
+      }
+      prevAllZeroHeight = (ht == 0) && prevAllZeroHeight;
+      return ht;
+    })
+      ..duration(theme.transitionDuration)
+      ..delay(50);
+
+    if (theme.defaultStrokeWidth > 0) {
+      enter.attr('stroke-width', '${theme.defaultStrokeWidth}px');
+      enter.styleWithCallback('stroke', (d, i, c) =>
+          colorForKey(_reverseIdx(i)));
+      bar.transition()
+        ..styleWithCallback('stroke', (d, i, c) => colorForKey(_reverseIdx(i)));
+    }
+
+    bar.exit.remove();
+  }
+
+  @override
+  double get bandInnerPadding =>
+      area.theme.dimensionAxisTheme.axisBandInnerPadding;
+
+  @override
+  Extent get extent {
+    assert(area != null && series != null);
+    var rows = area.data.rows,
+    max = rows[0][series.measures.first],
+    min = max,
+    runningTotal = 0;
+
+    for (int i = 0; i < rows.length; i++) {
+      var row = rows[i];
+      if (_isBaseRow(i)) {
+        runningTotal = 0;
+      }
+      series.measures.forEach((idx) {
+        runningTotal += row[idx];
+      });
+      if (runningTotal > max) max = runningTotal;
+      if (runningTotal < min) min = runningTotal;
+    }
+    return new Extent(min, max);
+  }
+
+  void _event(StreamController controller, data, int index, Element e) {
+    if (controller == null) return;
+    var rowStr = e.parent.dataset['row'];
+    var row = rowStr != null ? int.parse(rowStr) : null;
+    controller.add(new _ChartEvent(
+        scope.event, area, series, row, _reverseIdx(index), data));
+  }
+
+  // Because waterfall bar chart render the measures in reverse order to match
+  // the legend, we need to reverse the index for color and event.
+  int _reverseIdx(int index) => series.measures.length - 1 - index;
+
+  bool _isBaseRow(int index) =>
+      area.data is WaterfallChartData ?
+          (area.data as WaterfallChartData).baseRows.contains(index) : false;
+}
diff --git a/pub/charted/lib/charts/src/chart_area_impl.dart b/pub/charted/lib/charts/src/chart_area_impl.dart
new file mode 100644
index 0000000..995a3f5
--- /dev/null
+++ b/pub/charted/lib/charts/src/chart_area_impl.dart
@@ -0,0 +1,651 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+part of charted.charts;
+
+/**
+ * Generic implementation of ChartArea.
+ *
+ * Assumes that the chart displays either one or two dimension axes and zero
+ * or more measure axis.  The number of measure axes displayed is zero in charts
+ * similar to bubble chart - the number of dimension axes is two.
+ *
+ * The primary dimension axes is always at the bottom, the primary measure axis
+ * is always on the right.
+ */
+class _ChartArea implements ChartArea {
+  static const List MEASURE_AXIS_IDS = const['_default'];
+  static const List DIMENSION_AXIS_IDS = const['_primary', '_secondary'];
+
+  static const List MEASURE_AXIS_ORIENTATIONS =
+      const[ORIENTATION_LEFT, ORIENTATION_RIGHT];
+  static const List MEASURE_AXIS_ORIENTATIONS_ALT =
+      const[ORIENTATION_BOTTOM, ORIENTATION_TOP];
+
+  static const List DIMENSION_AXIS_ORIENTATIONS =
+      const[ORIENTATION_BOTTOM, ORIENTATION_LEFT];
+  static const List DIMENSION_AXIS_ORIENTATIONS_ALT =
+      const[ORIENTATION_LEFT, ORIENTATION_BOTTOM];
+
+  static const int MEASURE_AXES_COUNT = 2;
+  static const int MARGIN = 10;
+
+  final LinkedHashMap<String, _ChartAxis> _measureAxes = new LinkedHashMap();
+  final LinkedHashMap<int, _ChartAxis> _dimensionAxes = new LinkedHashMap();
+  final HashSet<int> dimensionsUsingBands = new HashSet();
+  final SubscriptionsDisposer _dataEventsDisposer = new SubscriptionsDisposer();
+  final SubscriptionsDisposer _configEventsDisposer =
+      new SubscriptionsDisposer();
+
+  final Element _host;
+
+  List<ChartBehavior> _behaviors = new List();
+  Map<ChartSeries, _ChartSeriesInfo> _seriesInfoCache = new Map();
+
+  ChartTheme theme;
+  bool autoUpdate = false;
+
+  ChartData _data;
+  ChartConfig _config;
+  int _dimensionAxesCount;
+
+  _ChartAreaLayout layout = new _ChartAreaLayout();
+  SelectionScope _scope;
+  Selection _svg;
+  Selection _group;
+  Iterable<ChartSeries> _series;
+  bool _pendingLegendUpdate = false;
+
+  StreamController<ChartEvent> _valueMouseOverController;
+  StreamController<ChartEvent> _valueMouseOutController;
+  StreamController<ChartEvent> _valueMouseClickController;
+
+  @override
+  Element upperBehaviorPane;
+
+  @override
+  Element lowerBehaviorPane;
+
+  _ChartArea(Element this._host, ChartData data, ChartConfig config,
+      bool this.autoUpdate, this._dimensionAxesCount) {
+    assert(_host != null);
+    assert(isNotInline(_host));
+
+    this.data = data;
+    this.config = config;
+    theme = ChartTheme.current;
+
+
+    Transition.defaultEasingType = theme.transitionEasingType;
+    Transition.defaultEasingMode = theme.transitionEasingMode;
+    Transition.defaultDuration = theme.transitionDuration;
+  }
+
+  void dispose() {
+    _configEventsDisposer.dispose();
+    _dataEventsDisposer.dispose();
+  }
+
+  static bool isNotInline(Element e) =>
+      e != null && e.getComputedStyle().display != 'inline';
+
+  Element get host => _host;
+
+  /*
+   * If [value] is [Observable], subscribes to changes and updates the
+   * chart when data changes.
+   */
+  @override
+  set data(ChartData value) {
+    _data = value;
+    _dataEventsDisposer.dispose();
+
+    if (autoUpdate && _data != null && _data is Observable) {
+      _dataEventsDisposer.add((_data as Observable).changes.listen((_) {
+        _pendingLegendUpdate = (_data is TransposeTransformer);
+        draw();
+      }));
+    }
+  }
+
+  @override
+  ChartData get data => _data;
+
+  /*
+   * If [value] is [Observable], subscribes to changes and updates the
+   * chart when series or dimensions change in configuration.
+   */
+  @override
+  set config(ChartConfig value) {
+    _config = value;
+    _configEventsDisposer.dispose();
+    _pendingLegendUpdate = true;
+
+    if (_config != null) {
+      _configEventsDisposer.add((_config as Observable).changes.listen((_) {
+        _pendingLegendUpdate = true;
+        draw();
+      }));
+    }
+  }
+
+  @override
+  ChartConfig get config => _config;
+
+  /*
+   * Number of dimension axes displayed in this chart.
+   */
+  @override
+  set dimensionAxesCount(int count) {
+    _dimensionAxesCount = count;
+    if (autoUpdate) draw();
+  }
+
+  @override
+  int get dimensionAxesCount => _dimensionAxesCount;
+
+  /*
+   * Gets measure axis from cache - creates a new instance of _ChartAxis
+   * if one was not already created for the given axis [id].
+   */
+  _ChartAxis _getMeasureAxis(String id) {
+    _measureAxes.putIfAbsent(id, () {
+      var axisConf = config.getMeasureAxis(id),
+          axis = axisConf != null ?
+              new _ChartAxis.withAxisConfig(this, axisConf) :
+                  new _ChartAxis(this);
+      return axis;
+    });
+    return _measureAxes[id];
+  }
+
+  /*
+   * Gets a dimension axis from cache - creates a new instance of _ChartAxis
+   * if one was not already created for the given dimension [column].
+   */
+  _ChartAxis _getDimensionAxis(int column) {
+    _dimensionAxes.putIfAbsent(column, () {
+      var axisConf = config.getDimensionAxis(column),
+          axis = axisConf != null ?
+              new _ChartAxis.withAxisConfig(this, axisConf) :
+                  new _ChartAxis(this);
+      return axis;
+    });
+    return _dimensionAxes[column];
+  }
+
+  /*
+   * All columns rendered by a series must be of the same type and the
+   * series must use a renderer that supports current [ChartArea]
+   * configuration.
+   */
+  bool _isSeriesValid(ChartSeries s) {
+    var first = data.columns.elementAt(s.measures.first).type;
+    return s.measures.every((i) =>
+        (i < data.columns.length) && data.columns.elementAt(i).type == first);
+  }
+
+  /*
+   * Indicates if the given ChartSeries needs an ordinal scale
+   */
+  bool _isOrdinalColumn(column) =>
+      column.useOrdinalScale == true ||
+          (column.useOrdinalScale == null &&
+              ChartColumnSpec.ORDINAL_SCALES.contains(column.type));
+
+  /*
+   * Get a list of dimension scales for this chart.
+   */
+  @override
+  Iterable<Scale> get dimensionScales =>
+      config.dimensions.map((int column) => _getDimensionAxis(column).scale);
+
+  /*
+   * Get a list of scales used by [series]
+   */
+  @override
+  Iterable<Scale> measureScales(ChartSeries series) {
+    var axisIds = isNullOrEmpty(series.measureAxisIds) ?
+        MEASURE_AXIS_IDS : series.measureAxisIds;
+    return axisIds.map((String id) => _getMeasureAxis(id).scale);
+  }
+
+  /*
+   * Computes the size of chart and if changed from the previous time
+   * size was computed, sets attributes on svg element
+   */
+  Rect _computeChartSize() {
+    int width = host.clientWidth,
+        height = host.clientHeight;
+
+    if (config.minimumSize != null) {
+      width = max([width, config.minimumSize.width]);
+      height = max([height, config.minimumSize.height]);
+    }
+
+    Rect current =
+        new Rect(MARGIN, MARGIN, width - 2 * MARGIN, height - 2 * MARGIN);
+    if (layout.chartArea == null || layout.chartArea != current) {
+      _svg.attr('width', width.toString());
+      _svg.attr('height', height.toString());
+      _group.attr('transform', 'translate($MARGIN, $MARGIN)');
+      layout.chartArea = current;
+    }
+    return layout.chartArea;
+  }
+
+  @override
+  draw() {
+    assert(data != null && config != null);
+    assert(config.series != null && config.series.isNotEmpty);
+
+    /* Create SVG element and other one-time initializations. */
+    if (_scope == null) {
+      _scope = new SelectionScope.element(host);
+      _svg = _scope.append('svg:svg')..classed('charted-chart');
+      _group = _svg.append('g')..classed('chart-wrapper');
+
+      /* Create groups for behaviors to add any SVG elements */
+      var lower = _group.append('g')..classed('lower-render-pane'),
+          upper = _group.append('g')..classed('upper-first-pane');
+
+      lowerBehaviorPane = lower.first;
+      upperBehaviorPane = upper.first;
+      if (_behaviors.isNotEmpty) {
+        _behaviors.forEach(
+            (b) => b.init(this, upperBehaviorPane, lowerBehaviorPane));
+      }
+    }
+
+    /* Compute sizes and filter out unsupported series */
+    var size = _computeChartSize(),
+        series = config.series.where((s) =>
+            _isSeriesValid(s) && s.renderer.prepare(this, s)),
+        selection = _group.selectAll('.series-group').
+            data(series, (x) => x.hashCode),
+        axesDomainCompleter = new Completer();
+
+    /*
+     * Wait till the axes are rendered before rendering series.
+     * In an SVG, z-index is based on the order of nodes in the DOM.
+     */
+    axesDomainCompleter.future.then((_) {
+      /* If a series was not rendered before, add an SVG group for it */
+      selection.enter.append('svg:g')
+          ..classed('series-group');
+
+      /* For all series recompute axis ranges and update the rendering */
+      var transform =
+          'translate(${layout.renderArea.x},${layout.renderArea.y})';
+      selection.each((ChartSeries s, _, Element group) {
+        var info = _seriesInfoCache[s];
+        if (info == null) {
+          info = _seriesInfoCache[s] = new _ChartSeriesInfo(this, s);
+        }
+        info.check();
+        group.attributes['transform'] = transform;
+        s.renderer.draw(group);
+      });
+
+      /* A series that was rendered earlier isn't there anymore, remove it */
+      selection.exit
+          ..each((ChartSeries s, _, __) {
+            var info = _seriesInfoCache[s];
+            if (info != null) info.dispose();
+            _seriesInfoCache.remove(s);
+          })
+          ..remove();
+    });
+
+    // Save the list of valid series for use with legend and axes.
+    _series = series;
+
+    // If we have atleast one dimension axis, render the axes.
+    if (dimensionAxesCount != 0) {
+      _initAxes();
+    } else {
+      _computeLayoutWithoutAxes();
+    }
+
+    // Render the chart, now that the axes layer is already in DOM.
+    axesDomainCompleter.complete();
+
+    // Updates the legend if required.
+    _updateLegend();
+  }
+
+  _initAxes() {
+    var measureAxisUsers = {};
+
+    /* Create necessary measures axes */
+    _series.forEach((ChartSeries s) {
+      var ids = isNullOrEmpty(s.measureAxisIds) ?
+          MEASURE_AXIS_IDS : s.measureAxisIds;
+      ids.forEach((id) {
+        var axis = _getMeasureAxis(id),
+            users = measureAxisUsers[id];
+        if (users == null) {
+          measureAxisUsers[id] = [s];
+        } else {
+          users.add(s);
+        }
+      });
+    });
+
+    /* Configure measure axes */
+    measureAxisUsers.forEach((id, listOfSeries) {
+      var sampleCol = listOfSeries.first.measures.first,
+          sampleColSpec = data.columns.elementAt(sampleCol),
+          axis = _getMeasureAxis(id),
+          domain;
+
+      if (sampleColSpec.useOrdinalScale) {
+        /* TODO(prsd): Ordinal measure scale */
+      } else {
+        var lowest = min(listOfSeries.map((s) => s.renderer.extent.min)),
+            highest = max(listOfSeries.map((s) => s.renderer.extent.max));
+
+        // Use default domain if lowest and highest are the same, right now
+        // lowest is always 0, change to lowest when we make use of it.
+        domain = (highest != 0) ? [0, highest] : [0, 1];
+      }
+      axis.initAxisDomain(sampleCol, false, domain);
+    });
+
+    /* Configure dimension axes */
+    config.dimensions.take(dimensionAxesCount).forEach((int column) {
+       var axis = _getDimensionAxis(column),
+           sampleColumnSpec = data.columns.elementAt(column),
+           values = data.rows.map((row) => row.elementAt(column)),
+           domain;
+
+       if (sampleColumnSpec.useOrdinalScale) {
+         domain = values.map((e) => e.toString()).toList();
+       } else {
+         var extent = new Extent.items(values);
+         domain = [extent.min, extent.max];
+       }
+       axis.initAxisDomain(column, true, domain);
+    });
+
+    /* Build a list of dimension axes that use range bands */
+    dimensionsUsingBands.clear();
+    _series.forEach((ChartSeries s) =>
+        dimensionsUsingBands.addAll(s.renderer.dimensionsUsingBand.map((i) =>
+            config.dimensions.elementAt(i))));
+
+    /* List of measure and dimension axes that are displayed */
+    var measureAxesCount = dimensionAxesCount == 1 ? MEASURE_AXES_COUNT : 0,
+        displayedMeasureAxes = (config.displayedMeasureAxes == null ?
+            _measureAxes.keys.take(measureAxesCount) :
+                config.displayedMeasureAxes.take(measureAxesCount)).
+                    toList(growable:false),
+        displayedDimensionAxes =
+            config.dimensions.take(dimensionAxesCount).toList(growable:false);
+
+    /* Compute size of the dimension axes */
+    if (config.renderDimensionAxes != false) {
+      var dimensionAxisOrientations = config.leftAxisIsPrimary ?
+          DIMENSION_AXIS_ORIENTATIONS_ALT : DIMENSION_AXIS_ORIENTATIONS;
+      displayedDimensionAxes.asMap().forEach((int index, int column) {
+        var axis = _dimensionAxes[column],
+            orientation = dimensionAxisOrientations[index];
+        axis.prepareToDraw(orientation, theme.dimensionAxisTheme);
+        layout._axes[orientation] = axis.size;
+      });
+    }
+
+    /* Compute size of the measure axes */
+    if (displayedMeasureAxes.isNotEmpty) {
+      var measureAxisOrientations = config.leftAxisIsPrimary ?
+          MEASURE_AXIS_ORIENTATIONS_ALT : MEASURE_AXIS_ORIENTATIONS;
+      displayedMeasureAxes.asMap().forEach((int index, String key) {
+        var axis = _measureAxes[key],
+            orientation = measureAxisOrientations[index];
+        axis.prepareToDraw(orientation, theme.measureAxisTheme);
+        layout._axes[orientation] = axis.size;
+      });
+    }
+
+    _computeLayoutWithAxes();
+
+    /* Initialize output range on the invisible measure axes */
+    if (_measureAxes.length != displayedMeasureAxes.length) {
+      _measureAxes.keys.forEach((String axisId) {
+        if (displayedMeasureAxes.contains(axisId)) return;
+        _getMeasureAxis(axisId).initAxisScale(
+            [layout.renderArea.height, 0], theme.measureAxisTheme);
+      });
+    }
+
+    /* Display measure axes if we need to */
+    if (displayedMeasureAxes.isNotEmpty) {
+      var axisGroups =
+          _group.selectAll('.measure-group').data(displayedMeasureAxes);
+
+      /* Update measure axis (add/remove/update) */
+      axisGroups.enter.append('svg:g');
+      axisGroups
+          ..each((axisId, index, group) {
+              _getMeasureAxis(axisId).draw(group);
+              group.classes.clear();
+              group.classes.addAll(['measure-group','measure-${index}']);
+            });
+      axisGroups.exit.remove();
+    }
+
+    if (config.renderDimensionAxes != false) {
+      /* Display the dimension axes */
+      var dimAxisGroups =
+              _group.selectAll('.dim-group').data(displayedDimensionAxes);
+
+      /* Update dimension axes (add new / remove old / update remaining) */
+      dimAxisGroups.enter.append('svg:g');
+      dimAxisGroups
+          ..each((column, index, group) {
+              _getDimensionAxis(column).draw(group);
+              group.classes.clear();
+              group.classes.addAll(['dim-group', 'dim-${index}']);
+            });
+      dimAxisGroups.exit.remove();
+    } else {
+      /* Initialize output range on the invisible axis */
+      var dimensionAxisOrientations = config.leftAxisIsPrimary ?
+          DIMENSION_AXIS_ORIENTATIONS_ALT : DIMENSION_AXIS_ORIENTATIONS;
+      for (int i = 0; i < dimensionAxesCount; i++) {
+        var column = config.dimensions.elementAt(i),
+            axis = _dimensionAxes[column],
+            orientation = dimensionAxisOrientations[i];
+        axis.initAxisScale(orientation == ORIENTATION_LEFT ?
+            [layout.renderArea.height, 0] : [0, layout.renderArea.width],
+            theme.dimensionAxisTheme);
+      };
+    }
+  }
+
+  _computeLayoutWithoutAxes() {
+    layout.renderArea =
+        new Rect(0, 0, layout.chartArea.width, layout.chartArea.height);
+  }
+
+  /* Compute chart render area size and positions of all elements */
+  _computeLayoutWithAxes() {
+    var topAxis = layout.axes[ORIENTATION_TOP],
+        leftAxis = layout.axes[ORIENTATION_LEFT],
+        bottomAxis = layout.axes[ORIENTATION_BOTTOM],
+        rightAxis = layout.axes[ORIENTATION_RIGHT],
+        renderAreaHeight = layout.chartArea.height -
+            (topAxis.height + layout.axes[ORIENTATION_BOTTOM].height),
+        renderAreaWidth = layout.chartArea.width -
+            (leftAxis.width + layout.axes[ORIENTATION_RIGHT].width);
+
+    layout.renderArea = new Rect(
+        leftAxis.width, topAxis.height, renderAreaWidth, renderAreaHeight);
+
+    layout._axes
+      ..[ORIENTATION_TOP] =
+        new Rect(leftAxis.width, 0, renderAreaWidth, topAxis.height)
+      ..[ORIENTATION_RIGHT] =
+        new Rect(leftAxis.width + renderAreaWidth, topAxis.y,
+            rightAxis.width, renderAreaHeight)
+      ..[ORIENTATION_BOTTOM] =
+        new Rect(leftAxis.width, topAxis.height + renderAreaHeight,
+            renderAreaWidth, bottomAxis.height)
+      ..[ORIENTATION_LEFT] =
+        new Rect(leftAxis.width, topAxis.height,
+            leftAxis.width, renderAreaHeight);
+  }
+
+  /*
+   * Updates the legend, if configuration changed since the last
+   * time the legend was updated.
+   */
+  _updateLegend() {
+    if (!_pendingLegendUpdate) return;
+    if (_config == null || _config.legend == null || _series.isEmpty) return;
+
+    var legend = <ChartLegendItem>[];
+    List seriesByColumn =
+        new List.generate(data.columns.length, (i) => new List());
+
+    _series.forEach((s) =>
+        s.measures.forEach((m) => seriesByColumn[m].add(s)));
+
+    seriesByColumn.asMap().forEach((int i, List s) {
+      if (s.length == 0) return;
+      legend.add(new ChartLegendItem(
+          column:i, label:data.columns.elementAt(i).label, series:s,
+          color:theme.getColorForKey(i)));
+    });
+
+    _config.legend.update(legend, this);
+    _pendingLegendUpdate = false;
+  }
+
+  @override
+  Stream<ChartEvent> get onMouseUp =>
+      host.onMouseUp
+          .map((MouseEvent e) => new _ChartEvent(e, this));
+
+  @override
+  Stream<ChartEvent> get onMouseDown =>
+      host.onMouseDown
+          .map((MouseEvent e) => new _ChartEvent(e, this));
+
+  @override
+  Stream<ChartEvent> get onMouseOver =>
+      host.onMouseOver
+          .map((MouseEvent e) => new _ChartEvent(e, this));
+
+  @override
+  Stream<ChartEvent> get onMouseOut =>
+      host.onMouseOut
+          .map((MouseEvent e) => new _ChartEvent(e, this));
+
+  @override
+  Stream<ChartEvent> get onMouseMove =>
+      host.onMouseMove
+          .map((MouseEvent e) => new _ChartEvent(e, this));
+
+  @override
+  Stream<ChartEvent> get onValueClick {
+    if (_valueMouseClickController == null) {
+      _valueMouseClickController = new StreamController.broadcast(sync: true);
+    }
+    return _valueMouseClickController.stream;
+  }
+
+  @override
+  Stream<ChartEvent> get onValueMouseOver {
+    if (_valueMouseOverController == null) {
+      _valueMouseOverController = new StreamController.broadcast(sync: true);
+    }
+    return _valueMouseOverController.stream;
+  }
+
+  @override
+  Stream<ChartEvent> get onValueMouseOut {
+    if (_valueMouseOutController == null) {
+      _valueMouseOutController = new StreamController.broadcast(sync: true);
+    }
+    return _valueMouseOutController.stream;
+  }
+
+  @override
+  void addChartBehavior(ChartBehavior behavior) {
+    if (behavior == null || _behaviors.contains(behavior)) return;
+    _behaviors.add(behavior);
+    if (upperBehaviorPane != null && lowerBehaviorPane != null) {
+      behavior.init(this, upperBehaviorPane, lowerBehaviorPane);
+    }
+  }
+
+  @override
+  void removeChartBehavior(ChartBehavior behavior) {
+    if (behavior == null || !_behaviors.contains(behavior)) return;
+    if (upperBehaviorPane != null && lowerBehaviorPane != null) {
+      behavior.dispose();
+    }
+    _behaviors.remove(behavior);
+  }
+}
+
+class _ChartAreaLayout implements ChartAreaLayout {
+  @override
+  final _axes = <String, Rect>{
+        ORIENTATION_LEFT: const Rect(),
+        ORIENTATION_RIGHT: const Rect(),
+        ORIENTATION_TOP: const Rect(),
+        ORIENTATION_BOTTOM: const Rect()
+      };
+
+  UnmodifiableMapView<String, Rect> _axesView;
+
+  get axes => _axesView;
+
+  @override
+  Rect renderArea;
+
+  @override
+  Rect chartArea;
+
+  _ChartAreaLayout() {
+    _axesView = new UnmodifiableMapView(_axes);
+  }
+}
+
+class _ChartSeriesInfo {
+  ChartRenderer _renderer;
+  SubscriptionsDisposer _disposer = new SubscriptionsDisposer();
+
+  _ChartSeries _series;
+  _ChartArea _area;
+  _ChartSeriesInfo(this._area, this._series);
+
+  _event(StreamController controller, ChartEvent evt) {
+    if (controller == null) return;
+    controller.add(evt);
+  }
+
+  check() {
+    if (_renderer != _series.renderer) dispose();
+    _renderer = _series.renderer;
+    try {
+      _disposer.addAll([
+          _renderer.onValueMouseClick.listen(
+              (ChartEvent e) => _event(_area._valueMouseClickController, e)),
+          _renderer.onValueMouseOver.listen(
+              (ChartEvent e) => _event(_area._valueMouseOverController, e)),
+          _renderer.onValueMouseOut.listen(
+              (ChartEvent e) => _event(_area._valueMouseOutController, e))
+      ]);
+    } on UnimplementedError {};
+  }
+
+  dispose() => _disposer.dispose();
+}
diff --git a/pub/charted/lib/charts/src/chart_axis_impl.dart b/pub/charted/lib/charts/src/chart_axis_impl.dart
new file mode 100644
index 0000000..688f4a2
--- /dev/null
+++ b/pub/charted/lib/charts/src/chart_axis_impl.dart
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+part of charted.charts;
+
+class _ChartAxis {
+  static const List _VERTICAL_ORIENTATIONS =
+      const [ ORIENTATION_LEFT, ORIENTATION_RIGHT ];
+
+  _ChartArea area;
+  ChartAxisConfig config;
+  ChartAxisTheme _theme;
+
+  int _column;
+  Iterable _domain;
+  bool _isDimension;
+  ChartColumnSpec _columnSpec;
+
+  GElement _element;
+  SvgAxis _axis;
+
+  bool _isVertical;
+  String _orientation;
+  Scale _scale;
+  SelectionScope _scope;
+  Selection _group;
+
+  EditableRect size;
+
+  _ChartAxis.withAxisConfig(this.area, this.config);
+  _ChartAxis(this.area);
+
+  void initAxisDomain(int column, bool isDimension, Iterable domain) {
+    _columnSpec = area.data.columns.elementAt(column);
+    _column = column;
+    _domain = domain;
+    _isDimension = isDimension;
+    if (scale == null) _scale = _columnSpec.createDefaultScale();
+  }
+
+  void initAxisScale(Iterable range, ChartAxisTheme theme) {
+    assert(scale != null);
+
+    // Sets the domain if not using a custom scale.
+    if (config == null || (config != null && config.scale == null)) {
+      scale.domain = _domain;
+      scale.nice(theme.axisTickCount);
+    }
+
+    // Sets the range if not using a custom scale, or the custom scale uses
+    // default range.
+    /* TODO(prsd): Scale needs some cleanup */
+    if (scale is OrdinalScale) {
+      var usingBands = area.dimensionsUsingBands.contains(_column),
+          innerPadding = usingBands ? theme.axisBandInnerPadding : 1.0,
+          outerPadding = usingBands ?
+              theme.axisBandOuterPadding : theme.axisOuterPadding;
+      (scale as OrdinalScale).
+          rangeRoundBands(range, innerPadding, outerPadding);
+    } else {
+      scale.range = range;
+    }
+  }
+
+  void prepareToDraw(String orientation, ChartAxisTheme theme) {
+    if (orientation == null) orientation = ORIENTATION_BOTTOM;
+
+    _theme = theme;
+    _orientation = orientation;
+    _isVertical = _orientation == ORIENTATION_LEFT ||
+        _orientation == ORIENTATION_RIGHT;
+
+    if (false && _theme.axisAutoResize) {
+      /* TODO(prsd): Implement axis size computations */
+    } else {
+      var width = _isVertical ?
+          _theme.verticalAxisWidth : area.layout.chartArea.width;
+      var height = _isVertical ?
+          area.layout.chartArea.height : _theme.horizontalAxisHeight;
+      size = new EditableRect.size(width, height);
+    }
+  }
+
+  void draw(GElement element) {
+    assert(element != null && element is GElement);
+    assert(scale != null);
+
+    var rect = area.layout.axes[_orientation],
+        renderAreaRect = area.layout.renderArea,
+        range =  _isVertical ? [rect.height, 0] : [0, rect.width],
+        className = (_isVertical ? 'vertical-axis': 'horizontal-axis');
+
+    element.attributes['transform'] = 'translate(${rect.x}, ${rect.y})';
+
+    if (_axis == null || _element != element) {
+      _element = element;
+      _axis = new SvgAxis()
+          ..orientation = _orientation
+          ..suggestedTickCount = _theme.axisTickCount
+          ..tickPadding = _theme.axisTickPadding
+          ..outerTickSize = 0
+          ..tickFormat = _columnSpec.formatter;
+
+      if (config != null && config.tickValues != null) {
+        _axis.tickValues = config.tickValues;
+      }
+
+      _scope = new SelectionScope.element(_element);
+      _group = _scope.selectElements([_element]);
+    }
+    
+    _axis.innerTickSize = _theme.axisTickSize;
+    if (_axis.innerTickSize <= ChartAxisTheme.FILL_RENDER_AREA) {
+      _axis.innerTickSize =
+          0 - (_isVertical ? renderAreaRect.width : renderAreaRect.height);
+    }
+    initAxisScale(range, _theme);
+    if (_axis.scale != scale) _axis.scale = scale;
+    _axis.axis(_group);
+  }
+
+  void clear() {
+  }
+
+  // Scale passed through configuration takes precedence
+  Scale get scale =>
+      (config != null && config.scale != null) ? config.scale : _scale;
+
+  set scale(Scale value) => _scale = value;
+}
diff --git a/pub/charted/lib/charts/src/chart_config_impl.dart b/pub/charted/lib/charts/src/chart_config_impl.dart
new file mode 100644
index 0000000..bb4364b
--- /dev/null
+++ b/pub/charted/lib/charts/src/chart_config_impl.dart
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+part of charted.charts;
+
+class _ChartConfig extends ChangeNotifier implements ChartConfig {
+  final Map<String,ChartAxisConfig> _measureAxisRegistry = {};
+  final Map<int,ChartAxisConfig> _dimensionAxisRegistry = {};
+  final SubscriptionsDisposer _disposer = new SubscriptionsDisposer();
+
+  Iterable<ChartSeries> _series;
+  Iterable<int> _dimensions;
+  StreamSubscription _dimensionsSubscription;
+
+  @override
+  Rect minimumSize = const Rect.size(400, 300);
+
+  @override
+  bool leftAxisIsPrimary = false;
+
+  @override
+  bool autoResizeAxis = true;
+
+  @override
+  ChartLegend legend;
+
+  @override
+  Iterable<String> displayedMeasureAxes;
+
+  @override
+  bool renderDimensionAxes = true;
+
+  _ChartConfig(Iterable<ChartSeries> series, Iterable<int> dimensions) {
+    this.series = series;
+    this.dimensions = dimensions;
+  }
+
+  @override
+  set series(Iterable<ChartSeries> values) {
+    assert(values != null && values.isNotEmpty);
+
+    _disposer.dispose();
+    _series = values;
+    notifyChange(const ChartConfigChangeRecord());
+
+    // Monitor each series for changes on them
+    values.forEach((item) => _disposer.add(item.changes.listen(
+        (_) => notifyChange(const ChartConfigChangeRecord())), item));
+
+    // Monitor series for changes.  When the list changes, update
+    // subscriptions to ChartSeries changes.
+    if (_series is ObservableList) {
+      var observable = _series as ObservableList;
+      _disposer.add(observable.listChanges.listen((records) {
+        records.forEach((record) {
+          record.removed.forEach((value) => _disposer.unsubscribe(value));
+          for (int i = 0; i < record.addedCount; i++) {
+            var added = observable[i + record.index];
+            _disposer.add(added.changes.listen(
+                (_) => notifyChange(const ChartConfigChangeRecord())));
+          }
+        });
+        notifyChange(const ChartConfigChangeRecord());
+      }));
+    }
+  }
+
+  @override
+  Iterable<ChartSeries> get series => _series;
+
+  @override
+  set dimensions(Iterable<int> values) {
+    _dimensions = values;
+
+    if (_dimensionsSubscription != null) {
+      _dimensionsSubscription.cancel();
+      _dimensionsSubscription = null;
+    }
+
+    if (values == null || values.isEmpty) return;
+
+    if (_dimensions is ObservableList) {
+      _dimensionsSubscription =
+          (_dimensions as ObservableList).listChanges.listen(
+              (_) => notifyChange(const ChartConfigChangeRecord()));
+    }
+  }
+
+  @override
+  Iterable<int> get dimensions => _dimensions;
+
+  @override
+  void registerMeasureAxis(String id, ChartAxisConfig config) {
+    assert(config != null);
+    _measureAxisRegistry[id] = config;
+  }
+
+  @override
+  ChartAxisConfig getMeasureAxis(String id) => _measureAxisRegistry[id];
+
+  @override
+  void registerDimensionAxis(int column, ChartAxisConfig config) {
+    assert(config != null);
+    assert(dimensions.contains(column));
+    _dimensionAxisRegistry[column] = config;
+  }
+
+  @override
+  ChartAxisConfig getDimensionAxis(int column) =>
+      _dimensionAxisRegistry[column];
+}
diff --git a/pub/charted/lib/charts/src/chart_data_impl.dart b/pub/charted/lib/charts/src/chart_data_impl.dart
new file mode 100644
index 0000000..723b8eb
--- /dev/null
+++ b/pub/charted/lib/charts/src/chart_data_impl.dart
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+part of charted.charts;
+
+class _ChartData extends ChangeNotifier implements ChartData {
+  Iterable<ChartColumnSpec> _columns;
+  Iterable<Iterable> _rows;
+
+  bool _hasObservableRows = false;
+  SubscriptionsDisposer _disposer = new SubscriptionsDisposer();
+
+  _ChartData(Iterable<ChartColumnSpec> columns, Iterable<Iterable> rows) {
+    this.columns = columns;
+    this.rows = rows;
+  }
+
+  set columns(Iterable<ChartColumnSpec> value) {
+    assert(value != null && value.isNotEmpty);
+
+    // Create a copy of columns.  We do not currently support
+    // changes to the list of columns.  Any changes to the spec
+    // will be applied at the next ChartBase.draw();
+    this._columns = new List<ChartColumnSpec>.from(value);
+  }
+
+  Iterable<ChartColumnSpec> get columns => _columns;
+
+  set rows(Iterable<Iterable> value) {
+    assert(value != null);
+
+    _rows = value;
+    if (_rows is ObservableList) {
+      _disposer.add(
+          (_rows as ObservableList).listChanges.listen(rowsChanged));
+    }
+
+    if (_rows.every((row) => row is ObservableList)) {
+      _hasObservableRows = true;
+      for (int i = 0; i < _rows.length; i++) {
+        var row = _rows.elementAt(i);
+        _disposer.add(row.listChanges.listen((changes)
+            => _valuesChanged(i, changes)), row);
+      };
+    } else if (_rows is Observable) {
+      logger.info('List of rows is Observable, but not rows themselves!');
+    }
+  }
+
+  Iterable<Iterable> get rows => _rows;
+
+  rowsChanged(List<ListChangeRecord> changes) {
+    if (_rows is! ObservableList) return;
+    notifyChange(new ChartRowChangeRecord(changes));
+
+    if (!_hasObservableRows) return;
+    changes.forEach((ListChangeRecord change) {
+      change.removed.forEach((item) => _disposer.unsubscribe(item));
+
+      for(int i = 0; i < change.addedCount; i++) {
+        var index = change.index + i,
+            row = _rows.elementAt(index);
+
+        if (row is! ObservableList) {
+          logger.severe('A non-observable row was added! '
+              'Changes on this row will not be monitored');
+        } else {
+          _disposer.add(row.listChanges.listen((changes)
+              => _valuesChanged(index, changes)), row);
+        }
+      }
+    });
+  }
+
+  _valuesChanged(int index, List<ListChangeRecord> changes) {
+    if (!_hasObservableRows) return;
+    notifyChange(new ChartValueChangeRecord(index, changes));
+  }
+
+  @override
+  String toString() {
+    var cellDataLength = new List.filled(rows.elementAt(0).length, 0);
+    for (var i = 0; i < columns.length; i++) {
+      if (cellDataLength[i] < columns.elementAt(i).label.toString().length) {
+        cellDataLength[i] = columns.elementAt(i).label.toString().length;
+      }
+    }
+    for (var row in rows) {
+      for (var i = 0; i < row.length; i++) {
+        if (cellDataLength[i] < row.elementAt(i).toString().length) {
+          cellDataLength[i] = row.elementAt(i).toString().length;
+        }
+      }
+    }
+
+    var totalLength = 1;  // 1 for the leading '|'.
+    for (var length in cellDataLength) {
+      // 3 for the leading and trailing ' ' padding and trailing '|'.
+      totalLength += length + 3;
+    }
+
+    // Second pass for building the string buffer and padd each cell with space
+    // according to the difference between cell string length and max length.
+    var strBuffer = new StringBuffer();
+    strBuffer.write('-' * totalLength + '\n');
+    strBuffer.write('|');
+
+    // Process columns.
+    for (var i = 0; i < columns.length; i++) {
+      var label = columns.elementAt(i).label;
+      var lengthDiff = cellDataLength[i] - label.length;
+      strBuffer.write(' ' * lengthDiff + ' ${label} |');
+    }
+    strBuffer.write('\n' + '-' * totalLength + '\n');
+
+    // Process rows.
+    for (var row in rows) {
+      strBuffer.write('|');
+      for (var i = 0; i < row.length; i++) {
+        var data = row.elementAt(i).toString();
+        var lengthDiff = cellDataLength[i] - data.length;
+        strBuffer.write(' ' * lengthDiff + ' ${data} |');
+
+        if (i == row.length - 1) {
+          strBuffer.write('\n' + '-' * totalLength + '\n');
+        }
+      }
+    }
+    return strBuffer.toString();
+  }
+}
diff --git a/pub/charted/lib/charts/src/chart_data_waterfall_impl.dart b/pub/charted/lib/charts/src/chart_data_waterfall_impl.dart
new file mode 100644
index 0000000..2c24004
--- /dev/null
+++ b/pub/charted/lib/charts/src/chart_data_waterfall_impl.dart
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2015 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+part of charted.charts;
+
+class _WaterfallChartData extends _ChartData implements WaterfallChartData {
+  Iterable<int> _baseRows;
+
+  // The last row should always be base (no shifting on y-axis). If not,
+  // calculate the sum since last base, and store them in these tables.
+  List<int> _extendedBaseRows;
+  List<List> _extendedRows;
+  bool _extended = false;
+
+  SubscriptionsDisposer _disposer = new SubscriptionsDisposer();
+
+  _WaterfallChartData(Iterable<ChartColumnSpec> columns,
+       Iterable<Iterable> rows, [Iterable<int> baseRows])
+       : super(columns, rows) {
+    // Force calling setter.
+    this.baseRows = baseRows;
+  }
+
+  @override
+  set rows(Iterable<Iterable> value) {
+    super.rows = value;
+    _extendRowsOrNot();
+  }
+
+  @override
+  Iterable<Iterable> get rows => _extended ? _extendedRows : _rows;
+
+  set baseRows(Iterable<int> value) {
+    _baseRows = value == null? new ObservableList() : value;
+
+    if (_baseRows is ObservableList) {
+      _disposer.add(
+          (_baseRows as ObservableList).listChanges.listen(_baseRowsChanged));
+    }
+
+    _extendRowsOrNot();
+  }
+
+  Iterable<int> get baseRows => _extended ? _extendedBaseRows : _baseRows;
+
+  @override
+  rowsChanged(List<ListChangeRecord> changes) {
+    super.rowsChanged(changes);
+    _extendRowsOrNot();
+  }
+
+  _baseRowsChanged(List<ListChangeRecord> changes) {
+    if (_baseRows is! ObservableList) return;
+    _extendRowsOrNot();
+    // force re-draw?
+    notifyChange(new ChartRowChangeRecord(changes));
+  }
+
+  /*
+   * If [_baseRows] does not specify last row as base row, add one more row
+   * as the running totals of measures since last base row.
+   */
+  _extendRowsOrNot() {
+    _extended = false;
+    if (_baseRows == null || _rows == null || _rows.length < 2) return;
+
+    if (!_baseRows.contains(_rows.length - 1)) {
+      _fillExtendRows();
+      _extended = true;
+    }
+  }
+
+  /*
+   * Set [_extendedRows] and [_extendedBaseRows] to include the computed
+   * last row with running totals of measures since last base row, which
+   * will be shown as base row.
+   */
+  _fillExtendRows() {
+    var start = 0;
+    _baseRows.forEach((index) => start = index > start? index : start);
+
+    var runningTotal = new List.from(_rows.elementAt(start));
+    for (int i = start + 1; i < _rows.length; i++) {
+      for (int j = 1 /* skip label */; j < _rows.elementAt(i).length; j++) {
+        runningTotal[j] += _rows.elementAt(i).elementAt(j);
+      }
+    }
+    runningTotal[0] = 'Total';
+
+    _extendedRows = new List.from(_rows)
+      ..add(runningTotal);
+    _extendedBaseRows = new List.from(_baseRows)
+      ..add(_extendedRows.length - 1);
+  }
+}
diff --git a/pub/charted/lib/charts/src/chart_events_impl.dart b/pub/charted/lib/charts/src/chart_events_impl.dart
new file mode 100644
index 0000000..2ca79c3
--- /dev/null
+++ b/pub/charted/lib/charts/src/chart_events_impl.dart
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+part of charted.charts;
+
+class _ChartEvent implements ChartEvent {
+  @override
+  final _ChartArea area;
+
+  @override
+  final ChartSeries series;
+
+  @override
+  final MouseEvent source;
+
+  @override
+  final int column;
+
+  @override
+  final int row;
+
+  @override
+  final num value;
+
+  @override
+  num scaledX;
+
+  @override
+  num scaledY;
+
+  @override
+  num chartX;
+
+  @override
+  num chartY;
+
+  _ChartEvent(this.source, this.area,
+      [this.series, this.row, this.column, this.value]) {
+    var host = area.host;
+    var hostRect = host.getBoundingClientRect();
+    chartX = source.client.x - hostRect.left - _ChartArea.MARGIN;
+    chartY = source.client.y - hostRect.top - _ChartArea.MARGIN;
+  }
+}
diff --git a/pub/charted/lib/charts/src/chart_legend_impl.dart b/pub/charted/lib/charts/src/chart_legend_impl.dart
new file mode 100644
index 0000000..00a4420
--- /dev/null
+++ b/pub/charted/lib/charts/src/chart_legend_impl.dart
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+part of charted.charts;
+
+class _ChartLegend implements ChartLegend {
+  final Element host;
+  final int _maxItems;
+  String _title;
+  SelectionScope _scope;
+  Selection _selected;
+
+  _ChartLegend(Element this.host, int this._maxItems, String this._title) {
+    assert(host != null);
+  }
+
+  /**
+   * Sets the title of the legend, if the legend is already drawn, updates the
+   * title on the legend as well.
+   */
+  void set title(String title) {
+    _title = title;
+    if (_scope == null) return;
+    _updateTitle();
+  }
+
+  String get title => _title;
+
+  /** Updates the title of the legend. */
+  void _updateTitle() {
+    if (_title.isNotEmpty) {
+      if (_selected.select('.legend-title').length == 0) {
+        _selected.select('.legend-title');
+        _selected.append('div')
+          ..classed('legend-title')
+          ..text(_title);
+      } else {
+        _selected.select('.legend-title').text(_title);
+      }
+    }
+  }
+
+  /** Updates the legend base on a new list of ChartLegendItems. */
+  update(Iterable<ChartLegendItem> items, ChartArea chart) {
+    assert(items != null);
+
+    if (_scope == null) {
+      _scope = new SelectionScope.element(host);
+      _selected = _scope.selectElements([host]);
+    }
+
+    _updateTitle();
+
+    _createLegendItems(_selected, 'legend',
+        (_maxItems > 0) ? items.take(_maxItems) : items);
+
+    // Add more item label if there's more items than the max display items.
+    if ((_maxItems > 0) && (_maxItems < items.length)) {
+      _selected.select('.legend-more').remove();
+      _selected.append('div')
+        ..on('mouseover', (d, i, e) => _displayMoreItem(items.skip(_maxItems)))
+        ..on('mouseleave', (d, i, e) => _hideMoreItem())
+        ..text('${items.length - _maxItems} more...')
+        ..classed('legend-more');
+    }
+  }
+
+  /** Hides extra legend items. */
+  void _hideMoreItem() {
+    var tooltip = _selected.select('.legend-more-tooltip');
+    tooltip.style('opacity', '0');
+  }
+
+  /** Display more legend items. */
+  void _displayMoreItem(Iterable<ChartLegendItem> items) {
+    var tooltip = _selected.select('.legend-more-tooltip');
+    if (tooltip.isEmpty) {
+      tooltip = _selected.select('.legend-more').append('div')
+          ..classed('legend-more-tooltip');
+    }
+    tooltip.style('opacity', '1');
+
+    _createLegendItems(tooltip, 'legend-more', items);
+  }
+
+  /**
+   * Creates a list of legend items base on the label of the [items], appending
+   * legend item classes and prefix them with [classPrefix] and attatch the
+   * items to [host].
+   */
+  void _createLegendItems(Selection host, String classPrefix,
+      Iterable<ChartLegendItem> items) {
+    var rows = host.selectAll('.${classPrefix}-row').data(items);
+    rows.enter.appendWithCallback((d, i, e) {
+      Element row = new Element.tag('div')
+          ..append(new Element.tag('div')
+              ..className = '${classPrefix}-color')
+          ..append(new Element.tag('div')
+              ..className = '${classPrefix}-column');
+      return row;
+    });
+
+    rows.classed('${classPrefix}-row');
+    rows.exit.remove();
+
+    // This is needed to update legend colors when a column is removed or
+    // inserted not at the tail of the list of ChartLegendItem.
+    _selected.selectAll('.${classPrefix}-color').data(items).styleWithCallback(
+        'background-color', (d, i, c) => d.color);
+
+    _selected.selectAll('.${classPrefix}-column').data(items)
+        ..textWithCallback((d, i, e) => d.label);
+  }
+}
diff --git a/pub/charted/lib/charts/src/chart_series_impl.dart b/pub/charted/lib/charts/src/chart_series_impl.dart
new file mode 100644
index 0000000..9103a44
--- /dev/null
+++ b/pub/charted/lib/charts/src/chart_series_impl.dart
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+part of charted.charts;
+
+class _ChartSeries extends ChangeNotifier implements ChartSeries {
+  final String name;
+
+  Iterable<String> _measureAxisIds;
+  Iterable<int> _measures;
+  ChartRenderer _renderer;
+
+  SubscriptionsDisposer _disposer = new SubscriptionsDisposer();
+
+  _ChartSeries(this.name, Iterable<int> measures, this._renderer,
+      Iterable<String> measureAxisIds) {
+    this.measures = measures;
+    this.measureAxisIds = measureAxisIds;
+  }
+
+  set renderer(ChartRenderer value) {
+    if (value != null && value == _renderer) return;
+    _renderer.dispose();
+    _renderer = value;
+    notifyChange(new ChartSeriesChangeRecord(this));
+  }
+
+  ChartRenderer get renderer => _renderer;
+
+  set measures(Iterable<int> value) {
+    _measures = value;
+
+    if (_measures is ObservableList) {
+      _disposer.add(
+          (_measures as ObservableList).listChanges.listen(_measuresChanged));
+    }
+  }
+
+  Iterable<int> get measures => _measures;
+
+  set measureAxisIds(Iterable<String> value) => _measureAxisIds = value;
+  Iterable<String> get measureAxisIds => _measureAxisIds;
+
+  _measuresChanged(_) {
+    if (_measures is! ObservableList) return;
+    notifyChange(new ChartSeriesChangeRecord(this));
+  }
+}
diff --git a/pub/charted/lib/charts/themes/quantum_theme.dart b/pub/charted/lib/charts/themes/quantum_theme.dart
new file mode 100644
index 0000000..42901d9
--- /dev/null
+++ b/pub/charted/lib/charts/themes/quantum_theme.dart
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+part of charted.charts;
+
+class QuantumChartTheme extends ChartTheme {
+  static const List<List<String>> COLORS = const[
+    const [ '#C5D9FB', '#4184F3', '#2955C5' ],
+    const [ '#F3C6C2', '#DB4437', '#DB4437' ],
+    const [ '#FBE7B1', '#F4B400', '#EF9200' ],
+    const [ '#B6E0CC', '#0F9D58', '#0A7F42' ],
+    const [ '#E0BDE6', '#AA46BB', '#691A99' ],
+    const [ '#B1EAF1', '#00ABC0', '#00828E' ],
+    const [ '#FFCBBB', '#FF6F42', '#E54918' ],
+    const [ '#EFF3C2', '#9D9C23', '#817616' ],
+    const [ '#C4C9E8', '#5B6ABF', '#3848AA' ],
+    const [ '#F7BACF', '#EF6191', '#E81D62' ],
+    const [ '#B1DEDA', '#00786A', '#004C3F' ],
+    const [ '#F38EB0', '#C1175A', '#870D4E' ],
+  ];
+
+  static const List<List<String>> COLORS_ASSIST = const[
+    const [ '#C5D9FB', '#4184F3', '#2955C5' ],
+    const [ '#F3C6C2', '#DB4437', '#DB4437' ],
+    const [ '#FBE7B1', '#F4B400', '#EF9200' ],
+    const [ '#B6E0CC', '#0F9D58', '#0A7F42' ],
+    const [ '#E0BDE6', '#AA46BB', '#691A99' ],
+    const [ '#B1EAF1', '#00ABC0', '#00828E' ],
+    const [ '#FFCBBB', '#FF6F42', '#E54918' ],
+    const [ '#EFF3C2', '#9D9C23', '#817616' ]
+  ];
+
+  final OrdinalScale _scale = new OrdinalScale()..range = COLORS;
+
+  /* Implementation of ChartTheme */
+  String getColorForKey(key, [int state = ChartTheme.STATE_NORMAL]) {
+    var result = _scale.apply(key);
+    return (result is List && result.length > state) ?
+        result.elementAt(state) : result;
+  }
+
+  ChartAxisTheme get measureAxisTheme =>
+      const _QuantumChartAxisTheme(ChartAxisTheme.FILL_RENDER_AREA, 5);
+  ChartAxisTheme get dimensionAxisTheme =>
+      const _QuantumChartAxisTheme(0, 5);
+
+  /* Padding for charts */
+  double outerPadding = 0.1;
+  double bandInnerPadding = 0.35;
+  double bandOuterPadding = 0.175;
+
+  /* Tick sizes */
+  int dimensionTickSize = 0;
+  int measureTickSize = -1000;
+  int dimensionTickPadding = 6;
+  int measureTickPadding = 6;
+}
+
+class _QuantumChartAxisTheme implements ChartAxisTheme {
+  final axisOuterPadding = 0.1;
+  final axisBandInnerPadding = 0.35;
+  final axisBandOuterPadding = 0.175;
+  final axisTickPadding = 6;
+  final axisTickSize;
+  final axisTickCount;
+  final axisAutoResize = true;
+  final verticalAxisWidth = 50;
+  final horizontalAxisHeight = 50;
+  const _QuantumChartAxisTheme(this.axisTickSize, this.axisTickCount);
+}
diff --git a/pub/charted/lib/charts/transformers/aggregation.dart b/pub/charted/lib/charts/transformers/aggregation.dart
new file mode 100644
index 0000000..3a8978f
--- /dev/null
+++ b/pub/charted/lib/charts/transformers/aggregation.dart
@@ -0,0 +1,728 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+part of charted.charts;
+
+/**
+ * Function callback to filter items in the input
+ */
+typedef bool AggregationFilterFunc(var item);
+
+typedef dynamic FieldAccessor(dynamic item, dynamic key);
+
+
+// TODO(midoringo, prsd): Consider splitting each aggregation type into its own
+// strategy object for readability, maintainability, and scalability.
+/**
+ * Given list of items, dimensions and facts, compute
+ * aggregates (COUNT, SUM, MIN, MAX) for facts at each dimension level.
+ */
+class AggregationModel {
+
+  // Number of aggregations we collect on each fact
+  int _aggregationTypesCount = 0;
+
+  // Currently supported list of aggregations.
+  static final List<String> supportedAggregationTypes =
+      ['sum', 'min', 'max', 'valid'];
+
+  // Computed aggregation types.
+  List<String> computedAggregationTypes;
+
+  // Offsets of aggregations that are computed once per fact per dimension
+  // If an offset is null, it will not be computed
+  int _offsetSum;
+  int _offsetMin;
+  int _offsetMax;
+  int _offsetCnt;
+
+  // Offset of aggregations that one computed once per dimension
+  int _offsetFilteredCount;
+  int _offsetSortedIndex;
+
+  // Number of bits we can use in an integer without making it medium int
+  static const int SMI_BITS = 30;
+
+  // Computed aggregations
+  static const int AGGREGATIONS_BUFFER_LENGTH = 1024 * 1024;
+  Float64List _aggregations;
+
+  // Cache of fact values
+  Float64List _factsCache;
+
+  // Cache of enumerated dimensions
+  List<int> _dimEnumCache;
+
+  // Sorted list of indices (for data in _rows/_dimEnumCache/_factsCache)
+  List<int> _sorted;
+
+  // Enumeration map for dimension values
+  List<Map<dynamic, int>> _dimToIntMap;
+
+  // Sort orders for dimension values
+  List<List<int>> _dimSortOrders;
+
+  // Input
+  List _rows;
+  List _dimFields;
+  List _factFields;
+
+  // When groupBy is called, this value represents the
+  // common prefix of the old and new dimensions list.
+  int _dimPrefixLength = 0;
+
+  // Dimensions mapped to computed aggregates
+  Map<String, int> _dimToAggrMap;
+
+  // null when no filter was applied.
+  // Otherwise, store a bitmap indicating if an item was included.
+  List<int> _filterResults;
+
+  // Cache of entities created for the facts on this aggregation view.
+  Map<String, AggregationItem> _entityCache;
+
+  // List of field names that aggregation items will have.
+  List<String> _itemFieldNamesCache;
+
+  // Walk through the map, by splitting key at '.'
+  final bool walkThroughMap;
+
+  // Map of fieldName to comparator function.
+  final Map<String, Function> comparators;
+
+  // Timing operations
+  static final Logger _logger = new Logger('aggregations');
+  Stopwatch _timeItWatch;
+  String _timeItName;
+
+  FieldAccessor dimensionAccessor;
+  FieldAccessor factsAccessor;
+
+  /**
+   * Create a new [AggregationModel] from a [collection] of items,
+   * list of [dimensions] on which the items are grouped and a list of [facts]
+   * on which aggregations are computed.
+   */
+  AggregationModel(List collection, List dimensions,
+                   List facts,
+                   { List<String> aggregationTypes,
+                     this.walkThroughMap: false,
+                     this.comparators,
+                     this.dimensionAccessor,
+                     this.factsAccessor}) {
+    _init(collection, dimensions, facts, aggregationTypes);
+  }
+
+  void _timeItStart(String name) {
+    _timeItName = name;
+    _timeItWatch = new Stopwatch();
+    _timeItWatch.start();
+  }
+
+  void _timeItEnd() {
+    _timeItWatch.stop();
+    _logger.info('[aggregations/$_timeItName] '
+                 '${_timeItWatch.elapsed.inMilliseconds}ms/${_rows.length}r');
+  }
+
+  List get factFields => _factFields;
+  List get dimensionFields => _dimFields;
+
+  /**
+   * Initialize the view
+   */
+  void _init(List collection, List dimensions,
+             List facts, List<String> aggregationTypes) {
+    if (collection == null) {
+      throw new ArgumentError('Data cannot be empty or null');
+    }
+
+    if (facts == null || facts.isEmpty) {
+      throw new ArgumentError('Facts cannot be empty or null');
+    }
+
+    if (dimensions == null) {
+      dimensions = [];
+    }
+
+    if (dimensionAccessor == null) {
+      dimensionAccessor = _fetch;
+    }
+
+    if (factsAccessor == null) {
+      factsAccessor = _fetch;
+    }
+
+    if (aggregationTypes != null) {
+      Iterable unknownAggregationTypes =
+          aggregationTypes.where((e) => !supportedAggregationTypes.contains(e));
+      if (unknownAggregationTypes.length != 0) {
+        throw new ArgumentError(
+            'Unknown aggregation types: ${unknownAggregationTypes.join(', ')}');
+      }
+    } else {
+      aggregationTypes = ['sum'];
+    }
+
+    // Always adding 'count' for correct computation of average and count.
+    if (!aggregationTypes.contains('valid')) {
+      aggregationTypes.add('valid');
+    }
+
+    _rows = collection;
+    _dimFields = new List.from(dimensions, growable: false);
+    _factFields = new List.from(facts, growable: false);
+    _entityCache = new Map<String, AggregationItem>();
+
+    _createBuffers();
+
+    _aggregationTypesCount = aggregationTypes.length;
+    for (int i = 0; i < _aggregationTypesCount; i++) {
+      switch(aggregationTypes[i]) {
+        case 'sum':
+          _offsetSum = i;
+          break;
+        case 'min':
+          _offsetMin = i;
+          break;
+        case 'max':
+          _offsetMax = i;
+          break;
+        case 'valid':
+          _offsetCnt = i;
+          break;
+      }
+    }
+    computedAggregationTypes = new List.from(aggregationTypes, growable: false);
+
+    // Preprocess the data
+    _preprocess();
+  }
+
+  /**
+   * Re-calculate aggregations based on new dimensions.
+   */
+  void groupBy(List dimensions, [AggregationFilterFunc filter = null]) {
+    if (dimensions == null) {
+      dimensions = [];
+    }
+
+    List savedDimFields = _dimFields;
+    _dimFields = new List.from(dimensions, growable: false);
+
+    _dimPrefixLength = 0;
+    while (_dimPrefixLength < _dimFields.length &&
+           _dimPrefixLength < savedDimFields.length &&
+           savedDimFields[_dimPrefixLength] == _dimFields[_dimPrefixLength]) {
+      ++_dimPrefixLength;
+    }
+
+    _createBuffers();
+    _preprocess(groupBy:true);
+
+    // For groupBy, compute immediately.
+    compute(filter);
+
+    // Ensure that cache represents updated dimensions
+    _updateCachedEntities();
+  }
+
+  /**
+   * Create buffers.
+   * This method is called when the object is being created and when
+   * a groupBy is called to change the dimensions on which
+   * aggregations are computed.
+   */
+  void _createBuffers() {
+    // Create both when object is created and groupBy is called
+    _dimEnumCache = new Int32List(_dimFields.length * _rows.length);
+
+    // Create only when the object is created
+    if (_factsCache == null) {
+      _factsCache = new Float64List((_factFields.length + 1) * _rows.length);
+    }
+
+    // Create only when the object is created
+    if (_filterResults == null) {
+      _filterResults = new List<int>((_rows.length) ~/ SMI_BITS + 1);
+    }
+
+    // Create both when object is created and groupBy is called
+    // Reuse dimension enumerations if possible.
+    var oldDimToInt = _dimToIntMap;
+    _dimToIntMap = new List<Map<dynamic, int>>.generate(_dimFields.length,
+        (i) => i < _dimPrefixLength ? oldDimToInt[i] : new Map<dynamic, int>());
+  }
+
+  /**
+   * Check cache entries
+   * When data is grouped by a new dimensions, entities that were
+   * created prior to the groupBy should be cleared and removed from cache
+   * if they aren't valid anymore.
+   * Update the entities that are valid after the groupBy.
+   */
+  void _updateCachedEntities() {
+    List keys = new List.from(_entityCache.keys, growable: false);
+    keys.forEach((key) {
+      _AggregationItemImpl entity = _entityCache[key];
+      if (entity == null) {
+        _entityCache.remove(key);
+      } else if (entity != null && entity.isValid) {
+        if (key.split(':').length <= _dimPrefixLength) {
+          entity.update();
+        } else {
+          _entityCache.remove(key);
+          entity.clear();
+        }
+      }
+    });
+  }
+
+  final Map<String, List> _parsedKeys = {};
+  /**
+   * Get value from a map-like object
+   */
+  dynamic _fetch(var item, String key) {
+    if (walkThroughMap && key.contains('.')) {
+      return walk(item, key, _parsedKeys);
+    } else {
+      return item[key];
+    }
+  }
+
+  /*
+   * Preprocess Data
+   *  - Enumerate dimension values
+   *  - Create sort orders for dimension values
+   *  - Cache facts in lists
+   */
+  void _preprocess({bool groupBy: false}) {
+
+    _timeItStart('preprocess');
+
+    // Enumerate dimensions...
+    // Cache dimensions and facts.
+
+    List<int> dimensionValCount =
+        new List<int>.generate(_dimFields.length, (idx) => 0);
+
+    int dimensionsCount = _dimFields.length;
+    int factsCount = _factFields.length;
+    int rowCount = _rows.length;
+
+    for (int ri = 0, factsDataOffset = 0, dimDataOffset = 0;
+         ri < rowCount; ++ri, factsDataOffset += factsCount,
+         dimDataOffset += dimensionsCount) {
+      var item = _rows[ri];
+
+      // Cache the fact values in the big buffer, but only
+      // when we are initializing (not when a groupBy was called
+      // after initialization)
+      if (!groupBy) {
+        for (int fi = 0; fi < factsCount; fi++) {
+          var value = factsAccessor(item,_factFields[fi]);
+          _factsCache[factsDataOffset + fi] =
+              (value == null) ? double.NAN : value.toDouble();
+        }
+      }
+
+      // Enumerate the dimension values and cache enumerated rows
+      for (int di = 0; di < dimensionsCount; di++) {
+        var dimensionVal = dimensionAccessor(item, _dimFields[di]);
+        int dimensionValEnum = _dimToIntMap[di][dimensionVal];
+        if (dimensionValEnum == null) {
+          _dimToIntMap[di][dimensionVal] = dimensionValCount[di];
+          dimensionValEnum = dimensionValCount[di]++;
+        }
+        _dimEnumCache[dimDataOffset + di] = dimensionValEnum;
+      }
+    }
+
+    // Sort all dimensions internally
+    // The resulting arrays would be used to sort the entire data
+
+    List oldSortOrders = _dimSortOrders;
+    _dimSortOrders = new List.generate(dimensionsCount, (i) {
+      if (groupBy && i < _dimPrefixLength) {
+        return oldSortOrders[i];
+      }
+
+      List dimensionVals = new List.from(_dimToIntMap[i].keys);
+      List retval = new List(_dimToIntMap[i].length);
+
+      // When a comparator is not specified, our implementation of the
+      // comparator tries to gracefully handle null values.
+      dimensionVals.sort(
+          comparators != null && comparators.containsKey(_dimFields[i]) ?
+              comparators[_dimFields[i]] : _defaultDimComparator);
+
+      for (int si = 0; si < retval.length; ++si) {
+        retval[_dimToIntMap[i][dimensionVals[si]]] = si;
+      }
+      return retval;
+    }, growable: false);
+
+    // Create a list of sorted indices - only if we are not having a full
+    // overlap of dimensionFields.
+    if (!groupBy || _dimPrefixLength != _dimFields.length) {
+      _sorted = new List<int>.generate(_rows.length, (i) => i, growable: false);
+      _sorted.sort(_comparator);
+    }
+
+    // Pre-compute frequently used values
+    _offsetSortedIndex = factsCount * _aggregationTypesCount;
+    _offsetFilteredCount = factsCount * _aggregationTypesCount + 1;
+
+    _timeItEnd();
+  }
+
+  // Ensures that null dimension values don't cause an issue with sorting
+  _defaultDimComparator(Comparable left, Comparable right) =>
+      (left == null && right == null) ? 0 :
+          (left == null) ? -1 :
+              (right == null) ? 1 : left.compareTo(right);
+
+  /*
+   * Given item indices in rows, compare them based
+   * on the sort orders created while preprocessing data.
+   */
+  _comparator(int one, int two) {
+    if (one == two) {
+      return 0;
+    }
+
+    int offsetOne = _dimFields.length * one;
+    int offsetTwo = _dimFields.length * two;
+
+    for (int i = 0; i < _dimFields.length; ++i) {
+      int diff = _dimSortOrders[i][_dimEnumCache[offsetOne + i]] -
+                 _dimSortOrders[i][_dimEnumCache[offsetTwo + i]];
+      if (diff != 0) {
+        return diff;
+      }
+    }
+    return 0;
+  }
+
+  /**
+   * Compute aggregations
+   * If [filter] is not null, it would be used to filter out items that
+   * should not be included in the aggregates.
+   */
+  void compute([AggregationFilterFunc filter = null]) {
+    _timeItStart('compute');
+
+    _dimToAggrMap = new Map<String, int>();
+    _aggregations = new Float64List(AGGREGATIONS_BUFFER_LENGTH);
+    _filterResults = filter == null ?
+        null : new List<int>.filled((_rows.length ~/ SMI_BITS) + 1, 0);
+
+    int rowCount = _rows.length;
+    int dimensionCount = _dimFields.length;
+    int factsCount = _factFields.length;
+
+    // Saves current dimension value to which we are aggregating
+    // Values of dimensions are in even indices (starting at 0) and
+    // location of respective dimension in buffer is in odd indices.
+    List<int> currentDim = new List<int>(dimensionCount * 2);
+    bool reset = true;
+    bool isNewDimension = false;
+    int aggregationSizePerDim = factsCount * _aggregationTypesCount;
+
+    // Reserve the 0th position for top-level aggregations.
+    int currentBufferPos = (factsCount * _aggregationTypesCount + 2);
+    _dimToAggrMap[''] = 0;
+    _aggregations[_offsetSortedIndex] = 0.0;
+
+
+    for (int ri = 0, index = 0, dimensionDataOffset = 0, factsDataOffset = 0;
+         ri < rowCount; ++ri, reset = false) {
+
+      // If filter is not null, check if this row must be included in
+      // the aggregations and mark it accordingly.
+      index = _sorted[ri];
+      if (filter != null) {
+        if (!filter(_rows[index])) {
+          continue;
+        } else {
+          _filterResults[ri ~/ SMI_BITS] |= (1 << (ri % SMI_BITS));
+        }
+      }
+
+      dimensionDataOffset = index * dimensionCount;
+      factsDataOffset = index * factsCount;
+
+      // Update top-level aggregations.
+      _updateAggregationsAt(0, factsDataOffset, ri == 0 ? true : false);
+
+      // See which dimensions get effected by this row.
+      // di => the index of the dimension
+      // ci => index of the cached value in [currentDim]
+      for (int di = 0, ci = 0; di < dimensionCount; ++di, ci += 2) {
+        // If a dimension value changed, then all dimensions that are lower
+        // in the heirarchy change too.
+        if (reset ||
+            currentDim[ci] != _dimEnumCache[dimensionDataOffset + di]) {
+          currentDim[ci] = _dimEnumCache[dimensionDataOffset + di];
+          currentDim[ci + 1] = currentBufferPos;
+
+          // Save location to aggregations position in the buffer
+          _dimToAggrMap[new List.generate(di + 1,
+              (i) => currentDim[2 * i]).join(':')] = currentBufferPos;
+
+          // Store items start position
+          _aggregations[currentBufferPos + _offsetSortedIndex] = ri.toDouble();
+
+          // After the aggregated values, we save the filtered count,
+          // index of the first item (in sorted)
+          currentBufferPos += (aggregationSizePerDim + 2);
+          reset = true;
+          isNewDimension = true;
+        }
+
+        _updateAggregationsAt(currentDim[ci + 1],
+            factsDataOffset, isNewDimension);
+        isNewDimension = false;
+      }
+    }
+
+    _timeItEnd();
+  }
+
+  /**
+   * Helper function that does the actual aggregations.
+   * This function is called once per row per dimension.
+   */
+  _updateAggregationsAt(int aggrDataOffset,
+                        int factsDataOffset, bool isNewDimension) {
+    // Update count.
+    _aggregations[aggrDataOffset + _offsetFilteredCount] += 1;
+
+    // Update aggregation for each of the facts.
+    for (int fi = 0, bufferFactOffset = aggrDataOffset;
+         fi < _factFields.length;
+         bufferFactOffset += _aggregationTypesCount, ++fi) {
+
+      double factValue = _factsCache[factsDataOffset + fi];
+      if (factValue.isNaN) {
+        continue;
+      }
+
+      // Sum
+      if (_offsetSum != null) {
+        _aggregations[bufferFactOffset + _offsetSum] += factValue;
+      }
+
+      // Min
+      if (_offsetMin != null && (isNewDimension || factValue <
+          _aggregations[bufferFactOffset + _offsetMin])) {
+        _aggregations[bufferFactOffset + _offsetMin] = factValue;
+      }
+
+      // Max
+      if (_offsetMax != null && (isNewDimension || factValue >
+          _aggregations[bufferFactOffset + _offsetMax])) {
+        _aggregations[bufferFactOffset + _offsetMax] = factValue;
+      }
+
+      // Count
+      if (_offsetCnt != null) {
+        _aggregations[bufferFactOffset + _offsetCnt]++;
+      }
+    }
+  }
+
+  /*
+   * TODO(prsd):
+   * 1. Implementation of updates and posting updates to entities.
+   *    patchEntity and addToEntity must add listeners on AggregationItems
+   *    and any changes must be propagated to entities.
+   * 2. Updates (add/remove/update) should do their best to update the
+   *    aggregations and then maybe do a delayed recomputation (sort etc;)
+   */
+
+  /**
+   * Update an item.
+   * If aggregates were already computed, they are updated to reflect the
+   * new value and any observers are notified.
+   */
+  void updateItem(dynamic item, String field) {
+    throw new UnimplementedError();
+  }
+
+  /**
+   * Add a new item.
+   * If aggregates were already computed, they are updated to reflect
+   * values on the new item.
+   */
+  void addItem(dynamic item) {
+    throw new UnimplementedError();
+  }
+
+  /**
+   * Remove an existing item.
+   * If aggregates were already computed, they are updated to reflect
+   * facts on the removed item.
+   */
+  void removeItem(dynamic item) {
+    throw new UnimplementedError();
+  }
+
+  /**
+   * Return an [AggregationItem] that represents facts for dimension
+   * represented by [dimension] Only one instance of an entity is created
+   * per dimension (even if this function is called multiple times)
+   *
+   * Callers of this method can observe the returned entity for updates to
+   * aggregations caused by changes to filter or done through add, remove
+   * or modify of items in the collection.
+   */
+  AggregationItem facts(List dimension) {
+    List<int> enumeratedList = new List<int>();
+    for (int i = 0; i < dimension.length; ++i) {
+      enumeratedList.add(_dimToIntMap[i][dimension[i]]);
+    }
+
+    String key = enumeratedList.join(':');
+    AggregationItem item = _entityCache[key];
+
+    if (item == null && _dimToAggrMap.containsKey(key)) {
+      item = new _AggregationItemImpl(this, dimension, key);
+      _entityCache[key] = item;
+    }
+
+    return item;
+  }
+
+  /**
+   * Return a list of values that are present for a dimension field.
+   */
+  List valuesForDimension(dynamic dimensionFieldName) {
+    int di = _dimFields.indexOf(dimensionFieldName);
+    if (di < 0) {
+      return null;
+    }
+    List values = new List.from(_dimToIntMap[di].keys);
+    values.sort(
+        comparators != null && comparators.containsKey(dimensionFieldName) ?
+            comparators[dimensionFieldName] : _defaultDimComparator);
+    return values;
+  }
+}
+
+/**
+ * Parse a path for nested map-like objects.
+ * Caches the parsed key in the passed map.
+ *
+ * Takes map keys of the format:
+ *     "list(key=val;val=m).another(state=good).state"
+ * and outputs:
+ *     ["list", {"key": "val", "val": "m"},
+ *      "another", {"state": "good"}, "state"]
+ */
+List _parseKey(String key, Map parsedKeysCache) {
+  List parts = parsedKeysCache == null ? null : parsedKeysCache[key];
+  if (parts == null && key != null) {
+    parts = new List();
+    if (key.contains(').')) {
+      int start = 0;
+      int cursor = 0;
+      bool inParams = false;
+      List matchKeyVals;
+      Map listMatchingMap = {};
+
+      while (cursor < key.length) {
+        if (!inParams) {
+          cursor = key.indexOf('(', start);
+          if (cursor == -1) {
+            parts.addAll(key.substring(start).split('.'));
+            break;
+          }
+          parts.addAll(key.substring(start, cursor).split('.'));
+          cursor++;
+          start = cursor;
+          inParams = true;
+        } else {
+          cursor = key.indexOf(')', start);
+          if (cursor == -1) {
+            throw new ArgumentError('Invalid field name: $key');
+          }
+          listMatchingMap.clear();
+          matchKeyVals = key.substring(start, cursor).split(';');
+          matchKeyVals.forEach((value) {
+            List keyval = value.split('=');
+            if (keyval.length != 2) {
+              throw new ArgumentError('Invalid field name: ${key}');
+            }
+            listMatchingMap[keyval[0]] = keyval[1];
+          });
+          parts.add(listMatchingMap);
+          cursor += 2;
+          start = cursor;
+          inParams = false;
+        }
+      }
+    } else {
+      parts = key.split('.');
+    }
+    if (parsedKeysCache != null) {
+      parsedKeysCache[key] = parts;
+    }
+  }
+
+  return parts;
+}
+
+/**
+ * Walk a map-like structure that could have list in the path.
+ *
+ * Example:
+ *     Map testMap = {
+ *       "first": "firstval",
+ *       "list": [
+ *         { "val": "m",
+ *           "key": "val",
+ *           "another": [
+ *             { 'state': 'good' },
+ *             { 'state': 'bad' }
+ *           ]
+ *         },
+ *         { "val": "m", "key": "invalid" },
+ *         { "val": "o" }
+ *       ]
+ *     };
+ *
+ *  For the above map:
+ *     walk(testMap, "list(key=val;val=m).another(state=good).state");
+ *  outputs:
+ *     good
+ */
+dynamic walk(initial, String key, Map parsedKeyCache) {
+  List parts = _parseKey(key, parsedKeyCache);
+  return parts.fold(initial, (current, part) {
+    if (current == null) {
+      return null;
+    } else if (current is List && part is Map) {
+      for (int i = 0; i < current.length; i++) {
+        bool match = true;
+        part.forEach((key, val) {
+          if ((key.contains('.') &&
+               walk(part, key, parsedKeyCache).toString() != val) ||
+              part[key] != val) {
+            match = false;
+          }
+        });
+        if (match) {
+          return current[i];
+        }
+      }
+    } else {
+      return current[part];
+    }
+  });
+}
diff --git a/pub/charted/lib/charts/transformers/aggregation_item.dart b/pub/charted/lib/charts/transformers/aggregation_item.dart
new file mode 100644
index 0000000..b6c3480
--- /dev/null
+++ b/pub/charted/lib/charts/transformers/aggregation_item.dart
@@ -0,0 +1,268 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+part of charted.charts;
+
+/**
+ * AggregationItem is created by [AggregationModel] to make access to facts
+ * observable. Users must use AggregationItem.isValid before trying to access
+ * the aggregations.
+ */
+abstract class AggregationItem extends ChangeNotifier {
+  /**
+   * List of dimension fields in effect
+   */
+  List<String> dimensions;
+
+  /**
+   * Check if this entity is valid.
+   * Currently the only case where an entity becomes invalid
+   * is when a groupBy is called on the model.
+   */
+  bool get isValid;
+
+  /**
+   * Fetch the fact from AggregationModel and return it
+   * Currently takes keys in the form of "sum(spend)", where sum is
+   * the aggregation type and spend is fact's field name.
+   *
+   * Currently, "sum", "count", "min", "max", "avg", "valid" and "avgOfValid"
+   * are supported as the operators.
+   */
+
+  operator[](String key);
+
+  /**
+   * Check if we support a given key.
+   */
+  bool containsKey(String key);
+
+  /**
+   * List of valid field names for this entity.
+   * It's the combined list of accessors for individual items, items in
+   * the next dimension and all possible facts defined on the view.
+   */
+  Iterable<String> get fieldNames;
+}
+
+/*
+ * Implementation of AggregationItem
+ * Instances of _AggregationItemImpl are created only by AggregationModel
+ */
+class _AggregationItemImpl extends ChangeNotifier implements AggregationItem {
+  static final List<String> derivedAggregationTypes = ['count', 'avg'];
+
+  AggregationModel model;
+  List<String> dimensions;
+
+  String _key;
+
+  int _length;
+  int _factsOffset;
+
+  /*
+   * Currently entities are created only when they have valid aggregations
+   */
+  _AggregationItemImpl(this.model, this.dimensions, this._key) {
+    if (model == null) {
+      throw new ArgumentError('Model cannot be null');
+    }
+    if (_key == null) {
+      _key = '';
+    }
+
+    // facts + list of items + list of children (drilldown)
+    _length =  model._aggregationTypesCount * model._factFields.length + 2;
+    _factsOffset = model._dimToAggrMap[_key];
+  }
+
+  /**
+   * _dimToAggrMap got updated on the model, update ourselves accordingly
+   */
+  void update() {
+    _factsOffset = model._dimToAggrMap[_key];
+  }
+
+  /*
+   * Mark this entity as invalid.
+   */
+  void clear() {
+    _factsOffset = null;
+  }
+
+  bool get isValid => _factsOffset != null;
+
+  dynamic operator[](String key) {
+    if (!isValid) {
+      throw new StateError('Entity is not valid anymore');
+    }
+
+    int argPos = key.indexOf('(');
+    if (argPos == -1) {
+      return _nonAggregationMember(key);
+    }
+
+    String aggrFunc = key.substring(0, argPos);
+    int aggrFuncIndex = model.computedAggregationTypes.indexOf(aggrFunc);
+    if (aggrFuncIndex == -1 && !derivedAggregationTypes.contains(aggrFunc)) {
+      throw new ArgumentError('Unknown aggregation method: ${aggrFunc}');
+    }
+
+    String factName = key.substring(argPos + 1, key.lastIndexOf(')'));
+    int factIndex = model._factFields.indexOf(factName);
+
+    // Try parsing int if every element in factFields is int.
+    if (model._factFields.every((e) => e is int)) {
+      factIndex = model._factFields.indexOf(int.parse(factName,
+          onError: (e) {
+            throw new ArgumentError('Type of factFields are int but factName' +
+                'contains non int value');
+          }));
+    }
+    if (factIndex == -1) {
+      throw new ArgumentError('Model not configured for ${factName}');
+    }
+
+    int offset = _factsOffset + factIndex * model._aggregationTypesCount;
+    // No items for the corresponding fact, so return null.
+    if (aggrFunc != 'count' && aggrFunc != 'avg' &&
+        model._aggregations[offset + model._offsetCnt].toInt() == 0) {
+      return null;
+    }
+
+    if (aggrFuncIndex != -1) {
+      return model._aggregations[offset + aggrFuncIndex];
+    } else if (aggrFunc == 'count') {
+      return model._aggregations[_factsOffset +
+                                 model._offsetFilteredCount].toInt();
+    } else if (aggrFunc == 'avg') {
+      return model._aggregations[offset + model._offsetSum] /
+             model._aggregations[_factsOffset + model._offsetFilteredCount].
+          toInt();
+    }  else if (aggrFunc == 'avgOfValid') {
+      return model._aggregations[offset + model._offsetSum] /
+          model._aggregations[offset + model._offsetCnt].toInt();
+    }
+    return null;
+  }
+
+  dynamic _nonAggregationMember(String key) {
+    if (key == 'items') {
+      return new _AggregationItemsIterator(model, dimensions, _key);
+    }
+    if (key == 'aggregations') {
+      return _lowerAggregations();
+    }
+    return null;
+  }
+
+  List<AggregationItem> _lowerAggregations() {
+    List<AggregationItem> aggregations = new List<AggregationItem>();
+    if (dimensions.length == model._dimFields.length) {
+      return aggregations;
+    }
+
+    var lowerDimensionField = model._dimFields[dimensions.length];
+    List lowerVals = model.valuesForDimension(lowerDimensionField);
+
+    lowerVals.forEach((name) {
+      List lowerDims = new List.from(dimensions)..add(name);
+      AggregationItem entity = model.facts(lowerDims);
+      if (entity != null) {
+        aggregations.add(entity);
+      }
+    });
+
+    return aggregations;
+  }
+
+  bool containsKey(String key) => fieldNames.contains(key);
+
+  Iterable<String> get fieldNames {
+    if (!isValid) {
+      throw new StateError('Entity is not valid anymore');
+    }
+
+    if (model._itemFieldNamesCache == null) {
+      List<String> cache = new List<String>.from(['items', 'children']);
+      model._factFields.forEach((var name) {
+        AggregationModel.supportedAggregationTypes.forEach((String aggrType) {
+          cache.add('${aggrType}(${name})');
+        });
+      });
+      model._itemFieldNamesCache = cache;
+    }
+    return model._itemFieldNamesCache;
+  }
+
+  /*
+   * TODO(prsd): Implementation of [Observable]
+   */
+  Stream<List<ChangeRecord>> get changes {
+    throw new UnimplementedError();
+  }
+}
+
+class _AggregationItemsIterator implements Iterator {
+  final AggregationModel model;
+  List<String> dimensions;
+  String key;
+
+  int _current;
+  int _counter = 0;
+
+  int _start;
+  int _count;
+  int _endOfRows;
+
+  _AggregationItemsIterator(this.model, List<String> this.dimensions,
+                           String this.key) {
+    int offset = model._dimToAggrMap[key];
+    if (offset != null) {
+      int factsEndOffset = offset +
+          model._factFields.length * model._aggregationTypesCount;
+      _start = model._aggregations[factsEndOffset].toInt();
+      _count = model._aggregations[factsEndOffset + 1].toInt();
+      _endOfRows = model._rows.length;
+    }
+  }
+
+  bool moveNext() {
+    if (_current == null) {
+      _current = _start;
+    } else {
+      ++_current;
+    }
+
+    if (++_counter > _count) {
+      return false;
+    }
+
+    /*
+     * If model had a filter applied, then check if _current points to a
+     * filtered-in row, else skip till we find one.
+     * Also, make sure (even if something else went wrong) we don't go
+     * beyond the number of items in the model.
+     */
+    if (this.model._filterResults != null) {
+      while ((this.model._filterResults[_current ~/ AggregationModel.SMI_BITS] &
+              (1 << _current % AggregationModel.SMI_BITS)) == 0 &&
+          _current <= _endOfRows) {
+        ++_current;
+      }
+    }
+    return (_current < _endOfRows);
+  }
+
+  get current {
+    if (_current == null || _counter > _count) {
+      return null;
+    }
+    return model._rows[model._sorted[_current]];
+  }
+}
diff --git a/pub/charted/lib/charts/transformers/aggregation_transformer.dart b/pub/charted/lib/charts/transformers/aggregation_transformer.dart
new file mode 100644
index 0000000..8199175
--- /dev/null
+++ b/pub/charted/lib/charts/transformers/aggregation_transformer.dart
@@ -0,0 +1,224 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+part of charted.charts;
+
+/**
+ * Transforms the ChartData base on the specified dimension columns and facts
+ * columns indices. The values in the facts columns will be aggregated by the
+ * tree heirarcy generated by the dimension columns.  Expand and Collapse
+ * methods may be called to display different levels of aggragation.
+ *
+ * The output ChartData produced by transform() will contain only columns in the
+ * original ChartData that were specified in dimensions or facts column indices.
+ * The output column will be re-ordered first by the indices specified in the
+ * dimension column indices then by the facts column indices.  The data in the
+ * cells of each row will also follow this rule.
+ */
+class AggregationTransformer extends ChangeNotifier
+    implements ChartDataTransform, ChartData {
+
+  static const String AGGREGATION_TYPE_SUM = 'sum';
+  static const String AGGREGATION_TYPE_MIN = 'min';
+  static const String AGGREGATION_TYPE_MAX = 'max';
+  static const String AGGREGATION_TYPE_VALID = 'valid';
+  final SubscriptionsDisposer _dataSubscriptions = new SubscriptionsDisposer();
+  final Set<List> _expandedSet = new Set();
+  Iterable<ChartColumnSpec> columns;
+  ObservableList<Iterable> rows = new ObservableList();
+  List<int> _dimensionColumnIndices;
+  List<int> _factsColumnIndices;
+  String _aggregationType;
+  AggregationModel _model;
+  bool _expandAllDimension = false;
+  List _selectedColumns = [];
+  FieldAccessor _indexFieldAccessor = (List row, int index) => row[index];
+  ChartData _data;
+
+  AggregationTransformer(this._dimensionColumnIndices,
+      this._factsColumnIndices,
+      [String aggregationType = AGGREGATION_TYPE_SUM]) {
+    _aggregationType = aggregationType;
+  }
+
+  /**
+   * Transforms the ChartData base on the specified dimension columns and facts
+   * columns, aggregation type and currently expanded dimensions.
+   */
+  ChartData transform(ChartData data) {
+    assert(data.columns.length > max(_dimensionColumnIndices));
+    assert(data.columns.length > max(_factsColumnIndices));
+    _data = data;
+    _registerListeners();
+    _transform();
+    return this;
+  }
+
+  /** Registers listeners if data.rows or data.columns are Observable. */
+  _registerListeners() {
+    _dataSubscriptions.dispose();
+
+    if(_data is Observable) {
+      var observable = (_data as Observable);
+      _dataSubscriptions.add(observable.changes.listen((records) {
+        _transform();
+
+        // NOTE: Currently we're only passing the first change because the chart
+        // area just draw with the updated data.  When we add partial update
+        // to chart area, we'll need to handle this better.
+        notifyChange(records.first);
+      }));
+    }
+  }
+
+  /**
+   * Performs the filter transform with _data.  This is called on transform and
+   * onChange if the input ChartData is Observable.
+   */
+  _transform() {
+    _model = new AggregationModel(_data.rows, _dimensionColumnIndices,
+        _factsColumnIndices, aggregationTypes: [_aggregationType],
+        dimensionAccessor: _indexFieldAccessor,
+        factsAccessor: _indexFieldAccessor);
+    _model.compute();
+
+    // If user called expandAll prior to model initiation, do it now.
+    if (_expandAllDimension) {
+      expandAll();
+    }
+
+    _selectedColumns.clear();
+    _selectedColumns.addAll(_dimensionColumnIndices);
+    _selectedColumns.addAll(_factsColumnIndices);
+
+    // Process rows.
+    rows.clear();
+    var transformedRows = <Iterable>[];
+    for (var value in _model.valuesForDimension(_dimensionColumnIndices[0])) {
+      _generateAggregatedRow(transformedRows, [value]);
+    }
+    rows.addAll(transformedRows);
+
+    // Process columns.
+    columns = new List<ChartColumnSpec>.generate(_selectedColumns.length, (index) =>
+        _data.columns.elementAt(_selectedColumns[index]));
+  }
+
+  /**
+   * Fills the aggregatedRows List with data base on the set of expanded values
+   * recursively.  Currently when a dimension is expanded, rows are
+   * generated for its children but not for itself.  If we want to change the
+   * logic to include itself, just move the expand check around the else clause
+   * and always write a row of data whether it's expanded or not.
+   */
+  _generateAggregatedRow(List<Iterable> aggregatedRows, List dimensionValues) {
+    var entity = _model.facts(dimensionValues);
+    var dimensionLevel = dimensionValues.length - 1;
+    var currentDimValue = dimensionValues.last;
+
+    // Dimension is not expanded at this level.  Generate data rows and fill int
+    // value base on whether the column is dimension column or facts column.
+    if (!_isExpanded(dimensionValues) ||
+        dimensionValues.length == _dimensionColumnIndices.length) {
+      aggregatedRows.add(new List.generate(_selectedColumns.length, (index) {
+
+        // Dimension column.
+        if (index < _dimensionColumnIndices.length) {
+          if (index < dimensionLevel) {
+            // If column index is in a higher level, write parent value.
+            return dimensionValues[0];
+          } else if (index == dimensionLevel) {
+            // If column index is at current level, write value.
+            return dimensionValues.last;
+          } else {
+            // If column Index is in a lower level, write empty string.
+            return '';
+          }
+        } else {
+          // Write aggregated value for facts column.
+          return entity['${_aggregationType}(${_selectedColumns[index]})'];
+        }
+      }));
+    } else {
+      // Dimension is expanded, process each child dimension in the expanded
+      // dimension.
+      for (AggregationItem childAggregation in entity['aggregations']) {
+        _generateAggregatedRow(aggregatedRows, childAggregation.dimensions);
+      }
+    }
+  }
+
+  /**
+   * Expands a specific dimension and optionally expands all of its parent
+   * dimensions.
+   */
+  void expand(List dimension, [bool expandParent = true]) {
+    _expandAllDimension = false;
+    _expandedSet.add(dimension);
+    if (expandParent && dimension.length > 1) {
+      Function eq = const ListEquality().equals;
+      var dim = dimension.take(dimension.length - 1).toList();
+      if (!_expandedSet.any((e) => eq(e, dim))) {
+        expand(dim);
+      }
+    }
+  }
+
+  /**
+   * Collapses a specific dimension and optionally collapse all of its
+   * Children dimensions.
+   */
+  void collapse(List dimension, [bool collapseChildren = true]) {
+    _expandAllDimension = false;
+    if (collapseChildren) {
+      Function eq = const ListEquality().equals;
+      // Doing this because _expandedSet.where doesn't work.
+      var collapseList = [];
+      for (List dim in _expandedSet) {
+        if (eq(dim.take(dimension.length).toList(), dimension)) {
+          collapseList.add(dim);
+        }
+      }
+      _expandedSet.removeAll(collapseList);
+    } else {
+      _expandedSet.remove(dimension);
+    }
+  }
+
+  /** Expands all dimensions. */
+  void expandAll() {
+    if (_model != null) {
+      for (var value in _model.valuesForDimension(_dimensionColumnIndices[0])) {
+        _expandAll([value]);
+      }
+      _expandAllDimension = false;
+    } else {
+      _expandAllDimension = true;
+    }
+  }
+
+  void _expandAll(value) {
+    var entity = _model.facts(value);
+    _expandedSet.add(value);
+    for (AggregationItem childAggregation in entity['aggregations']) {
+      _expandAll(childAggregation.dimensions);
+    }
+  }
+
+  /** Collapses all dimensions. */
+  void collapseAll() {
+    _expandAllDimension = false;
+    _expandedSet.clear();
+  }
+
+  /** Tests if specific dimension is expanded. */
+  bool _isExpanded(List dimension) {
+    Function eq = const ListEquality().equals;
+    return _expandedSet.any((e) => eq(e, dimension));
+  }
+}
diff --git a/pub/charted/lib/charts/transformers/filter_transformer.dart b/pub/charted/lib/charts/transformers/filter_transformer.dart
new file mode 100644
index 0000000..65794be
--- /dev/null
+++ b/pub/charted/lib/charts/transformers/filter_transformer.dart
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+part of charted.charts;
+
+typedef bool FilterFunction(dynamic value);
+
+/**
+ * Transforms the ChartData base on the specified FilterDefinitions.  Each row
+ * of data will be tested by passing the value at target column to the filter
+ * function.  If filter function returns false, the row will be filtered out.
+ * This transformer does not modify the column part of the input ChartData.
+ */
+class FilterTransformer extends ChangeNotifier
+    implements ChartDataTransform, ChartData {
+  final SubscriptionsDisposer _dataSubscriptions = new SubscriptionsDisposer();
+  Iterable<ChartColumnSpec> columns;
+  ObservableList<Iterable> rows = new ObservableList();
+  List<FilterDefinition> filterFunctions;
+  ChartData _data;
+
+  FilterTransformer(this.filterFunctions);
+
+  /**
+   * Transforms the input data with the list of [FilterDefinition] specified in
+   * the constructor.  If the rows and columns are ObservableList in the data,
+   * changes in rows and columns in input data will trigger tranform to be
+   * performed again to update the output rows and columns.
+   */
+  ChartData transform(ChartData data) {
+    _data = data;
+    _registerListeners();
+    _transform();
+    return this;
+  }
+
+  /** Registers listeners if data.rows or data.columns are Observable. */
+  _registerListeners() {
+    _dataSubscriptions.dispose();
+
+    if(_data is Observable) {
+      var observable = (_data as Observable);
+      _dataSubscriptions.add(observable.changes.listen((records) {
+        _transform();
+
+        // NOTE: Currently we're only passing the first change because the chart
+        // area just draw with the updated data.  When we add partial update
+        // to chart area, we'll need to handle this better.
+        notifyChange(records.first);
+      }));
+    }
+  }
+
+  /**
+   * Performs the filter transform with _data.  This is called on transform and
+   * onChange if the input ChartData is Observable.
+   */
+  _transform() {
+    columns = _data.columns;
+    rows.clear();
+
+    for (var row in _data.rows) {
+      // Add the row if each value in target column passes the filter function.
+      if (filterFunctions.every((e) =>
+          e.filterFunc(row.elementAt(e.targetColumn)))) {
+        rows.add(row);
+      }
+    }
+  }
+}
+
+class FilterDefinition {
+  final FilterFunction filterFunc;
+  final int targetColumn;
+  FilterDefinition(this.targetColumn, this.filterFunc);
+}
diff --git a/pub/charted/lib/charts/transformers/transpose_transformer.dart b/pub/charted/lib/charts/transformers/transpose_transformer.dart
new file mode 100644
index 0000000..e3acbce
--- /dev/null
+++ b/pub/charted/lib/charts/transformers/transpose_transformer.dart
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+part of charted.charts;
+
+/**
+ * Transforms the ChartData by transposing the columns and rows.  A label column
+ * index in the original data will need to be specified (default to 0), all
+ * values in the specified label column will be used as the label for the
+ * transformed data, all the labels in the original Chart data columns will be
+ * populated in the label column as values of that column.
+ *
+ * All values in the data except for the data in the label column must have the
+ * same type; All columns except for the lable column must have the same
+ * formatter if a formatter exist for columns.
+ */
+class TransposeTransformer extends ChangeNotifier
+    implements ChartDataTransform, ChartData {
+  final SubscriptionsDisposer _dataSubscriptions = new SubscriptionsDisposer();
+  ObservableList<ChartColumnSpec> columns = new ObservableList();
+  ObservableList<Iterable> rows = new ObservableList();
+
+  // If specified, this values of this column in the input chart data will be
+  // used as labels of the transposed column label.  Defaults to first column.
+  int _labelColumn;
+  ChartData _data;
+
+  TransposeTransformer([this._labelColumn = 0]);
+
+  /**
+   * Transforms the input data with the specified label column in the
+   * constructor.  If the ChartData is Observable, changes fired by the input
+   * data will trigger tranform to be performed again to update the output rows
+   * and columns.
+   */
+  ChartData transform(ChartData data) {
+    _data = data;
+    _registerListeners();
+    _transform();
+    return this;
+  }
+
+  /** Registers listeners if input ChartData is Observable. */
+  _registerListeners() {
+    _dataSubscriptions.dispose();
+
+    if(_data is Observable) {
+      var observable = (_data as Observable);
+      _dataSubscriptions.add(observable.changes.listen((records) {
+        _transform();
+
+        // NOTE: Currently we're only passing the first change because the chart
+        // area just draw with the updated data.  When we add partial update
+        // to chart area, we'll need to handle this better.
+        notifyChange(records.first);
+      }));
+    }
+  }
+
+  /**
+   * Performs the transpose transform with _data.  This is called on transform
+   * and on changes if ChartData is Observable.
+   */
+  _transform() {
+    // Assert all columns are of the same type and formatter, excluding the
+    // label column.
+    var type;
+    var formatter;
+    for (var i = 0; i < _data.columns.length; i++) {
+      if (i != _labelColumn) {
+        if (type == null) {
+          type = _data.columns.elementAt(i).type;
+        } else {
+          assert(type == _data.columns.elementAt(i).type);
+        }
+        if (formatter == null) {
+          formatter = _data.columns.elementAt(i).formatter;
+        } else {
+          assert(formatter == _data.columns.elementAt(i).formatter);
+        }
+      }
+    }
+
+    columns.clear();
+    rows.clear();
+    rows.addAll(new List<Iterable>.generate(_data.columns.length - 1, (i) => []));
+
+    // Populate the transposed rows' data, excluding the label column, visit
+    // each value in the original data once.
+    var columnLabels = [];
+    for (var row in _data.rows) {
+      for (var i = 0; i < row.length; i++) {
+        var columnOffset = (i < _labelColumn) ? 0 : 1;
+        if (i != _labelColumn) {
+          (rows.elementAt(i - columnOffset) as List).add(row.elementAt(i));
+        } else {
+          columnLabels.add(row.elementAt(i));
+        }
+      }
+    }
+
+    // Transpose the ColumnSpec's label into the column where the original
+    // column that is used as the new label.
+    for (var i = 0; i < rows.length; i++) {
+      var columnOffset = (i < _labelColumn) ? 0 : 1;
+      (rows.elementAt(i) as List).insert(_labelColumn,
+          _data.columns.elementAt(i + columnOffset).label);
+
+    }
+
+    // Construct new ColumnSpaces base on the label column.
+    for (var label in columnLabels) {
+      columns.add(new ChartColumnSpec(type: type, label: label,
+          formatter: formatter));
+    }
+    columns.insert(_labelColumn,
+        new ChartColumnSpec(type: ChartColumnSpec.TYPE_STRING, label:
+        _data.columns.elementAt(_labelColumn).label));
+  }
+}
diff --git a/pub/charted/lib/core/color.dart b/pub/charted/lib/core/color.dart
new file mode 100644
index 0000000..fa1547c
--- /dev/null
+++ b/pub/charted/lib/core/color.dart
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+part of charted.core;
+
+class Color {
+  int r;
+  int g;
+  int b;
+  double a;
+
+  Color.fromHex(String hex, [this.a = 1.0]) {
+   var rgb = _hexToRgb(hex);
+   r = rgb[0];
+   g = rgb[1];
+   b = rgb[2];
+  }
+
+  Color.fromRgb(this.r, this.g, this.b, [this.a = 1.0]);
+
+  List _hexToRgb(String hex) {
+    List rgb = [];
+
+    if (hex.startsWith('#')) {
+      hex = hex.substring(1);
+    }
+    if (hex.length == 3) {
+      // expand to 6-digit hex code
+      hex = hex[0]*2 + hex[1]*2 + hex[2]*2;
+    }
+    rgb.add(int.parse(hex.substring(0, 2), radix: 16));
+    rgb.add(int.parse(hex.substring(2, 4), radix: 16));
+    rgb.add(int.parse(hex.substring(4, 6), radix: 16));
+    return rgb;
+  }
+
+  Color.fromColorString(String color) {
+    // TODO(midoringo): add rgba
+    if (color.startsWith('#')) {
+      var rgb = _hexToRgb(color);
+      r = rgb[0];
+      g = rgb[1];
+      b = rgb[2];
+      a = 1.0;
+    } else if (color.startsWith('rgb')) {
+      var numberRegEx = new RegExp(
+          r'rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)');
+      var match = numberRegEx.firstMatch(color);
+      r = int.parse(match.group(1));
+      g = int.parse(match.group(2));
+      b = int.parse(match.group(3));
+      a = 1.0;
+    } else {
+      r = g = b = 0;
+      a = 1.0;
+    }
+  }
+
+  String get rgbString => "rgb($r,$g,$b)";
+
+  String get rgbaString => "rgba($r,$g,$b,$a)";
+
+  String get hexString => '#' + _hexify(r) + _hexify(g) + _hexify(b);
+
+  String toString() => rgbString;
+
+  String _hexify(int v) {
+    return v < 0x10
+        ? "0" + math.max(0, v).toInt().toRadixString(16)
+        : math.min(255, v).toInt().toRadixString(16);
+  }
+
+  static bool isColorString(String input) {
+    return (input.startsWith('#') || input.startsWith('rgb'));
+  }
+  
+  bool operator ==(other) {
+    if (other is! Color) return false;
+    Color color = other;
+    return r == color.r && g == color.g && b == color.b && a == color.a;
+  }
+
+  // Override hashCode using strategy from Effective Java, Chapter 3.
+  int get hashCode {
+    int result = 17;
+    result = 37 * result + r.hashCode;
+    result = 37 * result + g.hashCode;
+    result = 37 * result + b.hashCode;
+    result = 37 * result + a.hashCode;
+    return result;
+  }
+}
+
diff --git a/pub/charted/lib/core/core.dart b/pub/charted/lib/core/core.dart
new file mode 100644
index 0000000..8c1b0e9
--- /dev/null
+++ b/pub/charted/lib/core/core.dart
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+library charted.core;
+
+import "dart:html" show Element;
+import "dart:math" as math;
+import "dart:collection";
+
+part 'color.dart';
+part 'lists.dart';
+part 'math.dart';
+part 'namespace.dart';
+part 'object_factory.dart';
+part 'rect.dart';
+
+const String ORIENTATION_LEFT = 'left';
+const String ORIENTATION_RIGHT = 'right';
+const String ORIENTATION_TOP = 'top';
+const String ORIENTATION_BOTTOM = 'bottom';
+
+const String EASE_TYPE_LINEAR = 'linear';
+const String EASE_TYPE_POLY = 'poly';
+const String EASE_TYPE_QUAD = 'quad';
+const String EASE_TYPE_CUBIC = 'cubic';
+const String EASE_TYPE_SIN = 'sin';
+const String EASE_TYPE_EXP = 'exp';
+const String EASE_TYPE_CIRCLE = 'circle';
+const String EASE_TYPE_ELASTIC = 'elastic';
+const String EASE_TYPE_BACK = 'back';
+const String EASE_TYPE_BOUNCE = 'bounce';
+
+const String EASE_MODE_IN = 'in';
+const String EASE_MODE_OUT = 'out';
+const String EASE_MODE_IN_OUT = 'in-out';
+const String EASE_MODE_OUT_IN = 'out-in';
+
+/** IdentityFunction */
+identityFunction(x) => x;
+
+/** Utility method to test if [val] is null or isEmpty */
+bool isNullOrEmpty(val) => val == null || val.isEmpty;
+
+/** Class representing a pair of values */
+class Pair<T1, T2> {
+  final T1 first;
+  final T2 last;
+
+  const Pair(this.first, this.last);
+
+  bool operator==(other) =>
+      other is Pair && first == other.first && last == other.last;
+}
+
+/*
+ * TODO(prsd): Move everything below this comment to the respective
+ * sub-libraries.
+ */
+
+class ScaleUtil {
+  static List nice(List domain, Nice 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;
+    }
+
+    domain[i0] = nice.floor(x0);
+    domain[i1] = nice.ceil(x1);
+    return domain;
+  }
+
+  static Nice _niceIdentity = new Nice(identityFunction, identityFunction);
+
+  static Nice niceStep(num step) {
+    return (step > 0) ? new Nice((x) => (x / step).ceil() * step,
+        (x) => (x / step).floor() * step) : _niceIdentity;
+  }
+
+  /**
+   * Returns a Function that given a value x on the domain, returns the
+   * corrsponding value on the range on a bilinear scale.
+   *
+   * @param domain         The domain of the scale.
+   * @param range          The range of the scale.
+   * @param uninterpolator The uninterpolator for domain values.
+   * @param interpolator   The interpolator for range values.
+   */
+  static Function bilinearScale(List domain, List range,
+      Function uninterpolator, Function interpolator) {
+    var u = uninterpolator(domain[0], domain[1]),
+        i = interpolator(range[0], range[1]);
+    return (x) => i(u(x));
+  }
+
+  /**
+   * Returns a Function that given a value x on the domain, returns the
+   * corrsponding value on the range on a polylinear scale.
+   *
+   * @param domain         The domain of the scale.
+   * @param range          The range of the scale.
+   * @param uninterpolator The uninterpolator for domain values.
+   * @param interpolator   The interpolator for range values.
+   */
+  static Function polylinearScale(List domain, List range,
+      Function uninterpolator, Function interpolator) {
+    var u = [],
+        i = [],
+        j = 0,
+        k = math.min(domain.length, range.length) - 1;
+
+    // Handle descending domains.
+    if (domain[k] < domain[0]) {
+      domain = domain.reversed.toList();
+      range = range.reversed.toList();
+    }
+
+    while (++j <= k) {
+      u.add(uninterpolator(domain[j - 1], domain[j]));
+      i.add(interpolator(range[j - 1], range[j]));
+    }
+
+    return (x) {
+      int index = bisect(domain, x, 1, k) - 1;
+      return i[index](u[index](x));
+    };
+  }
+
+  /**
+   * Returns the insertion point i for value x such that all values in a[lo:i]
+   * will be less than x and all values in a[i:hi] will be equal to or greater
+   * than x.
+   */
+  static int bisectLeft(List a, num x, [int lo = 0, int hi = -1]) {
+    if (hi == -1) {
+      hi = a.length;
+    }
+    while (lo < hi) {
+      int mid = ((lo + hi) / 2).floor();
+      if (a[mid] < x) {
+        lo = mid + 1;
+      } else {
+        hi = mid;
+      }
+    }
+    return lo;
+  }
+
+  /**
+   * Returns the insertion point i for value x such that all values in a[lo:i]
+   * will be less than or equalto x and all values in a[i:hi] will be greater
+   * than x.
+   */
+  static int bisectRight(List a, num x, [int lo = 0, int hi = -1]) {
+    if (hi == -1) {
+      hi = a.length;
+    }
+    while (lo < hi) {
+      int mid = ((lo + hi) / 2).floor();
+      if (x < a[mid]) {
+        hi = mid;
+      } else {
+        lo = mid + 1;
+      }
+    }
+    return lo;
+  }
+
+  static Function bisect = bisectRight;
+}
+
+class Nice {
+  Function _floor;
+  Function _ceil;
+  Nice(Function this._ceil, Function this._floor);
+  set floor(Function f) {
+    _floor = f;
+  }
+  get floor => _floor;
+  set ceil(Function f) {
+      _ceil = f;
+    }
+  get ceil => _ceil;
+}
+
diff --git a/pub/charted/lib/core/lists.dart b/pub/charted/lib/core/lists.dart
new file mode 100644
index 0000000..e731df0
--- /dev/null
+++ b/pub/charted/lib/core/lists.dart
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+part of charted.core;
+
+/** Returns a sum of all values in the given list of values */
+num sum(List values) =>
+    values == null || values.isEmpty ?
+        0: values.fold(0.0, (old, next) => old + next);
+
+/** Returns the smallest number in the given list of values */
+num min(Iterable values) =>
+    values == null || values.isEmpty ?
+        null : values.fold(values.elementAt(0), math.min);
+
+/** Returns the largest number in the given list of values */
+num max(Iterable values) =>
+    values == null || values.isEmpty ?
+        null : values.fold(values.elementAt(0), math.max);
+
+/**
+ * Represents a tuple of mininum and maximum values in a List.
+ */
+class Extent<T> extends Pair<T, T> {
+  final T min;
+  final T max;
+
+  factory Extent.items(Iterable<T> items,
+      [ Comparator compare = Comparable.compare ]) {
+    if (items.length == 0) return new Extent(null, null);
+    var max = items.first,
+        min = items.first;
+    for (var value in items) {
+      if (compare(max, value) < 0) max = value;
+      if (compare(min, value) > 0) min = value;
+    }
+    return new Extent(min, max);
+  }
+
+  const Extent(T min, T max) : min = min, max = max, super(min, max);
+}
+
+/**
+ * Iterable representing a range of values containing the start, stop
+ * and each of the step values between them.
+ */
+class Range extends IterableBase<num> {
+  final List<num> _range = <num>[];
+
+  factory Range.integers(num start, [num stop, num step = 1]) =>
+      new Range(start, stop, step, true);
+
+  Range(num start, [num stop, num step = 1, bool integers = false]) {
+    if (stop == null) {
+      stop = start;
+      start = 0;
+    }
+
+    if (step == 0 || start < stop && step < 0 || start > stop && step > 0) {
+      throw new ArgumentError('Invalid range.');
+    }
+
+    var k = _integerConversionFactor(step.abs()),
+        i = -1,
+        j;
+
+    start *= k;
+    stop *= k;
+    step *= k;
+
+    if (step < 0) {
+      while ((j = start + step * ++i) > stop) {
+        _range.add(integers ? j ~/ k : j / k);
+      }
+    } else {
+      while ((j = start + step * ++i) < stop) {
+        _range.add(integers ? j ~/ k : j / k);
+      }
+    }
+  }
+
+  @override
+  int get length => _range.length;
+
+  @override
+  num elementAt(int index) => _range.elementAt(index);
+
+  @override
+  Iterator<num> get iterator => _range.iterator;
+
+  static int _integerConversionFactor(num val) {
+    int k = 1;
+    while (val * k % 1 > 0) {
+      k *= 10;
+    }
+    return k;
+  }
+}
diff --git a/pub/charted/lib/core/math.dart b/pub/charted/lib/core/math.dart
new file mode 100644
index 0000000..a5afa05
--- /dev/null
+++ b/pub/charted/lib/core/math.dart
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+part of charted.core;
+
+const double PI = math.PI;
+const double HALF_PI = PI / 2.0;
+const double TAU = PI * 2.0;
+const double EPSILON = 1e-6;
+const double EPSILON_SQUARE = EPSILON * EPSILON;
+
+// Maximum (and minimum) value that would fit in Dart SMI
+const int SMALL_INT_MAX = (1 << 30) - 1;
+const int SMALL_INT_MIN = -1 * (1 << 30);
+
+num cosh(num x) => ((x = math.exp(x)) + 1 / x) / 2;
+num sinh(num x) => ((x = math.exp(x)) - 1 / x) / 2;
+num tanh(num x) => ((x = math.exp(2 * x)) - 1) / (x + 1);
+num toRadians(num degrees) => degrees * math.PI / 180.0;
+num toDegrees(num radians) => radians * 180.0 / math.PI;
diff --git a/pub/charted/lib/core/namespace.dart b/pub/charted/lib/core/namespace.dart
new file mode 100644
index 0000000..df6ece9
--- /dev/null
+++ b/pub/charted/lib/core/namespace.dart
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+part of charted.core;
+
+/**
+ * Basic namespace handing for Charted - includes utilities to
+ * parse the namespace prefixes and to create DOM elements using a
+ * namespace.
+ */
+class Namespace {
+  /** Support namespace prefixes mapped to their URIs. */
+  static const Map<String,String> NS_PREFIXES = const {
+    "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/"
+  };
+
+  /**
+   * Create an element from [tag]. If tag is prefixed with a
+   * supported namespace prefix, the created element will
+   * have the namespaceUri set to the correct URI.
+   */
+  static Element createChildElement(String tag, Element parent) {
+    Namespace parsed = new Namespace(tag);
+    return parsed.namespaceUri == null ?
+        parent.ownerDocument.createElementNS(parent.namespaceUri, tag) :
+        parent.ownerDocument.createElementNS(parsed.namespaceUri,
+            parsed.localName);
+  }
+
+  String localName;
+  String namespaceUri;
+
+  /**
+   * Parses a tag for namespace prefix and local name.
+   * If a known namespace prefix is found, sets the namespaceUri property
+   * to the URI of the namespace.
+   */
+  Namespace(String tagName) {
+    int separatorIdx = tagName.indexOf(':');
+    String prefix = tagName;
+    if (separatorIdx >= 0) {
+      prefix = tagName.substring(0, separatorIdx);
+      localName = tagName.substring(separatorIdx + 1);
+    }
+
+    if (NS_PREFIXES.containsKey(prefix)) {
+      namespaceUri = NS_PREFIXES[prefix];
+    } else {
+      localName = tagName;
+    }
+  }
+}
diff --git a/pub/charted/lib/core/object_factory.dart b/pub/charted/lib/core/object_factory.dart
new file mode 100644
index 0000000..c966858
--- /dev/null
+++ b/pub/charted/lib/core/object_factory.dart
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+part of charted.core;
+
+typedef T ObjectCreator<T>();
+
+/**
+ * Provides a registry and factory service.
+ *
+ * Registration:
+ * ObjectFactory.register(“type”, () => { new TypeCreator(); });
+ *
+ * Usage:
+ * instance = ObjectFactory.create('type');
+ */
+class ObjectFactory<T> {
+  Map<String, ObjectCreator<T>> _components = {};
+
+  /**
+   * Registers a component.
+   */
+  void register(String name, ObjectCreator<T> creator) {
+    _components[name] = creator;
+  }
+
+  /**
+   * Creates an instance of the component.
+   */
+  T create(String name) {
+    if (!_components.containsKey(name)) {
+      throw new ArgumentError('Element $name not found in ComponentFactory');
+    }
+    var creator = _components[name],
+        instance = creator();
+    if (instance == null) {
+      throw new ArgumentError('Component $name initialization failed.');
+    }
+    return instance;
+  }
+}
diff --git a/pub/charted/lib/core/rect.dart b/pub/charted/lib/core/rect.dart
new file mode 100644
index 0000000..8e1e8e6
--- /dev/null
+++ b/pub/charted/lib/core/rect.dart
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+part of charted.core;
+
+/** Interface representing size and position of an element */
+class Rect {
+  final num x;
+  final num y;
+  final num width;
+  final num height;
+
+  const Rect([this.x = 0, this.y = 0, this.width = 0, this.height = 0]);
+  const Rect.size(this.width, this.height) : x = 0, y = 0;
+  const Rect.position(this.x, this.y) : width = 0, height = 0;
+
+  bool operator==(other) =>
+      other is Rect && isSameSizeAs(other) && isSamePositionAs(other);
+
+  bool isSameSizeAs(Rect other) =>
+      other != null && width == other.width && height == other.height;
+
+  bool isSamePositionAs(Rect other) =>
+      other != null && x == other.x && y == other.y;
+
+  bool contains(num otherX, num otherY) =>
+      otherX >= x && otherX <= x + width &&
+      otherY >= y && otherY <= y + height;
+
+  String toString() => '$x, $y, $width, $height';
+}
+
+/**
+ * Editable Rect
+ * TODO(prsd): Can we avoid duplication here?
+ */
+class EditableRect implements Rect {
+  num x;
+  num y;
+  num width;
+  num height;
+
+  EditableRect(this.x, this.y, this.width, this.height);
+  EditableRect.size(this.width, this.height);
+  EditableRect.position(this.x, this.y);
+
+  bool operator==(other) =>
+      other is Rect && isSameSizeAs(other) && isSamePositionAs(other);
+
+  bool isSameSizeAs(Rect other) =>
+      other != null && width == other.width && height == other.height;
+
+  bool isSamePositionAs(Rect other) =>
+      other != null && x == other.x && y == other.y;
+
+  bool contains(num otherX, num otherY) =>
+      otherX >= x && otherX <= x + width &&
+      otherY >= y && otherY <= y + height;
+}
diff --git a/pub/charted/lib/event/event.dart b/pub/charted/lib/event/event.dart
new file mode 100644
index 0000000..c7272a7
--- /dev/null
+++ b/pub/charted/lib/event/event.dart
@@ -0,0 +1,15 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+library charted.event;
+
+import 'dart:async';
+import 'dart:collection';
+import 'dart:html';
+
+part 'timer.dart';
diff --git a/pub/charted/lib/event/timer.dart b/pub/charted/lib/event/timer.dart
new file mode 100644
index 0000000..939801f
--- /dev/null
+++ b/pub/charted/lib/event/timer.dart
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+part of charted.event;
+
+class ChartTimer {
+  static DoubleLinkedQueue<DoubleLinkedQueueEntry<ChartTimer>> _timerQueue =
+      new DoubleLinkedQueue<DoubleLinkedQueueEntry<ChartTimer>>();
+
+  static bool _interval = false;
+  static Timer _timer;
+  static var activeTimer;
+
+  Function _callback;
+  num _time;
+  bool _finished = false;
+
+  /**
+   * Start a custom animation timer, invoking the specified function
+   * repeatedly until it returns true. There is no way to cancel the timer
+   * after it starts, so make sure your timer function returns true when done!
+   *
+   * An optional numeric delay in milliseconds may be specified when the given
+   * function should only be invoked after a delay. The delay is relative to
+   * the specified time in milliseconds since UNIX epoch; if time is not
+   * specified, it defaults to Date.now
+   */
+  ChartTimer(this._callback, [int delay = 0, DateTime then = null]) {
+    if (then == null) {
+      then = new DateTime.now();
+    }
+    _time = then.add(new Duration(milliseconds: delay)).millisecondsSinceEpoch;
+    _timerQueue.add(new DoubleLinkedQueueEntry(this));
+
+    if (!_interval) {
+      if (_timer != null) {
+        _timer.cancel();
+      }
+      _interval = true;
+      window.animationFrame.then(_step);
+    }
+  }
+
+  /**
+   * Immediately execute (invoke once) any active timers. Normally, zero-delay
+   * transitions are executed after an instantaneous delay (<10ms). This can
+   * cause a brief flicker if the browser renders the page twice: once at the
+   * end of the first event loop, then again immediately on the first timer
+   * callback. By flushing the timer queue at the end of the first event loop,
+   * you can run any zero-delay transitions immediately and avoid the flicker.
+   */
+  void flush() {
+    _mark();
+    _sweep();
+  }
+
+  /**
+   * Interates through each of the timer and execute the callback if the set
+   * delay has elasped.
+   */
+  _mark() {
+    var now = new DateTime.now().millisecondsSinceEpoch;
+    for (DoubleLinkedQueueEntry e in _timerQueue) {
+      if (now > e.element._time) {
+        activeTimer = e.element;
+        e.element._finished = e.element._callback(now - e.element._time);
+      }
+    }
+    return now;
+  }
+
+  /**
+   * Flush after callbacks to avoid concurrent queue modification.
+   * Removes all finished timer from the queue and returns the time of the
+   * earliest active timer, post-sweep.
+   */
+  _sweep() {
+    var time = double.INFINITY;
+    for (DoubleLinkedQueueEntry e in _timerQueue) {
+      if (e.element._finished) {
+        _timerQueue.remove(e);
+      } else {
+        if (e.element._time < time) {
+          time = e.element._time;
+        }
+      }
+    }
+    return time;
+  }
+
+  /*
+   * If the delay of the timer in the nearest future is less than 24ms, use
+   * animationFrame to step, otherwise schedule the step of the chart timer
+   * using a Dart Timer with the delay.
+   */
+  _step([num delta = 0]) {
+    var now = _mark(),
+        delay = _sweep() - now;
+
+    if (delay > 24) {
+      // If delay is infinity there's no more timer in queue.
+      if (delay.isFinite) {
+        if (_timer != null) {
+          _timer.cancel();
+        }
+        _timer = new Timer(new Duration(milliseconds: delay), _step);
+      }
+      _interval = false;
+      activeTimer = null;
+    }
+    else {
+      _interval = true;
+      window.animationFrame.then(_step);
+    }
+  }
+}
+
diff --git a/pub/charted/lib/format/format.dart b/pub/charted/lib/format/format.dart
new file mode 100644
index 0000000..52bdd5e
--- /dev/null
+++ b/pub/charted/lib/format/format.dart
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+library charted.format;
+
+import 'dart:math' as math;
+import 'package:charted/locale/locale.dart';
+
+typedef String NumberFormatFunction(num x, [int precision]);
+typedef String FormatFunction(num x);
+
+/**
+ * Returns a new format function with the given string specifier.
+ * The format specifier is modeled after Python 3.1's built-in format
+ * specification mini-language.
+ *
+ * The general form of a specifier is:
+ * [​[fill]align][sign][symbol][0][width][,][.precision][type]
+ *
+ * @see <a href="http://docs.python.org/release/3.1.3/library/string.html#formatspec">Python format specification mini-language</a>
+ */
+FormatFunction format(String specifier, [Locale locale = null]) {
+  if (locale == null) {
+    locale = new EnusLocale();
+  }
+  return locale.numberFormat.format(specifier);
+}
+
+
+/*
+ * Class for computing the SI format prefix for the given value.
+ */
+class FormatPrefix {
+  // SI scale units in increments of 1000.
+  static const List unitPrefixes = const
+      ["y","z","a","f","p","n","µ","m","","k","M","G","T","P","E","Z","Y"];
+
+  Function _scale;
+  String _symbol;
+
+  FormatPrefix(num value, [int precision = 0]) {
+    var i = 0;
+    if (value < 0) {
+      value *= -1;
+    }
+    if (precision > 0) {
+      value = _roundToPrecision(value, _formatPrecision(value, precision));
+    }
+
+    // Determining SI scale of the value in increment of 1000.
+    i = 1 + (1e-12 + math.log(value) / math.LN10).floor();
+    i = math.max(-24, math.min(24,
+        ((i - 1) / 3).floor() * 3));
+    i = 8 + (i / 3).floor();
+
+    // Sets the scale and symbol of the value.
+    var k = math.pow(10, (8 - i).abs() * 3);
+    _scale = i > 8 ? (d) => d / k : (d) => d * k;
+    _symbol = unitPrefixes[i];
+  }
+
+  _formatPrecision(num x, num p) {
+    return p - (x != 0 ? (math.log(x) / math.LN10).ceil() : 1);
+  }
+
+  /** Returns the value of x rounded to the nth digit. */
+  _roundToPrecision(num x, num n) {
+    return n != 0 ?
+        (x * (n = math.pow(10, n))).round() / n : x.round();
+  }
+
+  /** Returns the SI prefix for the value. */
+  get symbol => _symbol;
+
+  /** Returns the scale for the value corresponding to the SI prefix. */
+  get scale => _scale;
+}
diff --git a/pub/charted/lib/interpolators/easing_impl.dart b/pub/charted/lib/interpolators/easing_impl.dart
new file mode 100644
index 0000000..fbbc45d
--- /dev/null
+++ b/pub/charted/lib/interpolators/easing_impl.dart
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+part of charted.interpolators;
+
+EasingFn clampEasingFn(EasingFn f) =>
+    (t) => t <= 0 ? 0 : t >= 1 ? 1 : f(t);
+
+// Ease-in is defined is the identifyFunction.
+/** Ease-out */
+EasingFn reverseEasingFn(EasingFn f) =>
+    (t) => 1 - f(1 - t);
+
+/** Ease-in-out */
+EasingFn reflectEasingFn(EasingFn f) =>
+    (t) => .5 * (t < .5 ? f(2 * t) : (2 - f(2 - 2 * t)));
+
+/** Ease-out-in */
+EasingFn reflectReverseEasingFn(EasingFn f) =>
+    reflectEasingFn(reverseEasingFn(f));
+
+EasingFn easePoly([e = 1]) => (t) => math.pow(t, e);
+
+EasingFn easeElastic([a = 1, p = 0.45]) {
+  var s = p / 2 * math.PI * math.asin(1 / a);
+  return (t) => 1 + a * math.pow(2, -10 * t) *
+      math.sin((t - s) * 2 * math.PI / p);
+}
+
+EasingFn easeBack([s = 1.70158]) =>
+    (num t) => t * t * ((s + 1) * t - s);
+
+EasingFn easeQuad() => (num t) => t * t;
+
+EasingFn easeCubic() => (num t) => t * t * t;
+
+EasingFn easeCubicInOut() =>
+    (num t) {
+      if (t <= 0) return 0;
+      if (t >= 1) return 1;
+      var t2 = t * t,
+          t3 = t2 * t;
+      return 4 * (t < .5 ? t3 : 3 * (t - t2) + t3 - .75);
+    };
+
+EasingFn easeSin() =>
+    (num t) => 1 - math.cos(t * math.PI / 2);
+
+EasingFn easeExp() =>
+    (num t) => math.pow(2, 10 * (t - 1));
+
+EasingFn easeCircle() =>
+    (num t) => 1 - math.sqrt(1 - t * t);
+
+EasingFn easeBounce() =>
+    (num t) =>  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;
diff --git a/pub/charted/lib/interpolators/interpolators.dart b/pub/charted/lib/interpolators/interpolators.dart
new file mode 100644
index 0000000..b9c1473
--- /dev/null
+++ b/pub/charted/lib/interpolators/interpolators.dart
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+library charted.interpolators;
+
+import 'dart:math' as math;
+import 'package:charted/core/core.dart';
+import 'package:csslib/parser.dart' as cssParser;
+
+part 'interpolators_impl.dart';
+part 'easing_impl.dart';
+
+/**
+ * [InterpolateFn] accepts [t], such that 0.0 < t < 1.0 and returns
+ * a value in a pre-defined range.
+ */
+typedef InterpolateFn(num t);
+
+/**
+ * [Interpolator] accepts two parameters [a], [b] and returns a function
+ * that takes a number t, such that 0.0 <= t <= 1.0 and interpolates it
+ * to a value between [a] and [b]
+ */
+typedef InterpolateFn Interpolator(a, b);
+
+/** [EasingFn] is same as [InterpolateFn] but is for computing easing */
+typedef num EasingFn(num t);
+
+/**
+ * [EasingMode] a mode that can be applied on a [Easingfn] and returns a new
+ * EasingFn.
+ */
+typedef EasingFn EasingMode(EasingFn fn);
+
+/**
+ * List of registered interpolators - [interpolator] iterates through
+ * this list from backwards and the first non-null interpolate function
+ * is returned to the caller.
+ */
+List<Interpolator> interpolators = [ interpolatorByType ];
+
+/**
+ * Returns a default interpolator between values [a] and [b]. Unless
+ * more interpolators are added, one of the internal implementations are
+ * selected by the type of [a] and [b].
+ */
+InterpolateFn interpolator(a, b) {
+  var fn, i = interpolators.length;
+  while (--i >= 0 && fn == null) {
+    fn = interpolators[i](a, b);
+  }
+  return fn;
+}
+
+/** Returns an interpolator based on the type of [a] and [b] */
+InterpolateFn interpolatorByType(a, b) =>
+    (a is List && b is List) ? interpolateList(a, b) :
+    (a is Map && b is Map) ? interpolateMap(a, b) :
+    (a is String && b is String) ? interpolateString(a, b) :
+    (a is num && b is num) ? interpolateNumber(a, b) :
+    (a is Color && b is Color) ? interpolateColor(a, b) :
+    (t) => (t <= 0.5) ? a : b;
+
+/*
+ * Creates an easing function based on type and mode.
+ * Assumes that all easing function generators support calling
+ * without any parameters.
+ */
+EasingFn easeFunctionByName(String type,
+    [String mode = EASE_MODE_IN, List params]) {
+  const Map _easeType = const {
+    'linear': identityFunction,
+    'poly': easePoly,
+    'quad': easeQuad,
+    'cubic': easeCubic,
+    'sin': easeSin,
+    'exp': easeExp,
+    'circle': easeCircle,
+    'elastic': easeElastic,
+    'back': easeBack,
+    'bounce': easeBounce
+  };
+
+  const Map _easeMode = const {
+    'in': identityFunction,
+    'out': reverseEasingFn,
+    'in-out': reflectEasingFn,
+    'out-in': reflectReverseEasingFn
+  };
+
+  assert(_easeType.containsKey(type));
+  assert(_easeMode.containsKey(mode));
+
+  var fn = Function.apply(_easeType[type], params);
+  return clampEasingFn(_easeMode[mode](fn));
+}
diff --git a/pub/charted/lib/interpolators/interpolators_impl.dart b/pub/charted/lib/interpolators/interpolators_impl.dart
new file mode 100644
index 0000000..8d3e86a
--- /dev/null
+++ b/pub/charted/lib/interpolators/interpolators_impl.dart
@@ -0,0 +1,352 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+part of charted.interpolators;
+
+/** Returns a numeric interpolator between numbers [a] and [b] */
+InterpolateFn interpolateNumber(num a, num b) {
+  b -= a;
+  return (t) => a + b * t;
+}
+
+/** Returns a rounded number interpolator between numbers [a] and [b] */
+InterpolateFn interpolateRound(num a, num b) {
+  b -= a;
+  return (t) => (a + b * t).round();
+}
+
+/**
+ * Returns the interpolator between two strings [a] and [b].
+ *
+ * The interpolator will interpolate all the number pairs in both strings
+ * that have same number of numeric parts.  The function assumes the non
+ * number part of the string to be identical and would use string [b] for
+ * merging the non numeric part of the strings.
+ *
+ * Eg: Interpolate between $100.0 and $150.0
+ */
+InterpolateFn interpolateString(String a, String b) {
+  if (a == null || b == null) return (t) => b;
+  if (Color.isColorString(a) && Color.isColorString(b)) {
+    return interpolateColor(new Color.fromColorString(a),
+              new Color.fromColorString(b));
+  }
+  var numberRegEx =
+          new RegExp(r'[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?'),
+      numMatchesInA = numberRegEx.allMatches(a),
+      numMatchesInB = numberRegEx.allMatches(b),
+      stringParts = [],
+      numberPartsInA = [],
+      numberPartsInB = [],
+      interpolators = [],
+      s0 = 0;
+
+  // Get all the non number parts in a.
+  numberPartsInA.addAll(numMatchesInA.map((m) => m.group(0)));
+
+  // Get all the non number parts in b and record string parts.
+  for (Match m in numMatchesInB) {
+    stringParts.add(b.substring(s0, m.start));
+    numberPartsInB.add(m.group(0));
+    s0 = m.end;
+  }
+
+  if (s0 < b.length) stringParts.add(b.substring(s0));
+
+  int numberLength = math.min(numberPartsInA.length, numberPartsInB.length);
+  int maxLength = math.max(numberPartsInA.length, numberPartsInB.length);
+  for (var i = 0; i < numberLength; i++) {
+    interpolators.add(interpolateNumber(num.parse(numberPartsInA[i]),
+        num.parse(numberPartsInB[i])));
+  }
+  if (numberPartsInA.length < numberPartsInB.length) {
+    for (var i = numberLength; i < maxLength; i++) {
+      interpolators.add(interpolateNumber(num.parse(numberPartsInB[i]),
+        num.parse(numberPartsInB[i])));
+    }
+  }
+
+  return (t) {
+    StringBuffer sb = new StringBuffer();
+    for (var i = 0; i < stringParts.length; i++) {
+      sb.write(stringParts[i]);
+      if (interpolators.length > i) {
+        sb.write(interpolators[i](t));
+      }
+    }
+    return sb.toString();
+  };
+}
+
+/** Returns the interpolator for RGB values. */
+InterpolateFn interpolateColor(Color a, Color b) {
+  if (a == null || b == null) return (t) => b;
+  var ar = a.r,
+      ag = a.g,
+      ab = a.b,
+      br = b.r - ar,
+      bg = b.g - ag,
+      bb = b.b - ab;
+
+  return (t) => new Color.fromRgb(
+      (ar + br * t).round(), (ag + bg * t).round(), (ab + bb * t).round());
+}
+
+/** Returns the interpolator using HSL color system converted to Hex string. */
+InterpolateFn interpolateHsl(a, b) {
+  if (a == null || b == null) return (t) => b;
+  if (a is String && Color.isColorString(a)) a = new Color.fromColorString(a);
+  if (a is Color) a = new cssParser.Hsla.fromString(a.hexString);
+  if (b is String && Color.isColorString(b)) b = new Color.fromColorString(b);
+  if (b is Color) b = new cssParser.Hsla.fromString(b.hexString);
+
+  var ah = a.hue,
+      as = a.saturation,
+      al = a.lightness,
+      bh = b.hue - ah,
+      bs = b.saturation - as,
+      bl = b.lightness - al;
+
+  return (t) => "#" + new cssParser.Hsla(
+      ah + bh * t, as + bs * t, al + bl * t).toHexArgbString();
+}
+
+/**
+ * Returns the interpolator that interpolators each element between lists
+ * [a] and [b] using registered interpolators.
+ */
+InterpolateFn interpolateList(List a, List b) {
+  if (a == null || b == null) return (t) => b;
+  var x = [],
+      na = a.length,
+      nb = b.length,
+      n0 = math.min(na, nb),
+      c = new List.filled(math.max(na, nb), null),
+      i;
+
+  for (i = 0; i < n0; i++) x.add(interpolator(a[i], b[i]));
+  for (; i < na; ++i) c[i] = a[i];
+  for (; i < nb; ++i) c[i] = b[i];
+
+  return (t) {
+    for (i = 0; i < n0; ++i) c[i] = x[i](t);
+    return c;
+  };
+}
+
+/**
+ * Returns the interpolator that interpolators each element between maps
+ * [a] and [b] using registered interpolators.
+ */
+InterpolateFn interpolateMap(Map a, Map b) {
+  if (a == null || b == null) return (t) => b;
+  var x = new Map(),
+      c = new Map(),
+      ka = a.keys.toList(),
+      kb = b.keys.toList();
+
+  ka.forEach((k) {
+    if (b[k] != null) x[k] = (interpolator(a[k], b[k]));
+    else c[k] = a[k];
+  });
+  kb.forEach((k) {
+    if (c[k] == null) c[k] = b[k];
+  });
+
+  return (t) {
+    x.forEach((k, v) => c[k] = v(t));
+    return c;
+  };
+}
+
+InterpolateFn uninterpolateNumber(num a, num b) {
+  b = 1 / (b - a);
+  return (x) => (x - a) * b;
+}
+
+InterpolateFn uninterpolateClamp(num a, num b) {
+  b = 1 / (b - a);
+  return (x) => math.max(0, math.min(1, (x - a) * b));
+}
+
+/**
+ * Returns the interpolator that interpolators two transform strings
+ * [a] and [b] by their translate, rotate, scale and skewX parts.
+ */
+InterpolateFn interpolateTransform(String a, String b) {
+  if (a == null || b == null) return (t) => b;
+  var numRegExStr = r'[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?',
+      translateRegExStr =
+          r'translate\(' + numRegExStr + r',' + numRegExStr + r'\)',
+      scaleRegExStr = r'scale\(' + numRegExStr + r',' + numRegExStr + r'\)',
+      rotateRegExStr = r'rotate\(' + numRegExStr + r'(deg)?\)',
+      skewRegExStr = r'skewX\(' + numRegExStr + r'(deg)?\)',
+
+      numberRegEx = new RegExp(numRegExStr),
+      translateRegEx = new RegExp(translateRegExStr),
+      scaleRegEx = new RegExp(scaleRegExStr),
+      rotateRegEx = new RegExp(rotateRegExStr),
+      skewRegEx = new RegExp(skewRegExStr),
+
+      translateA = translateRegEx.firstMatch(a),
+      scaleA = scaleRegEx.firstMatch(a),
+      rotateA = rotateRegEx.firstMatch(a),
+      skewA = skewRegEx.firstMatch(a),
+
+      translateB = translateRegEx.firstMatch(b),
+      scaleB = scaleRegEx.firstMatch(b),
+      rotateB = rotateRegEx.firstMatch(b),
+      skewB = skewRegEx.firstMatch(b);
+
+  var numSetA = [],
+      numSetB = [],
+      tempStr, match;
+
+  // translate
+  if (translateA != null) {
+    tempStr = a.substring(translateA.start, translateA.end);
+    match = numberRegEx.allMatches(tempStr);
+    for (Match m in match) {
+      numSetA.add(num.parse(m.group(0)));
+    }
+  } else {
+    numSetA.addAll(const[0, 0]);
+  }
+
+  if (translateB != null) {
+    tempStr = b.substring(translateB.start, translateB.end);
+    match = numberRegEx.allMatches(tempStr);
+    for (Match m in match) {
+      numSetB.add(num.parse(m.group(0)));
+    }
+  } else {
+    numSetB.addAll(const[0, 0]);
+  }
+
+  // scale
+  if (scaleA != null) {
+    tempStr = a.substring(scaleA.start, scaleA.end);
+    match = numberRegEx.allMatches(tempStr);
+    for (Match m in match) {
+      numSetA.add(num.parse(m.group(0)));
+    }
+  } else {
+    numSetA.addAll(const[1, 1]);
+  }
+
+  if (scaleB != null) {
+    tempStr = b.substring(scaleB.start, scaleB.end);
+    match = numberRegEx.allMatches(tempStr);
+    for (Match m in match) {
+      numSetB.add(num.parse(m.group(0)));
+    }
+  } else {
+    numSetB.addAll(const[1, 1]);
+  }
+
+  // rotate
+  if (rotateA != null) {
+    tempStr = a.substring(rotateA.start, rotateA.end);
+    match = numberRegEx.firstMatch(tempStr);
+    numSetA.add(num.parse(match.group(0)));
+  } else {
+    numSetA.add(0);
+  }
+
+  if (rotateB != null) {
+    tempStr = b.substring(rotateB.start, rotateB.end);
+    match = numberRegEx.firstMatch(tempStr);
+    numSetB.add(num.parse(match.group(0)));
+  } else {
+    numSetB.add(0);
+  }
+
+  // rotate < 180 degree
+  if (numSetA[4] != numSetB[4]) {
+    if (numSetA[4] - numSetB[4] > 180) {
+      numSetB[4] += 360;
+    } else if (numSetB[4] - numSetA[4] > 180) {
+      numSetA[4] += 360;
+    }
+  }
+
+  // skew
+  if (skewA != null) {
+    tempStr = a.substring(skewA.start, skewA.end);
+    match = numberRegEx.firstMatch(tempStr);
+    numSetA.add(num.parse(match.group(0)));
+  } else {
+    numSetA.add(0);
+  }
+
+  if (skewB != null) {
+    tempStr = b.substring(skewB.start, skewB.end);
+    match = numberRegEx.firstMatch(tempStr);
+    numSetB.add(num.parse(match.group(0)));
+  } else {
+    numSetB.add(0);
+  }
+
+  return (t) {
+    return "translate("+
+        interpolateNumber(numSetA[0], numSetB[0])(t).toString()+"," +
+        interpolateNumber(numSetA[1], numSetB[1])(t).toString()+")scale("+
+        interpolateNumber(numSetA[2], numSetB[2])(t).toString()+","+
+        interpolateNumber(numSetA[3], numSetB[3])(t).toString()+")rotate("+
+        interpolateNumber(numSetA[4], numSetB[4])(t).toString()+")skewX("+
+        interpolateNumber(numSetA[5], numSetB[5])(t).toString()+")";
+  };
+}
+
+/**
+ * Returns the interpolator that interpolators two Zoom lists [a] and [b].
+ * [a] and [b] are described by triple elements
+ * [ux0, uy0, w0] and [ux1, uy1, w1].
+ */
+InterpolateFn interpolateZoom(List a, List b) {
+  if (a == null || b == null) return (t) => b;
+  assert(a.length == b.length && a.length == 3);
+
+  var sqrt2 = math.SQRT2,
+      param2 = 2,
+      param4 = 4;
+
+  var ux0 = a[0], uy0 = a[1], w0 = a[2],
+      ux1 = b[0], uy1 = b[1], w1 = b[2];
+
+  var dx = ux1 - ux0,
+      dy = uy1 - uy0,
+      d2 = dx * dx + dy * dy,
+      d1 = math.sqrt(d2),
+      b0 = (w1 * w1 - w0 * w0 + param4 * d2) / (2 * w0 * param2 * d1),
+      b1 = (w1 * w1 - w0 * w0 - param4 * d2) / (2 * w1 * param2 * d1),
+      r0 = math.log(math.sqrt(b0 * b0 + 1) - b0),
+      r1 = math.log(math.sqrt(b1 * b1 + 1) - b1),
+      dr = r1 - r0,
+      S = ((!dr.isNaN) ? dr : math.log(w1 / w0)) / sqrt2;
+
+  return (t) {
+    var s = t * S;
+    if (!dr.isNaN) {
+      // General case.
+      var coshr0 = cosh(r0),
+          u = w0 / (param2 * d1) * (coshr0 * tanh(sqrt2 * s + r0) - sinh(r0));
+      return [
+        ux0 + u * dx,
+        uy0 + u * dy,
+        w0 * coshr0 / cosh(sqrt2 * s + r0)
+      ];
+    }
+    // Special case for u0 ~= u1.
+    return [
+      ux0 + t * dx,
+      uy0 + t * dy,
+      w0 * math.exp(sqrt2 * s)
+    ];
+  };
+}
diff --git a/pub/charted/lib/layout/layout.dart b/pub/charted/lib/layout/layout.dart
new file mode 100644
index 0000000..ecfbd1e
--- /dev/null
+++ b/pub/charted/lib/layout/layout.dart
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+/*
+ * TODO(prsd): Document library
+ */
+library charted.layout;
+
+import 'dart:html' show Element;
+import 'package:charted/core/core.dart';
+import 'package:charted/selection/selection.dart';
+import 'package:charted/svg/svg.dart' show SvgArcData;
+
+part 'pie_layout.dart';
\ No newline at end of file
diff --git a/pub/charted/lib/layout/pie_layout.dart b/pub/charted/lib/layout/pie_layout.dart
new file mode 100644
index 0000000..fcedbd4
--- /dev/null
+++ b/pub/charted/lib/layout/pie_layout.dart
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+part of charted.layout;
+
+/**
+ * Utility class to create arc definitions that can be used by the SvgArc
+ * to generate arcs for pie and donut charts
+ */
+class PieLayout {
+  /**
+   * Callback to convert datum to values used for layout
+   * Defaults to [defaultValueAccessor]
+   */
+  SelectionValueAccessor<num> accessor = defaultValueAccessor;
+
+  /**
+   * Callback to get the start angle for the pie. This callback is
+   * called once per list of value (i.e once per call to [layout])
+   * Defaults to [defaultStartAngleCallback]
+   */
+  SelectionCallback<num> startAngleCallback = defaultStartAngleCallback;
+
+  /**
+   * Callback to get the start angle for the pie. This callback is
+   * called once per list of value (i.e once per call to [layout])
+   * Defaults to [defaultEndAngleCallback]
+   */
+  SelectionCallback<num> endAngleCallback = defaultEndAngleCallback;
+
+  /**
+   * Comparator that is used to set the sort order of values. If not
+   * specified, the input order is used.
+   */
+  Comparator<num> compare = null;
+
+  /**
+   * Return a list of SvgArcData objects that could be used to create
+   * arcs in a pie-chart or donut-chart.
+   */
+  List layout(List data, [int ei, Element e]) {
+    var values = new List.generate(data.length,
+            (int i) => accessor(data[i], i)),
+        startAngle = startAngleCallback(data, ei, e),
+        endAngle = endAngleCallback(data, ei, e),
+        total = sum(values),
+        scaleFactor = (endAngle - startAngle) / (total > 0 ? total : 1),
+        index = new Range.integers(values.length).toList(),
+        arcs = new List(data.length);
+
+    if (compare != null) {
+      index.sort((left, right) => compare(data[left], data[right]));
+    }
+
+    int count = 0;
+    index.forEach((i) {
+      endAngle = startAngle + values[i] * scaleFactor;
+      arcs[count++] = new SvgArcData(data[i], values[i], startAngle, endAngle);
+      startAngle = endAngle;
+    });
+
+    return arcs;
+  }
+
+  /** Sets a constant value to start angle of the layout */
+  set startAngle(num value) =>
+      startAngleCallback = toCallback(value);
+
+  /** Sets a constant value to end angle of the layout */
+  set endAngle(num value) =>
+      endAngleCallback = toCallback(value);
+
+  /** Default value accessor */
+  static num defaultValueAccessor(num d, i) => d;
+
+  /** Default start angle callback - returns 0 */
+  static num defaultStartAngleCallback(d, i, _) => 0;
+
+  /** Default end angle calback - returns 2 * PI */
+  static num defaultEndAngleCallback(d, i, _) => 2 * PI;
+}
diff --git a/pub/charted/lib/locale/en_us.dart b/pub/charted/lib/locale/en_us.dart
new file mode 100644
index 0000000..e37d7ab
--- /dev/null
+++ b/pub/charted/lib/locale/en_us.dart
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+part of charted.locale;
+
+// Defines the en_us locale and related format properties.
+class EnusLocale extends Locale {
+  get identifier => 'en_US';
+  get decimal => '.';
+  get thousands => ',';
+  get grouping => [3];
+  get currency => ['\$', ''];
+  get dateTime => '%a %b %e %X %Y';
+  get date => '%m/%d/%Y';
+  get time => '%H =>%M =>%S';
+  get periods => ['AM', 'PM'];
+  get days => ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
+  get shortDays => ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
+  get months => ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
+  get shortMonths => ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
+}
diff --git a/pub/charted/lib/locale/locale.dart b/pub/charted/lib/locale/locale.dart
new file mode 100644
index 0000000..d555ed0
--- /dev/null
+++ b/pub/charted/lib/locale/locale.dart
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+/*
+ * TODO(midoringo): Document library
+ */
+library charted.locale;
+
+import 'dart:math' as math;
+import 'package:charted/core/core.dart';
+import 'package:charted/format/format.dart';
+import 'package:charted/time/time.dart' as chartTime;
+import 'package:charted/scale/scale.dart';
+import 'package:intl/date_symbol_data_local.dart';
+import 'package:intl/intl.dart';
+
+import 'package:charted/interpolators/interpolators.dart' as interpolators;
+
+part 'en_us.dart';
+part 'number_format.dart';
+part 'time_format.dart';
+part 'time_scale.dart';
+
+abstract class Locale {
+  String get identifier;
+  String get decimal;
+  String get thousands;
+  List get grouping;
+  List get currency;
+  String get dateTime;
+  String get date;
+  String get time;
+  List get periods;
+  List get days;
+  List get shortDays;
+  List get months;
+  List get shortMonths;
+
+  Locale() {
+    initializeDateFormatting(this.identifier, null);
+  }
+
+  NumberFormat get numberFormat => new NumberFormat(this);
+  // TODO(songrenchu): port time format.
+  TimeFormat timeFormat([specifier = null]) =>
+    new TimeFormat(specifier, this.identifier);
+
+}
diff --git a/pub/charted/lib/locale/number_format.dart b/pub/charted/lib/locale/number_format.dart
new file mode 100644
index 0000000..fcdf29d
--- /dev/null
+++ b/pub/charted/lib/locale/number_format.dart
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+part of charted.locale;
+
+typedef String NumberFormatFunction(num x, [int precision]);
+typedef String FormatFunction(num x);
+
+// TODO(midoringo): Add more test on NumberFormat.
+
+/**
+ * The number formatter of a given locale.  Applying the locale specific
+ * number format, number grouping and currency symbol, etc..  The format
+ * function in the NumberFormat class is used to format a number by the given
+ * specifier with the number properties of the locale.
+ */
+class NumberFormat {
+
+  // [[fill]align][sign][symbol][0][width][,][.precision][type]
+  static RegExp FORMAT_REGEX =
+      new RegExp(r'(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?'
+      r'(\.-?\d+)?([a-z%])?', caseSensitive: false);
+
+  String localeDecimal;
+  String localeThousands;
+  List localeGrouping;
+  List localeCurrency;
+  Function formatGroup;
+
+  NumberFormat(Locale locale) {
+    localeDecimal = locale.decimal;
+    localeThousands = locale.thousands;
+    localeGrouping = locale.grouping;
+    localeCurrency = locale.currency;
+    formatGroup = (localeGrouping != null) ? (value) {
+      var i = value.length,
+          t = [],
+          j = 0,
+          g = localeGrouping[0];
+      while (i > 0 && g > 0) {
+        if (i - g >= 0) {
+          i = i - g;
+        } else {
+          g = i;
+          i = 0;
+        }
+        var length = (i + g) < value.length ? (i + g) : value.length;
+        t.add(value.substring(i, length));
+        g = localeGrouping[j = (j + 1) % localeGrouping.length];
+      }
+      return t.reversed.join(localeThousands);
+    } : (x) => x;
+  }
+
+  /**
+   * Returns a new format function with the given string specifier. A format
+   * function takes a number as the only argument, and returns a string
+   * representing the formatted number. The format specifier is modeled after
+   * Python 3.1's built-in format specification mini-language. The general form
+   * of a specifier is:
+   * [​[fill]align][sign][symbol][0][width][,][.precision][type].
+   *
+   * @see <a href="http://docs.python.org/release/3.1.3/library/string.html#formatspec">format specification mini-language</a>
+   */
+  FormatFunction format(String specifier) {
+    Match match = FORMAT_REGEX.firstMatch(specifier);
+    var fill = match.group(1) != null ? match.group(1) : ' ',
+        align = match.group(2) != null ? match.group(2) : '>',
+        sign = match.group(3) != null ? match.group(3) : '',
+        symbol = match.group(4) != null ? match.group(4) : '',
+        zfill = match.group(5),
+        width = match.group(6) != null ? int.parse(match.group(6)) : 0,
+        comma = match.group(7) != null,
+        precision = match.group(8) != null ?
+            int.parse(match.group(8).substring(1)) : null,
+        type = match.group(9),
+        scale = 1,
+        prefix = '',
+        suffix = '',
+        integer = false;
+
+    if (zfill != null || fill == '0' && align == '=') {
+      zfill = fill = '0';
+      align = '=';
+      if (comma) {
+        width -= ((width - 1) / 4).floor();
+      }
+    }
+
+    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 'b':
+      case 'o':
+      case 'x':
+      case 'X': if (symbol == '#') prefix = '0' + type.toLowerCase(); break;
+      case 'c':
+      case 'd': integer = true; precision = 0; break;
+      case 's': scale = -1; type = 'r'; break;
+    }
+
+    if (symbol == '\$') {
+      prefix = localeCurrency[0];
+      suffix = localeCurrency[1];
+    }
+
+    // If no precision is specified for r, fallback to general notation.
+    if (type == 'r' && precision == null) {
+      type = 'g';
+    }
+
+    // Ensure that the requested precision is in the supported range.
+    if (precision != null) {
+      if (type == 'g') {
+        precision = math.max(1, math.min(21, precision));
+      } else if (type == 'e' || type == 'f') {
+        precision = math.max(0, math.min(20, precision));
+      }
+    }
+
+    NumberFormatFunction formatFunction = _getFormatFunction(type);
+
+    var zcomma = (zfill != null) && comma;
+
+    return (value) {
+      var fullSuffix = suffix;
+
+      // Return the empty string for floats formatted as ints.
+      if (integer && (value % 1) > 0) return '';
+
+      // Convert negative to positive, and record the sign prefix.
+      var negative;
+      if (value < 0 || value == 0 && 1 / value < 0) {
+        value = -value;
+        negative = '-';
+      } else {
+        negative = sign;
+      }
+
+      // Apply the scale, computing it from the value's exponent for si
+      // format.  Preserve the existing suffix, if any, such as the
+      // currency symbol.
+      if (scale < 0) {
+        FormatPrefix unit = new FormatPrefix(value,
+            (precision != null) ? precision : 0);
+        value = unit.scale(value);
+        fullSuffix = unit.symbol + suffix;
+      } else {
+        value *= scale;
+      }
+
+      // Convert to the desired precision.
+      if (precision != null) {
+        value = formatFunction(value, precision);
+      } else {
+        value = formatFunction(value);
+      }
+
+      // Break the value into the integer part (before) and decimal part
+      // (after).
+      var i = value.lastIndexOf('.'),
+          before = i < 0 ? value : value.substring(0, i),
+          after = i < 0 ? '' : localeDecimal + value.substring(i + 1);
+
+      // If the fill character is not '0', grouping is applied before
+      //padding.
+      if (zfill == null && comma) {
+        before = formatGroup(before);
+      }
+
+      var length = prefix.length + before.length + after.length +
+              (zcomma ? 0 : negative.length),
+          padding = length < width ? new List.filled(
+              (length = width - length + 1), '').join(fill) : '';
+
+      // If the fill character is '0', grouping is applied after padding.
+      if (zcomma) {
+        before = formatGroup(padding + before);
+      }
+
+      // Apply prefix.
+      negative += prefix;
+
+      // Rejoin integer and decimal parts.
+      value = before + after;
+
+      // Apply any padding and alignment attributes before returning the string.
+      return (align == '<' ? negative + value + padding
+          : align == '>' ? padding + negative + value
+          : align == '^' ? padding.substring(0, length >>= 1) + negative +
+              value + padding.substring(length)
+          : negative + (zcomma ? value : padding + value)) + fullSuffix;
+    };
+  }
+
+  // Gets the format function by given type.
+  NumberFormatFunction _getFormatFunction(String type) {
+    switch(type) {
+      case 'b':
+        return (num x, [int p = 0]) => x.toInt().toRadixString(2);
+      case 'c':
+        return (num x, [int p = 0]) => new String.fromCharCodes([x]);
+      case 'o':
+        return (num x, [int p = 0]) => x.toInt().toRadixString(8);
+      case 'x':
+        return (num x, [int p = 0]) => x.toInt().toRadixString(16);
+      case 'X':
+        return (num x, [int p = 0]) =>
+            x.toInt().toRadixString(16).toUpperCase();
+      case 'g':
+        return (num x, [int p = 1]) => x.toStringAsPrecision(p);
+      case 'e':
+        return (num x, [int p = 0]) => x.toStringAsExponential(p);
+      case 'f':
+        return (num x, [int p = 0]) => x.toStringAsFixed(p);
+      case 'r':
+      default:
+        return (num x, [int p = 0]) => x.toString();
+    }
+  }
+}
\ No newline at end of file
diff --git a/pub/charted/lib/locale/time_format.dart b/pub/charted/lib/locale/time_format.dart
new file mode 100644
index 0000000..5bd5d8b
--- /dev/null
+++ b/pub/charted/lib/locale/time_format.dart
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+part of charted.locale;
+
+typedef String TimeFormatFunction(DateTime date);
+
+//TODO(songrenchu): Document time format; Add test for time format.
+
+class TimeFormat {
+  String _template;
+  String _locale;
+  DateFormat _dateFormat;
+
+  TimeFormat([String template = null, String identifier = 'en_US']) {
+    _template = template;
+    _locale = identifier;
+    if (_template != null)
+      _dateFormat = new DateFormat(_wrapStrptime2ICU(_template), _locale);
+  }
+
+  TimeFormat _getInstance(String template) {
+    return new TimeFormat(template, _locale);
+  }
+
+  String apply(DateTime date) {
+    assert(_dateFormat != null);
+    return _dateFormat.format(date);
+  }
+
+  String toString() => _template;
+
+  DateTime parse(String string) {
+    assert(_dateFormat != null);
+    return _dateFormat.parse(string);
+  }
+
+  TimeFormatFunction multi(List<List> formats) {
+    var n = formats.length,
+        i = -1;
+    while (++i < n)
+      formats[i][0] = _getInstance(formats[i][0] as String);
+    return (var date) {
+      if (date is num) {
+        date = new DateTime.fromMillisecondsSinceEpoch(date.toInt());
+      }
+      var i = 0,
+          f = formats[i];
+      while (f.length < 2 || f[1](date) == false) {
+        i++;
+        if (i < n) f = formats[i];
+      }
+      if (i == n) return null;
+      return f[0].apply(date);
+    };
+  }
+
+  UTCTimeFormat utc([String specifier = null]) {
+    return new UTCTimeFormat(specifier == null ?
+        _template : specifier, _locale);
+  }
+
+  static UTCTimeFormat iso() {
+    return new UTCTimeFormat("%Y-%m-%dT%H:%M:%S.%LZ");
+  }
+
+  static Map timeFormatPads = {"-": "", "_": " ", "0": "0"};
+  // TODO(songrenchu): Cannot fully be equivalent now.
+  static Map timeFormatsTransform = {
+    'a': 'EEE',
+    'A': 'EEEE',
+    'b': 'MMM',
+    'B': 'MMMM',
+    'c': 'EEE MMM d HH:mm:ss yyyy',
+    'd': 'dd',
+    'e': 'd',         // TODO(songrenchu): zero padding not supported
+    'H': 'HH',
+    'I': 'hh',
+    'j': 'DDD',
+    'm': 'MM',
+    'M': 'mm',
+    'L': 'SSS',
+    'p': 'a',
+    'S': 'ss',
+    'U': 'ww',        // TODO(songrenchu): ICU doesn't disdinguish 'U' and 'W',
+                      // and not supported by Dart: DateFormat
+    'w': 'ee',        // TODO(songrenchu): e not supported by Dart: DateFormat
+    'W': 'ww',        // TODO(songrenchu): ICU doesn't disdinguish 'U' and 'W',
+                      // and not supported by Dart: DateFormat
+    'x': 'MM/dd/yyyy',
+    'X': 'HH:mm:ss',
+    'y': 'yy',
+    'Y': 'yyyy',
+    'Z': 'Z',
+    '%': '%'
+  };
+
+  String _wrapStrptime2ICU(String template) {
+    var string = [],
+        i = -1,
+        j = 0,
+        n = template.length,
+        formatPad,
+        tempChar;
+    while (++i < n) {
+      if (template[i] == '%') {
+        string.add(template.substring(j, i));
+        if ((formatPad = timeFormatPads[tempChar = template[++i]]) != null)
+          tempChar = template[++i];
+        if (timeFormatsTransform[tempChar] != null)
+          string.add(timeFormatsTransform[tempChar]);
+        j = i + 1;
+      }
+    }
+    if (j < i)
+      string.add("'" + template.substring(j, i) + "'");
+    return string.join("");
+  }
+}
+
+class UTCTimeFormat extends TimeFormat {
+  UTCTimeFormat(String template, [String identifier = 'en_US']):
+    super(template, identifier);
+
+  UTCTimeFormat _getInstance(String template) {
+    return new UTCTimeFormat(template, _locale);
+  }
+
+  DateTime parse(String string) {
+    assert(_dateFormat != null);
+    return _dateFormat.parseUTC(string);
+  }
+}
\ No newline at end of file
diff --git a/pub/charted/lib/locale/time_scale.dart b/pub/charted/lib/locale/time_scale.dart
new file mode 100644
index 0000000..02a5ad4
--- /dev/null
+++ b/pub/charted/lib/locale/time_scale.dart
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+part of charted.locale;
+
+class TimeScale extends LinearScale {
+  static List _scaleSteps = [
+    1e3,    // 1-second
+    5e3,    // 5-second
+    15e3,   // 15-second
+    3e4,    // 30-second
+    6e4,    // 1-minute
+    3e5,    // 5-minute
+    9e5,    // 15-minute
+    18e5,   // 30-minute
+    36e5,   // 1-hour
+    108e5,  // 3-hour
+    216e5,  // 6-hour
+    432e5,  // 12-hour
+    864e5,  // 1-day
+    1728e5, // 2-day
+    6048e5, // 1-week
+    2592e6, // 1-month
+    7776e6, // 3-month
+    31536e6 // 1-year
+  ];
+
+  static List _scaleLocalMethods = [
+    [chartTime.Time.second, 1],
+    [chartTime.Time.second, 5],
+    [chartTime.Time.second, 15],
+    [chartTime.Time.second, 30],
+    [chartTime.Time.minute, 1],
+    [chartTime.Time.minute, 5],
+    [chartTime.Time.minute, 15],
+    [chartTime.Time.minute, 30],
+    [chartTime.Time.hour,   1],
+    [chartTime.Time.hour,   3],
+    [chartTime.Time.hour,   6],
+    [chartTime.Time.hour,   12],
+    [chartTime.Time.day,    1],
+    [chartTime.Time.day,    2],
+    [chartTime.Time.week,   1],
+    [chartTime.Time.month,  1],
+    [chartTime.Time.month,  3],
+    [chartTime.Time.year,   1]
+  ];
+
+  static TimeFormatFunction _scaleLocalFormat = new TimeFormat().multi([
+      [".%L", (DateTime d) => d.millisecond > 0],
+      [":%S", (DateTime d) => d.second > 0],
+      ["%I:%M", (DateTime d) => d.minute > 0],
+      ["%I %p", (DateTime d) => d.hour > 0],
+      ["%a %d", (DateTime d) => (d.weekday % 7) > 0 && d.day != 1],
+      ["%b %d", (DateTime d) => d.day != 1],
+      ["%B", (DateTime d) => d.month > 1],
+      ["%Y", (d) => true]
+  ]);
+
+  TimeScale([List domain = LinearScale.defaultDomainRange,
+      List range = LinearScale.defaultDomainRange,
+      interpolators.Interpolator interpolator = interpolators.interpolateNumber,
+      bool clamp = false]) : super(domain, range, interpolator, clamp);
+
+  DateTime _timeScaleDate(num t) {
+    return new DateTime.fromMillisecondsSinceEpoch(t);
+  }
+
+  List _tickMethod(Extent extent, int count) {
+    var span  = extent.max - extent.min,
+        target = span / count,
+        i = ScaleUtil.bisect(_scaleSteps, target);
+
+    return i == _scaleSteps.length ?
+        [chartTime.Time.year, linearTickRange(
+            [extent.min / 31536e6, extent.max / 31536e6], count)[2]] :
+        i == 0 ? [new ScaleMilliSeconds(),
+            linearTickRange([extent.min, extent.max], count)[2]] :
+        _scaleLocalMethods[target / _scaleSteps[i - 1] <
+            _scaleSteps[i] / target ? i - 1 : i];
+  }
+
+  /**
+   * Given a value x as DateTime or TimeStamp, returns the corresponding value
+   * in the output range.
+   */
+  apply(x){
+    return super.apply(x is DateTime ? x.millisecondsSinceEpoch: x);
+  }
+
+  /**
+   * Returns the value in the input domain x for the corresponding value in the
+   * output range y. This represents the inverse mapping from range to domain.
+   * If elements in range are not number, the invert function returns null.
+   */
+  invert(y) {
+    return super.invert(y);
+  }
+
+  /** Sets the domain of the scale. */
+  set domain(List newDomain) {
+    assert(newDomain.length > 1);
+    super.domain = newDomain.map(
+        (d) => d is DateTime ? d.millisecondsSinceEpoch : d).toList();
+  }
+
+  Function tickFormat(int ticks, [String format = null]) {
+    return _scaleLocalFormat;
+  }
+
+
+  /**
+   * Returns an exact copy of this time scale. Changes to this scale will not
+   * affect the returned scale, and vice versa.
+   **/
+  TimeScale copy() => new TimeScale(domain, range, interpolator, clamp);
+
+  List niceInterval(var interval, [int skip = 1]) {
+    var extent = _scaleDomainExtent();
+
+    var method = interval == null ? _tickMethod(extent, 10) :
+                 interval is int ? _tickMethod(extent, interval) : null;
+
+    if (method != null) {
+      interval = method[0];
+      skip = method[1];
+    }
+
+    bool skipped(var date) {
+      if (date is DateTime) date = date.millisecondsSinceEpoch;
+      return (interval as chartTime.Interval)
+          .range(date, date + 1, skip).length == 0;
+    }
+
+    if (skip > 1) {
+      domain = scaleNice(domain,
+        (date) {
+          while (skipped(date = (interval as chartTime.Interval).floor(date))) {
+            date = _timeScaleDate(date.millisecondsSinceEpoch - 1);
+          }
+          return date.millisecondsSinceEpoch;
+        },
+        (date) {
+          while (skipped(date = (interval as chartTime.Interval).ceil(date))) {
+            date = _timeScaleDate(date.millisecondsSinceEpoch + 1);
+          }
+          return date.millisecondsSinceEpoch;
+        }
+      );
+    } else {
+      domain = scaleNice(domain,
+        (date) => interval.floor(date).millisecondsSinceEpoch,
+        (date) => interval.ceil(date).millisecondsSinceEpoch
+      );
+    }
+    return domain;
+  }
+
+  void nice([int ticks]) {
+    domain = niceInterval(ticks);
+  }
+
+  Extent _scaleDomainExtent() {
+    var extent = scaleExtent(domain);
+    return new Extent(extent[0], extent[1]);
+  }
+
+  List ticksInterval(var interval, [int skip = 1]) {
+    var extent = _scaleDomainExtent();
+    var method = interval == null ? _tickMethod(extent, 10) :
+        interval is int ? _tickMethod(extent, interval) :
+        [interval, skip];
+
+    if (method != null) {
+      interval = method[0];
+      skip = method[1];
+    }
+
+    return interval.range(extent.min, extent.max + 1, skip < 1 ? 1 : skip);
+  }
+
+  List ticks([int ticks = 10]) {
+    return ticksInterval(ticks);
+  }
+}
+
+class ScaleMilliSeconds extends chartTime.Interval {
+  DateTime floor(var date) =>
+      date is num ? new DateTime.fromMillisecondsSinceEpoch(date) : date;
+  DateTime ceil(var date) =>
+      date is num ? new DateTime.fromMillisecondsSinceEpoch(date) : date;
+  List range(var t0, var t1, int step) {
+    int start = t0 is DateTime ? t0.millisecondsSinceEpoch : t0,
+        stop = t1 is DateTime ? t1.millisecondsSinceEpoch : t1;
+    return new Range((start / step).ceil() * step, stop, step).map(
+        (d) => new DateTime.fromMillisecondsSinceEpoch(d)).toList();
+  }
+}
diff --git a/pub/charted/lib/scale/linear_scale.dart b/pub/charted/lib/scale/linear_scale.dart
new file mode 100644
index 0000000..2b56769
--- /dev/null
+++ b/pub/charted/lib/scale/linear_scale.dart
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+part of charted.scale;
+
+class LinearScale extends Scale {
+  Function input;
+  Function output;
+  static const defaultDomainRange = const [0, 1];
+
+  /**
+   * Constructs a new linear scale with the default domain [0,1] and the default
+   * range [0,1]. Thus, the default linear scale is equivalent to the identity
+   * function for numbers; for example linear(0.5) returns 0.5.
+   */
+  LinearScale([List domain = defaultDomainRange,
+      List range = defaultDomainRange,
+      interpolators.Interpolator interpolator = interpolators.interpolateNumber,
+      bool clamp = false]) {
+    _initializeScale(domain, range, interpolator, clamp);
+  }
+
+  _initializeScale(List domain, List range,
+      interpolators.Interpolator interpolator, bool clamp) {
+    _domain = domain;
+    _range = range;
+    _interpolator = interpolator;
+    _clamp = clamp;
+
+    Function linear = math.min(domain.length, range.length) > 2 ?
+        ScaleUtil.polylinearScale: ScaleUtil.bilinearScale;
+    Function uninterpolator = clamp ? interpolators.uninterpolateClamp :
+        interpolators.uninterpolateNumber;
+    if (range[0] is num) {
+      input = linear(range, domain, uninterpolator,
+          interpolators.interpolateNumber);
+    }
+    output = linear(domain, range, uninterpolator, interpolator);
+  }
+
+  /**
+   * Given a value x in the input domain, returns the corresponding value in
+   * the output range.
+   */
+  apply(x) {
+    _initializeScale(_domain, _range, _interpolator, _clamp);
+    return output(x);
+  }
+
+  /**
+   * Returns the value in the input domain x for the corresponding value in the
+   * output range y. This represents the inverse mapping from range to domain.
+   * If elements in range are not number, the invert function returns null.
+   */
+  invert(y) {
+    _initializeScale(_domain, _range, _interpolator, _clamp);
+    return input != null ? input(y) : null;
+  }
+
+  /** Sets the domain of the scale. */
+  set domain(List newDomain) {
+    _domain = newDomain;
+  }
+
+  get domain => _domain;
+
+  /** Sets the range of the scale. */
+  set range(List newRange) {
+    _range = newRange;
+  }
+
+  get range => _range;
+
+  /**
+   * Sets the scale's output range to the specified array of values, while also
+   * setting the scale's interpolator to d3.interpolateRound. This is a
+   * convenience routine for when the values output by the scale should be
+   * exact integers, such as to avoid antialiasing artifacts. It is also
+   * possible to round the output values manually after the scale is applied.
+   */
+  rangeRound(List newRange) {
+    _initializeScale(_domain, newRange, interpolators.interpolateRound, _clamp);
+  }
+
+  /**
+   * Enables or disables clamping accordingly. By default, clamping is
+   * disabled, such that if a value outside the input domain is passed to the
+   * scale, the scale may return a value outside the output range through linear
+   * extrapolation.
+   */
+  set clamp(bool clamp) {
+    _clamp = clamp;
+  }
+
+  get clamp => _clamp;
+
+  /**
+   * Sets the interpolator of the scale.  If it's not set, the scale will try to
+   * find the correct interpolator base on the domain and range input.
+   */
+  set interpolator(interpolators.Interpolator newInterpolator) {
+    _interpolator = newInterpolator;
+  }
+
+  get interpolator => _interpolator;
+
+  /** Sets the amount of ticks in the scale, default is 10. */
+  List ticks([int ticks = 10]) {
+    return _linearTicks(domain, ticks);
+  }
+
+  Function tickFormat(int ticks, [String format = null]) {
+    return _linearTickFormat(_domain, ticks, format);
+  }
+
+  _linearTickFormat(List domain, int ticks, String format) {
+    var tickRange = _linearTickRange(domain, ticks);
+    return new EnusLocale().numberFormat.format((format != null) ?
+        format : ",." + _linearPrecision(tickRange[2]).toString() + "f");
+  }
+
+  // Returns the number of significant digits after the decimal point.
+  int _linearPrecision(value) {
+    return -(math.log(value) / math.LN10 + .01).floor();
+  }
+
+  /**
+   * Extends the domain so that it starts and ends on nice round values.
+   * The optional tick count argument allows greater control over the step size
+   * used to extend the bounds, guaranteeing that the returned ticks will
+   * exactly cover the domain.
+   **/
+  void nice([int ticks = 10]) {
+    _domain = _linearNice(_domain, ticks);
+  }
+
+  /**
+   * Returns an exact copy of this linear scale. Changes to this scale will not
+   * affect the returned scale, and vice versa.
+   **/
+  LinearScale copy() => new LinearScale(_domain, _range, _interpolator, _clamp);
+
+  List _linearNice(List domain, [int ticks = 10]) {
+    return ScaleUtil.nice(domain,
+        ScaleUtil.niceStep(_linearTickRange(domain, ticks)[2]));
+  }
+
+  List _linearTicks(List domain, int ticks) {
+    List args = _linearTickRange(domain, ticks);
+    return new Range(args[0], args[1], args[2]).toList();
+  }
+
+  List _linearTickRange(List domain, int ticks) {
+    var extent = scaleExtent(domain),
+        span = extent[1] - extent[0],
+        step = math.pow(10, (math.log(span / ticks) / math.LN10).floor()),
+        err = ticks / span * step;
+
+    // Filter ticks to get closer to the desired count.
+    if (err <= .15) step *= 10;
+    else if (err <= .35) step *= 5;
+    else if (err <= .75) step *= 2;
+
+    List tickRange = new List(3);
+    // Round start and stop values to step interval.
+    tickRange[0] = (extent[0] / step).ceil() * step;
+    tickRange[1] = (extent[1] / step).floor() * step + step * .5; // inclusive
+    tickRange[2] = step;
+    return tickRange;
+  }
+
+  get linearTickRange => _linearTickRange;
+}
diff --git a/pub/charted/lib/scale/log_scale.dart b/pub/charted/lib/scale/log_scale.dart
new file mode 100644
index 0000000..7d1107c
--- /dev/null
+++ b/pub/charted/lib/scale/log_scale.dart
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+part of charted.scale;
+
+// TODO(midoringo): Write more test for this class.
+/**
+ * Log scales are similar to linear scales, except there's a logarithmic
+ * transform that is applied to the input domain value before the output range
+ * value is computed. The mapping to the output range value y can be expressed
+ * as a function of the input domain value x: y = m log(x) + b.
+ *
+ * As log(0) is negative infinity, a log scale must have either an
+ * exclusively-positive or exclusively-negative domain; the domain must not
+ * include or cross zero. A log scale with a positive domain has a well-defined
+ * behavior for positive values, and a log scale with a negative domain has a
+ * well-defined behavior for negative values (the input value is multiplied by
+ * -1, and the resulting output value is also multiplied by -1). The behavior of
+ * the scale is undefined if you pass a negative value to a log scale with a
+ * positive domain or vice versa.
+ */
+class LogScale extends Scale {
+  Function input;
+  Function output;
+  static const defaultBase = 10;
+  static const defaultDomain = const [1, 10];
+
+  LinearScale _linear;
+  int _base;
+  bool _positive;
+  List _domain;
+
+  LogScale([LinearScale linear = null,
+      this._base = defaultBase,
+      this._positive = true,
+      this._domain = defaultDomain]) {
+    _linear = (linear != null) ? linear : new LinearScale(_domain);
+  }
+
+  num _log(x) => (_positive ? math.log(x < 0 ? 0 : x) :
+      -math.log(x > 0 ? 0 : -x)) / math.log(_base);
+
+  _pow(x) => _positive ? math.pow(_base, x) : -math.pow(_base, -x);
+
+  /**
+   * Given a value x in the input domain, returns the corresponding value in
+   * the output range.
+   */
+  apply(x) => _linear.apply(_log(x));
+
+  /**
+   * Returns the value in the input domain x for the corresponding value in the
+   * output range y. This represents the inverse mapping from range to domain.
+   * For a valid value y in the output range, log(log.invert(y)) equals y;
+   * similarly, for a valid value x in the input domain, log.invert(log(x))
+   * equals x. Equivalently, you can construct the invert operator by building
+   * a new scale while swapping the domain and range. The invert operator is
+   * particularly useful for interaction, say to determine the value in the
+   * input domain that corresponds to the pixel location under the mouse.
+   */
+  invert(x) => _pow(_linear.invert(x));
+
+  /** Sets the domain of the scale. */
+  set domain(List x) {
+    _positive = x[0] >= 0;
+    _domain = x;
+    _linear.domain = _domain.map((e) => _log(e)).toList();
+  }
+  get domain => _domain;
+
+  /** Sets the base of the logarithmic scale. */
+  get base => _base;
+  set base(int newBase) {
+    this._base = newBase;
+    _linear.domain = _domain.map((e) => _log(e)).toList();
+  }
+
+  /** Sets the range of the scale. */
+  get range => _linear.range;
+  set range(List newRange) {
+    _linear.range = newRange;
+  }
+
+  /**
+   * Sets the scale's output range to the specified array of values, while also
+   * setting the scale's interpolator to d3.interpolateRound. This is a
+   * convenience routine for when the values output by the scale should be
+   * exact integers, such as to avoid antialiasing artifacts. It is also
+   * possible to round the output values manually after the scale is applied.
+   */
+  void rangeRound(List newRange) {
+    _linear.rangeRound(newRange);
+  }
+
+  /** Sets the interpolator used in the scale. */
+  set interpolator(interpolators.Interpolator newInterpolator) {
+    _linear.interpolator = newInterpolator;
+  }
+
+  get interpolator => _linear.interpolator;
+
+  /**
+   * Enables or disables clamping accordingly. By default, clamping is
+   * disabled, such that if a value outside the input domain is passed to the
+   * scale, the scale may return a value outside the output range through linear
+   * extrapolation.
+   */
+  set clamp(bool clamp) {
+    _linear.clamp = clamp;
+  }
+
+  get clamp => _linear.clamp;
+
+  /**
+   * Extends the domain so that it starts and ends on nice round values.
+   * The optional tick count argument allows greater control over the step size
+   * used to extend the bounds, guaranteeing that the returned ticks will
+   * exactly cover the domain.
+   **/
+  nice([int ticks]) {
+    var niced;
+    if (_positive) {
+      niced = scaleNice(_domain.map((e) => _log(e)).toList());
+    } else {
+      var floor = (x) => -(-x).ceil();
+      var ceil = (x) => -(-x).floor();
+      niced = scaleNice(_domain.map((e) => _log(e)).toList(), floor, ceil);
+    }
+    _linear.domain = niced;
+    domain = niced.map((e) => _pow(e)).toList();
+  }
+
+  /**
+   * Returns representative values from the scale's input domain. The returned
+   * tick values are uniformly spaced within each power of ten,
+   * and are guaranteed to be within the extent of the input domain.
+   */
+  List ticks([int ticks = 10]) {
+    var extent = scaleExtent(_domain),
+        ticks = [],
+        u = extent[0],
+        v = extent[1],
+        i = (_log(u)).floor(),
+        j = (_log(v)).ceil(),
+        n = (_base % 1 > 0) ? 2 : _base;
+
+    if ((j - i).isFinite) {
+      if (_positive) {
+        for (; i < j; i++) for (var k = 1; k < n; k++) ticks.add(_pow(i) * k);
+        ticks.add(_pow(i));
+      } else {
+        ticks.add(_pow(i));
+        for (; i++ < j;) for (var k = n - 1; k > 0; k--) ticks.add(_pow(i) * k);
+      }
+      for (i = 0; ticks[i] < u; i++) {} // strip small values
+      for (j = ticks.length; ticks[j - 1] > v; j--) {} // strip big values
+      ticks = ticks.sublist(i, j);
+    }
+    return ticks;
+  }
+
+  /**
+   * Returns a number format function suitable for displaying a tick value.
+   * The returned tick format is implemented as d.toPrecision(1)
+   */
+  Function tickFormat(int ticks, [String formatString = null]) {
+    var logFormatFunction = formatString != null ?
+        format(formatString) : format(".0e");
+    var k = math.max(.1, ticks / this.ticks().length),
+        e = _positive ? 1e-12 : -1e-12;
+    return (d) {
+      if (_positive) {
+        return d / _pow((_log(d) + e).ceil()) <= k ? logFormatFunction(d) : '';
+      } else {
+        return d / _pow((_log(d) + e).floor()) <= k ? logFormatFunction(d) : '';
+      }
+    };
+  }
+
+  copy() {
+    return new LogScale(_linear.copy(), _base, _positive, _domain);
+  }
+}
diff --git a/pub/charted/lib/scale/ordinal_scale.dart b/pub/charted/lib/scale/ordinal_scale.dart
new file mode 100644
index 0000000..f1e86c9
--- /dev/null
+++ b/pub/charted/lib/scale/ordinal_scale.dart
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+part of charted.scale;
+
+class OrdinalScale extends Scale {
+  Map index = new Map();
+  Function _rangeSetupFunction;
+  List _rangeSetupArgs = [[]];
+
+  /**
+   * Constructs a new ordinal scale with an empty domain and an empty range.
+   * The ordinal scale is invalid (always returning undefined) until an output
+   * range is specified.
+   */
+  OrdinalScale() {
+    _rangeSetupFunction = defaultRange;
+    domain = [];
+  }
+
+  /**
+   * Given a value x in the input domain, returns the corresponding value in
+   * the output range.
+   */
+  apply(input) {
+    if (index[input] == null) {
+      index[input] = _domain.length;
+      _domain.add(input);
+    }
+    return range[(index[input]) % range.length];
+  }
+
+  List steps(start, step) {
+    var s = new Range(domain.length).toList();
+    return s.map((num i) => start + step * i).toList();
+  }
+
+  void nice([int ticks]) {
+    // NO-OP for an ordinal scale.
+  }
+
+  /**
+   * Sets the domain Value. Setting the domain on an ordinal scale is optional.
+   * If no domain is set, a range must be set explicitly. Then, each unique
+   * value that is passed to the scale function will be assigned a new value
+   * from the output range.
+   */
+  // TODO(midoringo): It would be nice to be able to match both D3 and dart
+  // pattern here.  In D3, if newDomain is not defined, this returns the current
+  // domain.  Here we use the Dart syntax for setting and getting domain.
+  set domain(List newDomain) {
+    _domain = [];
+    index = new Map();
+    int i = -1;
+    int n = newDomain.length;
+    var xi;
+    while (++i < n) {
+      xi = newDomain[i];
+      if (index[xi] == null) {
+        index[xi] = _domain.length;
+        _domain.add(xi);
+      }
+    }
+    Function.apply(_rangeSetupFunction, _rangeSetupArgs);
+  }
+
+  get domain => _domain;
+
+  /**
+   * Sets the output range of the ordinal scale to the specified array of
+   * values. The first element in the domain will be mapped to the first element
+   * in values, the second domain value to the second range value, and so on. If
+   * there are fewer elements in the range than in the domain, the scale will
+   * recycle values from the start of the range.
+   */
+  set range(List newRange) {
+    _range = newRange;
+    rangeBand = 0;
+    _rangeSetupFunction = defaultRange;
+    _rangeSetupArgs = [newRange];
+  }
+
+  get range => _range;
+
+  defaultRange(List newRange) {
+    range = newRange;
+  }
+
+  /**
+   * Sets the output range from the specified continuous interval. The array
+   * interval contains two elements representing the minimum and maximum numeric
+   * value. This interval is subdivided into n evenly-spaced points, where n is
+   * the number of (unique) values in the input domain. The first and last point
+   * may be offset from the edge of the interval according to the specified
+   * padding, which defaults to zero. The padding is expressed as a multiple of
+   * the spacing between points. A reasonable value is 1.0, such that the first
+   * and last point will be offset from the minimum and maximum value by half
+   * the distance between points.
+   */
+  void rangePoints(List x, [double padding = 0.0]) {
+    var start = x[0];
+    var stop = x[1];
+    var step = (stop - start) / (domain.length - 1 + padding);
+    range = steps(domain.length < 2 ?
+        (start + stop) / 2 : start + step * padding / 2, step);
+    rangeBand = 0;
+    _rangeSetupFunction = rangePoints;
+    _rangeSetupArgs = [x, padding];
+  }
+
+  /**
+   * Sets the output range from the specified continuous interval. The array
+   * interval contains two elements representing the minimum and maximum numeric
+   * value. This interval is subdivided into n evenly-spaced bands, where n is
+   * the number of (unique) values in the input domain. The bands may be offset
+   * from the edge of the interval and other bands according to the specified
+   * padding, which defaults to zero. The padding is typically in the range
+   * [0,1] and corresponds to the amount of space in the range interval to
+   * allocate to padding. A value of 0.5 means that the band width will be
+   * equal to the padding width. The outerPadding argument is for the entire
+   * group of bands; a value of 0 means there will be padding only between
+   * rangeBands.
+   */
+  void rangeBands(List x, [double padding = 0.0, double outerPadding]) {
+    if (outerPadding == null) outerPadding = padding;
+
+    var reverse = x[1] < x[0] ? 1 : 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 > 0) range = _range.reversed.toList();
+    rangeBand = step * (1 - padding);
+    _rangeSetupFunction = rangeBands;
+    _rangeSetupArgs = [x, padding];
+  }
+
+  /**
+   * Like rangeBands, except guarantees that the band width and offset are
+   * integer values, so as to avoid antialiasing artifact
+   */
+  void rangeRoundBands(List x, [double padding = 0.0, double outerPadding]) {
+    if (outerPadding == null) outerPadding = padding;
+
+    var reverse = x[1] < x[0] ? 1 : 0,
+        start = x[reverse - 0],
+        stop = x[1 - reverse],
+        step = ((stop - start) /
+            (domain.length - padding + 2 * outerPadding)).floor(),
+        error = stop - start - (domain.length - padding) * step;
+
+    range = steps(start + (error / 2).round(), step);
+    if (reverse > 0) range = _range.reversed.toList();
+    rangeBand = (step * (1 - padding)).round();
+    _rangeSetupFunction = rangeRoundBands;
+    _rangeSetupArgs = [x, padding];
+  }
+
+  List rangeExtent() {
+    return scaleExtent(_rangeSetupArgs[0]);
+  }
+
+  Scale copy() {
+    return new OrdinalScale()
+        ..domain = _domain
+        ..range = _range
+        ..rangeBand = rangeBand
+        .._rangeSetupFunction = _rangeSetupFunction
+        .._rangeSetupArgs = _rangeSetupArgs;
+  }
+
+  /**
+   * Returns the value in input domain x for the corresponding value in the
+   * output range y.  In Ordinal scale, the output are String values and are not
+   * interpolated, so y must match an element in the output range.  Null is
+   * returned if the specified y is not an element in range or if the index of
+   * the element in range is out of bound of domain's length.
+   */
+  invert(y) {
+    var valueIndex = _range.indexOf(y);
+    if (valueIndex > -1 && valueIndex < _domain.length) {
+      return _domain[_range.indexOf(y)];
+    } else {
+      return null;
+    }
+  }
+}
diff --git a/pub/charted/lib/scale/scale.dart b/pub/charted/lib/scale/scale.dart
new file mode 100644
index 0000000..2d9d462
--- /dev/null
+++ b/pub/charted/lib/scale/scale.dart
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+/*
+ * TODO(midoringo): Document library
+ */
+library charted.scale;
+
+import 'dart:math' as math;
+import 'package:charted/core/core.dart';
+import 'package:charted/format/format.dart';
+import 'package:charted/locale/locale.dart';
+import 'package:charted/interpolators/interpolators.dart' as interpolators;
+
+part 'ordinal_scale.dart';
+part 'linear_scale.dart';
+part 'log_scale.dart';
+
+/** Minimum common interface to be supported by all scales */
+abstract class Scale {
+  var rangeBand = 0;
+  List _domain;
+  List _range;
+  interpolators.Interpolator _interpolator;
+  bool _clamp;
+
+
+  /**
+   * Returns the extent of the scale as a list containing the min and max of the
+   * domain.
+   */
+  List scaleExtent(List domain) {
+    var start = domain[0];
+    var stop = domain[domain.length - 1];
+    return start < stop ? [start, stop] : [stop, start];
+  }
+
+  /**
+   * Returns the extent of the domain in nice round values.  If alternative
+   * floor and ceil functions are not provided, default floor and ceil will be
+   * used to produce the round values.
+   */
+  scaleNice(List domain, [altFloor = null, altCeil = null]) {
+    var i0 = 0;
+    var i1 = domain.length -1;
+    var x0 = domain[i0];
+    var x1 = domain[i1];
+    var dx;
+    if (x1 < x0) {
+      dx = i0;
+      i0 = i1;
+      i1 = dx;
+      dx = x0;
+      x0 = x1;
+      x1 = dx;
+    }
+
+    domain[i0] = (altFloor != null) ? altFloor(x0) : x0.floor();
+    domain[i1] = (altCeil != null) ? altCeil(x1) : x1.ceil();
+    return domain;
+  }
+
+  void nice([int ticks = 10]);
+
+  /**
+   * Returns the values in the domain as tick values for sub class that doesn't
+   * implement the ticks method.
+   **/
+  List ticks([int ticks = 10]) => _domain;
+
+  /**
+   * Returns the identity function as the tickformat Function for sub class that
+   * doesn't implment the tickFormat method.
+   */
+  Function tickFormat(int ticks, [String format = null]) => identityFunction;
+
+  /**
+   * Returns a two-element array representing the extent of the scale's range,
+   * i.e., the smallest and largest values.
+   */
+  List rangeExtent() => scaleExtent(_range);
+
+  set domain(List domain);
+  get domain => _domain;
+
+  set range(List range);
+  get range => _range;
+
+  apply(x);
+  Scale copy();
+}
diff --git a/pub/charted/lib/selection/selection.dart b/pub/charted/lib/selection/selection.dart
new file mode 100644
index 0000000..4c6f1db
--- /dev/null
+++ b/pub/charted/lib/selection/selection.dart
@@ -0,0 +1,390 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+/*
+ * TODO(prsd): Document library
+ */
+library charted.selection;
+
+import "dart:html" show Element, Event, document;
+import "dart:math" as math;
+import "package:charted/core/core.dart";
+import "package:charted/transition/transition.dart";
+
+part "selection_scope.dart";
+part "selection_impl.dart";
+
+/**
+ * Callback to access key value from a given data object. During the process
+ * of binding data to Elements, the key values are used to match Elements
+ * that have previously bound data
+ */
+typedef SelectionKeyFunction(datum);
+
+/**
+ * Callback for all DOM related operations - The first parameter [datum] is
+ * the piece of data associated with the node, [ei] is the index of the
+ * element in it's group and [c] is the context to which the data is
+ * associated to.
+ */
+typedef E SelectionCallback<E>(datum, int index, Element element);
+
+/** Callback used to access a value from a datum */
+typedef E SelectionValueAccessor<E>(datum, int index);
+
+/** Create a ChartedCallback that always returns [val] */
+SelectionCallback toCallback(val) => (datum, index, element) => val;
+
+/** Create a ChartedValueAccessor that always returns [val] */
+SelectionValueAccessor toValueAccessor(val) => (datum, index) => val;
+
+/**
+ * [Selection] is a collection of elements - this collection defines
+ * operators that can be applied on all elements of the collection.
+ *
+ * All operators accept parameters either as a constant value or a callback
+ * function (typically using the named parameters "val" and "fn"). These
+ * operators, when invoked with the callback function, the function is
+ * called once per element and is passed the "data" associated, the "index"
+ * and the element itself.
+ */
+abstract class Selection {
+  /**
+   * Collection of groups - A selection when created by calling [selectAll]
+   * on an existing [Selection], could contain more than one group.
+   */
+  Iterable<SelectionGroup> groups;
+
+  /**
+   * Scope of this selection that manages the element, data associations for
+   * all elements in this selection (and the sub-selections)
+   */
+  SelectionScope get scope;
+
+  /** Indicates if this selection is empty */
+  bool get isEmpty;
+
+  /** Number of elements in this selection */
+  int get length;
+
+  /** First non-null element in this selection, if any. */
+  Element get first;
+
+  /**
+   * Creates and returns a new [Selection] containing the first element
+   * matching [selector] under each element in the current selection.
+   *
+   * If an element does not have a matching decendant, a placeholder is used
+   * in it's position - thus being able to match the indices of elements in
+   * the current and the created sub-selection.
+   *
+   * Any data bound to elements in this selection is inherited by the
+   * selected decendants.
+   */
+  Selection select(String selector);
+
+  /**
+   * Same as [select], but calls [fn] for each non-null element in the
+   * selection (with data associated to the element, index of the element in
+   * it's group and the element itself) to get the selected element that will
+   * be selected.
+   */
+  Selection selectWithCallback(SelectionCallback<Element> fn);
+
+  /**
+   * Creates and returns a new [Selection] containing all elements matching
+   * [selector] under each element in the current selection.
+   *
+   * The resulting [Selection] is nested with elements from current selection
+   * as parents and the selected decendants grouped by elements in the
+   * current selection.  When no decendants match the selector, the
+   * collection of selected elements in a group is empty.
+   *
+   * Data bound to the elements is not automatically inherited by the
+   * selected decendants.
+   */
+  Selection selectAll(String selector);
+
+  /**
+   * Same as [selectAll], but calls [fn] for each non-null element in the
+   * selection (with data associated to the element, index of the element in
+   * it's group and the element itself) to get a collection of selected
+   * elements that will be part of the new selection.
+   */
+  Selection selectAllWithCallback(SelectionCallback<Iterable<Element>> fn);
+
+  /**
+   * Sets the attribute [name] on all elements when [val] is not null.
+   * Removes the attribute when [val] is null.
+   */
+  void attr(String name, val);
+
+  /**
+   * Same as [attr], but calls [fn] for each non-null element in the
+   * selection (with data associated to the element, index of the element in
+   * it's group and the element itself) to get the value of the attribute.
+   */
+  void attrWithCallback(String name, SelectionCallback fn);
+
+  /**
+   * Ensures presence of a class when [val] is true.  Ensures that the class
+   * isn't present if [val] is false.
+   */
+  void classed(String name, [bool val = true]);
+
+  /**
+   * Same as [classed], but calls [fn] for each non-null element in the
+   * selection (with data associated to the element, index of the element in
+   * it's group and the element itself) to get the boolean value that
+   * indicates if the class must be added or removed.
+   */
+  void classedWithCallback(String name, SelectionCallback<bool> fn);
+
+  /** Sets CSS [property] to [val] on all elements in the selection. */
+  void style(String property, val, {String priority});
+
+  /**
+   * Same as [style], but calls [fn] for each non-null element in the
+   * selection (with data associated to the element, index of the element in
+   * it's group and the element itself) to get the value of the property.
+   */
+  void styleWithCallback(String property,
+      SelectionCallback<String> fn, {String priority});
+
+  /**
+   * Sets textContent of all elements in the selection to [val]. A side-effect
+   * of this call is that any children of these elements will not be part of
+   * the DOM anymore.
+   */
+  void text(String val);
+
+  /**
+   * Same as [text], but calls [fn] for each non-null element in
+   * the selection (with data associated to the element, index of the
+   * element in it's group and the element itself) to get the text content
+   */
+  void textWithCallback(SelectionCallback<String> fn);
+
+  /**
+   * Sets innerHtml of all elements in the selection to [val]. A side-effect
+   * of this call is that any children of these elements will not be part of
+   * the DOM anymore.
+   */
+  void html(String val);
+
+  /**
+   * Same as [html], but calls [fn] for each non-null element in
+   * the selection (with data associated to the element, index of the
+   * element in it's group and the element itself) to get the html content
+   */
+  void htmlWithCallback(SelectionCallback<String> fn);
+
+  /**
+   * Appends a new child element to each element in the selection.
+   *
+   * Returns a [Selection] containing the newly created elements. As with
+   * [select], any data bound to the elements in this selection is inherited
+   * by the new elements.
+   */
+  Selection append(String tag);
+
+  /**
+   * Same as [append], but calls [fn] for each non-null element in the
+   * selection (with data associated to the element, index of the element in
+   * it's group and the element itself) to get the element to be appended.
+   */
+  Selection appendWithCallback(SelectionCallback<Element> fn);
+
+  /**
+   * Inserts a child node to each element in the selection before the first
+   * element matching [before] or before the element returned by [beforeFn].
+   *
+   * Returns a [Selection] containing the newly created elements. As with
+   * [select], any data bound to the elements in this selection is inherited
+   * by the new elements.
+   */
+  Selection insert(String tag,
+      {String before, SelectionCallback<Element> beforeFn});
+
+  /**
+   * Same as [insert], but calls [fn] for each non-null element in the
+   * selection (with data associated to the element, index of the element in
+   * it's group and the element itself) to get the element to be inserted.
+   */
+  Selection insertWithCallback(SelectionCallback<Element> fn,
+      {String before, SelectionCallback<Element> beforeFn});
+
+  /** Removes all selected elements from the DOM */
+  void remove();
+
+  /** Calls [fn] on each element in this selection */
+  void each(SelectionCallback fn);
+
+  /**
+   * Adds or removes an event [listener] to each element in the selection for
+   * the specified [type] (Eg: "mouseclick", "mousedown")
+   *
+   * Any existing listener of the same type will be removed.  To register
+   * multiple listener for the same event type, the [type] can be suffixed
+   * with a namespace. (Eg: "mouseclick.foo", "mousedown.bar")
+   *
+   * When [listener] is null, any existing listener of the same type and in
+   * the same namespace will be removed (Eg: Using "mouseclick.foo" as type
+   * will only remove listeners for "mouseclick.foo" and not "mouseclick.bar")
+   *
+   * To remove listeners of an event type in all namespaces, prefix the type
+   * with a "." (Eg: ".mouseclick" will remove "mouseclick.bar",
+   * "mouseclick .foo" and all other mouseclick event listeners)
+   *
+   * To summarize, [type] can be any DOM event type optionally in the format
+   * "event.namespace" where event is the DOM event type and namespace is
+   * used to distinguish between added listeners.
+   *
+   * When [listener] is called, it is passed the current value associated with
+   * the element.  Please note that index passed to the listener contains a
+   * value as it was at the time of adding the listener.
+   */
+  void on(String type, [SelectionCallback listener, bool capture]);
+
+  /**
+   * Associates data with the selected elements.
+   * Computes the enter, update and exit selections.
+   */
+  DataSelection data(Iterable vals, [SelectionKeyFunction keyFn]);
+
+  /**
+   * Same as [data], but calls [fn] for each non-null element in the
+   * selection (with data associated to the element, index of the element in
+   * it's group and the element itself) to get the data to be set on the
+   * current element.
+   */
+  DataSelection dataWithCallback(
+      SelectionCallback<Iterable> fn, [SelectionKeyFunction keyFn]);
+
+  /**
+   * Associates data with all the elements - no join is performed. Unlike
+   * [data], this does not compute the enter, update and exit selections.
+   */
+  void datum(Iterable vals);
+
+  /**
+   * Same as [datum], but calls [fn] for each non-null element in the
+   * selection (with data associated to the element, index of the element in
+   * it's group and the element itself) to get the data to be set on the
+   * current element.
+   */
+  void datumWithCallback(SelectionCallback<Iterable> fn);
+
+  /**
+   * Starts a transition for the current selection. Transitions behave much
+   * like selections, except operators animate smoothly over time rather than
+   * applying instantaneously.
+   */
+  Transition transition();
+}
+
+
+/*
+ * Group of elements in the selection.
+ * Each selection may contain more than one group of elements.
+ */
+abstract class SelectionGroup {
+  Iterable<Element> elements;
+  Element parent;
+}
+
+
+/**
+ * [EnterSelection] is a sub-selection that represents missing elements of a
+ * selection - an element is considered missing when there is data and no
+ * corresponding element in a selection.
+ */
+abstract class EnterSelection {
+  /**
+   * Indicate if this selection is empty
+   * See [Selection.isEmpty] for more information.
+   */
+  bool get isEmpty;
+
+  /** [DataSelection] that corresponds to this selection. */
+  DataSelection get update;
+
+  /**
+   * Appends an element to all elements in this selection and return
+   * [Selection] containing the newly added elements.
+   *
+   * See [Selection.append] for more information.
+   * The new nodes are merged into the [DataSelection]
+   */
+  Selection append(String tag);
+
+  /**
+   * Same as [append] but calls [fn] to get the element to be appended.
+   * See [Selection.appendWithCallback] for more information.
+   */
+  Selection appendWithCallback(SelectionCallback<Element> fn);
+
+  /**
+   * Insert a child node to each element in the selection and return
+   * [Selection] containing the newly added elements.
+   *
+   * See [Selection.insert] for more information.
+   * The new nodes are merged into the [UpdateSelection]
+   */
+  Selection insert(String tag,
+      {String before, SelectionCallback<Element> beforeFn});
+
+  /**
+   * Same as [insert] but calls [fn] to get the element to be inserted.
+   * See [Selection.insertWithCallback] for more information.
+   */
+  Selection insertWithCallback(SelectionCallback<Element> fn,
+      {String before, SelectionCallback<Element> beforeFn});
+
+  /**
+   * For each element in the current selection, select exactly one
+   * decendant and return [Selection] containing the selected elements.
+   *
+   * See [Selection.select] for more information.
+   */
+  Selection select(String selector);
+
+  /**
+   * Same as [select] but calls [fn] to get the element to be inserted.
+   * See [Selection.selectWithCallback] for more information.
+   */
+  Selection selectWithCallback(SelectionCallback<Element> fn);
+}
+
+/*
+ * [ExitSelection] is a sub-selection that represents elements that don't
+ * have data associated to them.
+ */
+abstract class ExitSelection extends Selection {
+  DataSelection get update;
+}
+
+/*
+ * Selection that consists elements in the selection that aren't part of
+ * [EnterSelection] or the [ExitSelection]
+ *
+ * An [UpdateSelection] is only available after data() is attached and is
+ * currently exactly the same as [Selection] itself.
+ */
+abstract class DataSelection extends Selection {
+  /**
+   * A view of the current selection that contains a collection of data
+   * elements which weren't associated with an element in the DOM.
+   */
+  EnterSelection get enter;
+
+  /**
+   * A view of the current selection containing elements that don't have data
+   * associated with them.
+   */
+  ExitSelection get exit;
+}
diff --git a/pub/charted/lib/selection/selection_impl.dart b/pub/charted/lib/selection/selection_impl.dart
new file mode 100644
index 0000000..850ab45
--- /dev/null
+++ b/pub/charted/lib/selection/selection_impl.dart
@@ -0,0 +1,548 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+part of charted.selection;
+
+/**
+ * Implementation of [Selection].
+ * Selections cannot be created directly - they are only created using
+ * the select or selectAll methods on [SelectionScope] and [Selection].
+ */
+class _SelectionImpl implements Selection {
+
+  Iterable<SelectionGroup> groups;
+  SelectionScope scope;
+
+  Transition _transition;
+
+  /**
+   * Creates a new selection.
+   *
+   * When [source] is not specified, the new selection would have exactly
+   * one group with [SelectionScope.root] as it's parent.  Otherwise, one group
+   * per for each non-null element is created with element as it's parent.
+   *
+   * When [selector] is specified, each group contains all elements matching
+   * [selector] and under the group's parent element.  Otherwise, [fn] is
+   * called once per group with parent element's "data", "index" and the
+   * "element" itself passed as parameters.  [fn] must return an iterable of
+   * elements to be used in each group.
+   */
+  _SelectionImpl.all({String selector, SelectionCallback<Iterable<Element>> fn,
+      SelectionScope this.scope, Selection source}) {
+    assert(selector != null || fn != null);
+    assert(source != null || scope != null);
+
+    if (selector != null) {
+      fn = (d, i, c) => c == null ?
+          scope.root.querySelectorAll(selector) :
+          c.querySelectorAll(selector);
+    }
+
+    var tmpGroups = new List<SelectionGroup>(),
+        index = 0;
+    if (source != null) {
+      scope = source.scope;
+      source.groups.forEach((g) {
+        g.elements.forEach((e) {
+          if (e != null) {
+            tmpGroups.add(
+              new _SelectionGroupImpl(
+                  fn(scope.datum(e), index, e), parent: e));
+          }
+          index++;
+        });
+      });
+    } else {
+      tmpGroups.add(
+          new _SelectionGroupImpl(fn(null, 0, null), parent: scope.root));
+    }
+    groups = tmpGroups;
+  }
+
+  /**
+   * Same as [all] but only uses the first element matching [selector] when
+   * [selector] is speficied.  Otherwise, call [fn] which must return the
+   * element to be selected.
+   */
+  _SelectionImpl.single({String selector, SelectionCallback<Element> fn,
+      SelectionScope this.scope, Selection source}) {
+    assert(selector != null || fn != null);
+    assert(source != null || scope != null);
+
+    if (selector != null) {
+      fn = (d, i, c) => c == null ?
+          scope.root.querySelector(selector) :
+          c.querySelector(selector);
+    }
+
+    if (source != null) {
+      scope = source.scope;
+      groups = new List<SelectionGroup>.generate(source.groups.length, (gi) {
+        SelectionGroup g = source.groups.elementAt(gi);
+        return new _SelectionGroupImpl(
+            new List.generate(g.elements.length, (ei) {
+          var e = g.elements.elementAt(ei);
+          if (e != null) {
+            var datum = scope.datum(e);
+            var enterElement = fn(datum, ei, e);
+            if (datum != null) {
+              scope.associate(enterElement, datum);
+            }
+            return enterElement;
+          } else {
+            return null;
+          }
+        }), parent: g.parent);
+      });
+    } else {
+      groups = new List<SelectionGroup>.generate(1,
+          (_) => new _SelectionGroupImpl(new List.generate(1,
+              (_) => fn(null, 0, null), growable: false)), growable: false);
+    }
+  }
+
+  /** Creates a selection using the pre-computed list of [SelectionGroup] */
+  _SelectionImpl.selectionGroups(
+      Iterable<SelectionGroup> this.groups, SelectionScope this.scope);
+
+  /**
+   * Creates a selection using the list of elements. All elements will
+   * be part of the same group, with [SelectionScope.root] as the group's parent
+   */
+  _SelectionImpl.elements(Iterable elements, SelectionScope this.scope) {
+    groups = new List<SelectionGroup>()..add(new _SelectionGroupImpl(elements));
+  }
+
+  /**
+   * Utility to evaluate value of parameters (uses value when given
+   * or invokes a callback to get the value) and calls [action] for
+   * each non-null element in this selection
+   */
+  void _do(SelectionCallback f, Function action) {
+    each((d, i, e) => action(e, f == null ? null : f(scope.datum(e), i, e)));
+  }
+
+  /** Calls a function on each non-null element in the selection */
+  void each(SelectionCallback fn) {
+    if (fn == null) return;
+    groups.forEach((SelectionGroup g) {
+      var index = 0;
+      g.elements.forEach((Element e) {
+        if (e != null) fn(scope.datum(e), index, e);
+        index++;
+      });
+    });
+  }
+
+  void on(String type, [SelectionCallback listener, bool capture]) {
+    Function getEventHandler(i, e) => (Event event) {
+      var previous = scope.event;
+      scope.event = event;
+      try {
+        Function.apply(listener, [scope.datum(e), i, e]);
+      } finally {
+        scope.event = previous;
+      }
+    };
+
+    if (!type.startsWith('.')) {
+      if (listener != null) {
+        // Add a listener to each element.
+        each((d, i, Element e){
+          var handlers = scope._listeners[e];
+          if (handlers == null) scope._listeners[e] = handlers = {};
+          handlers[type] = new Pair(getEventHandler(i, e), capture);
+          e.addEventListener(type, handlers[type].first, capture);
+        });
+      } else {
+        // Remove the listener from each element.
+        each((d, i, Element e) {
+          var handlers = scope._listeners[e];
+          if (handlers != null && handlers[type] != null) {
+            e.removeEventListener(
+                type, handlers[type].first, handlers[type].last);
+          }
+        });
+      }
+    } else {
+      // Remove all listeners on the event type (ignoring the namespace)
+      each((d, i, Element e) {
+        var handlers = scope._listeners[e],
+            keys = handlers.keys,
+            t = type.substring(1);
+        keys.forEach((String s) {
+          if (s.split('.')[0] == t) {
+            e.removeEventListener(s, handlers[s].first, handlers[s].last);
+          }
+        });
+      });
+    }
+  }
+
+  int get length {
+    int retval = 0;
+    each((d, i, e) => retval++);
+    return retval;
+  }
+
+  bool get isEmpty => length == 0;
+
+  /** First non-null element in this selection */
+  Element get first {
+    for (int gi = 0; gi < groups.length; gi++) {
+      SelectionGroup g = groups.elementAt(gi);
+      for (int ei = 0; ei < g.elements.length; ei++) {
+        if (g.elements.elementAt(ei) != null) {
+          return g.elements.elementAt(ei);
+        }
+      }
+    }
+    return null;
+  }
+
+  void attr(String name, val) {
+    assert(name != null && name.isNotEmpty);
+    attrWithCallback(name, toCallback(val));
+  }
+
+  void attrWithCallback(String name, SelectionCallback fn) {
+    assert(fn != null);
+    _do(fn, (e, v) => v == null ?
+        e.attributes.remove(name) : e.attributes[name] = "$v");
+  }
+
+  void classed(String name, [bool val = true]) {
+    assert(name != null && name.isNotEmpty);
+    classedWithCallback(name, toCallback(val));
+  }
+
+  void classedWithCallback(String name, SelectionCallback<bool> fn) {
+    assert(fn != null);
+    _do(fn, (e, v) =>
+        v == false ? e.classes.remove(name) : e.classes.add(name));
+  }
+
+  void style(String property, val, {String priority}) {
+    assert(property != null && property.isNotEmpty);
+    styleWithCallback(property,
+        toCallback(val as String), priority: priority);
+  }
+
+  void styleWithCallback(String property,
+      SelectionCallback<String> fn, {String priority}) {
+    assert(fn != null);
+    _do(fn, (Element e, String v) =>
+        v == null || v.isEmpty ?
+            e.style.removeProperty(property) :
+            e.style.setProperty(property, v, priority));
+  }
+
+  void text(String val) => textWithCallback(toCallback(val));
+
+  void textWithCallback(SelectionCallback<String> fn) {
+    assert(fn != null);
+    _do(fn, (e, v) => e.text = v == null ? '' : v);
+  }
+
+  void html(String val) => htmlWithCallback(toCallback(val));
+
+  void htmlWithCallback(SelectionCallback<String> fn) {
+    assert(fn != null);
+    _do(fn, (e, v) => e.innerHtml = v == null ? '' : v);
+  }
+
+  void remove() => _do(null, (e, _) => e.remove());
+
+  Selection select(String selector) {
+    assert(selector != null && selector.isNotEmpty);
+    return new _SelectionImpl.single(selector: selector, source: this);
+  }
+
+  Selection selectWithCallback(SelectionCallback<Element> fn) {
+    assert(fn != null);
+    return new _SelectionImpl.single(fn: fn, source:this);
+  }
+
+  Selection append(String tag) {
+    assert(tag != null && tag.isNotEmpty);
+    return appendWithCallback(
+        (d, ei, e) => Namespace.createChildElement(tag, e));
+  }
+
+  Selection appendWithCallback(SelectionCallback<Element> fn) {
+    assert(fn != null);
+    return new _SelectionImpl.single(fn: (datum, ei, e) {
+          Element child = fn(datum, ei, e);
+          return child == null ? null : e.append(child);
+        }, source: this);
+  }
+
+  Selection insert(String tag,
+      {String before, SelectionCallback<Element> beforeFn}) {
+    assert(tag != null && tag.isNotEmpty);
+    return insertWithCallback(
+        (d, ei, e) => Namespace.createChildElement(tag, e),
+        before: before, beforeFn: beforeFn);
+  }
+
+  Selection insertWithCallback(SelectionCallback<Element> fn,
+      {String before, SelectionCallback<Element> beforeFn}) {
+    assert(fn != null);
+    beforeFn =
+        before == null ? beforeFn : (d, ei, e) => e.querySelector(before);
+    return new _SelectionImpl.single(
+        fn: (datum, ei, e) {
+          Element child = fn(datum, ei, e);
+          Element before = beforeFn(datum, ei, e);
+          return child == null ? null : e.insertBefore(child, before);
+        },
+        source: this);
+  }
+
+  Selection selectAll(String selector) {
+    assert(selector != null && selector.isNotEmpty);
+    return new _SelectionImpl.all(selector: selector, source: this);
+  }
+
+  Selection selectAllWithCallback(SelectionCallback<Iterable<Element>> fn) {
+    assert(fn != null);
+    return new _SelectionImpl.all(fn: fn, source:this);
+  }
+
+  DataSelection data(Iterable vals, [SelectionKeyFunction keyFn]) {
+    assert(vals != null);
+    return dataWithCallback(toCallback(vals), keyFn);
+  }
+
+  DataSelection dataWithCallback(
+      SelectionCallback<Iterable> fn, [SelectionKeyFunction keyFn]) {
+    assert(fn != null);
+
+    var enterGroups = [],
+        updateGroups = [],
+        exitGroups = [];
+
+    // Create a dummy node to be used with enter() selection.
+    Element dummy(val) {
+      var element = new Element.tag('charted-dummy');
+      scope.associate(element, val);
+      return element;
+    };
+
+    // Joins data to all elements in the group.
+    void join(SelectionGroup g, Iterable vals) {
+      // Nodes exiting, entering and updating in this group.
+      // We maintain the nodes at the same index as they currently
+      // are (for exiting) or where they should be (for entering and updating)
+      var update = new List(vals.length),
+          enter = new List(vals.length),
+          exit = new List(g.elements.length);
+
+      // Use the key function to determine the DOM element to data
+      // associations.
+      if (keyFn != null) {
+        var keysOnDOM = [],
+            elementsByKey = {},
+            valuesByKey = {},
+            ei = 0,
+            vi = 0;
+
+        // Create a key to DOM element map.
+        // Used later to see if an element already exists for a key.
+        g.elements.forEach((e) {
+          var keyValue = keyFn(scope.datum(e));
+          if (elementsByKey.containsKey(keyValue)) {
+            exit[ei] = e;
+          } else {
+            elementsByKey[keyValue] = e;
+          }
+          keysOnDOM.add(keyValue);
+          ei++;
+        });
+
+        // Iterate through the values and find values that don't have
+        // corresponding elements in the DOM, collect the entering elements.
+        vals.forEach((v) {
+          var keyValue = keyFn(v);
+          Element e = elementsByKey[keyValue];
+          if (e != null) {
+            update[vi] = e;
+            scope.associate(e, v);
+          } else if (!valuesByKey.containsKey(keyValue)) {
+            enter[vi] = dummy(v);
+          }
+          valuesByKey[keyValue] = v;
+          elementsByKey.remove(keyValue);
+          vi++;
+        });
+
+        // Iterate through the previously saved keys to
+        // find a list of elements that don't have data anymore.
+        // We don't use elementsByKey.keys() becuase that does not
+        // guarantee the order of returned keys.
+        for (int i = 0; i < g.elements.length; i++) {
+          if (elementsByKey.containsKey(keysOnDOM[i])) {
+            exit[i] = g.elements.elementAt(i);
+          }
+        }
+      } else {
+        // When we don't have the key function, just use list index as the key
+        int updateElementsCount = math.min(g.elements.length, vals.length);
+        int i = 0;
+
+        // Collect a list of elements getting updated in this group
+        for (; i < updateElementsCount; i++) {
+          var e = g.elements.elementAt(i);
+          if (e != null) {
+            scope.associate(e, vals.elementAt(i));
+            update[i] = e;
+          } else {
+            enter[i] = dummy(vals.elementAt(i));
+          }
+        }
+
+        // List of elements newly getting added
+        for (; i < vals.length; i++) {
+          enter[i] = dummy(vals.elementAt(i));
+        }
+
+        // List of elements exiting this group
+        for (; i < g.elements.length; i++) {
+          exit[i] = g.elements.elementAt(i);
+        }
+      }
+
+      // Create the element groups and set parents from the current group.
+      enterGroups.add(new _SelectionGroupImpl(enter, parent: g.parent));
+      updateGroups.add(new _SelectionGroupImpl(update, parent: g.parent));
+      exitGroups.add(new _SelectionGroupImpl(exit, parent: g.parent));
+    };
+
+    var index = 0;
+    groups.forEach((SelectionGroup g) {
+      join(g, fn(scope.datum(g.parent), index++, g.parent));
+    });
+
+    return new _DataSelectionImpl(
+        updateGroups, enterGroups, exitGroups, scope);
+  }
+
+  void datum(Iterable vals) {
+    throw new UnimplementedError();
+  }
+
+  void datumWithCallback(SelectionCallback<Iterable> fn) {
+    throw new UnimplementedError();
+  }
+
+  Transition transition() {
+    return _transition = new Transition(this);
+  }
+}
+
+/* Implementation of [DataSelection] */
+class _DataSelectionImpl extends _SelectionImpl implements DataSelection {
+  EnterSelection enter;
+  ExitSelection exit;
+
+  _DataSelectionImpl(Iterable updated, Iterable entering, Iterable exiting,
+      SelectionScope scope) : super.selectionGroups(updated, scope) {
+    enter = new _EnterSelectionImpl(entering, this);
+    exit = new _ExitSelectionImpl(exiting, this);
+  }
+}
+
+/* Implementation of [EnterSelection] */
+class _EnterSelectionImpl implements EnterSelection {
+  final DataSelection update;
+
+  SelectionScope scope;
+  Iterable<SelectionGroup> groups;
+
+  _EnterSelectionImpl(Iterable this.groups, DataSelection this.update) {
+    scope = update.scope;
+  }
+
+  bool get isEmpty => false;
+
+  Selection insert(String tag,
+      {String before, SelectionCallback<Element> beforeFn}) {
+    assert(tag != null && tag.isNotEmpty);
+    return insertWithCallback(
+        (d, ei, e) => Namespace.createChildElement(tag, e),
+        before: before, beforeFn: beforeFn);
+  }
+
+  Selection insertWithCallback(SelectionCallback<Element> fn,
+      {String before, SelectionCallback<Element> beforeFn}) {
+    assert(fn != null);
+    return selectWithCallback((d, ei, e) {
+      Element child = fn(d, ei, e);
+      e.insertBefore(child, e.querySelector(before));
+      return child;
+    });
+  }
+
+  Selection append(String tag) {
+    assert(tag != null && tag.isNotEmpty);
+    return appendWithCallback(
+        (d, ei, e) => Namespace.createChildElement(tag, e));
+  }
+
+  Selection appendWithCallback(SelectionCallback<Element> fn) {
+    assert(fn != null);
+    return selectWithCallback((datum, ei, e) {
+          Element child = fn(datum, ei, e);
+          e.children.add(child);
+          return child;
+        });
+  }
+
+  Selection select(String selector) {
+    assert(selector == null && selector.isNotEmpty);
+    return selectWithCallback((d, ei, e) => e.querySelector(selector));
+  }
+
+  Selection selectWithCallback(SelectionCallback<Element> fn) {
+    var subgroups = [],
+        gi = 0;
+    groups.forEach((SelectionGroup g) {
+      var u = update.groups.elementAt(gi),
+          subgroup = [],
+          ei = 0;
+      g.elements.forEach((Element e) {
+        if (e != null) {
+          var datum = scope.datum(e),
+              selected = fn(datum, ei, g.parent);
+          scope.associate(selected, datum);
+          u.elements[ei] = selected;
+          subgroup.add(selected);
+        } else {
+          subgroup.add(null);
+        }
+        ei++;
+      });
+      subgroups.add(new _SelectionGroupImpl(subgroup, parent: g.parent));
+      gi++;
+    });
+    return new _SelectionImpl.selectionGroups(subgroups, scope);
+  }
+}
+
+/* Implementation of [ExitSelection] */
+class _ExitSelectionImpl extends _SelectionImpl implements ExitSelection {
+  final DataSelection update;
+  _ExitSelectionImpl(Iterable groups, DataSelection update)
+      : update = update, super.selectionGroups(groups, update.scope);
+}
+
+class _SelectionGroupImpl implements SelectionGroup {
+  Iterable<Element> elements;
+  Element parent;
+  _SelectionGroupImpl(this.elements, {this.parent});
+}
diff --git a/pub/charted/lib/selection/selection_scope.dart b/pub/charted/lib/selection/selection_scope.dart
new file mode 100644
index 0000000..1462848
--- /dev/null
+++ b/pub/charted/lib/selection/selection_scope.dart
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+part of charted.selection;
+
+/** SelectionScope represents a scope for all the data and DOM operations. */
+class SelectionScope {
+  Expando _associations = new Expando();
+  Expando<Map<String, Pair<Function, bool>>> _listeners = new Expando();
+  Element _root;
+
+  /** Creates a new selection scope with document as the root. */
+  SelectionScope() {
+    _root = document.documentElement;
+  }
+
+  /**
+   * Creates a new selection scope with the first element matching
+   * [selector] as the root.
+   */
+  SelectionScope.selector(String selector) {
+    if (selector == null || selector.isEmpty ){
+      throw new ArgumentError('Selector cannot be empty');
+    }
+    _root = document.querySelector(selector);
+  }
+
+  /**
+   * Creates a new selection scope with the passed [element] as [root].
+   * Charted assumes that the element is already part of the DOM.
+   */
+  SelectionScope.element(Element element) {
+    if (element == null) {
+      throw new ArgumentError('Root element for SelectionScope cannot be null');
+    }
+    _root = element;
+  }
+
+  /**
+   * Returns the [root] element for this scope.
+   * All [Selection]s and elements created using the Charted API,
+   * are created as decendants of root.
+   */
+  Element get root => _root;
+
+  /*
+   * Current event for which a callback is being called.
+   */
+  Event event;
+
+  /** Returns the stored for the given [element]. */
+  datum(Element element) => element == null ? null : _associations[element];
+
+  /** Associates data to the given [element]. */
+  associate(Element element, datum) =>
+      datum != null ? _associations[element] = datum : null;
+
+  /**
+   * Creates a new [Selection] containing the first element matching
+   * [selector].  If no element matches, the resulting selection will
+   * have a null element.
+   */
+  Selection select(String selector) =>
+      new _SelectionImpl.single(selector: selector, scope: this);
+
+  /**
+   * Creates a new [Selection] containing all elements matching [selector].
+   * If no element matches, the resulting selection will not have any
+   * elements in it.
+   */
+  Selection selectAll(String selector) =>
+      new _SelectionImpl.all(selector:selector, scope:this);
+
+  /**
+   * Creates a new [Selection] containing [elements].  Assumes that
+   * all the given elements are decendants of [root] in DOM.
+   */
+  Selection selectElements(List<Element> elements) =>
+      new _SelectionImpl.elements(elements, this);
+
+  /**
+   * Appends a new element to [root] and creates a selection containing
+   * the newly added element.
+   */
+  Selection append(String tag) {
+    var element = Namespace.createChildElement(tag, _root);
+    root.children.add(element);
+
+    return selectElements([element]);
+  }
+}
diff --git a/pub/charted/lib/svg/svg.dart b/pub/charted/lib/svg/svg.dart
new file mode 100644
index 0000000..0b48b60
--- /dev/null
+++ b/pub/charted/lib/svg/svg.dart
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+/*
+ * TODO(prsd): Document library
+ */
+library charted.svg;
+
+import "dart:math" as math;
+import "dart:html" show Element;
+import "package:charted/core/core.dart";
+import 'package:charted/interpolators/interpolators.dart';
+import "package:charted/scale/scale.dart";
+import "package:charted/selection/selection.dart";
+
+part 'svg_arc.dart';
+part 'svg_axis.dart';
+part 'svg_line.dart';
+
+/**
+ * Common interface supported by all path generators.
+ */
+abstract class SvgPathGenerator {
+  /**
+   * Generate the path based on the passed data [d], element index [i]
+   * and the element [e].  This method follows the same signature as the
+   * other callbacks used by selection API - [ChartedCallback<String>]
+   */
+  String path(d, int i, Element e);
+}
diff --git a/pub/charted/lib/svg/svg_arc.dart b/pub/charted/lib/svg/svg_arc.dart
new file mode 100644
index 0000000..50c5198
--- /dev/null
+++ b/pub/charted/lib/svg/svg_arc.dart
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+part of charted.svg;
+
+/**
+ * [SvgArc] provides a data-driven way to create Svg path descriptions
+ * that can be used to draw arcs - like those used in pie-charts.
+ */
+class SvgArc implements SvgPathGenerator {
+  static const num _OFFSET = -HALF_PI;
+  static const num _MAX = TAU - EPSILON;
+
+  /**
+   * [innerRadiusCallback] is called to get inner radius of the arc.
+   * As with other callbacks, [innerRadiusCallback] is passed data, index
+   * and element in the context.
+   *
+   * If not specified, [defaultInnerRadiusCallback] is used.
+   */
+  SelectionCallback<num> innerRadiusCallback = defaultInnerRadiusCallback;
+
+  /**
+   * [outerRadiusCallback] is called to get outer radius of the arc.
+   * As with other callbacks, [outerRadiusCallback] is passed data, index
+   * and element in the context.
+   *
+   * If not specified, [defaultOuterRadiusCallback] is used.
+   */
+  SelectionCallback<num> outerRadiusCallback = defaultOuterRadiusCallback;
+
+  /**
+   * [startAngleCallback] is called to get the start angle of the arc.
+   * As with other callbacks, [startAngleCallback] is passed data, index
+   * and element in the context.
+   *
+   * If not specified, [defaultStartAngleCallback] is used.
+   */
+  SelectionCallback<num> startAngleCallback = defaultStartAngleCallback;
+
+  /**
+   * [endAngleCallback] is called to get the start angle of the arc.
+   * As with other callbacks, [endAngleCallback] is passed data, index
+   * and element in the context.
+   *
+   * If not specified, [defaultEndAngleCallback] is used.
+   */
+  SelectionCallback<num> endAngleCallback = defaultEndAngleCallback;
+
+  /** Sets a constant value as inner radius of the arc. */
+  set innerRadius(num value) => innerRadiusCallback = toCallback(value);
+
+  /** Sets a constant value as outer radius of the arc. */
+  set outerRadius(num value) => outerRadiusCallback = toCallback(value);
+
+  /** Sets a constant value as start angle of the arc. */
+  set startAngle(num value) => startAngleCallback = toCallback(value);
+
+  /** Sets a constant value as end angle of the arc. */
+  set endAngle(num value) => endAngleCallback = toCallback(value);
+
+  String path(d, int i, Element e) {
+    var ir = innerRadiusCallback(d, i, e),
+        or = outerRadiusCallback(d, i, e),
+        start = startAngleCallback(d, i, e) + _OFFSET,
+        end = endAngleCallback(d, i, e) + _OFFSET,
+        sa = math.min(start, end),
+        ea = math.max(start, end),
+        delta = ea - sa;
+
+    if (delta > _MAX) {
+      return ir > 0 ?
+          // Concentric circles with area between them
+          "M0,$or" "A$or,$or 0 1,1 0,-$or" "A$or,$or 0 1,1 0,$or"
+          "M0,$ir" "A$ir,$ir 0 1,0 0,-$ir" "A$ir,$ir 0 1,0 0,$ir" "Z" :
+          // Circle with enclosed area
+          "M0,$or" "A$or,$or 0 1,1 0,-$or" "A$or,$or 0 1,1 0,$or" "Z";
+    }
+
+    var ss = math.sin(sa),
+        se = math.sin(ea),
+        cs = math.cos(sa),
+        ce = math.cos(ea),
+        df = delta < PI ? 0 : 1;
+
+    return ir > 0 ?
+        // Arc capped at ends with lines
+        "M${or * cs},${or * ss}" "A$or,$or 0 $df,1 ${or * ce},${or * se}"
+        "L${ir * ce},${ir * se}" "A$ir,$ir 0 $df,0 ${ir * cs},${ir * ss}"
+        "Z" :
+        // A circular sector
+        "M${or * cs},${or * ss}" "A$or,$or 0 $df,1 ${or * ce},${or * se}"
+        "L0,0" "Z";
+  }
+
+  List centroid(d, int i, Element e) {
+    var r = (innerRadiusCallback(d, i, e) + outerRadiusCallback(d, i, e)) / 2,
+        a = (startAngleCallback(d, i, e) + endAngleCallback(d, i, e)) / 2 -
+            math.PI / 2;
+    return [math.cos(a) * r, math.sin(a) * r];
+  }
+
+  /** Default [innerRadiusCallback] returns data.innerRadius */
+  static num defaultInnerRadiusCallback(d, i, e) => d is !SvgArcData ||
+      d == null || d.innerRadius == null ? 0 : d.innerRadius;
+
+  /** Default [outerRadiusCallback] returns data.outerRadius */
+  static num defaultOuterRadiusCallback(d, i, e) => d is !SvgArcData ||
+      d == null || d.outerRadius == null ? 0 : d.outerRadius;
+
+  /** Default [startAngleCallback] returns data.startAngle */
+  static num defaultStartAngleCallback(d, i, e) => d is !SvgArcData ||
+      d == null || d.startAngle == null ? 0 : d.startAngle;
+
+  /** Default [endAngleCallback] that returns data.endAngle */
+  static num defaultEndAngleCallback(d, i, e) => d is !SvgArcData ||
+      d == null || d.endAngle == null ? 0 : d.endAngle;
+}
+
+/** Value type for SvgArc as used by default property accessors in SvgArc */
+class SvgArcData {
+  var data;
+  num value;
+  num innerRadius;
+  num outerRadius;
+  num startAngle = 0;
+  num endAngle = 2 * PI;
+
+  SvgArcData(this.data, this.value, this.startAngle,
+      this.endAngle, [this.innerRadius=0, this.outerRadius=100]);
+}
+
+
+/**
+ * Returns the interpolator between two [SvgArcData] [a] and [b].
+ *
+ * The interpolator will interpolate the older innerRadius and outerDadius with
+ * newer ones, as well as older startAngle and endAngle with newer ones.
+ *
+ */
+InterpolateFn interpolateSvgArcData(SvgArcData a, SvgArcData b) {
+  var ast = a.startAngle,
+      aen = a.endAngle,
+      ai = a.innerRadius,
+      ao = a.outerRadius,
+      bst = b.startAngle - ast,
+      ben = b.endAngle - aen,
+      bi = b.innerRadius - ai,
+      bo = b.outerRadius - ao;
+
+  return (t) => new SvgArcData(b.data, b.value,
+    (ast + bst * t), (aen + ben * t), (ai + bi * t), (ao + bo * t));
+}
+
diff --git a/pub/charted/lib/svg/svg_axis.dart b/pub/charted/lib/svg/svg_axis.dart
new file mode 100644
index 0000000..d86b01d
--- /dev/null
+++ b/pub/charted/lib/svg/svg_axis.dart
@@ -0,0 +1,286 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+part of charted.svg;
+
+typedef String Formatter(x);
+
+/**
+ * [SvgAxis] helps draw chart axes based on a given scale.
+ */
+class SvgAxis {
+  /** Scale used on this axis */
+  Scale scale = new LinearScale();
+
+  /** Orientation of the axis.  Defaults to [ORIENTATION_BOTTOM] */
+  String orientation = ORIENTATION_BOTTOM;
+
+  /** Size of all inner ticks */
+  num innerTickSize = 6;
+
+  /** Size of the outer two ticks */
+  num outerTickSize = 6;
+
+  /** Padding on the ticks */
+  num tickPadding = 3;
+
+  /** Suggested number of ticks to be displayed on the axis */
+  num suggestedTickCount = 10;
+
+  /** List of values to be used on the ticks */
+  List tickValues;
+
+  /** Formatter for the tick labels */
+  Formatter tickFormat;
+
+  /** Previous rotate angle */
+  num _prevRotate = 0;
+
+  /* Store of axis roots mapped to currently used scales */
+  static Expando<Scale> _scales = new Expando<Scale>();
+
+  axis(Selection g) => g.each((d, i, e) => _create(e, g.scope));
+
+  _create(Element e, SelectionScope scope) {
+    var group = scope.selectElements([e]),
+        older = _scales[e],
+        current = _scales[e] = scale.copy();
+
+    if (older == null) older = scale;
+    var tickFormat = this.tickFormat == null ?
+            current.tickFormat(suggestedTickCount) : this.tickFormat,
+        tickValues = this.tickValues == null ?
+            current.ticks(suggestedTickCount) : this.tickValues;
+
+    var ticks = group.selectAll('.tick').data(tickValues, current.apply),
+        tickEnter = ticks.enter.insert('g', before:'.domain')
+            ..classed('tick')
+            ..style('opacity', EPSILON.toString()),
+        tickExit = ticks.exit..remove(),
+        tickUpdate = ticks..style('opacity', '1'),
+        tickTransform;
+
+    var range = current.rangeExtent(),
+        path = group.selectAll('.domain').data([0]);
+        path.enter.append('path');
+        path.attr('class', 'domain');
+
+    num axisLength = range[1] - range[0];
+
+    tickEnter.append('line');
+    tickEnter.append('text');
+
+    var lineEnter = tickEnter.select('line'),
+        lineUpdate = tickUpdate.select('line'),
+        textEnter = tickEnter.select('text'),
+        textUpdate = tickUpdate.select('text'),
+        text = ticks.select('text')
+            ..textWithCallback((d,i,e) => tickFormat(d));
+
+    var ellipsis = group.selectAll('.ellipsis').data(["... ..."]);
+    ellipsis.enter.append('text')
+        ..attr('class', 'ellipsis')
+        ..style('text-anchor', 'middle')
+        ..textWithCallback((d, i, e) => d)
+        ..attr('opacity', 0);
+
+    switch (orientation) {
+      case ORIENTATION_BOTTOM: {
+        tickTransform = _xAxisTransform;
+        ticks.attr('y2', innerTickSize);
+        lineEnter.attr('y2', innerTickSize);
+        textEnter.attr('y', math.max(innerTickSize, 0) + tickPadding);
+        lineUpdate
+            ..attr('x2', 0)
+            ..attr('y2', innerTickSize);
+        textUpdate
+            ..attr('x', 0)
+            ..attr('y', math.max(innerTickSize, 0) + tickPadding);
+        textEnter
+            ..attr('dy', '.71em')
+            ..style('text-anchor', 'middle');
+        ellipsis
+            ..attr('x', axisLength / 2)
+            ..attr('y', '.71em');
+        path.attr('d',
+            'M${range[0]},${outerTickSize}V0H${range[1]}V${outerTickSize}');
+      }
+        break;
+      case ORIENTATION_TOP: {
+        tickTransform = _xAxisTransform;
+        lineEnter.attr('y2', -innerTickSize);
+        textEnter.attr('y', -(math.max(innerTickSize, 0) + tickPadding));
+        lineUpdate
+            ..attr('x2', 0)
+            ..attr('y2', -innerTickSize);
+        textUpdate
+            ..attr('x', 0)
+            ..attr('y', -(math.max(innerTickSize, 0) + tickPadding));
+        textEnter
+            ..attr('dy', '0em')
+            ..style('text-anchor', 'middle');
+        ellipsis
+            ..attr('x', axisLength / 2)
+            ..attr('y', 0);
+        path.attr('d',
+            'M${range[0]},${-outerTickSize}V0H${range[1]}V${-outerTickSize}');
+      }
+        break;
+      case ORIENTATION_LEFT: {
+        tickTransform = _yAxisTransform;
+        lineEnter.attr('x2', -innerTickSize);
+        textEnter.attr('x', -(math.max(innerTickSize, 0) + tickPadding));
+        lineUpdate
+            ..attr('x2', -innerTickSize)
+            ..attr('y2', 0);
+        textUpdate
+            ..attr('x', -(math.max(innerTickSize, 0) + tickPadding))
+            ..attr('y', 0);
+        textEnter
+            ..attr('dy', '.32em')
+            ..style('text-anchor', 'end');
+        ellipsis
+            ..attr("transform",
+                "translate(${-tickPadding}, ${axisLength / 2})rotate(90)");
+        path.attr('d',
+            'M${-outerTickSize},${range[0]}H0V${range[1]}H${-outerTickSize}');
+      }
+      break;
+      case ORIENTATION_RIGHT: {
+        tickTransform = _yAxisTransform;
+        lineEnter.attr('x2', innerTickSize);
+        textEnter.attr('x', math.max(innerTickSize, 0) + tickPadding);
+        lineUpdate
+            ..attr('x2', innerTickSize)
+            ..attr('y2', 0);
+        textUpdate
+            ..attr('x', math.max(innerTickSize, 0) + tickPadding)
+            ..attr('y', 0);
+        textEnter
+            ..attr('dy', '.32em')
+            ..style('text-anchor', 'start');
+        ellipsis
+            ..attr("transform",
+                "translate(${tickPadding}, ${axisLength / 2})rotate(90)");
+        path.attr('d',
+            'M${outerTickSize},${range[0]}H0V${range[1]}H${outerTickSize}');
+      }
+      break;
+    }
+
+    num currentRotate = 0;
+    num prevRotate = _prevRotate;
+
+    lineEnter..attr('opacity', '0');
+    textEnter..attr('opacity', '0');
+    lineEnter.transition()
+      ..attr('opacity', '1')
+      ..delay(100);
+
+    List textOpacity = new List.filled(text.length, 1);
+
+    if (orientation == ORIENTATION_BOTTOM || orientation == ORIENTATION_TOP) {
+      num maxTextWidth = 0,
+          textHeight = text.first.clientHeight;
+      text.each((d, i, e)
+          => maxTextWidth = math.max(maxTextWidth, e.clientWidth));
+      if (maxTextWidth > axisLength / text.length) currentRotate = 45;
+      if (textHeight * 1.5 > axisLength / text.length) {
+        currentRotate = 90;
+      }
+      _prevRotate = currentRotate;
+      List preTransX = new List(),
+           nowTransX = new List(),
+           preTransY = new List(),
+           nowTransY = new List();
+      num prevArc = prevRotate * math.PI / 180,
+          nowArc = currentRotate * math.PI / 180;
+      text.each((d, i, e) {
+        preTransX.add(prevRotate > 0 ?
+            e.clientWidth * math.cos(prevArc) / 2 : 0);
+        nowTransX.add(currentRotate > 0 ? e.clientWidth * math.cos(nowArc) / 2 : 0);
+        preTransY.add(e.clientWidth * math.sin(prevArc) / 2);
+        nowTransY.add(e.clientWidth * math.sin(nowArc) / 2);
+      });
+      text.transition()
+          ..attrTween('transform', (d, i, e) {
+            return interpolateTransform(
+              "translate(${preTransX[i]},${preTransY[i]})rotate(${prevRotate})",
+              "translate(${nowTransX[i]},${nowTransY[i]})rotate(${currentRotate})");
+          })
+          ..attr('dx', '${.71 * math.sin(nowArc)}em')
+          ..attr('dy', '${.71 * math.cos(nowArc)}em');
+      if (currentRotate == 90) {
+        text.each((d, i, Element e) {
+          if (i > 0 && i < text.length - 1) {
+            textOpacity[i] = 0;
+          }
+          group.selectAll('.ellipsis').transition()
+              ..delay(100)
+              ..attr('opacity', '1');
+        });
+      } else {
+        group.selectAll('.ellipsis').transition()
+          ..delay(100)
+          ..attr('opacity', '0');
+      }
+    } else {
+      num textHeight = text.first.clientHeight;
+      if (textHeight > axisLength / text.length) {
+        text.each((d, i, Element e) {
+          if (i > 0 && i < text.length - 1) {
+            textOpacity[i] = 0;
+          }
+          group.selectAll('.ellipsis').transition()
+              ..delay(100)
+              ..attr('opacity', '1');
+        });
+      } else {
+        group.selectAll('.ellipsis').transition()
+            ..delay(100)
+            ..attr('opacity', '0');
+      }
+    }
+
+    text.transition()
+      ..attrWithCallback('opacity', (d, i, e) => textOpacity[i])
+      ..delay(100);
+
+    // If either the new or old scale is ordinal,
+    // entering ticks are undefined in the old scale,
+    // and so can fade-in in the new scale’s position.
+    // Exiting ticks are likewise undefined in the new scale,
+    // and so can fade-out in the old scale’s position.
+    var transformFn;
+    if (current.rangeBand != 0) {
+      var dx = current.rangeBand / 2;
+      transformFn = (d) => current.apply(d) + dx;
+    } else if (older.rangeBand != 0) {
+      older = current;
+    } else {
+      tickTransform(tickExit, current.apply);
+    }
+
+    tickTransform(tickEnter, transformFn != null ? transformFn : older.apply);
+    tickTransform(tickUpdate, transformFn != null ?
+        transformFn : current.apply);
+  }
+
+  _xAxisTransform(selection, transformFn) {
+    selection.transition()
+      ..attrWithCallback('transform', (d, i, e) =>
+        'translate(${transformFn(d)},0)'
+    );
+  }
+
+  _yAxisTransform(selection, transformFn) {
+    selection.transition()
+      ..attrWithCallback('transform', (d, i, e) =>
+        'translate(0,${transformFn(d)})'
+    );
+  }
+}
diff --git a/pub/charted/lib/svg/svg_line.dart b/pub/charted/lib/svg/svg_line.dart
new file mode 100644
index 0000000..d99d6ad
--- /dev/null
+++ b/pub/charted/lib/svg/svg_line.dart
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+part of charted.svg;
+
+/**
+ * Implementations of line interpolators are given a list of
+ * points and they must return Svg path description to draw
+ * a line connecting these points
+ */
+typedef String LineInterpolator(List<math.Point> points);
+
+/**
+ * [SvgLine] provides a data-driven way to create Svg path descriptions
+ * that can be used to draw lines.  It internally has support for a few
+ * interpolations and can easily be extended to add support for more.
+ */
+class SvgLine implements SvgPathGenerator {
+  // TODO(prsd): Implement more interpolators
+
+  /** Linear interpolator */
+  static const String LINEAR = "linear";
+
+  /** Default interpolator */
+  static LineInterpolator DEFAULT_INTERPOLATOR = _linear;
+
+  /** Map of interpolator names to implementations */
+  static Map<String, LineInterpolator> interpolators = {};
+
+  /**
+   * [xAccessor] is used to access/convert datum to x coordinate value.
+   * If not specified, [defaultDataToX] is used.
+   */
+  SelectionValueAccessor<num> xAccessor = defaultDataToX;
+
+  /**
+   * [yAccessor] is used to access/convert datum to y coordinate value.
+   * If not specified, [defaultDataToY] is used.
+   */
+  SelectionValueAccessor<num> yAccessor = defaultDataToY;
+
+  /**
+   * [defined] is used to determine if a value is considered valid.
+   * If this function returns false for any value, that value isn't
+   * included in the line and the line gets split.
+   */
+  SelectionCallback<bool> defined = (d, i, e) => true;
+
+  LineInterpolator _interpolate = DEFAULT_INTERPOLATOR;
+
+  /**
+   * Set line interpolation to one of the known/registered
+   * interpolation types.  Defaults to [SvgLine.LINEAR]
+   */
+  set interpolation(String value) {
+    if (!interpolators.containsKey(value)) {
+      throw new ArgumentError('Unregistered line interpolator.');
+    }
+    _interpolate = DEFAULT_INTERPOLATOR;
+  }
+
+  /** Set a constant value as the x-value on all points in the line */
+  set x(num value) => xAccessor = toValueAccessor(value);
+
+  /** Set a constant value as the x-value on all points in the line */
+  set y(num value) => yAccessor = toValueAccessor(value);
+
+  /**
+   * Get Svg path description for the given [data], element index [index]
+   * and element [e] to which the data is associated.
+   */
+  String path(data, int index, Element e) {
+    var segments = [],
+        points = [];
+    assert(data is List);
+    data.asMap().forEach((int i, d) {
+      if (defined(d, i, e)) {
+        points.add(new math.Point(xAccessor(d, i), yAccessor(d, i)));
+      } else if (points.isNotEmpty) {
+        segments.add('M${_interpolate(points)}');
+        points = [];
+      }
+    });
+
+    if (points.isNotEmpty) segments.add('M${_interpolate(points)}');
+
+    return segments.join();
+  }
+
+  /**
+   * Default implementation of a callback to extract X coordinate
+   * value for a given datum.  Assumes that datam is a non-empty
+   * array.
+   */
+  static num defaultDataToX(d, i) => d[0];
+
+  /**
+   * Default implementation of a callback to extract Y coordinate
+   * value for a given datum.  Assumes that datam is an array of
+   * atleast two elements.
+   */
+  static num defaultDataToY(d, i) => d[1];
+
+  /* Implementation of the linear interpolator */
+  static String _linear(List points) =>
+      points.map((pt) => '${pt.x},${pt.y}').join('L');
+}
diff --git a/pub/charted/lib/time/time.dart b/pub/charted/lib/time/time.dart
new file mode 100644
index 0000000..77b919f
--- /dev/null
+++ b/pub/charted/lib/time/time.dart
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+/*
+ * TODO(songrenchu): Document library
+ */
+library charted.time;
+
+part 'time_interval.dart';
+
+class Time {
+  static TimeInterval second = new TimeInterval(
+    (DateTime date) => new DateTime.fromMillisecondsSinceEpoch(
+        (date.millisecondsSinceEpoch ~/ 1000) * 1000),
+    (DateTime date, num offset) {
+      date = new DateTime.fromMillisecondsSinceEpoch(
+          date.millisecondsSinceEpoch + offset.toInt() * 1000);
+      return date;
+    },
+    (DateTime date) => date.second
+  );
+
+  static TimeInterval minute = new TimeInterval(
+    (DateTime date) => new DateTime.fromMillisecondsSinceEpoch(
+        (date.millisecondsSinceEpoch ~/ 60000) * 60000),
+    (DateTime date, num offset) {
+      date = new DateTime.fromMillisecondsSinceEpoch(
+          date.millisecondsSinceEpoch + offset.toInt() * 60000);
+      return date;
+    },
+    (DateTime date) => date.minute
+  );
+
+  static TimeInterval hour = new TimeInterval(
+    (DateTime date) => new DateTime.fromMillisecondsSinceEpoch(
+        (date.millisecondsSinceEpoch ~/ 3600000) * 3600000),
+    (DateTime date, num offset) {
+      date = new DateTime.fromMillisecondsSinceEpoch(
+          date.millisecondsSinceEpoch + offset.toInt() * 3600000);
+      return date;
+    },
+    (DateTime date) => date.hour
+  );
+
+  static TimeInterval day = new TimeInterval(
+    (DateTime date) => new DateTime(date.year, date.month, date.day),
+    (DateTime date, num offset) =>
+        new DateTime(date.year, date.month, date.day + offset.toInt(),
+            date.hour, date.minute, date.second, date.millisecond),
+    (DateTime date) => date.day - 1
+  );
+
+  // TODO(songrenchu): Implement seven days of a week, now only Sunday as
+  // the first day is supported.
+  static TimeInterval week = new TimeInterval(
+    (DateTime date) =>
+        new DateTime(date.year, date.month, date.day - date.day % 7),
+    (DateTime date, num offset) =>
+        new DateTime(date.year, date.month, date.day + offset.toInt()* 7,
+            date.hour, date.minute, date.second, date.millisecond ),
+    (DateTime date) {
+      var day = year.floor(date).day;
+      return (dayOfYear(date) +  day % 7) ~/ 7;
+    }
+  );
+
+  static TimeInterval month = new TimeInterval(
+    (DateTime date) => new DateTime(date.year, date.month, 1),
+    (DateTime date, num offset) =>
+        new DateTime(date.year, date.month + offset.toInt(), date.day,
+            date.hour, date.minute, date.second, date.millisecond),
+    (DateTime date) => date.month - 1
+  );
+
+  static TimeInterval year = new TimeInterval(
+    (DateTime date) => new DateTime(date.year),
+    (DateTime date, num offset) =>
+        new DateTime(date.year + offset.toInt(), date.month, date.day,
+            date.hour, date.minute, date.second, date.millisecond),
+    (DateTime date) => date.year
+  );
+
+  static Function seconds = second.range,
+                  minutes = minute.range,
+                  hours = hour.range,
+                  days = day.range,
+                  weeks = week.range,
+                  months = month.range,
+                  years = year.range;
+
+  static Function secondsUTC = second.rangeUTC,
+                  minutesUTC = minute.rangeUTC,
+                  hoursUTC = hour.rangeUTC,
+                  daysUTC = day.rangeUTC,
+                  weeksUTC = week.rangeUTC,
+                  monthsUTC = month.rangeUTC,
+                  yearsUTC = year.rangeUTC;
+
+  //TODO: better implementation
+  static int dayOfYear(DateTime date) {
+    var floorYear = year.floor(date);
+    return ((date.millisecondsSinceEpoch - floorYear.millisecondsSinceEpoch -
+        (date.timeZoneOffset - floorYear.timeZoneOffset).inMilliseconds * 60000)
+        ~/ 86400000);
+  }
+  //TODO: weekOfYear
+}
\ No newline at end of file
diff --git a/pub/charted/lib/time/time_interval.dart b/pub/charted/lib/time/time_interval.dart
new file mode 100644
index 0000000..b0844a7
--- /dev/null
+++ b/pub/charted/lib/time/time_interval.dart
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+part of charted.time;
+
+abstract class Interval {
+  DateTime floor(var date);
+  DateTime ceil(var date);
+  List range(var t0, var t1, int dt);
+}
+
+class TimeInterval extends Interval{
+  Function _local;
+  Function _step;
+  Function _number;
+
+  TimeInterval(this._local, this._step, this._number);
+
+  DateTime floor(var date) {
+    if (date is int) date = new DateTime.fromMillisecondsSinceEpoch(date);
+    return _local(date);
+  }
+
+  DateTime round(var date) {
+    if (date is int) date = new DateTime.fromMillisecondsSinceEpoch(date);
+    DateTime d0 = _local(date),
+             d1 = offset(d0, 1);
+    return date.millisecondsSinceEpoch - d0.millisecondsSinceEpoch <
+           d1.millisecondsSinceEpoch - date.millisecondsSinceEpoch ? d0 : d1;
+  }
+
+  DateTime ceil(var date) {
+    if (date is int) date = new DateTime.fromMillisecondsSinceEpoch(date);
+    DateTime d0 = _local(date),
+             d1 = offset(d0, 1);
+    return date.millisecondsSinceEpoch - d0.millisecondsSinceEpoch > 0 ?
+           d1 : d0;
+  }
+
+  offset(date, num k) {
+    date = _step(date, k);
+    return date;
+  }
+
+  List range(var t0, var t1, int dt) {
+    var time = ceil(t0),
+        times = [],
+        t1SinceEpoch = t1 is DateTime ? t1.millisecondsSinceEpoch : t1,
+        timeSinceEpoch = time.millisecondsSinceEpoch;
+    if (dt > 1) {
+      while (timeSinceEpoch < t1SinceEpoch) {
+        if ((_number(time) % dt) == 0) times.add(
+            new DateTime.fromMillisecondsSinceEpoch(timeSinceEpoch));
+        time = _step(time, 1);
+        timeSinceEpoch = time.millisecondsSinceEpoch;
+      }
+    } else {
+      while (timeSinceEpoch < t1SinceEpoch) {
+        times.add(new DateTime.fromMillisecondsSinceEpoch(timeSinceEpoch));
+        time = _step(time, 1);
+        timeSinceEpoch = time.millisecondsSinceEpoch;
+      }
+    }
+    return times;
+  }
+
+  // TODO(songrenchu): Implement UTC range
+  List rangeUTC(DateTime t0, DateTime t1, int dt) {
+    throw new UnimplementedError();
+  }
+}
+
+// TODO(songrenchu): Implement UTC time interval
+class TimeIntervalUTC extends TimeInterval {
+  TimeIntervalUTC(local, step, number): super(local, step, number) {
+    throw new UnimplementedError();
+  }
+}
\ No newline at end of file
diff --git a/pub/charted/lib/transition/transition.dart b/pub/charted/lib/transition/transition.dart
new file mode 100644
index 0000000..238885d
--- /dev/null
+++ b/pub/charted/lib/transition/transition.dart
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+library charted.transition;
+
+import "dart:html" show Element,document;
+import "package:charted/core/core.dart";
+import "package:charted/event/event.dart";
+import "package:charted/selection/selection.dart";
+import "package:charted/interpolators/interpolators.dart";
+
+part "transition_impl.dart";
+
+typedef InterpolateFn AttrTweenCallback(datum, int ei, String attr);
+typedef InterpolateFn StyleTweenCallback(datum, int ei, String style);
+
+/**
+ * Transitions are created using the transition operator on a selection.
+ * Transitions start automatically upon creation after a delay which defaults
+ * to zero; however, note that a zero-delay transition actually starts after a
+ * minimal (~17ms) delay, pending the first timer callback.
+ * Transitions have a default duration of 250ms.
+ */
+abstract class Transition {
+
+  /** A settable default easing type */
+  static EasingFn defaultEasingType = easeCubic();
+
+  /** A settable default easing mode */
+  static EasingMode defaultEasingMode = reflectEasingFn;
+
+  /** A settable default transition duration */
+  static int defaultDuration = 250;
+
+  /** Sets the ease function of the transition, default is cubic-in-out. */
+  InterpolateFn ease;
+
+  /**
+   * Specifies the transition delay in milliseconds. All elements are given the
+   * same delay. The default delay is 0.
+   */
+  void delay(int millisecond);
+
+  /**
+   * Sets the delay with a ChartedCallback function which would be evaluated for
+   * each selected element (in order), being passed the current datum d, the
+   * current index i, and the current DOM element. The function's return value
+   * is then used to set each element's delay.
+   */
+  void delayWithCallback(SelectionCallback fn);
+
+  /**
+   * Specifies per-element duration in milliseconds. All elements are given the
+   * same duration in millisecond.  The default duration is 250ms.
+   */
+  void duration(int millisecond);
+
+  /**
+   * Sets the duration with a ChartedCallback which would be evaluated for each
+   * selected element (in order), being passed the current datum d, the current
+   * index i, and the current DOM element.  The function's return value is then
+   * used to set each element's duration.
+   */
+  void durationWithCallback(SelectionCallback fn);
+
+  /**
+   * Sets the attribute [name] on all elements when [val] is not null.
+   * Removes the attribute when [val] is null.
+   */
+  void attr(String name, val);
+
+  /**
+   * Same as [attr], but calls [fn] for each non-null element in
+   * the selection (with data associated to the element, index of the
+   * element in it's group and the element itself) to get the value
+   * of the attribute.
+   */
+  void attrWithCallback(String name, SelectionCallback fn);
+
+  /**
+   * Transitions the value of the attribute with the specified name according to
+   * the specified tween function. The starting and ending value of the
+   * transition are determined by tween; the tween function is invoked when the
+   * transition starts on each element, being passed the current datum d, the
+   * current index i and the current attribute value a. The return value of
+   * tween must be an interpolator: a function that maps a parametric value t in
+   * the domain [0,1] to a color, number or arbitrary value.
+   */
+  void attrTween(String name, AttrTweenCallback tween);
+
+  /**
+   * Transitions the value of the CSS style property with the specified name to
+   * the specified value. An optional priority may also be specified, either as
+   * null or the string "important" (without the exclamation point). The
+   * starting value of the transition is the current computed style property
+   * value, and the ending value is the specified value. All elements are
+   * transitioned to the same style property value.
+   */
+  void style(String property, String val, [String priority]);
+
+  /**
+   * Transitions the style with a CartedCallback which would be evaluated for
+   * each selected element (in order), being passed the current datum d and the
+   * current index i, and the current DOM element.
+   * The function's return value is then used to transition each element's
+   * style property.
+   */
+  void styleWithCallback(String property,
+      SelectionCallback<String> fn, [String priority]);
+
+  /**
+   * Transitions the value of the CSS style property with the specified name
+   * according to the specified tween function. An optional priority may also
+   * be specified, either as null or the string "important" (without the
+   * exclamation point). The starting and ending value of the transition are
+   * determined by tween; the tween function is invoked when the transition
+   * starts on each element, being passed the current datum d, the current index
+   * i and the current attribute value a. The return value of tween must be an
+   * interpolator: a function that maps a parametric value t in the domain [0,1]
+   * to a color, number or arbitrary value.
+   */
+  void styleTween(String property, StyleTweenCallback tween, [String priority]);
+
+  /** Interrupts the transition. */
+  void interrupt();
+
+  /**
+   * For each element in the current transition, selects the first descendant
+   * element that matches the specified selector string. If no element matches
+   * the specified selector for the current element, the element at the current
+   * index will be null in the returned selection; operators (with the exception
+   * of data) automatically skip null elements, thereby preserving the index of
+   * the existing selection. If the current element has associated data, this
+   * data is inherited by the returned subselection, and automatically bound to
+   * the newly selected elements. If multiple elements match the selector, only
+   * the first matching element in document traversal order will be selected.
+   */
+  Transition select(String selector);
+
+  /**
+   * For each element in the current transition, selects descendant elements
+   * that match the specified selector string. The returned selection is grouped
+   * by the ancestor node in the current selection. If no element matches the
+   * specified selector for the current element, the group at the current index
+   * will be empty in the returned selection. The subselection does not inherit
+   * data from the current selection; however, if data was previously bound to
+   * the selected elements, that data will be available to operators.
+   */
+  Transition selectAll(String selector);
+
+  /**
+   * Creates a new transition on the same selected elements that starts with
+   * this transition ends. The new transition inherits this transition’s
+   * duration and easing. This can be used to define chained transitions without
+   * needing to listen for "end" events.  Only works when parent delay and
+   * duration are constant.
+   */
+  Transition transition();
+
+  /** Factory method to create an instance of the default implementation */
+  factory Transition(Selection selection) => new _TransitionImpl(selection);
+}
\ No newline at end of file
diff --git a/pub/charted/lib/transition/transition_impl.dart b/pub/charted/lib/transition/transition_impl.dart
new file mode 100644
index 0000000..db05b2e
--- /dev/null
+++ b/pub/charted/lib/transition/transition_impl.dart
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+part of charted.transition;
+
+class _TransitionImpl implements Transition {
+
+  SelectionCallback _delay = (d, i, c) => 0;
+  SelectionCallback _duration = (d, i, c) => Transition.defaultDuration;
+  Selection _selection;
+  Map _attrs = {};
+  Map _styles = {};
+  Map _attrTweens = {};
+  Map _styleTweens = {};
+  Map<ChartTimer, Element> _timerMap = {};
+  Map<Element, List<Map>> _attrMap = {};
+  Map<Element, int> _durationMap = {};
+  bool _interrupted = false;
+  var _timerDelay = 0;
+
+  _TransitionImpl(this._selection, [num delay = 0]) {
+    _transitionNode(delay);
+    _timerDelay = delay;
+  }
+
+  InterpolateFn ease = clampEasingFn(Transition.defaultEasingMode(
+        Transition.defaultEasingType));
+
+  void delay([int millisecond = 0]) {
+    delayWithCallback(toCallback(millisecond));
+  }
+
+  void delayWithCallback(SelectionCallback fn) {
+    _delay = fn;
+  }
+
+  void duration(int millisecond) {
+    durationWithCallback(toCallback(millisecond));
+  }
+
+  void durationWithCallback(SelectionCallback fn) {
+    _duration = fn;
+  }
+
+  void attr(String name, val) {
+    attrWithCallback(name, toCallback(val));
+  }
+
+  void attrWithCallback(String name, SelectionCallback fn) {
+    _attrs[name] = fn;
+  }
+
+  void attrTween(String name, AttrTweenCallback tween) {
+    _attrTweens[name] = tween;
+  }
+
+  void style(String property, String val, [String priority = '']) {
+    styleWithCallback(property, toCallback(val), priority);
+  }
+
+  void styleWithCallback(String property, SelectionCallback<String> fn,
+      [String priority = '']) {
+    _styles[property] = {'callback': fn, 'priority': priority};
+  }
+
+  void styleTween(String property, StyleTweenCallback tween,
+      [String priority]) {
+    _styleTweens[property] = {'callback': tween, 'priority': priority};
+  }
+
+  // Starts a timer that registers all attributes, durations, and delays for the
+  // transition of the current selection.
+  _transitionNode(num delay) {
+    new ChartTimer((elapsed) {
+      _selection.each((d, i, c) {
+        var tweenList = [];
+        _attrs.forEach((key, value) {
+            tweenList.add(_getAttrInterpolator(c, key, value(d, i, c)));
+        });
+        _attrTweens.forEach((key, value) {
+          tweenList.add((t) => c.setAttribute(key,
+              value(d, i, c.getAttribute(key))(t)));
+        });
+        _styles.forEach((key, value) {
+            tweenList.add(_getStyleInterpolator(c, key,
+                value['callback'](d, i, c), value['priority']));
+        });
+        _styleTweens.forEach((key, value) {
+          tweenList.add((t) => c.style.setProperty(key,
+              value['callback'](d, i,
+              c.style.getPropertyValue(key))(t).toString(), value['priority']));
+        });
+
+        _attrMap[c] = tweenList;
+        _durationMap[c] = _duration(d, i, c);
+        _timerMap[new ChartTimer(_tick, _delay(d, i, c))] = c;
+      });
+      return true;
+    }, delay);
+  }
+
+  // Returns the correct interpolator function for the old and new attribute.
+  _getAttrInterpolator(Element element, String attrName, newValue) {
+    var attr = element.attributes[attrName];
+    var interpolator = interpolateString(attr, newValue.toString());
+    return (t) => element.setAttribute(attrName, interpolator(t).toString());
+  }
+
+  // Returns the correct interpolator function for the old and new style.
+  _getStyleInterpolator(Element element, String styleName, newValue, priority) {
+    var style = element.style.getPropertyValue(styleName);
+
+    var interpolator = interpolateString(style, newValue.toString());
+
+    return (t) => element.style.setProperty(styleName,
+        interpolator(t).toString(), priority);
+  }
+
+  // Ticks of the transition, this is the callback registered to the
+  // ChartedTimer, called on each animation frame until the transition duration
+  // has been reached.
+  bool _tick(elapsed) {
+    if (_interrupted) {
+      return true;
+    }
+    var activeNode = _timerMap[ChartTimer.activeTimer];
+    var t = elapsed / _durationMap[activeNode];
+    for (InterpolateFn tween in _attrMap[activeNode]) {
+      tween(ease(t));
+    }
+    return (t >= 1) ? true : false;
+  }
+
+  // Interrupts the transition.
+  void interrupt() {
+    _interrupted = true;
+  }
+
+  Transition select(String selector) {
+    var t = new Transition(_selection.select(selector));
+    t.ease = ease;
+    t.delayWithCallback(_delay);
+    t.durationWithCallback(_duration);
+    return t;
+  }
+
+  Transition selectAll(String selector) {
+    var t = new Transition(_selection.selectAll(selector));
+    t.ease = ease;
+    t.delayWithCallback(_delay);
+    t.durationWithCallback(_duration);
+    return t;
+  }
+
+  Transition transition() {
+    var e = _selection.first;
+    var delay = _delay(_selection.scope.datum(e), 0, e) +
+        _duration(_selection.scope.datum(e), 0, e) + _timerDelay;
+    var t = new _TransitionImpl(_selection, delay);
+    t.ease = ease;
+    t.durationWithCallback(_duration);
+    return t;
+  }
+}
diff --git a/pub/charted/pubspec.yaml b/pub/charted/pubspec.yaml
new file mode 100644
index 0000000..09b7d53
--- /dev/null
+++ b/pub/charted/pubspec.yaml
@@ -0,0 +1,17 @@
+name: charted
+version: 0.0.10
+authors:
+- Prasad Sunkari <prsd@google.com>
+- Michael Cheng <midoringo@google.com>
+- Yan Qiao <yqiao@google.com>
+description: Visualization toolkit for Dart. Provides D3 (http://d3js.org) like Selection API, utilities to achieve data driven DOM and provides an easy-to-use Charting library based on the Selection API.
+homepage: https://github.com/google/charted
+dependencies:
+  browser: any
+  csslib: any
+  intl: any
+  logging: any
+  observe: any
+dev_dependencies:
+  hop: '>=0.27.0'
+  unittest: any
diff --git a/pub/charted/tool/hop_runner.dart b/pub/charted/tool/hop_runner.dart
new file mode 100644
index 0000000..d3b3e41
--- /dev/null
+++ b/pub/charted/tool/hop_runner.dart
@@ -0,0 +1,46 @@
+library charted.tool.hop_runner;
+
+import 'package:hop/hop.dart';
+import 'dart:io';
+
+main(List<String> args) {
+  addTask('unit-test', createUnitTestsTask());
+  runHop(args);
+}
+
+Task createUnitTestsTask() =>
+  new Task((TaskContext context) {
+    context.info("Running tests...");
+    return Process.run('./content_shell',
+        ['--dump-render-tree','test/test_in_browser.html']).
+            then((ProcessResult process) =>
+                checkTestsOutput(context, process));
+  });
+
+/**
+ * Reads the output from content_shell and checks for number of tests
+ * passed/failed/erred. This method requires that the tests be done
+ * in simple html unit test configuration - useHtmlConfiguration().
+ */
+void checkTestsOutput(TaskContext context, ProcessResult process) {
+  var output = (process.stdout as String),
+      passRegEx = new RegExp(r"^[0-9]+\s+PASS\s"),
+      failRegEx = new RegExp(r"^[0-9]+\s+FAIL\s"),
+      errorRegEx = new RegExp(r"^[0-9]+\s+ERROR\s"),
+      passCount = 0,
+      failCount = 0,
+      errorCount = 0;
+
+  context.info(output);
+
+  for (var line in output.split('\n')) {
+    if (line.contains(passRegEx)) passCount++;
+    if (line.contains(failRegEx)) failCount++;
+    if (line.contains(errorRegEx)) errorCount++;
+  }
+
+  if (failCount + errorCount > 0) {
+    context.fail('FAIL: $failCount\nERROR: $errorCount');
+  }
+}
+
diff --git a/pub/charted/web/charts_demo.dart b/pub/charted/web/charts_demo.dart
new file mode 100644
index 0000000..278551c
--- /dev/null
+++ b/pub/charted/web/charts_demo.dart
@@ -0,0 +1,36 @@
+
+library charted.demo;
+
+import 'dart:html';
+import 'package:charted/charted.dart';
+
+part 'src/dataset_small.dart';
+part 'src/dataset_large.dart';
+
+class ChartDemo {
+  final String title;
+  final int dimensionAxesCount;
+  final ChartConfig config;
+  final ChartData data;
+  final Element host;
+
+  ChartArea area;
+
+  ChartDemo(this.title, this.host, this.config, this.data,
+       { this.dimensionAxesCount: 1 }) {
+    var chartAreaHost = new DivElement()..classes.add('chart-host'),
+        chartLegendHost = new DivElement()..classes.add('legend-host'),
+        wrapper = new DivElement()
+            ..children.addAll([chartAreaHost, chartLegendHost])
+            ..classes.add('chart-wrapper');
+
+    host.children.addAll(
+        [ new Element.html('<div><h2>$title</h2></div>'), wrapper ]);
+
+    config.legend = new ChartLegend(chartLegendHost);
+    area = new ChartArea(chartAreaHost, data, config,
+        autoUpdate: false, dimensionAxesCount: dimensionAxesCount);
+  }
+
+  void draw() => area.draw();
+}
diff --git a/pub/charted/web/custom_axis_demo.dart b/pub/charted/web/custom_axis_demo.dart
new file mode 100644
index 0000000..a7bc5bf
--- /dev/null
+++ b/pub/charted/web/custom_axis_demo.dart
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+library charted.demo.custom_axis;
+
+import 'dart:html';
+import 'package:charted/charts/charts.dart';
+import 'package:charted/scale/scale.dart';
+
+List COLUMNS = [
+    new ChartColumnSpec(label:'Month', type:ChartColumnSpec.TYPE_STRING),
+    new ChartColumnSpec(label:'Precipitation'),
+    new ChartColumnSpec(label:'High Temperature'),
+    new ChartColumnSpec(label:'Low Temperature'),
+    new ChartColumnSpec(label:'Random'),
+    new ChartColumnSpec(label:'Extra1'),
+    new ChartColumnSpec(label:'Extra2'),
+    new ChartColumnSpec(label:'Extra3'),
+  ];
+
+List DATA = [
+    ['January',  4.50, 27, 46, 1,   20, 23, 1],
+    ['February', 4.61, 60, 28, 10,  15, 45, 23],
+    ['March',    3.26, 32, 49, 100, 4,  34, 1],
+    ['April',    1.46, 63, 49, 30,  34, 89, 3]
+  ];
+
+main() {
+  // Default Chart
+  var series1 = new ChartSeries("one", [1, 3, 2, 6],
+          new StackedBarChartRenderer()),
+      data = new ChartData(COLUMNS, DATA),
+      config = new ChartConfig([series1], [0]),
+      area = new ChartArea(querySelector('.default'),
+          data, config, autoUpdate:false, dimensionAxesCount:1);
+  area.draw();
+
+
+
+  // Chart with custom measure axis with specific domain on the scale.
+  var series2 = new ChartSeries("one", [1, 3, 2, 6],
+          new StackedBarChartRenderer(),
+      // measureAxisId matches the id later used in registerMeasureAxis().
+          measureAxisIds: ['fixed_domain']),
+      data2 = new ChartData(COLUMNS, DATA),
+      config2 = new ChartConfig([series2], [0]);
+
+  // Add custom scale and axis config.
+  var scale = new LinearScale();
+  scale.domain = [0, 1000];
+  var axisConfig = new ChartAxisConfig();
+  axisConfig.title = 'Axis title';
+  axisConfig.scale = scale;
+
+  config2.registerMeasureAxis('fixed_domain', axisConfig);
+  var customAxisChart = new ChartArea(querySelector('.custom-domain'),
+          data2, config2, autoUpdate:false, dimensionAxesCount:1);
+  customAxisChart.draw();
+
+
+
+  // Chart with custom measure axis with specific tick values.
+  var series3 = new ChartSeries("one", [1, 3, 2, 6],
+      new StackedBarChartRenderer(),
+  // measureAxisId matches the id later used in registerMeasureAxis().
+      measureAxisIds: ['fixed_ticks']),
+  data3 = new ChartData(COLUMNS, DATA),
+  config3 = new ChartConfig([series3], [0]);
+
+  // Add custom scale and axis config.
+  var scale2 = new LinearScale();
+  scale2.domain = [0, 300];
+  var axisConfig2 = new ChartAxisConfig();
+  axisConfig2.title = 'Axis title';
+  axisConfig2.scale = scale2;
+  axisConfig2.tickValues = [0, 25, 50, 230, 250];
+
+  config3.registerMeasureAxis('fixed_ticks', axisConfig2);
+  var fixedTickValueChart = new ChartArea(querySelector('.custom-ticks'),
+          data3, config3, autoUpdate:false, dimensionAxesCount:1);
+  fixedTickValueChart.draw();
+}
diff --git a/pub/charted/web/interactive_demo.dart b/pub/charted/web/interactive_demo.dart
new file mode 100644
index 0000000..3f38111
--- /dev/null
+++ b/pub/charted/web/interactive_demo.dart
@@ -0,0 +1,256 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+library charted.demo.interactive;
+
+import 'dart:html';
+import 'package:observe/observe.dart';
+import 'package:charted/charts/charts.dart';
+
+import 'charts_demo.dart';
+
+const List DIMENSION_COLUMNS =  const[0, 4];
+
+int customSeriesCounter = 0;
+
+List DEMOS = [
+  {
+    'name': 'One',
+    'title': 'One &mdash; A chart with one dimension axis',
+    'sub-title': 'Compatible with bar, line and stacked-bar renderers',
+    'series': [
+      {
+        'name': 'Series-01',
+        'renderer': 'bar-chart',
+        'columns': [ 1, 2, 3 ]
+      }
+    ]
+  }
+];
+
+Map RENDERERS = {
+  'bar-chart': 'Bar chart',
+  'line-chart': 'Line chart',
+  'stacked-bar-chart': 'Stacked bar chart',
+  'waterfall-chart': 'Waterfall chart',
+};
+
+ChartRenderer getRendererForType(String name) {
+  if (name == 'bar-chart') return new BarChartRenderer();
+  if (name == 'line-chart') return new LineChartRenderer();
+  if (name == 'stacked-bar-chart') return new StackedBarChartRenderer();
+  if (name == 'waterfall-chart') return new WaterfallChartRenderer();
+  return new BarChartRenderer();
+}
+
+String getTypeForRenderer(ChartRenderer renderer) {
+  if (renderer is BarChartRenderer) return 'bar-chart';
+  if (renderer is LineChartRenderer) return 'line-chart';
+  if (renderer is StackedBarChartRenderer) return 'stacked-bar-chart';
+  if (renderer is WaterfallChartRenderer) return 'waterfall-chart';
+  return 'bar-chart';
+}
+
+main() {
+  ChartSeries activeSeries,
+      defaultSeries = new ChartSeries("Default series",
+          new ObservableList.from([ 2, 3 ]), new BarChartRenderer());
+
+  ObservableList rows = new ObservableList.from(SMALL_DATA.sublist(0, 3)),
+      columns = new ObservableList.from(SMALL_DATA_COLUMNS),
+      seriesList = new ObservableList.from([ defaultSeries ]);
+
+  ChartData data = new ChartData(columns, rows);
+  ChartConfig config = new ChartConfig(seriesList, DIMENSION_COLUMNS);
+
+  ChartArea area = new ChartArea(querySelector('.chart-host'), data, config,
+      autoUpdate: true, dimensionAxesCount: 1);
+
+  area.addChartBehavior(new ChartTooltip());
+  config.legend = new ChartLegend(querySelector('.legend-host'));
+
+  area.draw();
+
+  /*
+   * Create and hook up the control panel
+   */
+
+  ButtonElement addRowButton = querySelector('#add-row'),
+      removeRowButton = querySelector('#remove-row'),
+      addSeriesButton = querySelector('#add-series'),
+      removeSeriesButton = querySelector('#remove-series');
+
+  SelectElement seriesSelect = querySelector('#select-series'),
+      rendererSelect = querySelector('#select-renderer');
+
+  Element columnButtons = querySelector('#column-buttons');
+
+
+  /*
+   * Updating rows
+   */
+
+  updateRowButtonStates() {
+    addRowButton.disabled = rows.length >= SMALL_DATA.length;
+    removeRowButton.disabled = rows.length <= 1;
+  }
+
+  addRowButton.onClick.listen((_) {
+    if (rows.length < SMALL_DATA.length) {
+      rows.add(SMALL_DATA.elementAt(rows.length));
+    }
+    updateRowButtonStates();
+  });
+
+  removeRowButton.onClick.listen((_) {
+    if (rows.length > 0) rows.removeLast();
+    updateRowButtonStates();
+  });
+
+
+  /*
+   * Series selection
+   */
+
+  selectSeries(ChartSeries series) {
+    activeSeries = series;
+
+    List<Element> options = rendererSelect.children.toList();
+    String rendererType = getTypeForRenderer(series.renderer);
+    for (int i = 0; i < options.length; i++) {
+      if ((options[i] as OptionElement).value == rendererType)
+        rendererSelect.selectedIndex = i;
+    }
+
+    List<InputElement> buttons = querySelectorAll('.column-button');
+    Iterable measures = series.measures;
+    for (int i = 0; i < buttons.length; i++) {
+      buttons[i].checked = measures.contains(i + 1);
+    }
+  }
+
+  updateSeriesSelector([ChartSeries selected]) {
+    seriesSelect.children.clear();
+    seriesList.forEach((ChartSeries item) {
+      var option = new OptionElement()
+          ..value = item.name
+          ..text = item.name;
+      seriesSelect.append(option);
+    });
+    selectSeries(selected == null ? seriesList.first : selected);
+  }
+
+  seriesSelect.onChange.listen(
+      (_) => selectSeries(seriesList.firstWhere(
+          (ChartSeries x) => x.name == seriesSelect.value)));
+
+
+  /*
+   * Renderer selection for active series
+   */
+
+  updateRendererSelector() {
+    rendererSelect.children.clear();
+    RENDERERS.forEach((name, label) {
+      var option = new OptionElement()
+          ..value = name
+          ..text = label;
+      rendererSelect.append(option);
+    });
+  }
+
+  rendererSelect.onChange.listen((_) {
+      if (rendererSelect.value == "waterfall-chart" &&
+          area.data is! WaterfallChartData) {
+        area.data = new WaterfallChartData(columns, rows);
+      } else if (rendererSelect.value != "waterfall-chart" &&
+          area.data is WaterfallChartData) {
+        area.data = new ChartData(columns, rows);
+      }
+      activeSeries.renderer = getRendererForType(rendererSelect.value);
+    });
+
+
+  /*
+   * Column selection on active series
+   */
+
+  updateColumns([Event e]) {
+    if (activeSeries == null) return;
+
+    List<Element> buttons = querySelectorAll('.column-button');
+    InputElement firstChecked;
+    for (int i = 0; i < buttons.length && firstChecked == null; i++) {
+      if ((buttons[i] as InputElement).checked) firstChecked = buttons[i];
+    }
+
+    List measures = activeSeries.measures as List;
+    int index = buttons.indexOf(e.target) + 1;
+    if ((e.target as InputElement).checked) {
+      measures.add(index);
+      buttons.forEach((InputElement b) => b.disabled = false);
+    } else {
+      measures.remove(index);
+      if (measures.length <= 1) firstChecked.disabled = true;
+    }
+  }
+
+  updateColumnsList() {
+    columnButtons.children.clear();
+    SMALL_DATA_COLUMNS.asMap().forEach((int index, ChartColumnSpec spec) {
+      if (index == 0) return;
+      var row = new DivElement();
+      var button = new InputElement()
+          ..className = 'column-button'
+          ..type = 'checkbox'
+          ..value = spec.label
+          ..id = 'column-$index'
+          ..onChange.listen((e) => updateColumns(e));
+      var label = new LabelElement()
+          ..text = spec.label
+          ..htmlFor = 'column-$index';
+      row.children.addAll([button,label]);
+      columnButtons.append(row);
+    });
+  }
+
+
+  /*
+   * Updates to series
+   */
+
+  updateSeriesButtonStates() {
+    removeSeriesButton.disabled = seriesList.length <= 1;
+  }
+
+  addSeriesButton.onClick.listen((_) {
+    var name = 'New Series ${customSeriesCounter}',
+        series = new ChartSeries(name, new ObservableList.from([1, 2, 3]),
+            new BarChartRenderer());
+    seriesList.add(series);
+    updateSeriesSelector(series);
+    seriesSelect.selectedIndex = seriesList.length - 1;
+    updateSeriesButtonStates();
+    customSeriesCounter++;
+  });
+
+  removeSeriesButton.onClick.listen((_) {
+    if (seriesList.length > 0) seriesList.removeLast();
+    updateSeriesSelector();
+    updateSeriesButtonStates();
+  });
+
+
+  /*
+   * Update UI
+   */
+
+  updateColumnsList();
+  updateRendererSelector();
+  updateSeriesSelector();
+}
diff --git a/pub/charted/web/renderers_demo.dart b/pub/charted/web/renderers_demo.dart
new file mode 100644
index 0000000..3d70bd2
--- /dev/null
+++ b/pub/charted/web/renderers_demo.dart
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+library charted.demo.renderers;
+
+import 'dart:html';
+import 'package:charted/charts/charts.dart';
+
+import 'charts_demo.dart';
+
+main() {
+  var data = new ChartData(SMALL_DATA_COLUMNS, SMALL_DATA);
+  var dataWaterfallWithSum = new WaterfallChartData(
+      SMALL_WATERFALL_DATA_COLUMNS, SMALL_WATERFALL_DATA_WITH_SUM, [0, 2, 5]);
+
+  // Bar Chart
+  var bar_series = new ChartSeries("one", [2, 3], new BarChartRenderer()),
+      bar_config = new ChartConfig([bar_series], [0]),
+      bar_demo = new ChartDemo(
+          'Bar chart', querySelector('.bar-chart'), bar_config, data);
+  bar_demo.draw();
+
+  // Line chart
+  var line_series = new ChartSeries("one", [2, 3], new LineChartRenderer()),
+      line_config = new ChartConfig([line_series], [0]),
+      line_demo = new ChartDemo(
+          'Line chart', querySelector('.line-chart'), line_config, data);
+  line_demo.draw();
+
+  // Stacked bar chart
+  var stacked_series =
+          new ChartSeries("one", [2, 3], new StackedBarChartRenderer()),
+      stacked_config = new ChartConfig([stacked_series], [0]),
+      stacked_demo = new ChartDemo('Stacked bar chart',
+          querySelector('.stacked-bar-chart'), stacked_config, data);
+  stacked_demo.draw();
+
+  // Waterfall Chart
+  var waterfall_series = new ChartSeries("one", [1, 2],
+                                         new WaterfallChartRenderer()),
+      waterfall_config = new ChartConfig([waterfall_series], [0]),
+      waterfall_demo = new ChartDemo(
+          'Waterfall chart', querySelector('.waterfall-chart'),
+          waterfall_config, dataWaterfallWithSum);
+  waterfall_demo.draw();
+
+  // Combo chart
+  var combo_bar_series = new ChartSeries("one", [2, 3], new BarChartRenderer()),
+      combo_line_series = new ChartSeries("two", [1], new LineChartRenderer()),
+      combo_config =
+          new ChartConfig([combo_bar_series, combo_line_series], [0]),
+      combo_demo = new ChartDemo(
+          'Combo chart', querySelector('.combo-chart'), combo_config, data);
+  combo_demo.draw();
+
+  // Pie chart
+  var pie_data = new ChartData(SMALL_DATA_COLUMNS, SMALL_DATA.sublist(0, 1)),
+      pie_series = new ChartSeries("one", [1, 2, 3], new PieChartRenderer()),
+      pie_config = new ChartConfig([pie_series], [0]),
+      pie_demo = new ChartDemo('Pie chart with single row',
+          querySelector('.pie-chart'), pie_config, pie_data,
+          dimensionAxesCount: 0);
+  pie_demo.draw();
+
+  // Pie chart with multiple rows
+  var multi_pie_data =
+          new ChartData(SMALL_DATA_COLUMNS, SMALL_DATA.sublist(0, 3)),
+      multi_pie_series =
+          new ChartSeries("one", [1, 2, 3], new PieChartRenderer()),
+      multi_pie_config = new ChartConfig([multi_pie_series], [0]),
+      multi_pie_demo = new ChartDemo('Pie chart with multiple row',
+          querySelector('.multi-pie-chart'), multi_pie_config, multi_pie_data,
+          dimensionAxesCount: 0);
+  multi_pie_demo.draw();
+}
diff --git a/pub/charted/web/src/dataset_large.dart b/pub/charted/web/src/dataset_large.dart
new file mode 100644
index 0000000..25e2107
--- /dev/null
+++ b/pub/charted/web/src/dataset_large.dart
@@ -0,0 +1,2 @@
+
+part of charted.demo;
\ No newline at end of file
diff --git a/pub/charted/web/src/dataset_small.dart b/pub/charted/web/src/dataset_small.dart
new file mode 100644
index 0000000..12032a4
--- /dev/null
+++ b/pub/charted/web/src/dataset_small.dart
@@ -0,0 +1,41 @@
+
+part of charted.demo;
+
+List SMALL_DATA_COLUMNS = [
+    new ChartColumnSpec(label: 'Month', type: ChartColumnSpec.TYPE_STRING),
+    new ChartColumnSpec(label: 'Precipitation'),
+    new ChartColumnSpec(label: 'High Temperature'),
+    new ChartColumnSpec(label: 'Low Temperature'),
+];
+
+List SMALL_DATA = const [
+    const ['January',   4.50,  7,  6],
+    const ['February',  5.61, 16,  8],
+    const ['March',     8.26, 36,  9],
+    const ['April',    15.46, 63, 49],
+    const ['May',      18.50, 77, 46],
+    const ['June',     14.61, 60,  8],
+    const ['July',      3.26,  9,  6],
+    const ['August',    1.46,  9,  3],
+    const ['September', 1.46, 13,  9],
+    const ['October',   2.46, 29,  3],
+    const ['November',  4.46, 33,  9],
+    const ['December',  8.46, 19,  3]
+];
+
+List SMALL_WATERFALL_DATA_COLUMNS = [
+    new ChartColumnSpec(label: 'Category', type: ChartColumnSpec.TYPE_STRING),
+    new ChartColumnSpec(label: 'Quater 1'),
+    new ChartColumnSpec(label: 'Quater 2'),
+    new ChartColumnSpec(label: 'Quater 3'),
+    new ChartColumnSpec(label: 'Quater 4'),
+];
+
+List SMALL_WATERFALL_DATA_WITH_SUM = const [
+    const ['Product Revenue',  4200,   6000,  6000,  7000],
+    const ['Service Revenue',  2100,   1600,  3000,  4000],
+    const ['Revenue Subtotal', 6300,   7600,  9000, 11000],
+    const ['Fixed Costs',     -1700,  -1700, -1700, -1700],
+    const ['Viriable Costs',  -1000,  -2000, -3000, -4500],
+    const ['Gross Earning',    3600,   3900,  4300,  4800],
+];
diff --git a/pub/charted/web/transitions_demo.dart b/pub/charted/web/transitions_demo.dart
new file mode 100644
index 0000000..c4235f8
--- /dev/null
+++ b/pub/charted/web/transitions_demo.dart
@@ -0,0 +1,224 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+library charted.demos.transitions;
+
+import 'package:charted/charted.dart';
+
+void main() {
+  SelectionScope scope = new SelectionScope.selector('.wrapper');
+
+
+  Selection svg = scope.append('svg:svg')
+      ..style('width', '4000')
+      ..style('height', '4000');
+
+
+  Selection g1 = svg.append('g');
+  g1.attr('transform', 'translate(30, 30)');
+
+  SvgLine line = new SvgLine();
+
+  List lineData = [
+      [ [0, 10], [400, 10] ],
+      [ [0, 50], [400, 50] ],
+      [ [0, 90], [400, 90] ],
+      [ [0, 130], [400, 130] ]
+  ];
+  DataSelection lines = g1.selectAll('.line').data(lineData);
+
+  lines.enter.append('path');
+  lines
+      ..attrWithCallback('d', (d, i, e) => line.path(d, i, e))
+      ..classed('line');
+  lines.exit.remove();
+
+
+  var text1 = g1.append('text');
+  text1..attr('x', 0)
+       ..attr('y', 0)
+       ..text('Transition cubic-in-out');
+  var text2 = g1.append('text');
+  text2..attr('x', 0)
+       ..attr('y', 40)
+       ..text('Transition cubic-in');
+  var text3 = g1.append('text');
+  text3..attr('x', 0)
+       ..attr('y', 80)
+       ..text('Transition cubic-out');
+  var text4 = g1.append('text');
+  text4..attr('x', 0)
+       ..attr('y', 120)
+       ..text('Transition cubic-out-in');
+
+  var circle = g1.append('circle');
+  circle
+      ..attr('cx', 0)
+      ..attr('cy', 10)
+      ..attr('r', 4)
+      ..classed('circle');
+
+  // Doing transition for first circle with sub transition selection.
+  Transition t1 = new Transition(g1);
+  t1.duration(4000);
+  t1.select('.circle').attr('cx', 400);
+
+  var circle2 = g1.append('circle');
+  circle2
+      ..attr('cx', 0)
+      ..attr('cy', 50)
+      ..attr('r', 4);
+
+  Transition t2 = new Transition(circle2);
+  t2
+      ..ease = clampEasingFn(identityFunction(easeCubic()))
+      ..attr('cx', 400)
+      ..duration(4000);
+
+  var circle3 = g1.append('circle');
+  circle3
+      ..attr('cx', 0)
+      ..attr('cy', 90)
+      ..attr('r', 4);
+
+  Transition t3 = new Transition(circle3);
+  t3
+      ..ease = clampEasingFn(reverseEasingFn(easeCubic()))
+      ..attr('cx', 400)
+      ..duration(4000);
+
+  var circle4 = g1.append('circle');
+  circle4
+      ..attr('cx', 0)
+      ..attr('cy', 130)
+      ..attr('r', 4);
+
+  Transition t4 = new Transition(circle4);
+  t4
+      ..ease = clampEasingFn(reflectReverseEasingFn(easeCubic()))
+      ..attr('cx', 400)
+      ..duration(4000);
+
+
+  Color dotColor1 = new Color.fromRgb(10, 255, 0);
+  Color dotColor2 = new Color.fromRgb(40, 0, 255);
+  String shape1 = 'M50 100 L50 200 L100 200 Z';
+  String shape2 = 'M900 0 L750 200 L900 200 Z';
+
+  var stringInterpolator = interpolateString(shape1, shape2);
+
+  Selection g2 = svg.append('g');
+
+  var text5 = g2.append('text');
+  text5..attr('x', 0)
+       ..attr('y', 0)
+       ..text('Transition shape and color');
+
+  g2.attr('transform', 'translate(30, 200)');
+  g2.attr('width', '1000');
+  g2.attr('height', '400');
+  Selection shape = g2.append('path');
+  shape
+      ..attr('d', shape1)
+      ..attr('fill', dotColor1);
+
+  Transition t5 = new Transition(shape);
+  t5.duration(4000);
+  t5.attr('d', shape2);
+  t5.attr('fill', dotColor2);
+
+  Selection g3 = svg.append('g');
+
+  var text6 = g3.append('text');
+  text6..attr('x', 0)
+       ..attr('y', 0)
+       ..text('Transition delay');
+
+  g3.attr('transform', 'translate(30, 450)');
+  g3.attr('width', '1000');
+  g3.attr('height', '1000');
+
+  Selection rect = g3.append('rect');
+  rect
+      ..attr('x', 0)
+      ..attr('y', 10)
+      ..attr('width', 40)
+      ..attr('height', 40)
+      ..attr('fill', '#FF0000');
+
+  Transition t6 = new Transition(rect);
+  t6.duration(4000);
+  t6.delay(4000);
+  t6.attr('x', 400);
+  t6.attr('fill', '#00FF00');
+
+  var t7 = t6.transition();
+  t7.attr('y', 400);
+  t7.attr('fill', '#0000FF');
+
+  var t8 = t7.transition();
+  t8.attr('x', 0);
+  t8.attr('fill', '#FF00FF');
+
+  var t9 = t8.transition();
+  t9.attr('y', 10);
+  t9.attr('fill', '#FF0000');
+
+  Selection g4 = svg.append('g');
+  DataSelection bars =
+      g4.selectAll('bar').data([120, 160, 210, 260]);
+  g4.attr('transform', 'translate(30, 850)');
+  bars.enter.append('rect');
+  bars
+      ..attrWithCallback('x', (d,i,e) => i * 150)
+      ..attr('y', 350)
+      ..attr('width', 100)
+      ..attr('height', 0)
+      ..style('fill', '#6699FF');
+  bars.exit.remove();
+
+  var t = bars.transition();
+  t.duration(1000);
+  t.delayWithCallback((d, i, c) => i * 200);
+  t.attrTween('y', (d, i, attr) => interpolateString(
+      attr, (350 - d).toString()));
+  t.attrTween('height', (d, i, attr) => interpolateString(attr, d.toString()));
+
+  var color = t.transition();
+  color.delayWithCallback((d, i, c) => i * 200);
+  color.styleTween('fill',
+      (d, i, style) => interpolateString(style, '#CC0088'));
+
+  Color hslColor1 = new Color.fromRgb(10, 255, 0);
+  Color hslColor2 = new Color.fromRgb(40, 0, 255);
+  Selection g5 = svg.append('g');
+  var text7 = g5.append('text');
+  text7..attr('x', 0)
+       ..attr('y', 0)
+       ..text('HSL Color Transition with Transform');
+
+  g5.attr('transform', 'translate(30, 1300)');
+
+  Selection rect2 = g5.append('rect');
+  rect2
+      ..attr('x', 0)
+      ..attr('y', 10)
+      ..attr('width', 200)
+      ..attr('height', 60);
+  rect2.transition()
+      ..attrTween('fill', (d, i, e) {
+        return interpolateHsl(hslColor1, hslColor2);})
+      ..duration(2000);
+
+  rect2.transition()
+      ..attrTween('transform', (d, i, e) => interpolateTransform(
+          "translate(10,10)rotate(30)skewX(0.5)scale(1,1)",
+          "translate(100,100)rotate(360)skewX(45)scale(3,3)"))
+      ..duration(5000);
+
+}
+
diff --git a/pub/code_transformers/.analysis_options b/pub/code_transformers/.analysis_options
new file mode 100644
index 0000000..a10d4c5
--- /dev/null
+++ b/pub/code_transformers/.analysis_options
@@ -0,0 +1,2 @@
+analyzer:
+  strong-mode: true
diff --git a/pub/code_transformers/.gitignore b/pub/code_transformers/.gitignore
new file mode 100644
index 0000000..f20a0d0
--- /dev/null
+++ b/pub/code_transformers/.gitignore
@@ -0,0 +1,18 @@
+# Don’t commit the following directories created by pub.
+.pub
+build/
+packages
+.packages
+
+# Or the files created by dart2js.
+*.dart.js
+*.dart.precompiled.js
+*.js_
+*.js.deps
+*.js.map
+*.sw?
+.idea/
+.pub/
+
+# Include when developing application packages.
+pubspec.lock
diff --git a/pub/code_transformers/.test_config b/pub/code_transformers/.test_config
new file mode 100644
index 0000000..2535563
--- /dev/null
+++ b/pub/code_transformers/.test_config
@@ -0,0 +1,3 @@
+{
+  "test_package": true
+}
diff --git a/pub/code_transformers/.travis.yml b/pub/code_transformers/.travis.yml
new file mode 100644
index 0000000..2331ace
--- /dev/null
+++ b/pub/code_transformers/.travis.yml
@@ -0,0 +1,9 @@
+language: dart
+sudo: false
+dart:
+  - dev
+  - stable
+cache:
+  directories:
+    - $HOME/.pub-cache/hosted
+script: ./tool/travis.sh
diff --git a/pub/code_transformers/AUTHORS b/pub/code_transformers/AUTHORS
new file mode 100644
index 0000000..0617765
--- /dev/null
+++ b/pub/code_transformers/AUTHORS
@@ -0,0 +1,9 @@
+# Names should be added to this file with this pattern:
+#
+# For individuals:
+#   Name <email address>
+#
+# For organizations:
+#   Organization <fnmatch pattern>
+#
+Google Inc. <*@google.com>
diff --git a/pub/code_transformers/BUILD.gn b/pub/code_transformers/BUILD.gn
new file mode 100644
index 0000000..cabfd56
--- /dev/null
+++ b/pub/code_transformers/BUILD.gn
@@ -0,0 +1,20 @@
+# This file is generated by importer.py for code_transformers-0.4.2+4
+
+import("//build/dart/dart_package.gni")
+
+dart_package("code_transformers") {
+  package_name = "code_transformers"
+
+  source_dir = "lib"
+
+  disable_analysis = true
+
+  deps = [
+    "//third_party/dart-pkg/pub/source_maps",
+    "//third_party/dart-pkg/pub/barback",
+    "//third_party/dart-pkg/pub/source_span",
+    "//third_party/dart-pkg/pub/cli_util",
+    "//third_party/dart-pkg/pub/path",
+    "//dart/pkg/analyzer",
+  ]
+}
diff --git a/pub/code_transformers/CHANGELOG.md b/pub/code_transformers/CHANGELOG.md
new file mode 100644
index 0000000..f14a303
--- /dev/null
+++ b/pub/code_transformers/CHANGELOG.md
@@ -0,0 +1,147 @@
+## 0.4.2+4
+
+* Update to work with analyzer 0.29.x and transform_test 0.2.x
+
+## 0.4.2+3
+
+* Update to work with analyzer 0.28.x.
+
+## 0.4.2+2
+
+* Update to work with analyzer 0.23.x.
+
+## 0.4.2+1
+
+* Contains a fix for the `useSharedSources` option that could result in null
+  library elements when running on multiple entry points.
+
+## 0.4.2
+
+* Use Strong Mode, fixes
+  [#38](https://github.com/dart-lang/code_transformers/issues/38).
+
+## 0.4.1
+
+* Added a fix for [#24890](https://github.com/dart-lang/sdk/issues/24890).
+  * All constants in all libraries will once again be resolved by default.
+  * Added a new `resolveAllLibraries` option to `Resolver#resolve` and
+    `Resolvers#get`. If `false` is passed then constants will not be resolved in
+    non entry points. This saves significant time if constants are not needed.
+* Added a `useSharedSources` option to `Resolvers`. This gives a significant
+  speed increase, but must be used carefully.
+  * If used, then all `Resolver` instances created from the same `Resolvers`
+    instance will share the same sources cache.
+  * This should be generally safe to use if the `Resolvers` instance is created
+    in the constructor of your `Transformer`.
+  * This option should probably not be used with a static or shared `Resolvers`
+    instance.
+
+## 0.4.0
+
+* Remove dependency on `test`, and move all test related apis to a new
+  `transformer_test` package which is now a dev dependency.
+
+## 0.3.1
+
+* Update to analyzer `>=0.27.0 <0.28.0`.
+
+## 0.3.0+1
+
+* Upgrade `test` to a real dependency.
+
+## 0.3.0
+
+* Re-release `0.2.10` release as `0.3.0`.
+
+## 0.2.11
+
+* Revert `0.2.10` release, will be re-released as `0.3.0` since it is actually
+  a breaking change.
+
+## 0.2.10
+
+* Update to use the `test` package instead of the `unittest` package.
+
+## 0.2.9+4
+
+* Republish 0.2.9+2 under new version.
+
+## 0.2.9+3
+
+* Republish of 0.2.9 to ensure nobody gets 0.2.9+1 in the future.
+
+## 0.2.9+2
+
+* Update to analyzer '>=0.26.0 <0.27.0'
+
+## 0.2.9+1
+
+* Update to analyzer '<0.27.0'
+* This version will be reverted as it wasn't compatible with <0.26.0.
+
+## 0.2.9
+
+* Update to analyzer `<=0.26.0`.
+
+## 0.2.8
+
+* Add `benchmarks.dart` file which exposes a `TransformerBenchmark`. This can be
+used to implement simple benchmarks of transformer code.
+
+## 0.2.7+2
+
+* Fix `assetIdToUri` on windows,
+[41](https://github.com/dart-lang/polymer-dart/issues/41)
+
+## 0.2.7+1
+
+* Fixes for missing overrides after upgrade to analzyer 0.24.0
+
+## 0.2.7
+
+* Added default set of mockSdkSources and upgraded to analyzer 0.24.0
+
+## 0.2.6
+
+* Added `assetIdToUri` to assets.dart.
+
+## 0.2.5
+* Improvements to `dartSdkDirectory` so it has a better chance of success.
+* `BuildLogger` now accepts `AggregateTransform` or `Transform`. If passing in
+an `AggregateTransform` you must also pass in an `AssetId` to use as the primary
+input.
+
+## 0.2.4
+
+* Added some basic string formatting options to `testPhases` to make it a bit
+  less strict if desired.
+
+## 0.2.3+2
+
+* Added logic to discover the location of the dart SDK when the dart binary is a
+  symlink.
+
+## 0.2.3
+
+* Added support for logging stable error messages from transformers.
+
+## 0.2.2
+
+* Added two transformers, 'delete_file' and 'remove_sourcemap_comment'.
+
+## 0.2.0+3
+
+* Raise the lower bound on the source_maps constraint to exclude incompatible
+  versions.
+
+## 0.2.0+2
+
+* Widen the constraint on source_maps.
+
+## 0.2.0+1
+
+* Widen the constraint on barback.
+
+## 0.2.0
+
+* Switch from `source_maps`' `Span` class to `source_span`'s `SourceSpan` class.
diff --git a/pub/code_transformers/LICENSE b/pub/code_transformers/LICENSE
new file mode 100644
index 0000000..5c60afe
--- /dev/null
+++ b/pub/code_transformers/LICENSE
@@ -0,0 +1,26 @@
+Copyright 2014, the Dart project authors. 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.
+    * Neither the name of Google Inc. nor the names of its
+      contributors may 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 THE COPYRIGHT
+OWNER OR CONTRIBUTORS 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.
diff --git a/pub/code_transformers/PATENTS b/pub/code_transformers/PATENTS
new file mode 100644
index 0000000..6954196
--- /dev/null
+++ b/pub/code_transformers/PATENTS
@@ -0,0 +1,23 @@
+Additional IP Rights Grant (Patents)
+
+"This implementation" means the copyrightable works distributed by
+Google as part of the Dart Project.
+
+Google hereby grants to you a perpetual, worldwide, non-exclusive,
+no-charge, royalty-free, irrevocable (except as stated in this
+section) patent license to make, have made, use, offer to sell, sell,
+import, transfer, and otherwise run, modify and propagate the contents
+of this implementation of Dart, where such license applies only to
+those patent claims, both currently owned by Google and acquired in
+the future, licensable by Google that are necessarily infringed by
+this implementation of Dart. This grant does not include claims that
+would be infringed only as a consequence of further modification of
+this implementation. If you or your agent or exclusive licensee
+institute or order or agree to the institution of patent litigation
+against any entity (including a cross-claim or counterclaim in a
+lawsuit) alleging that this implementation of Dart or any code
+incorporated within this implementation of Dart constitutes direct or
+contributory patent infringement, or inducement of patent
+infringement, then any patent rights granted to you under this License
+for this implementation of Dart shall terminate as of the date such
+litigation is filed.
diff --git a/pub/code_transformers/README.md b/pub/code_transformers/README.md
new file mode 100644
index 0000000..758d205
--- /dev/null
+++ b/pub/code_transformers/README.md
@@ -0,0 +1,75 @@
+# Code Transformers
+This package exposes various tools to help in the creation and testing of
+barback transformers, as well as an interface for logging messages with
+constant ids for documentation purposes.
+
+## Messages and logging
+
+This package exposes a `BuildLogger` class as well as `Message`,
+`MessageTemplate`, and `MessageId` classes. These work together to provide
+stable error messages with ids that can be referenced in documentation.
+
+### MessageId
+
+A `MessageId` is a constant definition of a message in a package. These are
+used to group messages with the same id or link to sections in a document.
+
+    const myId = const MessageId('myPackage', 0);
+ 
+These ids should typically never change or disappear throughout the entire
+lifetime of a package.
+
+### Message
+
+A `Message` is a const object which has a `MessageId` and `snippet`.
+
+    const myMessage = const Message(myId, 'my message');
+
+### MessageTemplate
+
+TODO(jakemac): Docs on this, see
+https://github.com/dart-lang/code-transformers/blob/master/lib/messages/messages.dart
+
+### BuildLogger
+
+The `BuildLogger` class just wraps a normal `TransformLogger` to provide some
+additional functionality. You use it in the same way as a `TransformLogger`
+except that the log methods can accept a `String` or a `Message`. This should
+usually be created in the first step of your transformers `apply` function:
+
+    apply(Transform transform) {
+      // If detailsUri is passed, then a link will be output with each log
+      // message that follows this format
+      // `$detailsUri#${msg.id.package}_${msg.id.id}`.
+      var logger = new BuildLogger(transform, detailsUri: 'http://foo.com');
+    }
+
+You can optionally dump out a file containing all the logs found in JSON
+format by calling the `writeOutput` method on the logger when you are done.
+The output file will have the same file path as the primary input of the
+transformer, with an extension matching this pattern `._buildLogs.$i`. The
+`i` increments starting at 0 each time that logs are output (if multiple
+transformers run on the same file for instance). These can all be combined
+into a single file later on by calling the static
+`combineLogFiles(Transform transform)` function.
+
+## Testing Transformers
+
+TODO(jakemac): Docs on this, see `testPhases` in
+https://github.com/dart-lang/code-transformers/blob/master/lib/tests.dart
+
+## Using the Analyzer in Transformers
+
+This package exposes a `Resolver` class which helps out when using the
+analyzer in a transform.
+
+TODO(jakemac): Docs on this, see
+https://github.com/dart-lang/code-transformers/blob/master/lib/src/resolver.dart
+
+## Barback AssetIds and Uris
+
+This package also provides some helpers to convert `AssetId`s to and from `Uri`s
+relative to a source asset.
+
+TODO(jakemac): Docs on this, see `uriToAssetId` & `assetIdToUri` in
+https://github.com/dart-lang/code-transformers/blob/master/lib/assets.dart
diff --git a/pub/code_transformers/codereview.settings b/pub/code_transformers/codereview.settings
new file mode 100644
index 0000000..4cda648
--- /dev/null
+++ b/pub/code_transformers/codereview.settings
@@ -0,0 +1,3 @@
+CODE_REVIEW_SERVER: http://codereview.chromium.org/
+VIEW_VC: https://github.com/dart-lang/code-transformers/commit/
+CC_LIST: reviews@dartlang.org
diff --git a/pub/code_transformers/lib/assets.dart b/pub/code_transformers/lib/assets.dart
new file mode 100644
index 0000000..dacc582
--- /dev/null
+++ b/pub/code_transformers/lib/assets.dart
@@ -0,0 +1,151 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Common methods used by transfomers for dealing with asset IDs.
+library code_transformers.assets;
+
+import 'dart:math' show min, max;
+
+import 'package:barback/barback.dart';
+import 'package:path/path.dart' as path;
+import 'package:source_span/source_span.dart';
+
+import 'messages/build_logger.dart';
+import 'src/messages.dart';
+
+/// Create an [AssetId] for a [url] seen in the [source] asset.
+///
+/// By default this is used to resolve relative urls that occur in HTML assets,
+/// including cross-package urls of the form "packages/foo/bar.html". Dart
+/// "package:" urls are not resolved unless [source] is Dart file (has a .dart
+/// extension).
+///
+/// This function returns null if [url] can't be resolved. We log a warning
+/// message in [logger] if we know the reason why resolution failed. This
+/// happens, for example, when using incorrect paths to reach into another
+/// package, or when [errorsOnAbsolute] is true and the url seems to be
+/// absolute. If [span] is not `null` it is used to provide context for any
+/// warning message(s) generated.
+// TODO(sigmund): delete once this is part of barback (dartbug.com/12610)
+AssetId uriToAssetId(
+    AssetId source, String url, TransformLogger logger, SourceSpan span,
+    {bool errorOnAbsolute: true}) {
+  if (url == null || url == '') return null;
+  var uri = Uri.parse(url);
+  var urlBuilder = path.url;
+  if (uri.host != '' || uri.scheme != '' || urlBuilder.isAbsolute(url)) {
+    if (source.extension == '.dart' && uri.scheme == 'package') {
+      var index = uri.path.indexOf('/');
+      if (index != -1) {
+        return new AssetId(
+            uri.path.substring(0, index), 'lib${uri.path.substring(index)}');
+      }
+    }
+
+    if (errorOnAbsolute) {
+      var msg = NO_ABSOLUTE_PATHS.create({'url': url});
+      logger.warning(logger is BuildLogger ? msg : msg.snippet, span: span);
+    }
+    return null;
+  }
+
+  var targetPath = urlBuilder
+      .normalize(urlBuilder.join(urlBuilder.dirname(source.path), url));
+  var segments = urlBuilder.split(targetPath);
+  var sourceSegments = urlBuilder.split(source.path);
+  assert(sourceSegments.length > 0);
+  var topFolder = sourceSegments[0];
+  var entryFolder = topFolder != 'lib' && topFolder != 'asset';
+
+  // Find the first 'packages/'  or 'assets/' segment:
+  var packagesIndex = segments.indexOf('packages');
+  var assetsIndex = segments.indexOf('assets');
+  var index = (packagesIndex >= 0 && assetsIndex >= 0)
+      ? min(packagesIndex, assetsIndex)
+      : max(packagesIndex, assetsIndex);
+  if (index > -1) {
+    if (entryFolder) {
+      // URLs of the form "packages/foo/bar" seen under entry folders (like
+      // web/, test/, example/, etc) are resolved as an asset in another
+      // package. 'packages' can be used anywhere, there is no need to walk up
+      // where the entrypoint file was.
+      // TODO(sigmund): this needs to change: Only resolve when index == 1 &&
+      // topFolder == segment[0], otherwise give a warning (dartbug.com/17596).
+      return _extractOtherPackageId(index, segments, logger, span);
+    } else if (index == 1 && segments[0] == '..') {
+      // Relative URLs of the form "../../packages/foo/bar" in an asset under
+      // lib/ or asset/ are also resolved as an asset in another package, but we
+      // check that the relative path goes all the way out where the packages
+      // folder lives (otherwise the app would not work in Dartium). Since
+      // [targetPath] has been normalized, "packages" or "assets" should be at
+      // index 1.
+      return _extractOtherPackageId(1, segments, logger, span);
+    } else {
+      var prefix = segments[index];
+      var fixedSegments = <String>[];
+      fixedSegments.addAll(sourceSegments.map((_) => '..'));
+      fixedSegments.addAll(segments.sublist(index));
+      var fixedUrl = urlBuilder.joinAll(fixedSegments);
+      var msg = INVALID_URL_TO_OTHER_PACKAGE
+          .create({'url': url, 'prefix': prefix, 'fixedUrl': fixedUrl});
+      logger.warning(logger is BuildLogger ? msg : msg.snippet, span: span);
+      return null;
+    }
+  }
+
+  // Otherwise, resolve as a path in the same package.
+  return new AssetId(source.package, targetPath);
+}
+
+AssetId _extractOtherPackageId(
+    int index, List<String> segments, TransformLogger logger, SourceSpan span) {
+  if (index >= segments.length) return null;
+  var prefix = segments[index];
+  if (prefix != 'packages' && prefix != 'assets') return null;
+  var folder = prefix == 'packages' ? 'lib' : 'asset';
+  if (segments.length < index + 3) {
+    var msg = INVALID_PREFIX_PATH.create({'prefix': prefix, 'folder': folder});
+    logger.warning(logger is BuildLogger ? msg : msg.snippet, span: span);
+    return null;
+  }
+  return new AssetId(segments[index + 1],
+      path.url.join(folder, path.url.joinAll(segments.sublist(index + 2))));
+}
+
+/// Gets a URI which would be appropriate for importing the file represented by
+/// [assetId].
+///
+/// This function returns null if we cannot determine a uri for [assetId].
+///
+/// Note that [assetId] may represent a non-importable file such as a part.
+String assetIdToUri(AssetId assetId,
+    {TransformLogger logger, SourceSpan span, AssetId from}) {
+  if (!assetId.path.startsWith('lib/')) {
+    // Cannot do absolute imports of non lib-based assets.
+    if (from == null) {
+      if (logger != null) {
+        var msg = UNSPECIFIED_FROM_IN_NON_LIB_ASSET.create({'id': '$assetId'});
+        logger.warning(logger is BuildLogger ? msg : msg.snippet, span: span);
+      }
+      return null;
+    }
+
+    if (assetId.package != from.package) {
+      if (logger != null) {
+        var msg = IMPORT_FROM_DIFFERENT_PACKAGE
+            .create({'toId': '$assetId', 'fromId': '$from'});
+        logger.warning(logger is BuildLogger ? msg : msg.snippet, span: span);
+      }
+      return null;
+    }
+    return new Uri(
+            path: path.url
+                .relative(assetId.path, from: path.url.dirname(from.path)))
+        .toString();
+  }
+
+  return Uri
+      .parse('package:${assetId.package}/${assetId.path.substring(4)}')
+      .toString();
+}
diff --git a/pub/code_transformers/lib/benchmarks.dart b/pub/code_transformers/lib/benchmarks.dart
new file mode 100644
index 0000000..c048457
--- /dev/null
+++ b/pub/code_transformers/lib/benchmarks.dart
@@ -0,0 +1,76 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+library code_transformers.benchmarks;
+
+import 'dart:async';
+import 'package:barback/barback.dart';
+import 'src/async_benchmark_base.dart';
+
+/// A benchmark for testing the performance of transformer phases.
+class TransformerBenchmark extends AsyncBenchmarkBase {
+  /// Internal abstraction layer for barback.
+  _BenchmarkHelper _helper;
+
+  /// The transformer phases to be ran.
+  final List<List<Transformer>> transformers;
+
+  /// The files to pass to barback.
+  final Map<AssetId, String> files;
+
+  TransformerBenchmark(this.transformers, this.files);
+
+  @override
+  Future setup() {
+    _helper = new _BenchmarkHelper(transformers, files);
+    return new Future.value();
+  }
+
+  @override
+  Future run() => _helper.run();
+
+  @override
+  teardown() {
+    _helper = null;
+    return new Future.value();
+  }
+}
+
+/// Barback abstraction layer.
+class _BenchmarkHelper implements PackageProvider {
+  /// All the files available.
+  final Map<AssetId, String> _files;
+
+  /// All the packages.
+  final Iterable<String> packages;
+
+  /// Internal instance of barback.
+  Barback _barback;
+
+  /// Subscription to barback results.
+  StreamSubscription<BuildResult> resultSubscription;
+
+  _BenchmarkHelper(
+      List<List<Transformer>> transformers, Map<AssetId, String> files)
+      : _files = files,
+        packages = new Set.from(files.keys.map((assetId) => assetId.package)) {
+    _barback = new Barback(this);
+    for (var package in packages) {
+      _barback.updateTransformers(package, transformers);
+    }
+  }
+
+  /// Look up an [AssetId] in [files] and return an [Asset] for it.
+  @override
+  Future<Asset> getAsset(AssetId id) =>
+      new Future.value(new Asset.fromString(id, _files[id]));
+
+  /// Tells barback which files have changed, and thus anything that depends on
+  /// it on should be computed. Returns a [Future] that completes once some
+  /// results are received.
+  Future run([Iterable<AssetId> assetIds]) {
+    if (assetIds == null) assetIds = _files.keys;
+    _barback.updateSources(assetIds);
+    return _barback.results.first;
+  }
+}
diff --git a/pub/code_transformers/lib/messages/build_logger.dart b/pub/code_transformers/lib/messages/build_logger.dart
new file mode 100644
index 0000000..39dfcf8
--- /dev/null
+++ b/pub/code_transformers/lib/messages/build_logger.dart
@@ -0,0 +1,155 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library code_transformers.messages.messages_logger;
+
+import 'dart:async';
+import 'dart:convert' show JSON;
+
+import 'package:barback/barback.dart';
+import 'package:source_span/source_span.dart';
+
+import 'messages.dart' show Message, BuildLogEntry, LogEntryTable;
+
+/// A [TransformLogger] used to track error and warning messages produced during
+/// a build.
+///
+/// This logger records all messages that were logged and then forwards
+/// the calls to an underlying [TransformLogger]. The internal records support
+/// serializing the errors and emiting them to an asset (so they can be
+/// presented to the user in a web-based client), clustering similar messages
+/// together, sorting messages in order of importance, etc.
+///
+/// The logger also supports reporting error messages as warnings. Barback makes
+/// error messages stop the transformation process, which sometimes can surprise
+/// users. Turning errors into warnings is especially useful when used within
+/// `pub serve`, where we would like the transformation to continue as far as it
+/// can. When this flag is turned on, the level is still recorded as an error,
+/// so a web client UI can still highlight their importance.
+// TODO(sigmund): also cluster messages when they are reported on the
+// command-line.
+class BuildLogger implements TransformLogger {
+  /// Underling transform that is currently active. This can be either an
+  /// [AggregateTransform] or [Transform].
+  final _transform;
+
+  /// The primary input id.
+  final AssetId _primaryId;
+
+  /// Logs created during the current transform.
+  final LogEntryTable _logs = new LogEntryTable();
+
+  /// Whether to use `warning` or `error` when forwarding error messages to the
+  /// underlying logger in `_transform.logger`.
+  final bool convertErrorsToWarnings;
+
+  /// Uri prefix to link for additional details. If set, messages logged through
+  /// this logger will contain an additional sentence, telling users to find
+  /// more details at `$detailsUri#packagename_id`.
+  final String detailsUri;
+
+  /// If transform is a [Transform] then [primaryId] will default to the
+  /// primaryInput.id, if it is an [AggregateTransform] then you must pass in
+  /// a [primaryId] to be used, otherwise you will get a runtime error.
+  BuildLogger(transform,
+      {this.convertErrorsToWarnings: false, AssetId primaryId, this.detailsUri})
+      : _transform = transform,
+        _primaryId = primaryId != null ? primaryId : transform.primaryInput.id;
+
+  /// Records a message at the fine level. If [msg] is a [Message] it is
+  /// recorded directly, otherwise it is first converted to a [String].
+  void fine(Object msg, {AssetId asset, SourceSpan span}) {
+    msg = msg is Message ? msg : new Message.unknown('$msg');
+    _transform.logger.fine(_snippet(msg), asset: asset, span: span);
+    _logs.add(new BuildLogEntry(msg, span, LogLevel.FINE.name));
+  }
+
+  /// Records a message at the info level. If [msg] is a [Message] it is
+  /// recorded directly, otherwise it is first converted to a [String].
+  void info(Object msg, {AssetId asset, SourceSpan span}) {
+    msg = msg is Message ? msg : new Message.unknown('$msg');
+    _transform.logger.info(_snippet(msg), asset: asset, span: span);
+    _logs.add(new BuildLogEntry(msg, span, LogLevel.INFO.name));
+  }
+
+  /// Records a warning message. If [msg] is a [Message] it is recorded
+  /// directly, otherwise it is first converted to a [String].
+  void warning(Object msg, {AssetId asset, SourceSpan span}) {
+    msg = msg is Message ? msg : new Message.unknown('$msg');
+    _transform.logger.warning(_snippet(msg), asset: asset, span: span);
+    _logs.add(new BuildLogEntry(msg, span, LogLevel.WARNING.name));
+  }
+
+  /// Records an error message. If [msg] is a [Message] it is recorded
+  /// directly, otherwise it is first converted to a [String].
+  void error(Object msg, {AssetId asset, SourceSpan span}) {
+    msg = msg is Message ? msg : new Message.unknown('$msg');
+    if (convertErrorsToWarnings) {
+      _transform.logger.warning(_snippet(msg), asset: asset, span: span);
+    } else {
+      _transform.logger.error(_snippet(msg), asset: asset, span: span);
+    }
+    _logs.add(new BuildLogEntry(msg, span, LogLevel.ERROR.name));
+  }
+
+  String _snippet(Message msg) {
+    var s = msg.snippet;
+    if (detailsUri == null) return s;
+    var dot = s.endsWith('.') || s.endsWith('!') || s.endsWith('?') ? '' : '.';
+    var hashTag = '${msg.id.package}_${msg.id.id}';
+    return '$s$dot See $detailsUri#$hashTag for details.';
+  }
+
+  /// Outputs the log data to a JSON serialized file.
+  Future writeOutput() {
+    return _getNextLogAssetId().then((id) {
+      _transform.addOutput(new Asset.fromString(id, JSON.encode(_logs)));
+    });
+  }
+
+  // Each phase outputs a new log file with an incrementing # appended, this
+  // figures out the next # to use.
+  Future<AssetId> _getNextLogAssetId([int nextNumber = 1]) async {
+    var nextAssetPath = _primaryId.addExtension('${LOG_EXTENSION}.$nextNumber');
+    bool exists = await _transform.hasInput(nextAssetPath);
+    if (!exists) return nextAssetPath;
+    return _getNextLogAssetId(++nextNumber);
+  }
+
+  // Reads all log files for an Asset into [logs].
+  static Future _readLogFilesForAsset(
+      AssetId id, Transform transform, LogEntryTable entries,
+      [nextNumber = 1]) {
+    var nextAssetPath = id.addExtension('${LOG_EXTENSION}.$nextNumber');
+    return transform.hasInput(nextAssetPath).then((exists) {
+      if (!exists) return null;
+      return transform.readInputAsString(nextAssetPath).then((data) {
+        entries.addAll(new LogEntryTable.fromJson(
+            JSON.decode(data) as Map<String, Iterable>));
+        return _readLogFilesForAsset(id, transform, entries, ++nextNumber);
+      });
+    });
+  }
+
+  /// Combines all existing ._buildLogs.* files into a single ._buildLogs file.
+  /// [transform] may be a [Transform] or [AggregateTransform]. If an
+  /// [AggregateTransform] is passed then [primaryId] must also be passed.
+  static Future combineLogFiles(transform, [AssetId primaryId]) {
+    if (primaryId == null) primaryId = transform.primaryInput.id;
+    var entries = new LogEntryTable();
+    return _readLogFilesForAsset(primaryId, transform, entries).then((_) {
+      return transform.addOutput(new Asset.fromString(
+          primaryId.addExtension(LOG_EXTENSION),
+          JSON.encode(entries.toJson())));
+    });
+  }
+
+  // Reads all logs for an asset and adds them to this loggers log output.
+  Future addLogFilesFromAsset(AssetId id, [int nextNumber = 1]) {
+    return _readLogFilesForAsset(id, _transform, _logs);
+  }
+}
+
+/// Extension used for assets that contained serialized logs.
+const String LOG_EXTENSION = '._buildLogs';
diff --git a/pub/code_transformers/lib/messages/messages.dart b/pub/code_transformers/lib/messages/messages.dart
new file mode 100644
index 0000000..77ea05f
--- /dev/null
+++ b/pub/code_transformers/lib/messages/messages.dart
@@ -0,0 +1,235 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Defines messages templates and an adapter for TransformLogger to be able
+/// report error messages from transformers and refer to them in a consistent
+/// manner long term.
+library code_transformers.messages;
+
+// Note: this library purposely doesn't depend on dart:io, dart:html, or barback
+// so it can easily be used both in the transformers and in client-side apps
+// (for example in the log_injector).
+import 'dart:collection' show LinkedHashMap;
+
+import 'package:source_span/source_span.dart';
+
+/// A globally unique identifier for an error message. This identifier should be
+/// stable, that is, it should never change after it is asigned to a particular
+/// message. That allows us to document our error messages and make them
+/// searchable for prosperity.
+class MessageId implements Comparable<MessageId> {
+  /// Name of the package that declares this message.
+  final String package;
+
+  /// Message identifier number, unique within the package.
+  final int id;
+
+  const MessageId(this.package, this.id);
+
+  static const MessageId NOT_SPECIFIED = const MessageId('unknown', 0);
+
+  /// Serialize this message. We use a string and not a map to encode ids so
+  /// they can be used as keys in JSON maps.
+  String toJson() => toString();
+
+  toString() => '${package}#$id';
+
+  int compareTo(MessageId other) {
+    var res = package.compareTo(other.package);
+    if (res != 0) return res;
+    return id.compareTo(other.id);
+  }
+
+  /// Creates a new [MessageId] from an encoded value produced via [toJson].
+  factory MessageId.fromJson(data) {
+    var index = data.lastIndexOf('#');
+    if (index == -1) throw 'Invalid message id: $data';
+    return new MessageId(
+        data.substring(0, index), int.parse(data.substring(index + 1)));
+  }
+
+  operator ==(Object other) =>
+      other is MessageId && package == other.package && id == other.id;
+
+  int get hashCode => 31 * package.hashCode + id;
+}
+
+/// An instance of an error message. These are typically produced from a
+/// [MessageTemplate].
+class Message {
+  /// A globally unique identifier for this message.
+  final MessageId id;
+
+  /// A snippet message that is presented to the user.
+  final String snippet;
+
+  const Message(this.id, this.snippet);
+
+  const Message.unknown(this.snippet) : id = MessageId.NOT_SPECIFIED;
+
+  /// Serializes this message to JSON.
+  Map toJson() => {'id': id.toJson(), 'snippet': snippet};
+  String toString() => 'id: $id, snippet: $snippet';
+
+  /// Creates a new [Message] from an encoded value produced via [toJson].
+  factory Message.fromJson(data) =>
+      new Message(new MessageId.fromJson(data['id']), data['snippet']);
+}
+
+/// Template for a message. Templates can include placeholders to indicate
+/// values that are different for each instance of the error. Calling [create]
+/// will generate the actual message, with the placeholders replaced with
+/// values. If there are no placeholders, an instance of [MessageTemplate] is a
+/// valid instance of [Message] as well.
+class MessageTemplate implements Message {
+  /// Unique and stable id for the message.
+  final MessageId id;
+
+  /// Template message with placeholders of the form `%-name-%`.
+  final String snippetTemplate;
+
+  /// This returns the message snippet, only if it the template has no
+  /// placeholders, otherwise this throws an exception. Most messages have no
+  /// placeholder arguments, in those cases, the snippet  can be computed
+  /// without specifying any arguments (exactly like calling `create()` with no
+  /// arguments).
+  String get snippet => _createSnippet();
+
+  /// Short description of the error message, typically used as a title of the
+  /// error message in autogenerated documentation. This should be a single
+  /// phrase, and cannot use placeholders.
+  final String description;
+
+  /// Additional details about this error message. These are used to
+  /// automatically generate documentation.
+  final String details;
+
+  const MessageTemplate(
+      this.id, this.snippetTemplate, this.description, this.details);
+
+  static final _placeholderPattern = new RegExp(r"%-(\w*)-%");
+
+  _createSnippet([Map args = const {}, bool fillUnknowns = false]) {
+    var snippet = snippetTemplate.replaceAllMapped(_placeholderPattern, (m) {
+      var arg = m.group(1);
+      var value = args[arg];
+      if (value != null) return '$value';
+      if (fillUnknowns) return '';
+      throw "missing argument $arg, for error message: $snippetTemplate";
+    });
+    return snippet;
+  }
+
+  create([Map args = const {}, bool fillUnknowns = false]) =>
+      new Message(id, _createSnippet(args, fillUnknowns));
+
+  /// Serializes this message to JSON.
+  Map toJson() => create().toJson();
+  String toString() => '${toJson()}';
+}
+
+/// Represents an actual log entry for a build error message. Including the
+/// actual message, its severity level (warning, error, etc), and a source span
+/// for a code location that is revelant to the message.
+class BuildLogEntry {
+  /// The actual message.
+  final Message message;
+
+  /// Severity level.
+  final String level;
+
+  /// Location associated with this message, if any.
+  final SourceSpan span;
+
+  BuildLogEntry(this.message, this.span, this.level);
+
+  /// Creates a new [BuildLogEntry] from an encoded value produced via [toJson].
+  factory BuildLogEntry.fromJson(Map data) {
+    var spanData = data['span'];
+    var span = null;
+    if (spanData != null) {
+      var locData = spanData['start'];
+      var start = new SourceLocation(locData['offset'],
+          sourceUrl: Uri.parse(locData['url']),
+          line: locData['line'],
+          column: locData['column']);
+      locData = spanData['end'];
+      var end = new SourceLocation(locData['offset'],
+          sourceUrl: Uri.parse(locData['url']),
+          line: locData['line'],
+          column: locData['column']);
+      span = new SourceSpan(start, end, spanData['text']);
+    }
+    return new BuildLogEntry(
+        new Message.fromJson(data['message']), span, data['level']);
+  }
+
+  /// Serializes this log entry to JSON.
+  Map toJson() {
+    var data = {
+      'level': level,
+      'message': message.toJson(),
+    };
+    if (span != null) {
+      data['span'] = {
+        'start': {
+          'url': span.start.sourceUrl.toString(),
+          'offset': span.start.offset,
+          'line': span.start.line,
+          'column': span.start.column,
+        },
+        'end': {
+          'url': span.end.sourceUrl.toString(),
+          'offset': span.end.offset,
+          'line': span.end.line,
+          'column': span.end.column,
+        },
+        'text': span.text,
+      };
+    }
+    return data;
+  }
+
+  String toString() => '${toJson()}';
+}
+
+/// A table of entries, that clusters error messages by id.
+class LogEntryTable {
+  final Map<MessageId, List<BuildLogEntry>> entries;
+
+  LogEntryTable() : entries = new LinkedHashMap();
+
+  /// Creates a new [LogEntryTable] from an encoded value produced via [toJson].
+  factory LogEntryTable.fromJson(Map<String, Iterable> json) {
+    var res = new LogEntryTable();
+    for (String key in json.keys) {
+      var id = new MessageId.fromJson(key);
+      res.entries[id] =
+          json[key].map((v) => new BuildLogEntry.fromJson(v)).toList();
+    }
+    return res;
+  }
+
+  /// Serializes this entire table as JSON.
+  Map toJson() {
+    var res = {};
+    entries.forEach((key, value) {
+      res['$key'] = value.map((e) => e.toJson()).toList();
+    });
+    return res;
+  }
+
+  String toString() => '${toJson()}';
+
+  void add(BuildLogEntry entry) {
+    entries.putIfAbsent(entry.message.id, () => []).add(entry);
+  }
+
+  void addAll(LogEntryTable other) {
+    for (var key in other.entries.keys) {
+      var values = entries.putIfAbsent(key, () => []);
+      values.addAll(other.entries[key]);
+    }
+  }
+}
diff --git a/pub/code_transformers/lib/resolver.dart b/pub/code_transformers/lib/resolver.dart
new file mode 100644
index 0000000..faf34db
--- /dev/null
+++ b/pub/code_transformers/lib/resolver.dart
@@ -0,0 +1,11 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Tools for working with resolved ASTs from Barback transformers.
+library code_transformers.resolver;
+
+export 'src/dart_sdk.dart';
+export 'src/entry_point.dart';
+export 'src/resolver.dart';
+export 'src/resolvers.dart';
diff --git a/pub/code_transformers/lib/src/async_benchmark_base.dart b/pub/code_transformers/lib/src/async_benchmark_base.dart
new file mode 100644
index 0000000..423ff64
--- /dev/null
+++ b/pub/code_transformers/lib/src/async_benchmark_base.dart
@@ -0,0 +1,69 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+library code_transformers.src.async_benchmark_base;
+
+import 'dart:async';
+
+/// An adaptation of [BenchmarkBase] from the `benchmark_harness` package that
+/// works for async benchmarks.
+/// TODO(jakemac): Get this merged into `benchmark_harness`.
+class AsyncBenchmarkBase {
+  // Empty constructor.
+  const AsyncBenchmarkBase();
+
+  // The benchmark code.
+  // This function is not used, if both [warmup] and [exercise] are overwritten.
+  Future run() => new Future.value();
+
+  // Runs a short version of the benchmark. By default invokes [run] once.
+  Future warmup() => run();
+
+  // Exercices the benchmark. By default invokes [run] 10 times.
+  Future exercise({int iterations: 10}) {
+    var i = 0;
+    return Future.doWhile(() {
+      if (i >= iterations) return new Future.value(false);
+      i++;
+      return run().then((_) => true);
+    });
+  }
+
+  // Not measured setup code executed prior to the benchmark runs.
+  Future setup() => new Future.value();
+
+  // Not measures teardown code executed after the benchark runs.
+  Future teardown() => new Future.value();
+
+  // Measures the score for this benchmark by executing it repeatedly until
+  // time minimum has been reached.
+  static Future<double> measureFor(Function f, int minimumMillis) {
+    int minimumMicros = minimumMillis * 1000;
+    int iter = 0;
+    Stopwatch watch = new Stopwatch();
+    watch.start();
+    int elapsed = 0;
+    return Future.doWhile(() {
+      if (elapsed > minimumMicros) return new Future.value(false);
+      return f().then((_) {
+        elapsed = watch.elapsedMicroseconds;
+        iter++;
+        return true;
+      });
+    }).then((_) => elapsed / iter);
+  }
+
+  // Measures the average time to call `run` once and returns it.
+  Future<double> measure({int iterations: 10}) async {
+    // Unmeasured setup code.
+    await setup();
+    // Warmup for at least 100ms. Discard result.
+    await measureFor(() => warmup(), 100);
+    // Run the benchmark for at least 2000ms.
+    var result = await measureFor(() => exercise(iterations: iterations), 2000);
+    // Tear down the test (unmeasured) and return the result divided by the
+    // number of iterations.
+    await teardown();
+    return result / iterations;
+  }
+}
diff --git a/pub/code_transformers/lib/src/dart_sdk.dart b/pub/code_transformers/lib/src/dart_sdk.dart
new file mode 100644
index 0000000..2b56f86
--- /dev/null
+++ b/pub/code_transformers/lib/src/dart_sdk.dart
@@ -0,0 +1,271 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library code_transformers.src.dart_sdk;
+
+import 'dart:io' show Directory;
+
+import 'package:analyzer/file_system/file_system.dart';
+import 'package:analyzer/src/context/context.dart';
+import 'package:analyzer/src/dart/sdk/sdk.dart';
+import 'package:analyzer/src/generated/engine.dart';
+import 'package:analyzer/src/generated/sdk.dart';
+import 'package:analyzer/src/generated/source.dart';
+import 'package:analyzer/src/summary/idl.dart';
+import 'package:cli_util/cli_util.dart' as cli_util;
+
+/// Attempts to provide the current Dart SDK directory.
+///
+/// This will return null if the SDK cannot be found
+///
+/// Note that this may not be correct when executing outside of `pub`.
+String get dartSdkDirectory {
+  Directory sdkDir = cli_util.getSdkDir();
+  return sdkDir != null ? sdkDir.path : null;
+}
+
+/// Sources that are annotated with a source uri, so it is easy to resolve how
+/// to support `Resolver.getImportUri`.
+abstract class UriAnnotatedSource extends Source {
+  Uri get uri;
+}
+
+/// Dart SDK which wraps all Dart sources as [UriAnnotatedSource] to ensure they
+/// are tracked with Uris.
+class FolderBasedDartSdkProxy extends FolderBasedDartSdk {
+  FolderBasedDartSdkProxy(
+      ResourceProvider resourceProvider, String sdkDirectory)
+      : super(resourceProvider, resourceProvider.getFolder(sdkDirectory));
+
+  Source mapDartUri(String dartUri) =>
+      DartSourceProxy.wrap(super.mapDartUri(dartUri), Uri.parse(dartUri));
+}
+
+/// Dart SDK resolver which wraps all Dart sources to ensure they are tracked
+/// with URIs.
+class DartUriResolverProxy implements DartUriResolver {
+  final DartUriResolver _proxy;
+  DartUriResolverProxy(DartSdk sdk) : _proxy = new DartUriResolver(sdk);
+
+  Source resolveAbsolute(Uri uri, [Uri actualUri]) =>
+      DartSourceProxy.wrap(_proxy.resolveAbsolute(uri, actualUri), uri);
+
+  DartSdk get dartSdk => _proxy.dartSdk;
+
+  Source fromEncoding(UriKind kind, Uri uri) =>
+      throw new UnsupportedError('fromEncoding is not supported');
+
+  Uri restoreAbsolute(Source source) =>
+      throw new UnsupportedError('restoreAbsolute is not supported');
+}
+
+/// Source file for dart: sources which track the sources with dart: URIs.
+///
+/// This is primarily to support [Resolver.getImportUri] for Dart SDK (dart:)
+/// based libraries.
+class DartSourceProxy implements UriAnnotatedSource {
+  /// Absolute URI which this source can be imported from
+  final Uri uri;
+
+  /// Underlying source object.
+  final Source _proxy;
+
+  Source get source => this;
+
+  DartSourceProxy(this._proxy, this.uri);
+
+  /// Ensures that [source] is a DartSourceProxy.
+  static DartSourceProxy wrap(Source source, Uri uri) {
+    if (source == null || source is DartSourceProxy) return source;
+    return new DartSourceProxy(source, uri);
+  }
+
+  // Note: to support both analyzer versions <0.22.0 and analyzer >=0.22.0, we
+  // implement both `resolveRelative` and `resolveRelativeUri`. Only one of them
+  // is available at a time in the analyzer package, so we use the `as dynamic`
+  // in these methods to hide warnings for the code that is missing. These APIs
+  // are invoked from the analyzer itself, so we don't expect them to cause
+  // failures.
+  Source resolveRelative(Uri relativeUri) {
+    // Assume that the type can be accessed via this URI, since these
+    // should only be parts for dart core files.
+    return wrap((_proxy as dynamic).resolveRelative(relativeUri), uri);
+  }
+
+  Uri resolveRelativeUri(Uri relativeUri) {
+    return (_proxy as dynamic).resolveRelativeUri(relativeUri);
+  }
+
+  bool exists() => _proxy.exists();
+
+  bool operator ==(Object other) =>
+      (other is DartSourceProxy && _proxy == other._proxy);
+
+  int get hashCode => _proxy.hashCode;
+
+  TimestampedData<String> get contents => _proxy.contents;
+
+  String get encoding => _proxy.encoding;
+
+  String get fullName => _proxy.fullName;
+
+  Source get librarySource => _proxy.librarySource;
+
+  int get modificationStamp => _proxy.modificationStamp;
+
+  String get shortName => _proxy.shortName;
+
+  UriKind get uriKind => _proxy.uriKind;
+
+  bool get isInSystemLibrary => _proxy.isInSystemLibrary;
+}
+
+/// Dart SDK which contains a mock implementation of the SDK libraries. May be
+/// used to speed up resultion when most of the core libraries is not needed.
+class MockDartSdk implements DartSdk {
+  final Map<Uri, _MockSdkSource> _sources = {};
+  final bool reportMissing;
+  final Map<String, SdkLibrary> _libs = {};
+  final String sdkVersion = '0';
+  List<String> get uris => _sources.keys.map((uri) => '$uri').toList();
+  final InternalAnalysisContext context;
+  DartUriResolver _resolver;
+  DartUriResolver get resolver => _resolver;
+
+  MockDartSdk(Map<String, String> sources, AnalysisOptions options,
+      {this.reportMissing})
+      : this.context = new SdkAnalysisContext(options) {
+    sources.forEach((uriString, contents) {
+      var uri = Uri.parse(uriString);
+      _sources[uri] = new _MockSdkSource(uri, contents);
+      _libs[uriString] = new SdkLibraryImpl(uri.path)
+        ..setDart2JsLibrary()
+        ..setVmLibrary();
+    });
+    _resolver = new DartUriResolver(this);
+    context.sourceFactory = new SourceFactory([_resolver]);
+  }
+
+  List<SdkLibrary> get sdkLibraries => _libs.values.toList();
+  SdkLibrary getSdkLibrary(String dartUri) => _libs[dartUri];
+  Source mapDartUri(String dartUri) => _getSource(Uri.parse(dartUri));
+
+  Source fromEncoding(UriKind kind, Uri uri) {
+    if (kind != UriKind.DART_URI) {
+      throw new UnsupportedError('expected dart: uri kind, got $kind.');
+    }
+    return _getSource(uri);
+  }
+
+  Source _getSource(Uri uri) {
+    var src = _sources[uri];
+    if (src == null) {
+      if (reportMissing) print('warning: missing mock for $uri.');
+      _sources[uri] =
+          src = new _MockSdkSource(uri, 'library dart.${uri.path};');
+    }
+    return src;
+  }
+
+  @override
+  Source fromFileUri(Uri uri) {
+    throw new UnsupportedError('MockDartSdk.fromFileUri');
+  }
+
+  @override
+  PackageBundle getLinkedBundle() => null;
+}
+
+class _MockSdkSource implements UriAnnotatedSource {
+  /// Absolute URI which this source can be imported from.
+  final Uri uri;
+  final String _contents;
+
+  Source get source => this;
+
+  Source get librarySource => null;
+
+  _MockSdkSource(this.uri, this._contents);
+
+  bool exists() => true;
+
+  int get hashCode => uri.hashCode;
+
+  final int modificationStamp = 1;
+
+  TimestampedData<String> get contents =>
+      new TimestampedData(modificationStamp, _contents);
+
+  String get encoding => "${uriKind.encoding}$uri";
+
+  String get fullName => shortName;
+
+  String get shortName => uri.path;
+
+  UriKind get uriKind => UriKind.DART_URI;
+
+  bool get isInSystemLibrary => true;
+
+  Source resolveRelative(Uri relativeUri) =>
+      throw new UnsupportedError('not expecting relative urls in dart: mocks');
+
+  Uri resolveRelativeUri(Uri relativeUri) =>
+      throw new UnsupportedError('not expecting relative urls in dart: mocks');
+
+  bool operator ==(Object other) => identical(this, other);
+}
+
+/// Sample mock SDK sources.
+final Map<String, String> mockSdkSources = {
+  // The list of types below is derived from types that are used internally by
+  // the resolver (see _initializeFrom in analyzer/src/generated/resolver.dart).
+  'dart:core': '''
+        library dart.core;
+
+        void print(Object o) {}
+
+        class Object {
+          String toString(){}
+        }
+        class Function {}
+        class StackTrace {}
+        class Symbol {}
+        class Type {}
+
+        class String {}
+        class bool {}
+        class num {
+          num operator +(num other) {}
+        }
+        class int extends num {
+          int operator-() {}
+        }
+        class double extends num {}
+        class DateTime {}
+        class Null {}
+
+        class Deprecated {
+          final String expires;
+          const Deprecated(this.expires);
+        }
+        const Object deprecated = const Deprecated("next release");
+        class _Override { const _Override(); }
+        const Object override = const _Override();
+        class _Proxy { const _Proxy(); }
+        const Object proxy = const _Proxy();
+
+        class Iterable<E> {}
+        class List<E> implements Iterable<E> {}
+        class Map<K, V> {}
+        ''',
+  'dart:async': '''
+        class Future<T> {
+          Future then(callback) {}
+        class Stream<T> {}
+  ''',
+  'dart:html': '''
+        library dart.html;
+        class HtmlElement {}
+        ''',
+};
diff --git a/pub/code_transformers/lib/src/delete_file.dart b/pub/code_transformers/lib/src/delete_file.dart
new file mode 100644
index 0000000..8eba05e
--- /dev/null
+++ b/pub/code_transformers/lib/src/delete_file.dart
@@ -0,0 +1,22 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Transformer that deletes everything that it sees, but only in release mode.
+library code_transformers.src.delete_file;
+
+import 'package:barback/barback.dart';
+
+// Deletes all files supplied in release mode.
+class DeleteFile extends Transformer {
+  BarbackSettings settings;
+
+  DeleteFile.asPlugin(this.settings);
+
+  /// Only apply to files in release mode.
+  isPrimary(_) => settings.mode == BarbackMode.RELEASE;
+
+  apply(Transform transform) {
+    transform.consumePrimary();
+  }
+}
diff --git a/pub/code_transformers/lib/src/entry_point.dart b/pub/code_transformers/lib/src/entry_point.dart
new file mode 100644
index 0000000..b654409
--- /dev/null
+++ b/pub/code_transformers/lib/src/entry_point.dart
@@ -0,0 +1,55 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+import 'dart:async';
+
+import 'package:analyzer/analyzer.dart' as analyzer;
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:barback/barback.dart';
+
+/// Checks to see if the provided AssetId is a Dart file in a directory which
+/// may contain entry points.
+///
+/// Directories are considered entry points if they are Dart files located in
+/// web/, test/, benchmark/ or example/.
+bool isPossibleDartEntryId(AssetId id) {
+  if (id.extension != '.dart') return false;
+
+  return ['benchmark', 'example', 'test', 'web']
+      .any((dir) => id.path.startsWith("$dir/"));
+}
+
+/// Checks to see if the provided Asset is possibly a Dart entry point.
+///
+/// Assets are considered entry points if they pass [isPossibleDartEntryId] and
+/// have a main() method.
+///
+/// Because this only analyzes the primary asset this may return true for files
+/// which are not dart entries if the file does not have a main() but does have
+/// parts or exports.
+Future<bool> isPossibleDartEntry(Asset asset) {
+  if (!isPossibleDartEntryId(asset.id)) return new Future.value(false);
+
+  return asset.readAsString().then((contents) {
+    return _couldBeEntrypoint(
+        analyzer.parseCompilationUnit(contents, suppressErrors: true));
+  });
+}
+
+bool _couldBeEntrypoint(CompilationUnit compilationUnit) {
+  // Allow two or fewer arguments so that entrypoints intended for use with
+  // [spawnUri] get counted.
+  var hasMain = compilationUnit.declarations.any((node) =>
+      node is FunctionDeclaration &&
+      node.name.name == "main" &&
+      node.functionExpression.parameters.parameters.length <= 2);
+
+  if (hasMain) return true;
+
+  // If it has an export or a part, assume the worst- that the main could be
+  // in there.
+  // We avoid loading those since this can be run from isPrimaryAsset calls
+  // where we do not have access to other sources.
+  return compilationUnit.directives
+      .any((node) => node is ExportDirective || node is PartDirective);
+}
diff --git a/pub/code_transformers/lib/src/messages.dart b/pub/code_transformers/lib/src/messages.dart
new file mode 100644
index 0000000..c91fe3d
--- /dev/null
+++ b/pub/code_transformers/lib/src/messages.dart
@@ -0,0 +1,73 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Contains all warning messages produced by the code_transformers package.
+library code_transformers.src.messages;
+
+import 'package:code_transformers/messages/messages.dart';
+
+const NO_ABSOLUTE_PATHS = const MessageTemplate(
+    const MessageId('code_transformers', 1),
+    'absolute paths not allowed: "%-url-%"',
+    'Absolute paths not allowed',
+    '''
+The transformers processing your code were trying to resolve a URL and identify
+a file that they correspond to. Currently only relative paths can be resolved.
+''');
+
+const INVALID_URL_TO_OTHER_PACKAGE = const MessageTemplate(
+    const MessageId('code_transformers', 2),
+    'Invalid URL to reach to another package: %-url-%. Path '
+    'reaching to other packages must first reach up all the '
+    'way to the %-prefix-% directory. For example, try changing the URL '
+    'to: %-fixedUrl-%',
+    'Invalid URL to reach another package',
+    '''
+To reach an asset that belongs to another package, use `package:` URLs in
+Dart code, but in any other language (like HTML or CSS) use relative URLs that
+first go all the way to the `packages/` directory.
+
+The rules for correctly writing these imports are subtle and have a lot of
+special cases. Please review
+<https://www.dartlang.org/polymer/app-directories.html> to learn
+more.
+''');
+
+const INVALID_PREFIX_PATH = const MessageTemplate(
+    const MessageId('code_transformers', 3),
+    'incomplete %-prefix-%/ path. It should have at least 3 '
+    'segments %-prefix-%/name/path_from_name\'s_%-folder-%_dir',
+    'Incomplete URL to asset in another package',
+    '''
+URLs that refer to assets in other packages need to explicitly mention the
+`packages/` directory. In the future this requirement might be removed, but for
+now you must use a canonical URL form for it.
+
+For example, if `packages/a/a.html` needs to import `packages/b/b.html`,
+you might expect a.html to import `../b/b.html`. Instead, it must import
+`../../packages/b/b.html`.
+
+See [issue 15797](http://dartbug.com/15797) and
+<https://www.dartlang.org/polymer/app-directories.html> to learn more.
+''');
+
+const UNSPECIFIED_FROM_IN_NON_LIB_ASSET = const MessageTemplate(
+    const MessageId('code_transformers', 4),
+    'Cannot create URI for %-id-% without specifying where to import it from.',
+    'Missing `from` argument.',
+    '''
+Assets outside of the lib folder can only be imported via relative URIs. Use
+the `from` argument in `assetIdToUri` to specify the location in the same
+package where you intend to import this asset from.
+''');
+
+const IMPORT_FROM_DIFFERENT_PACKAGE = const MessageTemplate(
+    const MessageId('code_transformers', 5),
+    'Not possible to import %-toId-% from %-fromId-%',
+    'Cannot import asset.',
+    '''
+Assets outside of the lib folder can only be imported via relative URIs from
+assets in the same package. To import an asset from another package, you need to
+move it into the lib folder of your package.
+''');
diff --git a/pub/code_transformers/lib/src/remove_sourcemap_comment.dart b/pub/code_transformers/lib/src/remove_sourcemap_comment.dart
new file mode 100644
index 0000000..70f4b9c
--- /dev/null
+++ b/pub/code_transformers/lib/src/remove_sourcemap_comment.dart
@@ -0,0 +1,31 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Transformer that removes any sourcemap comments from javascript files.
+library code_transformers.src.remove_sourcemap_comment;
+
+import 'package:barback/barback.dart';
+
+/// Transformer that removes any sourcemap comments from javascript files.
+/// Comments should be on their own line in the form: //# sourceMappingURL=*.map
+class RemoveSourcemapComment extends Transformer {
+  BarbackSettings settings;
+
+  RemoveSourcemapComment.asPlugin(this.settings);
+
+  /// Only apply to files in release mode.
+  isPrimary(_) => settings.mode == BarbackMode.RELEASE;
+
+  apply(Transform transform) {
+    var id = transform.primaryInput.id;
+    return transform.readInputAsString(id).then((file) {
+      if (file.contains(_SOURCE_MAP_COMMENT)) {
+        transform.addOutput(
+            new Asset.fromString(id, file.replaceAll(_SOURCE_MAP_COMMENT, '')));
+      }
+    });
+  }
+}
+
+final RegExp _SOURCE_MAP_COMMENT = new RegExp(r'\n\s*\/\/# sourceMappingURL.*');
diff --git a/pub/code_transformers/lib/src/resolver.dart b/pub/code_transformers/lib/src/resolver.dart
new file mode 100644
index 0000000..5b8ef1c
--- /dev/null
+++ b/pub/code_transformers/lib/src/resolver.dart
@@ -0,0 +1,109 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library code_transformer.src.resolver;
+
+import 'dart:async';
+
+import 'package:analyzer/dart/ast/ast.dart' show Expression;
+import 'package:analyzer/src/generated/constant.dart' show EvaluationResult;
+import 'package:analyzer/dart/element/element.dart';
+import 'package:barback/barback.dart';
+import 'package:source_maps/refactor.dart';
+import 'package:source_span/source_span.dart';
+
+/// Class for working with a barback based resolved AST.
+abstract class Resolver {
+  /// Update the status of all the sources referenced by the entry points and
+  /// update the resolved library. If [entryPoints] is omitted, the primary
+  /// asset of [transform] is used as the only entry point.
+  ///
+  /// [release] must be called when done handling this Resolver to allow it
+  /// to be used by later phases.
+  ///
+  /// If [resolveAllLibraries] is [false], then transitive imports will not
+  /// be resolved. This will result in faster resolution, but you will need to
+  /// manually call something like
+  /// `libary.context.computeLibraryElement(library.definingCompilationUnit.source);`
+  /// for each [LibraryElement] that you want to ensure is fully resolved. The
+  /// default value is [true].
+  Future<Resolver> resolve(Transform transform,
+      [List<AssetId> entryPoints, bool resolveAllLibraries]);
+
+  /// Release this resolver so it can be updated by following transforms.
+  void release();
+
+  /// Gets the resolved Dart library for an asset, or null if the AST has not
+  /// been resolved.
+  ///
+  /// If the AST has not been resolved then this normally means that the
+  /// transformer hosting this needs to be in an earlier phase.
+  LibraryElement getLibrary(AssetId assetId);
+
+  /// Gets all libraries accessible from the entry point, recursively.
+  ///
+  /// This includes all Dart SDK libraries as well.
+  Iterable<LibraryElement> get libraries;
+
+  /// Finds the first library identified by [libraryName], or null if no
+  /// library can be found.
+  LibraryElement getLibraryByName(String libraryName);
+
+  /// Finds the first library identified by [libraryName], or null if no
+  /// library can be found.
+  ///
+  /// [uri] must be an absolute URI of the form
+  /// `[dart:|package:]path/file.dart`.
+  LibraryElement getLibraryByUri(Uri uri);
+
+  /// Resolves a fully-qualified type name (library_name.ClassName).
+  ///
+  /// This will resolve the first instance of [typeName], because of potential
+  /// library name conflicts the name is not guaranteed to be unique.
+  ClassElement getType(String typeName);
+
+  /// Resolves a fully-qualified top-level library variable
+  /// (library_name.variableName).
+  ///
+  /// This will resolve the first instance of [variableName], because of
+  /// potential library name conflicts the name is not guaranteed to be unique.
+  Element getLibraryVariable(String variableName);
+
+  /// Resolves a fully-qualified top-level library function
+  /// (library_name.functionName).
+  ///
+  /// This will resolve the first instance of [functionName], because of
+  /// potential library name conflicts the name is not guaranteed to be unique.
+  Element getLibraryFunction(String functionName);
+
+  /// Gets the result of evaluating the constant [expression] in the context of
+  /// a [library].
+  EvaluationResult evaluateConstant(
+      LibraryElement library, Expression expression);
+
+  /// Gets an URI appropriate for importing the specified library.
+  ///
+  /// Returns null if the library cannot be imported via an absolute URI or
+  /// from [from] (if provided).
+  Uri getImportUri(LibraryElement lib, {AssetId from});
+
+  /// Get the asset ID of the file containing the asset.
+  AssetId getSourceAssetId(Element element);
+
+  /// Get the source span where the specified element was defined or null if
+  /// the element came from the Dart SDK.
+  SourceSpan getSourceSpan(Element element);
+
+  /// Get a [SourceFile] with the contents of the file that defines [element],
+  /// or null if the element came from the Dart SDK.
+  SourceFile getSourceFile(Element element);
+
+  /// Creates a text edit transaction for the given element if it is able
+  /// to be edited, returns null otherwise.
+  ///
+  /// The transaction contains the entire text of the source file where the
+  /// element originated. If the element was from a library part then the
+  /// source file is the part file rather than the library.
+  TextEditTransaction createTextEditTransaction(Element element);
+}
diff --git a/pub/code_transformers/lib/src/resolver_impl.dart b/pub/code_transformers/lib/src/resolver_impl.dart
new file mode 100644
index 0000000..1b87e88
--- /dev/null
+++ b/pub/code_transformers/lib/src/resolver_impl.dart
@@ -0,0 +1,582 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library code_transformer.src.resolver_impl;
+
+import 'dart:async';
+import 'package:analyzer/analyzer.dart' show parseDirectives;
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/src/generated/constant.dart'
+    show ConstantEvaluator, EvaluationResult;
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/src/generated/engine.dart';
+import 'package:analyzer/src/generated/sdk.dart' show DartSdk;
+import 'package:analyzer/src/generated/source.dart';
+import 'package:barback/barback.dart';
+import 'package:code_transformers/assets.dart';
+import 'package:path/path.dart' as native_path;
+import 'package:source_maps/refactor.dart';
+import 'package:source_span/source_span.dart';
+
+import 'resolver.dart';
+import 'dart_sdk.dart' show UriAnnotatedSource;
+
+// We should always be using url paths here since it's always Dart/pub code.
+final path = native_path.url;
+
+/// Resolves and updates an AST based on Barback-based assets.
+///
+/// This also provides a handful of useful APIs for traversing and working
+/// with the resolved AST.
+class ResolverImpl implements Resolver {
+  /// Cache of all asset sources currently referenced.
+  final Map<AssetId, AssetBasedSource> sources;
+
+  final InternalAnalysisContext _context =
+      AnalysisEngine.instance.createAnalysisContext();
+
+  /// Transform for which this is currently updating, or null when not updating.
+  Transform _currentTransform;
+
+  /// The currently resolved entry libraries, or null if nothing is resolved.
+  List<LibraryElement> _entryLibraries;
+  Set<LibraryElement> _libraries;
+
+  /// Future indicating when this resolver is done in the current phase.
+  Future _lastPhaseComplete = new Future.value();
+
+  /// Completer for wrapping up the current phase.
+  Completer _currentPhaseComplete;
+
+  /// Whether or not we are using a shared sources cache.
+  final bool _usingSharedSources;
+
+  /// Creates a resolver with a given [sdk] implementation for resolving
+  /// `dart:*` imports.
+  ResolverImpl(DartSdk sdk, DartUriResolver dartUriResolver,
+      {AnalysisOptions options, Map<AssetId, AssetBasedSource> sources})
+      : _usingSharedSources = sources != null,
+        sources = sources ?? <AssetId, AssetBasedSource>{} {
+    if (options == null) {
+      options = new AnalysisOptionsImpl()
+        ..cacheSize = 256 // # of sources to cache ASTs for.
+        ..preserveComments = false
+        ..analyzeFunctionBodies = true;
+    }
+    _context.analysisOptions = options;
+    _context.sourceFactory =
+        new SourceFactory([dartUriResolver, new _AssetUriResolver(this)]);
+  }
+
+  LibraryElement getLibrary(AssetId assetId) {
+    var source = sources[assetId];
+    return source == null ? null : _context.computeLibraryElement(source);
+  }
+
+  Future<Resolver> resolve(Transform transform,
+      [List<AssetId> entryPoints, bool resolveAllLibraries]) {
+    // Can only have one resolve in progress at a time, so chain the current
+    // resolution to be after the last one.
+    var phaseComplete = new Completer();
+    var future = _lastPhaseComplete.whenComplete(() {
+      _currentPhaseComplete = phaseComplete;
+      return _performResolve(
+          transform,
+          entryPoints == null ? [transform.primaryInput.id] : entryPoints,
+          resolveAllLibraries);
+    }).then((_) => this);
+    // Advance the lastPhaseComplete to be done when this phase is all done.
+    _lastPhaseComplete = phaseComplete.future;
+    return future;
+  }
+
+  void release() {
+    if (_currentPhaseComplete == null) {
+      throw new StateError('Releasing without current lock.');
+    }
+    _currentPhaseComplete.complete(null);
+    _currentPhaseComplete = null;
+
+    // Clear out libraries since they should not be referenced after release.
+    _entryLibraries = null;
+    _libraries = null;
+    _currentTransform = null;
+  }
+
+  Future _performResolve(Transform transform, List<AssetId> entryPoints,
+      bool resolveAllLibraries) {
+    resolveAllLibraries ??= true;
+    if (_currentTransform != null) {
+      throw new StateError('Cannot be accessed by concurrent transforms');
+    }
+    _currentTransform = transform;
+
+    // Basic approach is to start at the first file, update it's contents
+    // and see if it changed, then walk all files accessed by it.
+    var visited = new Set<AssetId>();
+    var visiting = new FutureGroup();
+    var toUpdate = [];
+
+    void processAsset(AssetId assetId) {
+      visited.add(assetId);
+
+      visiting.add(transform.readInputAsString(assetId).then((contents) {
+        var source = sources[assetId];
+        if (source == null) {
+          source = new AssetBasedSource(assetId, this);
+          sources[assetId] = source;
+        }
+        source.updateDependencies(contents);
+        toUpdate.add(new _PendingUpdate(source, contents));
+        source.dependentAssets
+            .where((id) => !visited.contains(id))
+            .forEach(processAsset);
+      }, onError: (e) {
+        var source = sources[assetId];
+        if (source != null && source.exists()) {
+          _context.applyChanges(new ChangeSet()..removedSource(source));
+          sources[assetId].updateContents(null);
+        }
+      }));
+    }
+
+    entryPoints.forEach(processAsset);
+
+    // Once we have all asset sources updated with the new contents then
+    // resolve everything.
+    return visiting.future.then((_) {
+      var changeSet = new ChangeSet();
+      toUpdate.forEach((pending) => pending.apply(changeSet));
+
+      // If we aren't using shared sources, then remove from the cache any
+      // sources which are no longer reachable.
+      if (!_usingSharedSources) {
+        var unreachableAssets =
+            sources.keys.toSet().difference(visited).map((id) => sources[id]);
+        for (var unreachable in unreachableAssets) {
+          changeSet.removedSource(unreachable);
+          unreachable.updateContents(null);
+          sources.remove(unreachable.assetId);
+        }
+      }
+
+      // Update the analyzer context with the latest sources
+      _context.applyChanges(changeSet);
+      // Force resolve each entry point (the getter will ensure the library is
+      // computed first).
+      _entryLibraries = entryPoints.map((id) {
+        var source = sources[id];
+        if (source == null) return null;
+        return _context.computeLibraryElement(source);
+      }).toList();
+
+      if (resolveAllLibraries) {
+        // Force resolve all other available libraries. As of analyzer > 0.27.1
+        // this is necessary to get resolved constants.
+        var newLibraries = new Set<LibraryElement>();
+        for (var library in libraries) {
+          if (library.source.uri.scheme == 'dart' ||
+              _entryLibraries.contains(library)) {
+            newLibraries.add(library);
+          } else {
+            newLibraries.add(_context
+                .computeLibraryElement(library.definingCompilationUnit.source));
+          }
+        }
+        _libraries = newLibraries;
+      }
+    });
+  }
+
+  Iterable<LibraryElement> get libraries {
+    if (_libraries == null) {
+      // Note: we don't use `lib.visibleLibraries` because that excludes the
+      // exports seen in the entry libraries.
+      _libraries = new Set<LibraryElement>();
+      _entryLibraries.forEach(_collectLibraries);
+    }
+    return _libraries;
+  }
+
+  void _collectLibraries(LibraryElement lib) {
+    if (lib == null || _libraries.contains(lib)) return;
+    _libraries.add(lib);
+    lib.importedLibraries.forEach(_collectLibraries);
+    lib.exportedLibraries.forEach(_collectLibraries);
+  }
+
+  LibraryElement getLibraryByName(String libraryName) =>
+      libraries.firstWhere((l) => l.name == libraryName, orElse: () => null);
+
+  LibraryElement getLibraryByUri(Uri uri) =>
+      libraries.firstWhere((l) => getImportUri(l) == uri, orElse: () => null);
+
+  ClassElement getType(String typeName) {
+    var dotIndex = typeName.lastIndexOf('.');
+    var libraryName = dotIndex == -1 ? '' : typeName.substring(0, dotIndex);
+
+    var className =
+        dotIndex == -1 ? typeName : typeName.substring(dotIndex + 1);
+
+    for (var lib in libraries.where((l) => l.name == libraryName)) {
+      var type = lib.getType(className);
+      if (type != null) return type;
+    }
+    return null;
+  }
+
+  Element getLibraryVariable(String variableName) {
+    var dotIndex = variableName.lastIndexOf('.');
+    var libraryName = dotIndex == -1 ? '' : variableName.substring(0, dotIndex);
+
+    var name =
+        dotIndex == -1 ? variableName : variableName.substring(dotIndex + 1);
+
+    return libraries
+        .where((lib) => lib.name == libraryName)
+        .expand((lib) => lib.units)
+        .expand((unit) => unit.topLevelVariables)
+        .firstWhere((variable) => variable.name == name, orElse: () => null);
+  }
+
+  Element getLibraryFunction(String fnName) {
+    var dotIndex = fnName.lastIndexOf('.');
+    var libraryName = dotIndex == -1 ? '' : fnName.substring(0, dotIndex);
+
+    var name = dotIndex == -1 ? fnName : fnName.substring(dotIndex + 1);
+
+    return libraries
+        .where((lib) => lib.name == libraryName)
+        .expand((lib) => lib.units)
+        .expand((unit) => unit.functions)
+        .firstWhere((fn) => fn.name == name, orElse: () => null);
+  }
+
+  EvaluationResult evaluateConstant(
+      LibraryElement library, Expression expression) {
+    return new ConstantEvaluator(library.source, _context.typeProvider)
+        .evaluate(expression);
+  }
+
+  Uri getImportUri(LibraryElement lib, {AssetId from}) =>
+      _getSourceUri(lib, from: from);
+
+  /// Similar to getImportUri but will get the part URI for parts rather than
+  /// the library URI.
+  Uri _getSourceUri(Element element, {AssetId from}) {
+    var source = element.source;
+    if (source is AssetBasedSource) {
+      var uriString = assetIdToUri(source.assetId, from: from);
+      return uriString != null ? Uri.parse(uriString) : null;
+    } else if (source is UriAnnotatedSource) {
+      return source.uri;
+    }
+    // Should not be able to encounter any other source types.
+    throw new StateError('Unable to resolve URI for ${source.runtimeType}');
+  }
+
+  AssetId getSourceAssetId(Element element) {
+    var source = element.source;
+    if (source is AssetBasedSource) return source.assetId;
+    return null;
+  }
+
+  SourceSpan getSourceSpan(Element element) {
+    var sourceFile = getSourceFile(element);
+    if (sourceFile == null) return null;
+    return sourceFile.span(
+        element.computeNode().offset, element.computeNode().end);
+  }
+
+  TextEditTransaction createTextEditTransaction(Element element) {
+    if (element.source is! AssetBasedSource) return null;
+
+    // Cannot edit unless there is an active transformer.
+    if (_currentTransform == null) return null;
+
+    AssetBasedSource source = element.source;
+    // Cannot modify assets in other packages.
+    if (source.assetId.package != _currentTransform.primaryInput.id.package) {
+      return null;
+    }
+
+    var sourceFile = getSourceFile(element);
+    if (sourceFile == null) return null;
+
+    return new TextEditTransaction(source.rawContents, sourceFile);
+  }
+
+  /// Gets the SourceFile for the source of the element.
+  SourceFile getSourceFile(Element element) {
+    var assetId = getSourceAssetId(element);
+    if (assetId == null) return null;
+
+    var importUri = _getSourceUri(element);
+    var spanPath = importUri != null ? importUri.toString() : assetId.path;
+    return new SourceFile(sources[assetId].rawContents, url: spanPath);
+  }
+}
+
+/// Implementation of Analyzer's Source for Barback based assets.
+class AssetBasedSource extends Source {
+  /// Asset ID where this source can be found.
+  final AssetId assetId;
+
+  /// The resolver this is being used in.
+  final ResolverImpl _resolver;
+
+  /// Cache of dependent asset IDs, to avoid re-parsing the AST.
+  Iterable<AssetId> _dependentAssets;
+
+  /// The current revision of the file, incremented only when file changes.
+  int _revision = 0;
+
+  /// The file contents.
+  String _contents;
+
+  AssetBasedSource(this.assetId, this._resolver);
+
+  /// Update the dependencies of this source. This parses [contents] but avoids
+  /// any analyzer resolution.
+  void updateDependencies(String contents) {
+    if (contents == _contents) return;
+    var unit = parseDirectives(contents, suppressErrors: true);
+    _dependentAssets = unit.directives
+        .where((d) => d is UriBasedDirective)
+        .map((d) => _resolve(assetId, (d as UriBasedDirective).uri.stringValue,
+            _logger, _getSpan(d, contents)))
+        .where((id) => id != null)
+        .toSet();
+  }
+
+  /// Update the contents of this file with [contents].
+  ///
+  /// Returns true if the contents of this asset have changed.
+  bool updateContents(String contents) {
+    if (contents == _contents) return false;
+    _contents = contents;
+    ++_revision;
+    return true;
+  }
+
+  /// Contents of the file.
+  TimestampedData<String> get contents {
+    if (!exists()) throw new StateError('$assetId does not exist');
+
+    return new TimestampedData<String>(modificationStamp, _contents);
+  }
+
+  /// Contents of the file.
+  String get rawContents => _contents;
+
+  Uri get uri => Uri.parse('asset:${assetId.package}/${assetId.path}');
+
+  /// Logger for the current transform.
+  ///
+  /// Only valid while the resolver is updating assets.
+  TransformLogger get _logger => _resolver._currentTransform.logger;
+
+  /// Gets all imports/parts/exports which resolve to assets (non-Dart files).
+  Iterable<AssetId> get dependentAssets => _dependentAssets;
+
+  bool exists() => _contents != null;
+
+  bool operator ==(Object other) =>
+      other is AssetBasedSource && assetId == other.assetId;
+
+  int get hashCode => assetId.hashCode;
+
+  void getContentsToReceiver(Source_ContentReceiver receiver) {
+    receiver.accept(rawContents, modificationStamp);
+  }
+
+  String get encoding =>
+      "${uriKind.encoding}${assetId.package}/${assetId.path}";
+
+  String get fullName => assetId.toString();
+
+  int get modificationStamp => _revision;
+
+  String get shortName => path.basename(assetId.path);
+
+  UriKind get uriKind {
+    if (assetId.path.startsWith('lib/')) return UriKind.PACKAGE_URI;
+    return UriKind.FILE_URI;
+  }
+
+  bool get isInSystemLibrary => false;
+
+  Source resolveRelative(Uri relativeUri) {
+    var id = _resolve(assetId, relativeUri.toString(), _logger, null);
+    if (id == null) return null;
+
+    // The entire AST should have been parsed and loaded at this point.
+    var source = _resolver.sources[id];
+    if (source == null) {
+      _logger.error('Could not load asset $id');
+    }
+    return source;
+  }
+
+  Uri resolveRelativeUri(Uri relativeUri) {
+    var id = _resolve(assetId, relativeUri.toString(), _logger, null);
+    if (id == null) return uri.resolveUri(relativeUri);
+
+    // The entire AST should have been parsed and loaded at this point.
+    var source = _resolver.sources[id];
+    if (source == null) {
+      _logger.error('Could not load asset $id');
+    }
+    return source.uri;
+  }
+
+  /// For logging errors.
+  SourceSpan _getSpan(AstNode node, [String contents]) =>
+      _getSourceFile(contents).span(node.offset, node.end);
+
+  /// For logging errors.
+  SourceFile _getSourceFile([String contents]) {
+    var uri = assetIdToUri(assetId);
+    var path = uri != null ? uri : assetId.path;
+    return new SourceFile(contents != null ? contents : rawContents, url: path);
+  }
+}
+
+/// Implementation of Analyzer's UriResolver for Barback based assets.
+class _AssetUriResolver implements UriResolver {
+  final ResolverImpl _resolver;
+  _AssetUriResolver(this._resolver);
+
+  Source resolveAbsolute(Uri uri, [Uri actualUri]) {
+    assert(uri.scheme != 'dart');
+    var assetId;
+    if (uri.scheme == 'asset') {
+      var parts = path.split(uri.path);
+      assetId = new AssetId(parts[0], path.joinAll(parts.skip(1)));
+    } else {
+      assetId = _resolve(null, uri.toString(), logger, null);
+      if (assetId == null) {
+        logger.error('Unable to resolve asset ID for "$uri"');
+        return null;
+      }
+    }
+    var source = _resolver.sources[assetId];
+    // Analyzer expects that sources which are referenced but do not exist yet
+    // still exist, so just make an empty source.
+    if (source == null) {
+      source = new AssetBasedSource(assetId, _resolver);
+      _resolver.sources[assetId] = source;
+    }
+    return source;
+  }
+
+  Source fromEncoding(UriKind kind, Uri uri) =>
+      throw new UnsupportedError('fromEncoding is not supported');
+
+  Uri restoreAbsolute(Source source) =>
+      throw new UnsupportedError('restoreAbsolute is not supported');
+
+  TransformLogger get logger => _resolver._currentTransform.logger;
+}
+
+/// Get an asset ID for a URL relative to another source asset.
+AssetId _resolve(
+    AssetId source, String url, TransformLogger logger, SourceSpan span) {
+  if (url == null || url == '') return null;
+  var uri = Uri.parse(url);
+
+  // Workaround for dartbug.com/17156- pub transforms package: imports from
+  // files of the transformers package to have absolute /packages/ URIs.
+  if (uri.scheme == '' &&
+      path.isAbsolute(url) &&
+      uri.pathSegments[0] == 'packages') {
+    uri = Uri.parse('package:${uri.pathSegments.skip(1).join(path.separator)}');
+  }
+
+  if (uri.scheme == 'package') {
+    var segments = new List.from(uri.pathSegments);
+    var package = segments[0];
+    segments[0] = 'lib';
+    return new AssetId(package, segments.join(path.separator));
+  }
+  // Dart SDK libraries do not have assets.
+  if (uri.scheme == 'dart') return null;
+
+  return uriToAssetId(source, url, logger, span);
+}
+
+/// A completer that waits until all added [Future]s complete.
+// TODO(blois): Copied from quiver. Remove from here when it gets
+// added to dart:core. (See #6626.)
+class FutureGroup<E> {
+  static const _FINISHED = -1;
+
+  int _pending = 0;
+  Future _failedTask;
+  final Completer<List> _completer = new Completer<List>();
+  final List results = [];
+
+  /** Gets the task that failed, if any. */
+  Future get failedTask => _failedTask;
+
+  /**
+   * Wait for [task] to complete.
+   *
+   * If this group has already been marked as completed, a [StateError] will be
+   * thrown.
+   *
+   * If this group has a [failedTask], new tasks will be ignored, because the
+   * error has already been signaled.
+   */
+  void add(Future task) {
+    if (_failedTask != null) return;
+    if (_pending == _FINISHED) throw new StateError("Future already completed");
+
+    _pending++;
+    var i = results.length;
+    results.add(null);
+    task.then((res) {
+      results[i] = res;
+      if (_failedTask != null) return;
+      _pending--;
+      if (_pending == 0) {
+        _pending = _FINISHED;
+        _completer.complete(results);
+      }
+    }, onError: (e, s) {
+      if (_failedTask != null) return;
+      _failedTask = task;
+      _completer.completeError(e, s);
+    });
+  }
+
+  /**
+   * A Future that completes with a List of the values from all the added
+   * tasks, when they have all completed.
+   *
+   * If any task fails, this Future will receive the error. Only the first
+   * error will be sent to the Future.
+   */
+  Future<List<E>> get future => _completer.future;
+}
+
+/// A pending update to notify the resolver that a [Source] has been added or
+/// changed. This is used by the `_performResolve` algorithm above to apply all
+/// changes after it first discovers the transitive closure of files that are
+/// reachable from the sources.
+class _PendingUpdate {
+  AssetBasedSource source;
+  String content;
+
+  _PendingUpdate(this.source, this.content);
+
+  void apply(ChangeSet changeSet) {
+    if (!source.updateContents(content)) return;
+    if (source._revision == 1 && source._contents != null) {
+      changeSet.addedSource(source);
+    } else {
+      changeSet.changedSource(source);
+    }
+  }
+}
diff --git a/pub/code_transformers/lib/src/resolvers.dart b/pub/code_transformers/lib/src/resolvers.dart
new file mode 100644
index 0000000..b691902
--- /dev/null
+++ b/pub/code_transformers/lib/src/resolvers.dart
@@ -0,0 +1,159 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library code_transformers.src.resolvers;
+
+import 'dart:async';
+import 'package:barback/barback.dart';
+
+import 'package:analyzer/file_system/physical_file_system.dart';
+import 'package:analyzer/src/generated/engine.dart' show AnalysisOptions;
+import 'package:analyzer/src/generated/sdk.dart' show DartSdk;
+import 'package:analyzer/src/generated/engine.dart';
+import 'package:analyzer/src/generated/source.dart' show DartUriResolver;
+
+import 'entry_point.dart';
+import 'resolver.dart';
+import 'resolver_impl.dart';
+import 'dart_sdk.dart' hide dartSdkDirectory;
+
+/// Barback-based code resolvers which maintains up-to-date resolved ASTs for
+/// the specified code entry points.
+///
+/// This can used by transformers dependent on resolved ASTs to handle the
+/// resolution of the AST and cache the results between compilations.
+///
+/// If multiple transformers rely on a resolved AST they should (ideally) share
+/// the same Resolvers object to minimize re-parsing the AST.
+class Resolvers {
+  final Map<AssetId, Resolver> _resolvers = {};
+  final DartSdk dartSdk;
+  final DartUriResolver dartUriResolver;
+  final AnalysisOptions options;
+
+  /// Null unless `useSharedSources` is true. This option should only be used if
+  /// you know that files are always in a consistent state wherever this
+  /// resolvers object is used. Any time that [Resolvers#get] or
+  /// [Resolver#resolve] are called it will update the sources globally when
+  /// this option is in use.
+  final Map<AssetId, AssetBasedSource> sharedSources;
+
+  Resolvers.fromSdk(this.dartSdk, this.dartUriResolver,
+      {this.options, bool useSharedSources})
+      : sharedSources =
+            useSharedSources == true ? <AssetId, AssetBasedSource>{} : null;
+
+  factory Resolvers(String dartSdkDirectory,
+      {AnalysisOptions options, bool useSharedSources}) {
+    _initAnalysisEngine();
+    var sdk = new FolderBasedDartSdkProxy(
+        PhysicalResourceProvider.INSTANCE, dartSdkDirectory);
+    var uriResolver = new DartUriResolverProxy(sdk);
+    return new Resolvers.fromSdk(sdk, uriResolver,
+        options: options, useSharedSources: useSharedSources);
+  }
+
+  factory Resolvers.fromMock(Map<String, String> sources,
+      {bool reportMissing: false,
+      AnalysisOptions options,
+      bool useSharedSources}) {
+    _initAnalysisEngine();
+    var sdk = new MockDartSdk(sources, options, reportMissing: reportMissing);
+    return new Resolvers.fromSdk(sdk, sdk.resolver,
+        options: options, useSharedSources: useSharedSources);
+  }
+
+  /// Get a resolver for [transform]. If provided, this resolves the code
+  /// starting from each of the assets in [entryPoints]. If not, this resolves
+  /// the code starting from `transform.primaryInput.id` by default.
+  ///
+  /// [Resolver.release] must be called once it's done being used, or
+  /// [ResolverTransformer] should be used to automatically release the
+  /// resolver.
+  ///
+  /// See [Resolver#resolve] for more info on the `resolveAllLibraries` option.
+  Future<Resolver> get(Transform transform,
+      [List<AssetId> entryPoints, bool resolveAllLibraries]) {
+    var id = transform.primaryInput.id;
+    var resolver = _resolvers.putIfAbsent(
+        id,
+        () => new ResolverImpl(dartSdk, dartUriResolver,
+            options: options, sources: sharedSources));
+    return resolver.resolve(transform, entryPoints, resolveAllLibraries);
+  }
+}
+
+/// Transformer mixin which automatically gets and releases resolvers.
+///
+/// To use mix this class in, set the resolvers field and override
+/// [applyResolver].
+abstract class ResolverTransformer implements Transformer {
+  /// The cache of resolvers- must be set from subclass.
+  Resolvers resolvers;
+
+  /// See [Resolver#resolve] for more info - can be overridden by a subclass.
+  bool get resolveAllLibraries => true;
+
+  /// By default only process possible entry point assets.
+  ///
+  /// This is only a preliminary check based on the asset ID.
+  Future<bool> isPrimary(assetOrId) {
+    // assetOrId is to handle the transition from Asset to AssetID between
+    // pub 1.3 and 1.4. Once support for 1.3 is dropped this should only
+    // support AssetId.
+    var id = assetOrId is AssetId ? assetOrId : (assetOrId as Asset).id;
+    return new Future.value(isPossibleDartEntryId(id));
+  }
+
+  /// Check to see if this should apply with the resolver on the provided asset.
+  ///
+  /// By default this will only apply on possible Dart entry points (see
+  /// [isPossibleDartEntry]).
+  Future<bool> shouldApplyResolver(Asset asset) => isPossibleDartEntry(asset);
+
+  /// This provides a default implementation of `Transformer.apply` that will
+  /// get and release resolvers automatically. Internally this:
+  ///   * Gets a resolver associated with the transform primary input.
+  ///   * Does resolution to the code starting from that input.
+  ///   * Calls [applyResolver].
+  ///   * Then releases the resolver.
+  ///
+  /// Use [applyToEntryPoints] instead if you need to override the entry points
+  /// to run the resolver on.
+  Future apply(Transform transform) =>
+      shouldApplyResolver(transform.primaryInput).then((result) {
+        if (result) return applyToEntryPoints(transform);
+      });
+
+  /// Helper function to make it easy to write an `Transformer.apply` method
+  /// that automatically gets and releases the resolver. This is typically used
+  /// as follows:
+  ///
+  ///    Future apply(Transform transform) {
+  ///       var entryPoints = ...; // compute entry points
+  ///       return applyToEntryPoints(transform, entryPoints);
+  ///    }
+  Future applyToEntryPoints(Transform transform, [List<AssetId> entryPoints]) {
+    return resolvers
+        .get(transform, entryPoints, resolveAllLibraries)
+        .then((resolver) {
+      return new Future(() => applyResolver(transform, resolver))
+          .whenComplete(() {
+        resolver.release();
+      });
+    });
+  }
+
+  /// Invoked when the resolver is ready to be processed.
+  ///
+  /// Return a Future to indicate when apply is completed.
+  applyResolver(Transform transform, Resolver resolver);
+}
+
+bool _analysisEngineInitialized = false;
+_initAnalysisEngine() {
+  if (_analysisEngineInitialized) return;
+  _analysisEngineInitialized = true;
+  AnalysisEngine.instance.processRequiredPlugins();
+}
diff --git a/pub/code_transformers/pubspec.yaml b/pub/code_transformers/pubspec.yaml
new file mode 100644
index 0000000..2f43a52
--- /dev/null
+++ b/pub/code_transformers/pubspec.yaml
@@ -0,0 +1,17 @@
+name: code_transformers
+version: 0.4.2+4
+author: Dart Team <misc@dartlang.org>
+description: Collection of utilities related to creating barback transformers.
+homepage: https://github.com/dart-lang/code-transformers
+environment:
+  sdk: '>=1.0.0 <2.0.0'
+dependencies:
+  analyzer: '>=0.28.0 <0.30.0'
+  barback: '>=0.14.2 <0.16.0'
+  cli_util: '>=0.0.1 <0.1.0'
+  path: '>=0.9.0 <2.0.0'
+  source_maps: '>=0.9.4 <0.11.0'
+  source_span: '>=1.0.0 <2.0.0'
+dev_dependencies:
+  test: '>=0.12.1 <0.13.0'
+  transformer_test: '>=0.1.0 <0.3.0'
diff --git a/pub/code_transformers/tool/travis.sh b/pub/code_transformers/tool/travis.sh
new file mode 100755
index 0000000..37492cc
--- /dev/null
+++ b/pub/code_transformers/tool/travis.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+# Fail fast
+set -e
+
+# Analyze
+dartanalyzer --fatal-warnings lib/*dart lib/messages/*dart test/*dart
+
+# Test
+pub run test
+
+# Check format
+pub global activate dart_style
+pub global run dart_style:format --set-exit-if-changed --dry-run lib/ test/
diff --git a/pub/csslib/BUILD.gn b/pub/csslib/BUILD.gn
index 702f257..28830d5 100644
--- a/pub/csslib/BUILD.gn
+++ b/pub/csslib/BUILD.gn
@@ -1,4 +1,4 @@
-# This file is generated by importer.py for csslib-0.13.4
+# This file is generated by importer.py for csslib-0.13.5
 
 import("//build/dart/dart_package.gni")
 
diff --git a/pub/csslib/CHANGELOG.md b/pub/csslib/CHANGELOG.md
index 7c5222d..3d83b2b 100644
--- a/pub/csslib/CHANGELOG.md
+++ b/pub/csslib/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 0.13.5
+
+* Adds support for `@-moz-document`.
+* Adds support for `@supports`.
+
 ## 0.13.4
 
 * Parses CSS 2.1 pseudo-elements as pseudo-elements instead of pseudo-classes.
diff --git a/pub/csslib/lib/parser.dart b/pub/csslib/lib/parser.dart
index 168c9da..4c65a29 100644
--- a/pub/csslib/lib/parser.dart
+++ b/pub/csslib/lib/parser.dart
@@ -23,6 +23,12 @@
 part 'src/tokenizer.dart';
 part 'src/tokenkind.dart';
 
+enum ClauseType {
+  none,
+  conjunction,
+  disjunction,
+}
+
 /** Used for parser lookup ahead (used for nested selectors Less support). */
 class ParserState extends TokenizerState {
   final Token peekToken;
@@ -485,7 +491,12 @@
    *  mixin:              '@mixin name [(args,...)] '{' declarations/ruleset '}'
    *  include:            '@include name [(@arg,@arg1)]
    *                      '@include name [(@arg...)]
-   *  content             '@content'
+   *  content:            '@content'
+   *  -moz-document:      '@-moz-document' [ <url> | url-prefix(<string>) |
+   *                          domain(<string>) | regexp(<string) ]# '{'
+   *                        declarations
+   *                      '}'
+   *  supports:           '@supports' supports_condition group_rule_body
    */
   processDirective() {
     var start = _peekToken.span;
@@ -762,11 +773,14 @@
 
       case TokenKind.DIRECTIVE_INCLUDE:
         return processInclude(_makeSpan(start));
-
       case TokenKind.DIRECTIVE_CONTENT:
         // TODO(terry): TBD
         _warning("@content not implemented.", _makeSpan(start));
         return null;
+      case TokenKind.DIRECTIVE_MOZ_DOCUMENT:
+        return processDocumentDirective();
+      case TokenKind.DIRECTIVE_SUPPORTS:
+        return processSupportsDirective();
     }
     return null;
   }
@@ -987,6 +1001,127 @@
     return new IncludeDirective(name.name, params, span);
   }
 
+  DocumentDirective processDocumentDirective() {
+    var start = _peekToken.span;
+    _next(); // '@-moz-document'
+    var functions = <LiteralTerm>[];
+    do {
+      var function;
+
+      // Consume function token: IDENT '('
+      var ident = identifier();
+      _eat(TokenKind.LPAREN);
+
+      // Consume function arguments.
+      if (ident.name == 'url-prefix' || ident.name == 'domain') {
+        // @-moz-document allows the 'url-prefix' and 'domain' functions to
+        // omit quotations around their argument, contrary to the standard
+        // in which they must be strings. To support this we consume a
+        // string with optional quotation marks, then reapply quotation
+        // marks so they're present in the emitted CSS.
+        var argumentStart = _peekToken.span;
+        var value = processQuotedString(true);
+        // Don't quote the argument if it's empty. '@-moz-document url-prefix()'
+        // is a common pattern used for browser detection.
+        var argument = value.isNotEmpty ? '"$value"' : '';
+        var argumentSpan = _makeSpan(argumentStart);
+
+        _eat(TokenKind.RPAREN);
+
+        var arguments = new Expressions(_makeSpan(argumentSpan))
+          ..add(new LiteralTerm(argument, argument, argumentSpan));
+        function = new FunctionTerm(
+            ident.name, ident.name, arguments, _makeSpan(ident.span));
+      } else {
+        function = processFunction(ident);
+      }
+
+      functions.add(function);
+    } while (_maybeEat(TokenKind.COMMA));
+
+    _eat(TokenKind.LBRACE);
+    var groupRuleBody = processGroupRuleBody();
+    _eat(TokenKind.RBRACE);
+    return new DocumentDirective(functions, groupRuleBody, _makeSpan(start));
+  }
+
+  SupportsDirective processSupportsDirective() {
+    var start = _peekToken.span;
+    _next(); // '@supports'
+    var condition = processSupportsCondition();
+    _eat(TokenKind.LBRACE);
+    var groupRuleBody = processGroupRuleBody();
+    _eat(TokenKind.RBRACE);
+    return new SupportsDirective(condition, groupRuleBody, _makeSpan(start));
+  }
+
+  SupportsCondition processSupportsCondition() {
+    if (_peekKind(TokenKind.IDENTIFIER)) {
+      return processSupportsNegation();
+    }
+
+    var start = _peekToken.span;
+    var conditions = <SupportsConditionInParens>[];
+    var clauseType = ClauseType.none;
+
+    while (true) {
+      conditions.add(processSupportsConditionInParens());
+
+      var type;
+      var text = _peekToken.text.toLowerCase();
+
+      if (text == 'and') {
+        type = ClauseType.conjunction;
+      } else if (text == 'or') {
+        type = ClauseType.disjunction;
+      } else {
+        break; // Done parsing clause.
+      }
+
+      if (clauseType == ClauseType.none) {
+        clauseType = type; // First operand and operator of clause.
+      } else if (clauseType != type) {
+        _error("Operators can't be mixed without a layer of parentheses",
+            _peekToken.span);
+        break;
+      }
+
+      _next(); // Consume operator.
+    }
+
+    if (clauseType == ClauseType.conjunction) {
+      return new SupportsConjunction(conditions, _makeSpan(start));
+    } else if (clauseType == ClauseType.disjunction) {
+      return new SupportsDisjunction(conditions, _makeSpan(start));
+    } else {
+      return conditions.first;
+    }
+  }
+
+  SupportsNegation processSupportsNegation() {
+    var start = _peekToken.span;
+    var text = _peekToken.text.toLowerCase();
+    if (text != 'not') return null;
+    _next(); // 'not'
+    var condition = processSupportsConditionInParens();
+    return new SupportsNegation(condition, _makeSpan(start));
+  }
+
+  SupportsConditionInParens processSupportsConditionInParens() {
+    var start = _peekToken.span;
+    _eat(TokenKind.LPAREN);
+    // Try to parse a condition.
+    var condition = processSupportsCondition();
+    if (condition != null) {
+      _eat(TokenKind.RPAREN);
+      return new SupportsConditionInParens.nested(condition, _makeSpan(start));
+    }
+    // Otherwise, parse a declaration.
+    var declaration = processDeclaration([]);
+    _eat(TokenKind.RPAREN);
+    return new SupportsConditionInParens(declaration, _makeSpan(start));
+  }
+
   RuleSet processRuleSet([SelectorGroup selectorGroup]) {
     if (selectorGroup == null) {
       selectorGroup = processSelectorGroup();
@@ -998,6 +1133,24 @@
     return null;
   }
 
+  List<TreeNode> processGroupRuleBody() {
+    var nodes = <TreeNode>[];
+    while (!(_peekKind(TokenKind.RBRACE) || _peekKind(TokenKind.END_OF_FILE))) {
+      var directive = processDirective();
+      if (directive != null) {
+        nodes.add(directive);
+        continue;
+      }
+      var ruleSet = processRuleSet();
+      if (ruleSet != null) {
+        nodes.add(ruleSet);
+        continue;
+      }
+      break;
+    }
+    return nodes;
+  }
+
   /**
    * Look ahead to see if what should be a declaration is really a selector.
    * If it's a selector than it's a nested selector.  This support's Less'
diff --git a/pub/csslib/lib/src/css_printer.dart b/pub/csslib/lib/src/css_printer.dart
index d3b62d0..0304fd7 100644
--- a/pub/csslib/lib/src/css_printer.dart
+++ b/pub/csslib/lib/src/css_printer.dart
@@ -77,22 +77,73 @@
     }
   }
 
+  void visitDocumentDirective(DocumentDirective node) {
+    emit('$_newLine@-moz-document ');
+    node.functions.first.visit(this);
+    for (var function in node.functions.skip(1)) {
+      emit(',$_sp');
+      function.visit(this);
+    }
+    emit('$_sp{');
+    for (var ruleSet in node.groupRuleBody) {
+      ruleSet.visit(this);
+    }
+    emit('$_newLine}');
+  }
+
+  void visitSupportsDirective(SupportsDirective node) {
+    emit('$_newLine@supports ');
+    node.condition.visit(this);
+    emit('$_sp{');
+    for (var rule in node.groupRuleBody) {
+      rule.visit(this);
+    }
+    emit('$_newLine}');
+  }
+
+  void visitSupportsConditionInParens(SupportsConditionInParens node) {
+    emit('(');
+    node.condition.visit(this);
+    emit(')');
+  }
+
+  void visitSupportsNegation(SupportsNegation node) {
+    emit('not$_sp');
+    node.condition.visit(this);
+  }
+
+  void visitSupportsConjunction(SupportsConjunction node) {
+    node.conditions.first.visit(this);
+    for (var condition in node.conditions.skip(1)) {
+      emit('${_sp}and$_sp');
+      condition.visit(this);
+    }
+  }
+
+  void visitSupportsDisjunction(SupportsDisjunction node) {
+    node.conditions.first.visit(this);
+    for (var condition in node.conditions.skip(1)) {
+      emit('${_sp}or$_sp');
+      condition.visit(this);
+    }
+  }
+
   void visitMediaDirective(MediaDirective node) {
-    emit(' @media');
+    emit('$_newLine@media');
     emitMediaQueries(node.mediaQueries);
-    emit(' {');
+    emit('$_sp{');
     for (var ruleset in node.rulesets) {
       ruleset.visit(this);
     }
-    emit('$_newLine\}');
+    emit('$_newLine}');
   }
 
   void visitHostDirective(HostDirective node) {
-    emit('\n@host {');
+    emit('$_newLine@host$_sp{');
     for (var ruleset in node.rulesets) {
       ruleset.visit(this);
     }
-    emit('$_newLine\}');
+    emit('$_newLine}');
   }
 
   /**
@@ -251,12 +302,11 @@
   }
 
   void visitDeclaration(Declaration node) {
-    String importantAsString() => node.important ? '$_sp!important' : '';
-
-    emit("${node.property}: ");
+    emit('${node.property}:$_sp');
     node._expression.visit(this);
-
-    emit("${importantAsString()}");
+    if (node.important) {
+      emit('$_sp!important');
+    }
   }
 
   void visitVarDefinition(VarDefinition node) {
diff --git a/pub/csslib/lib/src/tokenkind.dart b/pub/csslib/lib/src/tokenkind.dart
index 85e6d40..48696ab 100644
--- a/pub/csslib/lib/src/tokenkind.dart
+++ b/pub/csslib/lib/src/tokenkind.dart
@@ -161,6 +161,8 @@
   static const int DIRECTIVE_INCLUDE = 655;
   static const int DIRECTIVE_CONTENT = 656;
   static const int DIRECTIVE_EXTEND = 657;
+  static const int DIRECTIVE_MOZ_DOCUMENT = 658;
+  static const int DIRECTIVE_SUPPORTS = 659;
 
   // Media query operators
   static const int MEDIA_OP_ONLY = 665; // Unary.
@@ -218,6 +220,8 @@
     const {'type': TokenKind.DIRECTIVE_INCLUDE, 'value': 'include'},
     const {'type': TokenKind.DIRECTIVE_CONTENT, 'value': 'content'},
     const {'type': TokenKind.DIRECTIVE_EXTEND, 'value': 'extend'},
+    const {'type': TokenKind.DIRECTIVE_MOZ_DOCUMENT, 'value': '-moz-document'},
+    const {'type': TokenKind.DIRECTIVE_SUPPORTS, 'value': 'supports'},
   ];
 
   static const List<Map<String, dynamic>> MEDIA_OPERATORS = const [
diff --git a/pub/csslib/lib/src/tree.dart b/pub/csslib/lib/src/tree.dart
index c3709d2..d6b9dbf 100644
--- a/pub/csslib/lib/src/tree.dart
+++ b/pub/csslib/lib/src/tree.dart
@@ -435,6 +435,111 @@
   visit(VisitorBase visitor) => visitor.visitDirective(this);
 }
 
+class DocumentDirective extends Directive {
+  final List<LiteralTerm> functions;
+  final List<TreeNode> groupRuleBody;
+
+  DocumentDirective(this.functions, this.groupRuleBody, SourceSpan span)
+      : super(span);
+
+  DocumentDirective clone() {
+    var clonedFunctions = <LiteralTerm>[];
+    for (var function in functions) {
+      clonedFunctions.add(function.clone());
+    }
+    var clonedGroupRuleBody = <TreeNode>[];
+    for (var rule in groupRuleBody) {
+      clonedGroupRuleBody.add(rule.clone());
+    }
+    return new DocumentDirective(clonedFunctions, clonedGroupRuleBody, span);
+  }
+
+  visit(VisitorBase visitor) => visitor.visitDocumentDirective(this);
+}
+
+class SupportsDirective extends Directive {
+  final SupportsCondition condition;
+  final List<TreeNode> groupRuleBody;
+
+  SupportsDirective(this.condition, this.groupRuleBody, SourceSpan span)
+      : super(span);
+
+  SupportsDirective clone() {
+    var clonedCondition = condition.clone();
+    var clonedGroupRuleBody = <TreeNode>[];
+    for (var rule in groupRuleBody) {
+      clonedGroupRuleBody.add(rule.clone());
+    }
+    return new SupportsDirective(clonedCondition, clonedGroupRuleBody, span);
+  }
+
+  visit(VisitorBase visitor) => visitor.visitSupportsDirective(this);
+}
+
+abstract class SupportsCondition extends TreeNode {
+  SupportsCondition(SourceSpan span) : super(span);
+}
+
+class SupportsConditionInParens extends SupportsCondition {
+  /// A [Declaration] or nested [SupportsCondition].
+  final TreeNode condition;
+
+  SupportsConditionInParens(Declaration declaration, SourceSpan span)
+      : condition = declaration,
+        super(span);
+
+  SupportsConditionInParens.nested(SupportsCondition condition, SourceSpan span)
+      : condition = condition,
+        super(span);
+
+  SupportsConditionInParens clone() =>
+      new SupportsConditionInParens(condition.clone(), span);
+
+  visit(VisitorBase visitor) => visitor.visitSupportsConditionInParens(this);
+}
+
+class SupportsNegation extends SupportsCondition {
+  final SupportsConditionInParens condition;
+
+  SupportsNegation(this.condition, SourceSpan span) : super(span);
+
+  SupportsNegation clone() => new SupportsNegation(condition.clone(), span);
+
+  visit(VisitorBase visitor) => visitor.visitSupportsNegation(this);
+}
+
+class SupportsConjunction extends SupportsCondition {
+  final List<SupportsConditionInParens> conditions;
+
+  SupportsConjunction(this.conditions, SourceSpan span) : super(span);
+
+  SupportsConjunction clone() {
+    var clonedConditions = <SupportsCondition>[];
+    for (var condition in conditions) {
+      clonedConditions.add(condition.clone());
+    }
+    return new SupportsConjunction(clonedConditions, span);
+  }
+
+  visit(VisitorBase visitor) => visitor.visitSupportsConjunction(this);
+}
+
+class SupportsDisjunction extends SupportsCondition {
+  final List<SupportsConditionInParens> conditions;
+
+  SupportsDisjunction(this.conditions, SourceSpan span) : super(span);
+
+  SupportsDisjunction clone() {
+    var clonedConditions = <SupportsCondition>[];
+    for (var condition in conditions) {
+      clonedConditions.add(condition.clone());
+    }
+    return new SupportsDisjunction(clonedConditions, span);
+  }
+
+  visit(VisitorBase visitor) => visitor.visitSupportsDisjunction(this);
+}
+
 class ImportDirective extends Directive {
   /** import name specified. */
   final String import;
diff --git a/pub/csslib/lib/src/tree_printer.dart b/pub/csslib/lib/src/tree_printer.dart
index ac4512c..7ae67e4 100644
--- a/pub/csslib/lib/src/tree_printer.dart
+++ b/pub/csslib/lib/src/tree_printer.dart
@@ -90,6 +90,50 @@
     output.depth--;
   }
 
+  void visitDocumentDirective(DocumentDirective node) {
+    heading('DocumentDirective', node);
+    output.depth++;
+    output.writeNodeList('functions', node.functions);
+    output.writeNodeList('group rule body', node.groupRuleBody);
+    output.depth--;
+  }
+
+  void visitSupportsDirective(SupportsDirective node) {
+    heading('SupportsDirective', node);
+    output.depth++;
+    output.writeNode('condition', node.condition);
+    output.writeNodeList('group rule body', node.groupRuleBody);
+    output.depth--;
+  }
+
+  void visitSupportsConditionInParens(SupportsConditionInParens node) {
+    heading('SupportsConditionInParens', node);
+    output.depth++;
+    output.writeNode('condition', node.condition);
+    output.depth--;
+  }
+
+  void visitSupportsNegation(SupportsNegation node) {
+    heading('SupportsNegation', node);
+    output.depth++;
+    output.writeNode('condition', node.condition);
+    output.depth--;
+  }
+
+  void visitSupportsConjunction(SupportsConjunction node) {
+    heading('SupportsConjunction', node);
+    output.depth++;
+    output.writeNodeList('conditions', node.conditions);
+    output.depth--;
+  }
+
+  void visitSupportsDisjunction(SupportsDisjunction node) {
+    heading('SupportsDisjunction', node);
+    output.depth++;
+    output.writeNodeList('conditions', node.conditions);
+    output.depth--;
+  }
+
   void visitPageDirective(PageDirective node) {
     heading('PageDirective', node);
     output.depth++;
diff --git a/pub/csslib/lib/visitor.dart b/pub/csslib/lib/visitor.dart
index 1123493..ad94033 100644
--- a/pub/csslib/lib/visitor.dart
+++ b/pub/csslib/lib/visitor.dart
@@ -20,6 +20,12 @@
   visitNoOp(NoOp node);
   visitTopLevelProduction(TopLevelProduction node);
   visitDirective(Directive node);
+  visitDocumentDirective(DocumentDirective node);
+  visitSupportsDirective(SupportsDirective node);
+  visitSupportsConditionInParens(SupportsConditionInParens node);
+  visitSupportsNegation(SupportsNegation node);
+  visitSupportsConjunction(SupportsConjunction node);
+  visitSupportsDisjunction(SupportsDisjunction node);
   visitMediaExpression(MediaExpression node);
   visitMediaQuery(MediaQuery node);
   visitMediaDirective(MediaDirective node);
@@ -152,6 +158,32 @@
     }
   }
 
+  visitDocumentDirective(DocumentDirective node) {
+    _visitNodeList(node.functions);
+    _visitNodeList(node.groupRuleBody);
+  }
+
+  visitSupportsDirective(SupportsDirective node) {
+    node.condition.visit(this);
+    _visitNodeList(node.groupRuleBody);
+  }
+
+  visitSupportsConditionInParens(SupportsConditionInParens node) {
+    node.condition.visit(this);
+  }
+
+  visitSupportsNegation(SupportsNegation node) {
+    node.condition.visit(this);
+  }
+
+  visitSupportsConjunction(SupportsConjunction node) {
+    _visitNodeList(node.conditions);
+  }
+
+  visitSupportsDisjunction(SupportsDisjunction node) {
+    _visitNodeList(node.conditions);
+  }
+
   visitMediaDirective(MediaDirective node) {
     for (var mediaQuery in node.mediaQueries) {
       visitMediaQuery(mediaQuery);
diff --git a/pub/csslib/pubspec.yaml b/pub/csslib/pubspec.yaml
index 0e2e7da..81c97a8 100644
--- a/pub/csslib/pubspec.yaml
+++ b/pub/csslib/pubspec.yaml
@@ -1,5 +1,5 @@
 name: csslib
-version: 0.13.4
+version: 0.13.5
 author: Dart Team <misc@dartlang.org>
 description: A library for parsing CSS.
 homepage: https://github.com/dart-lang/csslib
diff --git a/pub/observable/.analysis_options b/pub/observable/.analysis_options
new file mode 100644
index 0000000..6f04432
--- /dev/null
+++ b/pub/observable/.analysis_options
@@ -0,0 +1,28 @@
+analyzer:
+  strong-mode: true
+linter:
+  rules:
+    # Errors
+    - avoid_empty_else
+    - control_flow_in_finally
+    - empty_statements
+    - test_types_in_equals
+    - throw_in_finally
+    - valid_regexps
+
+    # Style
+    - annotate_overrides
+    - avoid_init_to_null
+    - avoid_return_types_on_setters
+    - await_only_futures
+    - camel_case_types
+    - comment_references
+    - empty_catches
+    - empty_constructor_bodies
+    - hash_and_equals
+    - library_prefixes
+    - non_constant_identifier_names
+    - prefer_is_not_empty
+    - slash_for_doc_comments
+    - type_init_formals
+    - unrelated_type_equality_checks
diff --git a/pub/observable/.gitignore b/pub/observable/.gitignore
new file mode 100644
index 0000000..96bc6bb
--- /dev/null
+++ b/pub/observable/.gitignore
@@ -0,0 +1,8 @@
+# Files and directories created by pub
+.packages
+.pub/
+packages
+pubspec.lock
+
+# Directory created by dartdoc
+doc/api/
diff --git a/pub/observable/.travis.yml b/pub/observable/.travis.yml
new file mode 100644
index 0000000..4fb40c9
--- /dev/null
+++ b/pub/observable/.travis.yml
@@ -0,0 +1,8 @@
+language: dart
+
+dart:
+  - dev
+  # Currently requires a dev-branch in order to use properly.
+  # - stable
+
+script: ./tool/presubmit.sh
diff --git a/pub/observable/AUTHORS b/pub/observable/AUTHORS
new file mode 100644
index 0000000..0617765
--- /dev/null
+++ b/pub/observable/AUTHORS
@@ -0,0 +1,9 @@
+# Names should be added to this file with this pattern:
+#
+# For individuals:
+#   Name <email address>
+#
+# For organizations:
+#   Organization <fnmatch pattern>
+#
+Google Inc. <*@google.com>
diff --git a/pub/observable/BUILD.gn b/pub/observable/BUILD.gn
new file mode 100644
index 0000000..2c7626e
--- /dev/null
+++ b/pub/observable/BUILD.gn
@@ -0,0 +1,17 @@
+# This file is generated by importer.py for observable-0.17.0+1
+
+import("//build/dart/dart_package.gni")
+
+dart_package("observable") {
+  package_name = "observable"
+
+  source_dir = "lib"
+
+  disable_analysis = true
+
+  deps = [
+    "//third_party/dart-pkg/pub/meta",
+    "//third_party/dart-pkg/pub/quiver",
+    "//third_party/dart-pkg/pub/collection",
+  ]
+}
diff --git a/pub/observable/CHANGELOG.md b/pub/observable/CHANGELOG.md
new file mode 100644
index 0000000..d6f9d8f
--- /dev/null
+++ b/pub/observable/CHANGELOG.md
@@ -0,0 +1,67 @@
+## 0.17.0+1
+
+* Revert `PropertyChangeMixin`, which does not work in dart2js
+
+## 0.17.0
+
+This is a larger change with a goal of no runtime changes for current
+customers, but in the future `Observable` will [become][issue_10] a very
+lightweight interface, i.e.:
+
+```dart
+abstract class Observable<C extends ChangeRecord> {
+  Stream<List<C>> get changes;
+}
+```
+
+[issue_10]: https://github.com/dart-lang/observable/issues/10
+
+* Started deprecating the wide `Observable` interface
+    * `ChangeNotifier` should be used as a base class for these methods:
+        * `Observable.observed`
+        * `Observable.unobserved`
+        * `Observable.hasObservers`
+        * `Observable.deliverChanges`
+        * `Observable.notifyChange`
+    * `PropertyChangeNotifier` should be used for these methods:
+        * `Observable.notifyPropertyChange`
+    * Temporarily, `Observable` _uses_ `ChangeNotifier`
+        * Existing users of anything but `implements Observable` should
+          move to implementing or extending `ChangeNotifier`. In a
+          future release `Observable` will reduce API surface down to
+          an abstract `Stream<List<C>> get changes`.
+* Added the `ChangeNotifier` and `PropertyChangeNotifier` classes
+    * Can be used to implement `Observable` in a generic manner
+* Observable is now `Observable<C extends ChangeRecord>`
+    * When passing a generic type `C`, `notifyPropertyChange` is illegal
+
+## 0.16.0
+
+* Refactored `MapChangeRecord`
+    * Added equality and hashCode checks
+    * Added `MapChangeRecord.apply` to apply a change record
+* Added `MapDiffer`, which implements `Differ` for a `Map`
+
+## 0.15.0+2
+
+* Fix a bug in `ListDiffer` that caused a `RangeError`
+
+## 0.15.0+1
+
+* Fix analysis errors caused via missing `/*<E>*/` syntax in `0.15.0`
+
+## 0.15.0
+
+* Added the `Differ` interface, as well as `EqualityDiffer`
+* Refactored list diffing into a `ListDiffer`
+* Added concept of `ChangeRecord.ANY` and `ChangeRecord.NONE`
+    * Low-GC ways to expression "something/nothing" changed
+* Refactored `ListChangeRecord`
+    * Added named constructors for common use cases
+    * Added equality and hashCode checks
+    * Added `ListChangeRecord.apply` to apply a change record
+* Added missing `@override` annotations to satisfy `annotate_overrides`
+
+## 0.14.0+1
+
+* Add a missing dependency on `pkg/meta`.
diff --git a/pub/observable/CONTRIBUTING.md b/pub/observable/CONTRIBUTING.md
new file mode 100644
index 0000000..6f5e0ea
--- /dev/null
+++ b/pub/observable/CONTRIBUTING.md
@@ -0,0 +1,33 @@
+Want to contribute? Great! First, read this page (including the small print at
+the end).
+
+### Before you contribute
+Before we can use your code, you must sign the
+[Google Individual Contributor License Agreement](https://cla.developers.google.com/about/google-individual)
+(CLA), which you can do online. The CLA is necessary mainly because you own the
+copyright to your changes, even after your contribution becomes part of our
+codebase, so we need your permission to use and distribute your code. We also
+need to be sure of various other things—for instance that you'll tell us if you
+know that your code infringes on other people's patents. You don't have to sign
+the CLA until after you've submitted your code for review and a member has
+approved it, but you must do it before we can put your code into our codebase.
+
+Before you start working on a larger contribution, you should get in touch with
+us first through the issue tracker with your idea so that we can help out and
+possibly guide you. Coordinating up front makes it much easier to avoid
+frustration later on.
+
+### Code reviews
+All submissions, including submissions by project members, require review.
+
+### File headers
+All files in the project must start with the following header.
+
+    // Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
+    // for details. All rights reserved. Use of this source code is governed by a
+    // BSD-style license that can be found in the LICENSE file.
+
+### The small print
+Contributions made by corporations are covered by a different agreement than the
+one above, the
+[Software Grant and Corporate Contributor License Agreement](https://developers.google.com/open-source/cla/corporate).
diff --git a/pub/observable/LICENSE b/pub/observable/LICENSE
new file mode 100644
index 0000000..82e9b52
--- /dev/null
+++ b/pub/observable/LICENSE
@@ -0,0 +1,26 @@
+Copyright 2016, the Dart project authors. 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.
+    * Neither the name of Google Inc. nor the names of its
+      contributors may 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 THE COPYRIGHT
+OWNER OR CONTRIBUTORS 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.
diff --git a/pub/observable/PATENTS b/pub/observable/PATENTS
new file mode 100644
index 0000000..6954196
--- /dev/null
+++ b/pub/observable/PATENTS
@@ -0,0 +1,23 @@
+Additional IP Rights Grant (Patents)
+
+"This implementation" means the copyrightable works distributed by
+Google as part of the Dart Project.
+
+Google hereby grants to you a perpetual, worldwide, non-exclusive,
+no-charge, royalty-free, irrevocable (except as stated in this
+section) patent license to make, have made, use, offer to sell, sell,
+import, transfer, and otherwise run, modify and propagate the contents
+of this implementation of Dart, where such license applies only to
+those patent claims, both currently owned by Google and acquired in
+the future, licensable by Google that are necessarily infringed by
+this implementation of Dart. This grant does not include claims that
+would be infringed only as a consequence of further modification of
+this implementation. If you or your agent or exclusive licensee
+institute or order or agree to the institution of patent litigation
+against any entity (including a cross-claim or counterclaim in a
+lawsuit) alleging that this implementation of Dart or any code
+incorporated within this implementation of Dart constitutes direct or
+contributory patent infringement, or inducement of patent
+infringement, then any patent rights granted to you under this License
+for this implementation of Dart shall terminate as of the date such
+litigation is filed.
diff --git a/pub/observable/README.md b/pub/observable/README.md
new file mode 100644
index 0000000..c688514
--- /dev/null
+++ b/pub/observable/README.md
@@ -0,0 +1,6 @@
+Support for detecting and being notified when an object is mutated.
+
+There are two general ways to detect changes:
+
+* Listen to `Observable.changes` and be notified when an object changes
+* Use `Differ.diff` to determine changes between two objects
diff --git a/pub/observable/lib/observable.dart b/pub/observable/lib/observable.dart
new file mode 100644
index 0000000..516ff70
--- /dev/null
+++ b/pub/observable/lib/observable.dart
@@ -0,0 +1,14 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library observable;
+
+export 'src/change_notifier.dart' show ChangeNotifier, PropertyChangeNotifier;
+export 'src/differs.dart' show Differ, EqualityDiffer, ListDiffer, MapDiffer;
+export 'src/records.dart'
+    show ChangeRecord, ListChangeRecord, MapChangeRecord, PropertyChangeRecord;
+export 'src/observable.dart';
+export 'src/observable_list.dart';
+export 'src/observable_map.dart';
+export 'src/to_observable.dart';
diff --git a/pub/observable/lib/src/change_notifier.dart b/pub/observable/lib/src/change_notifier.dart
new file mode 100644
index 0000000..f60eb16
--- /dev/null
+++ b/pub/observable/lib/src/change_notifier.dart
@@ -0,0 +1,137 @@
+import 'dart:async';
+
+import 'package:meta/meta.dart';
+
+import 'internal.dart';
+import 'observable.dart';
+import 'records.dart';
+
+/// Supplies [changes] and various hooks to implement [Observable].
+///
+/// May use [notifyChange] to queue a change record; they are asynchronously
+/// delivered at the end of the VM turn.
+///
+/// [ChangeNotifier] may be extended, mixed in, or used as a delegate.
+class ChangeNotifier<C extends ChangeRecord> implements Observable<C> {
+  StreamController<List<C>> _changes;
+
+  bool _scheduled = false;
+  List<C> _queue;
+
+  /// Emits a list of changes when the state of the object changes.
+  ///
+  /// Changes should produced in order, if significant.
+  @override
+  Stream<List<C>> get changes {
+    return (_changes ??= new StreamController<List<C>>.broadcast(
+      sync: true,
+      onListen: observed,
+      onCancel: unobserved,
+    ))
+        .stream;
+  }
+
+  /// May override to be notified when [changes] is first observed.
+  @override
+  @protected
+  @mustCallSuper
+  void observed() {}
+
+  /// May override to be notified when [changes] is no longer observed.
+  @override
+  @protected
+  @mustCallSuper
+  void unobserved() {
+    _changes = _queue = null;
+  }
+
+  /// If [hasObservers], synchronously emits [changes] that have been queued.
+  ///
+  /// Returns `true` if changes were emitted.
+  @override
+  @protected
+  @mustCallSuper
+  bool deliverChanges() {
+    List<ChangeRecord> changes;
+    if (_scheduled && hasObservers) {
+      if (_queue != null) {
+        changes = freezeInDevMode(_queue);
+        _queue = null;
+      } else {
+        changes = ChangeRecord.ANY;
+      }
+      _scheduled = false;
+      _changes.add(changes);
+    }
+    return changes != null;
+  }
+
+  /// Whether [changes] has at least one active listener.
+  ///
+  /// May be used to optimize whether to produce change records.
+  @override
+  bool get hasObservers => _changes?.hasListener == true;
+
+  /// Schedules [change] to be delivered.
+  ///
+  /// If [change] is omitted then [ChangeRecord.ANY] will be sent.
+  ///
+  /// If there are no listeners to [changes], this method does nothing.
+  @override
+  void notifyChange([C change]) {
+    if (!hasObservers) {
+      return;
+    }
+    if (change != null) {
+      (_queue ??= <C>[]).add(change);
+    }
+    if (!_scheduled) {
+      scheduleMicrotask(deliverChanges);
+      _scheduled = true;
+    }
+  }
+
+  @Deprecated('Exists to make migrations off Observable easier')
+  @override
+  @protected
+  /*=T*/ notifyPropertyChange/*<T>*/(
+    Symbol field,
+    /*=T*/
+    oldValue,
+    /*=T*/
+    newValue,
+  ) {
+    throw new UnsupportedError('Not supported by ChangeNotifier');
+  }
+}
+
+/// Supplies property `changes` and various hooks to implement [Observable].
+///
+/// May use `notifyChange` or `notifyPropertyChange` to queue a property change
+/// record; they are asynchronously delivered at the end of the VM turn.
+///
+/// [PropertyChangeNotifier] may be extended or used as a delegate. To use as
+/// a mixin, instead use with [PropertyChangeMixin]:
+///     with ChangeNotifier<PropertyChangeRecord>, PropertyChangeMixin
+class PropertyChangeNotifier extends ChangeNotifier<PropertyChangeRecord> {
+  @override
+  /*=T*/ notifyPropertyChange/*<T>*/(
+    Symbol field,
+    /*=T*/
+    oldValue,
+    /*=T*/
+    newValue,
+  ) {
+    if (hasObservers && oldValue != newValue) {
+      notifyChange(
+        new PropertyChangeRecord/*<T>*/(
+          this,
+          field,
+          oldValue,
+          newValue,
+        ),
+      );
+    }
+    return newValue;
+  }
+}
diff --git a/pub/observable/lib/src/differs.dart b/pub/observable/lib/src/differs.dart
new file mode 100644
index 0000000..40f005e
--- /dev/null
+++ b/pub/observable/lib/src/differs.dart
@@ -0,0 +1,40 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library observable.src.differs;
+
+import 'dart:math' as math;
+
+import 'package:collection/collection.dart';
+
+import 'records.dart';
+
+import 'internal.dart';
+
+part 'differs/list_differ.dart';
+part 'differs/map_differ.dart';
+
+/// Generic comparisons between two comparable objects.
+abstract class Differ<E> {
+  /// Returns a list of change records between [oldValue] and [newValue].
+  ///
+  /// A return value of an empty [ChangeRecord.NONE] means no changes found.
+  List<ChangeRecord> diff(E oldValue, E newValue);
+}
+
+/// Uses [Equality] to determine a simple [ChangeRecord.ANY] response.
+class EqualityDiffer<E> implements Differ<E> {
+  final Equality<E> _equality;
+
+  const EqualityDiffer([this._equality = const DefaultEquality()]);
+
+  const EqualityDiffer.identity() : this._equality = const IdentityEquality();
+
+  @override
+  List<ChangeRecord> diff(E oldValue, E newValue) {
+    return _equality.equals(oldValue, newValue)
+        ? ChangeRecord.NONE
+        : ChangeRecord.ANY;
+  }
+}
diff --git a/pub/observable/lib/src/differs/list_differ.dart b/pub/observable/lib/src/differs/list_differ.dart
new file mode 100644
index 0000000..58004a7
--- /dev/null
+++ b/pub/observable/lib/src/differs/list_differ.dart
@@ -0,0 +1,470 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+part of observable.src.differs;
+
+/// Determines differences between two lists, returning [ListChangeRecord]s.
+///
+/// While [ListChangeRecord] has more information and can be replayed they carry
+/// a more significant cost to calculate and create and should only be used when
+/// the details in the record will actually be used.
+///
+/// See also [EqualityDiffer] for a simpler comparison.
+class ListDiffer<E> implements Differ<List<E>> {
+  final Equality<E> _equality;
+
+  const ListDiffer([this._equality = const DefaultEquality()]);
+
+  @override
+  List<ListChangeRecord<E>> diff(List<E> e1, List<E> e2) {
+    return _calcSplices/*<E>*/(
+      e2,
+      _equality,
+      0,
+      e2.length,
+      e1,
+      0,
+      e1.length,
+    );
+  }
+}
+
+enum _Edit {
+  leave,
+  update,
+  add,
+  delete,
+}
+
+// Note: This function is *based* on the computation of the Levenshtein
+// "edit" distance. The one change is that "updates" are treated as two
+// edits - not one. With List splices, an update is really a delete
+// followed by an add. By retaining this, we optimize for "keeping" the
+// maximum array items in the original array. For example:
+//
+//   'xxxx123' -> '123yyyy'
+//
+// With 1-edit updates, the shortest path would be just to update all seven
+// characters. With 2-edit updates, we delete 4, leave 3, and add 4. This
+// leaves the substring '123' intact.
+List<List<int>> _calcEditDistance/*<E>*/(
+  List/*<E>*/ current,
+  int currentStart,
+  int currentEnd,
+  List/*<E>*/ old,
+  int oldStart,
+  int oldEnd,
+) {
+  // 'Deletion' columns.
+  final rowCount = oldEnd - oldStart + 1;
+  final columnCount = currentEnd - currentStart + 1;
+  final distances = new List<List<int>>(rowCount);
+
+  // 'Addition' rows. Initialize null column.
+  for (var i = 0; i < rowCount; i++) {
+    distances[i] = new List<int>(columnCount);
+    distances[i][0] = i;
+  }
+
+  // Initialize null row.
+  for (var j = 0; j < columnCount; j++) {
+    distances[0][j] = j;
+  }
+
+  for (var i = 1; i < rowCount; i++) {
+    for (var j = 1; j < columnCount; j++) {
+      if (old[oldStart + i - 1] == current[currentStart + j - 1]) {
+        distances[i][j] = distances[i - 1][j - 1];
+      } else {
+        final north = distances[i - 1][j] + 1;
+        final west = distances[i][j - 1] + 1;
+        distances[i][j] = math.min(north, west);
+      }
+    }
+  }
+
+  return distances;
+}
+
+// This starts at the final weight, and walks "backward" by finding
+// the minimum previous weight recursively until the origin of the weight
+// matrix.
+Iterable<_Edit> _spliceOperationsFromEditDistances(List<List<int>> distances) {
+  var i = distances.length - 1;
+  var j = distances[0].length - 1;
+  var current = distances[i][j];
+  final edits = <_Edit>[];
+  while (i > 0 || j > 0) {
+    if (i == 0) {
+      edits.add(_Edit.add);
+      j--;
+      continue;
+    }
+    if (j == 0) {
+      edits.add(_Edit.delete);
+      i--;
+      continue;
+    }
+    final northWest = distances[i - 1][j - 1];
+    final west = distances[i - 1][j];
+    final north = distances[i][j - 1];
+
+    final min = math.min(math.min(west, north), northWest);
+    if (min == northWest) {
+      if (northWest == current) {
+        edits.add(_Edit.leave);
+      } else {
+        edits.add(_Edit.update);
+        current = northWest;
+      }
+      i--;
+      j--;
+    } else if (min == west) {
+      edits.add(_Edit.delete);
+      i--;
+      current = west;
+    } else {
+      edits.add(_Edit.add);
+      j--;
+      current = north;
+    }
+  }
+
+  return edits.reversed;
+}
+
+int _sharedPrefix/*<E>*/(
+  Equality/*<E>*/ equality,
+  List/*<E>*/ e1,
+  List/*<E>*/ e2,
+  int searchLength,
+) {
+  for (var i = 0; i < searchLength; i++) {
+    if (!equality.equals(e1[i], e2[i])) {
+      return i;
+    }
+  }
+  return searchLength;
+}
+
+int _sharedSuffix/*<E>*/(
+  Equality/*<E>*/ equality,
+  List/*<E>*/ e1,
+  List/*<E>*/ e2,
+  int searchLength,
+) {
+  var index1 = e1.length;
+  var index2 = e2.length;
+  var count = 0;
+  while (count < searchLength && equality.equals(e1[--index1], e2[--index2])) {
+    count++;
+  }
+  return count;
+}
+
+// Lacking individual splice mutation information, the minimal set of
+// splices can be synthesized given the previous state and final state of an
+// array. The basic approach is to calculate the edit distance matrix and
+// choose the shortest path through it.
+//
+// Complexity: O(l * p)
+//   l: The length of the current array
+//   p: The length of the old array
+List<ListChangeRecord/*<E>*/ > _calcSplices/*<E>*/(
+  List/*<E>*/ current,
+  Equality/*<E>*/ equality,
+  int currentStart,
+  int currentEnd,
+  List/*<E>*/ old,
+  int oldStart,
+  int oldEnd,
+) {
+  var prefixCount = 0;
+  var suffixCount = 0;
+  final minLength = math.min(currentEnd - currentStart, oldEnd - oldStart);
+  if (currentStart == 0 && oldStart == 0) {
+    prefixCount = _sharedPrefix(
+      equality,
+      current,
+      old,
+      minLength,
+    );
+  }
+  if (currentEnd == current.length && oldEnd == old.length) {
+    suffixCount = _sharedSuffix(
+      equality,
+      current,
+      old,
+      minLength - prefixCount,
+    );
+  }
+
+  currentStart += prefixCount;
+  oldStart += prefixCount;
+  currentEnd -= suffixCount;
+  oldEnd -= suffixCount;
+
+  if (currentEnd - currentStart == 0 && oldEnd - oldStart == 0) {
+    return ChangeRecord.NONE;
+  }
+
+  if (currentStart == currentEnd) {
+    final spliceRemoved = old.sublist(oldStart, oldEnd);
+    return [
+      new ListChangeRecord/*<E>*/ .remove(
+        current,
+        currentStart,
+        spliceRemoved,
+      ),
+    ];
+  }
+  if (oldStart == oldEnd) {
+    return [
+      new ListChangeRecord/*<E>*/ .add(
+        current,
+        currentStart,
+        currentEnd - currentStart,
+      ),
+    ];
+  }
+
+  final ops = _spliceOperationsFromEditDistances(
+    _calcEditDistance(
+      current,
+      currentStart,
+      currentEnd,
+      old,
+      oldStart,
+      oldEnd,
+    ),
+  );
+
+  var spliceIndex = -1;
+  var spliceRemovals = /*<E>*/ [];
+  var spliceAddedCount = 0;
+
+  bool hasSplice() => spliceIndex != -1;
+  void resetSplice() {
+    spliceIndex = -1;
+    spliceRemovals = /*<E>*/ [];
+    spliceAddedCount = 0;
+  }
+
+  var splices = <ListChangeRecord/*<E>*/ >[];
+
+  var index = currentStart;
+  var oldIndex = oldStart;
+  for (final op in ops) {
+    switch (op) {
+      case _Edit.leave:
+        if (hasSplice()) {
+          splices.add(new ListChangeRecord/*<E>*/(
+            current,
+            spliceIndex,
+            removed: spliceRemovals,
+            addedCount: spliceAddedCount,
+          ));
+          resetSplice();
+        }
+        index++;
+        oldIndex++;
+        break;
+      case _Edit.update:
+        if (!hasSplice()) {
+          spliceIndex = index;
+        }
+        spliceAddedCount++;
+        index++;
+        spliceRemovals.add(old[oldIndex]);
+        oldIndex++;
+        break;
+      case _Edit.add:
+        if (!hasSplice()) {
+          spliceIndex = index;
+        }
+        spliceAddedCount++;
+        index++;
+        break;
+      case _Edit.delete:
+        if (!hasSplice()) {
+          spliceIndex = index;
+        }
+        spliceRemovals.add(old[oldIndex]);
+        oldIndex++;
+        break;
+    }
+  }
+  if (hasSplice()) {
+    splices.add(new ListChangeRecord/*<E>*/(
+      current,
+      spliceIndex,
+      removed: spliceRemovals,
+      addedCount: spliceAddedCount,
+    ));
+  }
+  assert(() {
+    splices = new List<ListChangeRecord/*<E>*/ >.unmodifiable(splices);
+    return true;
+  });
+  return splices;
+}
+
+int _intersect(int start1, int end1, int start2, int end2) {
+  return math.min(end1, end2) - math.max(start1, start2);
+}
+
+void _mergeSplices/*<E>*/(
+  List<ListChangeRecord/*<E>*/ > splices,
+  ListChangeRecord/*<E>*/ record,
+) {
+  var spliceIndex = record.index;
+  var spliceRemoved = record.removed;
+  var spliceAdded = record.addedCount;
+
+  var inserted = false;
+  var insertionOffset = 0;
+
+  // I think the way this works is:
+  // - the loop finds where the merge should happen
+  // - it applies the merge in a particular splice
+  // - then continues and updates the subsequent splices with any offset diff.
+  for (var i = 0; i < splices.length; i++) {
+    var current = splices[i];
+    current = splices[i] = new ListChangeRecord/*<E>*/(
+      current.object,
+      current.index + insertionOffset,
+      removed: current.removed,
+      addedCount: current.addedCount,
+    );
+
+    if (inserted) continue;
+
+    var intersectCount = _intersect(
+      spliceIndex,
+      spliceIndex + spliceRemoved.length,
+      current.index,
+      current.index + current.addedCount,
+    );
+    if (intersectCount >= 0) {
+      // Merge the two splices.
+      splices.removeAt(i);
+      i--;
+
+      insertionOffset -= current.addedCount - current.removed.length;
+      spliceAdded += current.addedCount - intersectCount;
+
+      final deleteCount =
+          spliceRemoved.length + current.removed.length - intersectCount;
+      if (spliceAdded == 0 && deleteCount == 0) {
+        // Merged splice is a no-op, discard.
+        inserted = true;
+      } else {
+        final removed = current.removed.toList();
+        if (spliceIndex < current.index) {
+          // Some prefix of splice.removed is prepended to current.removed.
+          removed.insertAll(
+            0,
+            spliceRemoved.getRange(0, current.index - spliceIndex),
+          );
+        }
+        if (spliceIndex + spliceRemoved.length >
+            current.index + current.addedCount) {
+          // Some suffix of splice.removed is appended to current.removed.
+          removed.addAll(spliceRemoved.getRange(
+            current.index + current.addedCount - spliceIndex,
+            spliceRemoved.length,
+          ));
+        }
+        spliceRemoved = removed;
+        if (current.index < spliceIndex) {
+          spliceIndex = current.index;
+        }
+      }
+    } else if (spliceIndex < current.index) {
+      // Insert splice here.
+      inserted = true;
+      splices.insert(
+        i,
+        new ListChangeRecord/*<E>*/(
+          record.object,
+          spliceIndex,
+          removed: spliceRemoved,
+          addedCount: spliceAdded,
+        ),
+      );
+      i++;
+      final offset = spliceAdded - spliceRemoved.length;
+      current = splices[i] = new ListChangeRecord/*<E>*/(
+        current.object,
+        current.index + offset,
+        removed: current.removed,
+        addedCount: current.addedCount,
+      );
+      insertionOffset += offset;
+    }
+  }
+  if (!inserted) {
+    splices.add(new ListChangeRecord/*<E>*/(
+      record.object,
+      spliceIndex,
+      removed: spliceRemoved,
+      addedCount: spliceAdded,
+    ));
+  }
+}
+
+List<ListChangeRecord/*<E>*/ > _createInitialSplices/*<E>*/(
+  List/*<E>*/ list,
+  List<ListChangeRecord/*<E>*/ > records,
+) {
+  final splices = <ListChangeRecord/*<E>*/ >[];
+  for (var i = 0; i < records.length; i++) {
+    _mergeSplices(splices, records[i]);
+  }
+  return splices;
+}
+
+// We need to summarize change records. Consumers of these records want to
+// apply the batch sequentially, and ensure that they can find inserted
+// items by looking at that position in the list. This property does not
+// hold in our record-as-you-go records. Consider:
+//
+//     var model = toObservable(['a', 'b']);
+//     model.removeAt(1);
+//     model.insertAll(0, ['c', 'd', 'e']);
+//     model.removeRange(1, 3);
+//     model.insert(1, 'f');
+//
+// Here, we inserted some records and then removed some of them.
+// If someone processed these records naively, they would "play back" the
+// insert incorrectly, because those items will be shifted.
+List<ListChangeRecord/*<E>*/ > projectListSplices/*<E>*/(
+  List/*<E>*/ list,
+  List<ListChangeRecord/*<E>*/ > records, [
+  Equality/*<E>*/ equality = const DefaultEquality/*<E>*/(),
+]) {
+  if (records.length <= 1) return records;
+  final splices = <ListChangeRecord/*<E>*/ >[];
+  final initialSplices = _createInitialSplices(list, records);
+  for (final splice in initialSplices) {
+    if (splice.addedCount == 1 && splice.removed.length == 1) {
+      if (splice.removed[0] != list[splice.index]) {
+        splices.add(splice);
+      }
+      continue;
+    }
+    splices.addAll(
+      _calcSplices(
+        list,
+        equality,
+        splice.index,
+        splice.index + splice.addedCount,
+        splice.removed,
+        0,
+        splice.removed.length,
+      ),
+    );
+  }
+  return splices;
+}
diff --git a/pub/observable/lib/src/differs/map_differ.dart b/pub/observable/lib/src/differs/map_differ.dart
new file mode 100644
index 0000000..c6041a4
--- /dev/null
+++ b/pub/observable/lib/src/differs/map_differ.dart
@@ -0,0 +1,38 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+part of observable.src.differs;
+
+/// Determines differences between two maps, returning [MapChangeRecord]s.
+///
+/// While [MapChangeRecord] has more information and can be replayed they carry
+/// a more significant cost to calculate and create and should only be used when
+/// the details in the record will actually be used.
+///
+/// See also [EqualityDiffer] for a simpler comparison.
+class MapDiffer<K, V> implements Differ<Map<K, V>> {
+  const MapDiffer();
+
+  @override
+  List<MapChangeRecord<K, V>> diff(Map<K, V> oldValue, Map<K, V> newValue) {
+    if (identical(oldValue, newValue)) {
+      return ChangeRecord.NONE;
+    }
+    final changes = <MapChangeRecord<K, V>>[];
+    oldValue.forEach((oldK, oldV) {
+      final newV = newValue[oldK];
+      if (newV == null && !newValue.containsKey(oldK)) {
+        changes.add(new MapChangeRecord<K, V>.remove(oldK, oldV));
+      } else if (newV != oldV) {
+        changes.add(new MapChangeRecord<K, V>(oldK, oldV, newV));
+      }
+    });
+    newValue.forEach((newK, newV) {
+      if (!oldValue.containsKey(newK)) {
+        changes.add(new MapChangeRecord<K, V>.insert(newK, newV));
+      }
+    });
+    return freezeInDevMode(changes);
+  }
+}
diff --git a/pub/observable/lib/src/internal.dart b/pub/observable/lib/src/internal.dart
new file mode 100644
index 0000000..d0ae2e1
--- /dev/null
+++ b/pub/observable/lib/src/internal.dart
@@ -0,0 +1,12 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+List/*<E>*/ freezeInDevMode/*<E>*/(List/*<E>*/ list) {
+  if (list == null) return const [];
+  assert(() {
+    list = new List/*<E>*/ .unmodifiable(list);
+    return true;
+  });
+  return list;
+}
diff --git a/pub/observable/lib/src/observable.dart b/pub/observable/lib/src/observable.dart
new file mode 100644
index 0000000..2d2c49e
--- /dev/null
+++ b/pub/observable/lib/src/observable.dart
@@ -0,0 +1,107 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library observable.src.observable;
+
+import 'dart:async';
+
+import 'package:meta/meta.dart';
+
+import 'change_notifier.dart';
+import 'records.dart';
+
+/// Represents an object with observable state or properties.
+///
+/// The interface does not require any specific technique to implement
+/// observability. You may implement it in the following ways:
+/// - Extend or mixin [ChangeNotifier]
+/// - Implement the interface yourself and provide your own implementation
+abstract class Observable<C extends ChangeRecord> {
+  // To be removed when https://github.com/dart-lang/observable/issues/10
+  final ChangeNotifier<C> _delegate = new ChangeNotifier<C>();
+
+  // Whether Observable was not given a type.
+  final bool _isNotGeneric = C == dynamic;
+
+  /// Emits a list of changes when the state of the object changes.
+  ///
+  /// Changes should produced in order, if significant.
+  Stream<List<C>> get changes => _delegate.changes;
+
+  /// May override to be notified when [changes] is first observed.
+  @protected
+  @mustCallSuper
+  @Deprecated('Use ChangeNotifier instead to have this method available')
+  // REMOVE IGNORE when https://github.com/dart-lang/observable/issues/10
+  // ignore: invalid_use_of_protected_member
+  void observed() => _delegate.observed();
+
+  /// May override to be notified when [changes] is no longer observed.
+  @protected
+  @mustCallSuper
+  @Deprecated('Use ChangeNotifier instead to have this method available')
+  // REMOVE IGNORE when https://github.com/dart-lang/observable/issues/10
+  // ignore: invalid_use_of_protected_member
+  void unobserved() => _delegate.unobserved();
+
+  /// True if this object has any observers.
+  @Deprecated('Use ChangeNotifier instead to have this method available')
+  bool get hasObservers => _delegate.hasObservers;
+
+  /// If [hasObservers], synchronously emits [changes] that have been queued.
+  ///
+  /// Returns `true` if changes were emitted.
+  @Deprecated('Use ChangeNotifier instead to have this method available')
+  // REMOVE IGNORE when https://github.com/dart-lang/observable/issues/10
+  // ignore: invalid_use_of_protected_member
+  bool deliverChanges() => _delegate.deliverChanges();
+
+  /// Notify that the [field] name of this object has been changed.
+  ///
+  /// The [oldValue] and [newValue] are also recorded. If the two values are
+  /// equal, no change will be recorded.
+  ///
+  /// For convenience this returns [newValue].
+  ///
+  /// ## Deprecated
+  ///
+  /// All [Observable] objects will no longer be required to emit change records
+  /// when any property changes. For example, `ObservableList` will only emit
+  /// on `ObservableList.changes`, instead of on `ObservableList.listChanges`.
+  ///
+  /// If you are using a typed `implements/extends Observable<C>`, it is illegal
+  /// to call this method - will throw an [UnsupportedError] when called.
+  @Deprecated('Use PropertyChangeNotifier')
+  /*=T*/ notifyPropertyChange/*<T>*/(
+    Symbol field,
+    /*=T*/
+    oldValue,
+    /*=T*/
+    newValue,
+  ) {
+    if (hasObservers && oldValue != newValue) {
+      if (_isNotGeneric) {
+        notifyChange(
+          new PropertyChangeRecord(
+            this,
+            field,
+            oldValue,
+            newValue,
+          ) as C,
+        );
+      } else {
+        throw new UnsupportedError('Generic typed Observable does not support');
+      }
+    }
+    return newValue;
+  }
+
+  /// Schedules [change] to be delivered.
+  ///
+  /// If [change] is omitted then [ChangeRecord.ANY] will be sent.
+  ///
+  /// If there are no listeners to [changes], this method does nothing.
+  @Deprecated('Use ChangeNotifier instead to have this method available')
+  void notifyChange([C change]) => _delegate.notifyChange(change);
+}
diff --git a/pub/observable/lib/src/observable_list.dart b/pub/observable/lib/src/observable_list.dart
new file mode 100644
index 0000000..1618ce6
--- /dev/null
+++ b/pub/observable/lib/src/observable_list.dart
@@ -0,0 +1,333 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library observable.src.observable_list;
+
+import 'dart:async';
+import 'dart:collection' show ListBase, UnmodifiableListView;
+
+import 'differs.dart';
+import 'records.dart';
+import 'observable.dart' show Observable;
+
+/// Represents an observable list of model values. If any items are added,
+/// removed, or replaced, then observers that are listening to [changes]
+/// will be notified.
+class ObservableList<E> extends ListBase<E> with Observable {
+  List<ListChangeRecord> _listRecords;
+
+  StreamController<List<ListChangeRecord>> _listChanges;
+
+  /// The inner [List<E>] with the actual storage.
+  final List<E> _list;
+
+  /// Creates an observable list of the given [length].
+  ///
+  /// If no [length] argument is supplied an extendable list of
+  /// length 0 is created.
+  ///
+  /// If a [length] argument is supplied, a fixed size list of that
+  /// length is created.
+  ObservableList([int length])
+      : _list = length != null ? new List<E>(length) : <E>[];
+
+  /// Creates an observable list of the given [length].
+  ///
+  /// This constructor exists to work around an issue in the VM whereby
+  /// classes that derive from [ObservableList] and mixin other classes
+  /// require a default generative constructor in the super class that
+  /// does not take optional arguments.
+  ObservableList.withLength(int length) : this(length);
+
+  /// Creates an observable list with the elements of [other]. The order in
+  /// the list will be the order provided by the iterator of [other].
+  factory ObservableList.from(Iterable<E> other) =>
+      new ObservableList<E>()..addAll(other);
+
+  /// The stream of summarized list changes, delivered asynchronously.
+  ///
+  /// Each list change record contains information about an individual mutation.
+  /// The records are projected so they can be applied sequentially. For
+  /// example, this set of mutations:
+  ///
+  ///     var model = new ObservableList.from(['a', 'b']);
+  ///     model.listChanges.listen((records) => records.forEach(print));
+  ///     model.removeAt(1);
+  ///     model.insertAll(0, ['c', 'd', 'e']);
+  ///     model.removeRange(1, 3);
+  ///     model.insert(1, 'f');
+  ///
+  /// The change records will be summarized so they can be "played back", using
+  /// the final list positions to figure out which item was added:
+  ///
+  ///     #<ListChangeRecord index: 0, removed: [], addedCount: 2>
+  ///     #<ListChangeRecord index: 3, removed: [b], addedCount: 0>
+  ///
+  /// [deliverChanges] can be called to force synchronous delivery.
+  Stream<List<ListChangeRecord>> get listChanges {
+    if (_listChanges == null) {
+      // TODO(jmesserly): split observed/unobserved notions?
+      _listChanges = new StreamController.broadcast(
+        sync: true,
+        onCancel: () {
+          _listChanges = null;
+        },
+      );
+    }
+    return _listChanges.stream;
+  }
+
+  bool get hasListObservers => _listChanges != null && _listChanges.hasListener;
+
+  @override
+  int get length => _list.length;
+
+  @override
+  set length(int value) {
+    int len = _list.length;
+    if (len == value) return;
+
+    // Produce notifications if needed
+    _notifyChangeLength(len, value);
+    if (hasListObservers) {
+      if (value < len) {
+        _notifyListChange(value, removed: _list.getRange(value, len).toList());
+      } else {
+        _notifyListChange(len, addedCount: value - len);
+      }
+    }
+
+    _list.length = value;
+  }
+
+  @override
+  E operator [](int index) => _list[index];
+
+  @override
+  void operator []=(int index, E value) {
+    E oldValue = _list[index];
+    if (hasListObservers && oldValue != value) {
+      _notifyListChange(index, addedCount: 1, removed: [oldValue]);
+    }
+    _list[index] = value;
+  }
+
+  // Forwarders so we can reflect on the properties.
+  @override
+  bool get isEmpty => super.isEmpty;
+
+  @override
+  bool get isNotEmpty => super.isNotEmpty;
+
+  // TODO(jmesserly): should we support first/last/single? They're kind of
+  // dangerous to use in a path because they throw exceptions. Also we'd need
+  // to produce property change notifications which seems to conflict with our
+  // existing list notifications.
+
+  // The following methods are here so that we can provide nice change events.
+  @override
+  void setAll(int index, Iterable<E> iterable) {
+    if (iterable is! List && iterable is! Set) {
+      iterable = iterable.toList();
+    }
+    int length = iterable.length;
+    if (hasListObservers && length > 0) {
+      _notifyListChange(index,
+          addedCount: length, removed: _list.sublist(index, length));
+    }
+    _list.setAll(index, iterable);
+  }
+
+  @override
+  void add(E value) {
+    int len = _list.length;
+    _notifyChangeLength(len, len + 1);
+    if (hasListObservers) {
+      _notifyListChange(len, addedCount: 1);
+    }
+
+    _list.add(value);
+  }
+
+  @override
+  void addAll(Iterable<E> iterable) {
+    int len = _list.length;
+    _list.addAll(iterable);
+
+    _notifyChangeLength(len, _list.length);
+
+    int added = _list.length - len;
+    if (hasListObservers && added > 0) {
+      _notifyListChange(len, addedCount: added);
+    }
+  }
+
+  @override
+  bool remove(Object element) {
+    for (int i = 0; i < this.length; i++) {
+      if (this[i] == element) {
+        removeRange(i, i + 1);
+        return true;
+      }
+    }
+    return false;
+  }
+
+  @override
+  void removeRange(int start, int end) {
+    _rangeCheck(start, end);
+    int rangeLength = end - start;
+    int len = _list.length;
+
+    _notifyChangeLength(len, len - rangeLength);
+    if (hasListObservers && rangeLength > 0) {
+      _notifyListChange(start, removed: _list.getRange(start, end).toList());
+    }
+
+    _list.removeRange(start, end);
+  }
+
+  @override
+  void insertAll(int index, Iterable<E> iterable) {
+    if (index < 0 || index > length) {
+      throw new RangeError.range(index, 0, length);
+    }
+    // TODO(floitsch): we can probably detect more cases.
+    if (iterable is! List && iterable is! Set) {
+      iterable = iterable.toList();
+    }
+    int insertionLength = iterable.length;
+    // There might be errors after the length change, in which case the list
+    // will end up being modified but the operation not complete. Unless we
+    // always go through a "toList" we can't really avoid that.
+    int len = _list.length;
+    _list.length += insertionLength;
+
+    _list.setRange(index + insertionLength, this.length, this, index);
+    _list.setAll(index, iterable);
+
+    _notifyChangeLength(len, _list.length);
+
+    if (hasListObservers && insertionLength > 0) {
+      _notifyListChange(index, addedCount: insertionLength);
+    }
+  }
+
+  @override
+  void insert(int index, E element) {
+    if (index < 0 || index > length) {
+      throw new RangeError.range(index, 0, length);
+    }
+    if (index == length) {
+      add(element);
+      return;
+    }
+    // We are modifying the length just below the is-check. Without the check
+    // Array.copy could throw an exception, leaving the list in a bad state
+    // (with a length that has been increased, but without a new element).
+    if (index is! int) throw new ArgumentError(index);
+    _list.length++;
+    _list.setRange(index + 1, length, this, index);
+
+    _notifyChangeLength(_list.length - 1, _list.length);
+    if (hasListObservers) {
+      _notifyListChange(index, addedCount: 1);
+    }
+    _list[index] = element;
+  }
+
+  @override
+  E removeAt(int index) {
+    E result = this[index];
+    removeRange(index, index + 1);
+    return result;
+  }
+
+  void _rangeCheck(int start, int end) {
+    if (start < 0 || start > this.length) {
+      throw new RangeError.range(start, 0, this.length);
+    }
+    if (end < start || end > this.length) {
+      throw new RangeError.range(end, start, this.length);
+    }
+  }
+
+  void _notifyListChange(
+    int index, {
+    List removed: const [],
+    int addedCount: 0,
+  }) {
+    if (!hasListObservers) return;
+    if (_listRecords == null) {
+      _listRecords = [];
+      scheduleMicrotask(deliverListChanges);
+    }
+    _listRecords.add(new ListChangeRecord(
+      this,
+      index,
+      removed: removed,
+      addedCount: addedCount,
+    ));
+  }
+
+  void _notifyChangeLength(int oldValue, int newValue) {
+    notifyPropertyChange(#length, oldValue, newValue);
+    notifyPropertyChange(#isEmpty, oldValue == 0, newValue == 0);
+    notifyPropertyChange(#isNotEmpty, oldValue != 0, newValue != 0);
+  }
+
+  void discardListChanges() {
+    // Leave _listRecords set so we don't schedule another delivery.
+    if (_listRecords != null) _listRecords = [];
+  }
+
+  bool deliverListChanges() {
+    if (_listRecords == null) return false;
+    List<ListChangeRecord> records =
+        projectListSplices/*<E>*/(this, _listRecords);
+    _listRecords = null;
+
+    if (hasListObservers && records.isNotEmpty) {
+      _listChanges.add(new UnmodifiableListView<ListChangeRecord>(records));
+      return true;
+    }
+    return false;
+  }
+
+  /// Calculates the changes to the list, if lacking individual splice mutation
+  /// information.
+  ///
+  /// This is not needed for change records produced by [ObservableList] itself,
+  /// but it can be used if the list instance was replaced by another list.
+  ///
+  /// The minimal set of splices can be synthesized given the previous state and
+  /// final state of a list. The basic approach is to calculate the edit
+  /// distance matrix and choose the shortest path through it.
+  ///
+  /// Complexity is `O(l * p)` where `l` is the length of the current list and
+  /// `p` is the length of the old list.
+  static List<ListChangeRecord/*<E>*/ > calculateChangeRecords/*<E>*/(
+    List/*<E>*/ oldValue,
+    List/*<E>*/ newValue,
+  ) {
+    return const ListDiffer/*<E>*/().diff(oldValue, newValue);
+  }
+
+  /// Updates the [previous] list using the [changeRecords]. For added items,
+  /// the [current] list is used to find the current value.
+  static void applyChangeRecords(List<Object> previous, List<Object> current,
+      List<ListChangeRecord> changeRecords) {
+    if (identical(previous, current)) {
+      throw new ArgumentError("can't use same list for previous and current");
+    }
+
+    for (ListChangeRecord change in changeRecords) {
+      int addEnd = change.index + change.addedCount;
+      int removeEnd = change.index + change.removed.length;
+
+      Iterable addedItems = current.getRange(change.index, addEnd);
+      previous.replaceRange(change.index, removeEnd, addedItems);
+    }
+  }
+}
diff --git a/pub/observable/lib/src/observable_map.dart b/pub/observable/lib/src/observable_map.dart
new file mode 100644
index 0000000..caebbda
--- /dev/null
+++ b/pub/observable/lib/src/observable_map.dart
@@ -0,0 +1,167 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library observable.src.observable_map;
+
+import 'dart:collection';
+
+import 'observable.dart';
+import 'records.dart';
+import 'to_observable.dart';
+
+// TODO(jmesserly): this needs to be faster. We currently require multiple
+// lookups per key to get the old value.
+// TODO(jmesserly): this doesn't implement the precise interfaces like
+// LinkedHashMap, SplayTreeMap or HashMap. However it can use them for the
+// backing store.
+
+/// Represents an observable map of model values. If any items are added,
+/// removed, or replaced, then observers that are listening to [changes]
+/// will be notified.
+class ObservableMap<K, V> extends Observable implements Map<K, V> {
+  final Map<K, V> _map;
+
+  /// Creates an observable map.
+  ObservableMap() : _map = new HashMap<K, V>();
+
+  /// Creates a new observable map using a [LinkedHashMap].
+  ObservableMap.linked() : _map = new LinkedHashMap<K, V>();
+
+  /// Creates a new observable map using a [SplayTreeMap].
+  ObservableMap.sorted() : _map = new SplayTreeMap<K, V>();
+
+  /// Creates an observable map that contains all key value pairs of [other].
+  /// It will attempt to use the same backing map type if the other map is a
+  /// [LinkedHashMap], [SplayTreeMap], or [HashMap]. Otherwise it defaults to
+  /// [HashMap].
+  ///
+  /// Note this will perform a shallow conversion. If you want a deep conversion
+  /// you should use [toObservable].
+  factory ObservableMap.from(Map<K, V> other) {
+    return new ObservableMap<K, V>.createFromType(other)..addAll(other);
+  }
+
+  /// Like [ObservableMap.from], but creates an empty map.
+  factory ObservableMap.createFromType(Map<K, V> other) {
+    ObservableMap<K, V> result;
+    if (other is SplayTreeMap) {
+      result = new ObservableMap<K, V>.sorted();
+    } else if (other is LinkedHashMap) {
+      result = new ObservableMap<K, V>.linked();
+    } else {
+      result = new ObservableMap<K, V>();
+    }
+    return result;
+  }
+
+  /// Creates a new observable map wrapping [other].
+  ObservableMap.spy(Map<K, V> other) : _map = other;
+
+  @override
+  Iterable<K> get keys => _map.keys;
+
+  @override
+  Iterable<V> get values => _map.values;
+
+  @override
+  int get length => _map.length;
+
+  @override
+  bool get isEmpty => length == 0;
+
+  @override
+  bool get isNotEmpty => !isEmpty;
+
+  @override
+  bool containsValue(Object value) => _map.containsValue(value);
+
+  @override
+  bool containsKey(Object key) => _map.containsKey(key);
+
+  @override
+  V operator [](Object key) => _map[key];
+
+  @override
+  void operator []=(K key, V value) {
+    if (!hasObservers) {
+      _map[key] = value;
+      return;
+    }
+
+    int len = _map.length;
+    V oldValue = _map[key];
+
+    _map[key] = value;
+
+    if (len != _map.length) {
+      notifyPropertyChange(#length, len, _map.length);
+      notifyChange(new MapChangeRecord.insert(key, value));
+      _notifyKeysValuesChanged();
+    } else if (oldValue != value) {
+      notifyChange(new MapChangeRecord(key, oldValue, value));
+      _notifyValuesChanged();
+    }
+  }
+
+  @override
+  void addAll(Map<K, V> other) {
+    other.forEach((K key, V value) {
+      this[key] = value;
+    });
+  }
+
+  @override
+  V putIfAbsent(K key, V ifAbsent()) {
+    int len = _map.length;
+    V result = _map.putIfAbsent(key, ifAbsent);
+    if (hasObservers && len != _map.length) {
+      notifyPropertyChange(#length, len, _map.length);
+      notifyChange(new MapChangeRecord.insert(key, result));
+      _notifyKeysValuesChanged();
+    }
+    return result;
+  }
+
+  @override
+  V remove(Object key) {
+    int len = _map.length;
+    V result = _map.remove(key);
+    if (hasObservers && len != _map.length) {
+      notifyChange(new MapChangeRecord.remove(key, result));
+      notifyPropertyChange(#length, len, _map.length);
+      _notifyKeysValuesChanged();
+    }
+    return result;
+  }
+
+  @override
+  void clear() {
+    int len = _map.length;
+    if (hasObservers && len > 0) {
+      _map.forEach((key, value) {
+        notifyChange(new MapChangeRecord.remove(key, value));
+      });
+      notifyPropertyChange(#length, len, 0);
+      _notifyKeysValuesChanged();
+    }
+    _map.clear();
+  }
+
+  @override
+  void forEach(void f(K key, V value)) => _map.forEach(f);
+
+  @override
+  String toString() => Maps.mapToString(this);
+
+  // Note: we don't really have a reasonable old/new value to use here.
+  // But this should fix "keys" and "values" in templates with minimal overhead.
+  void _notifyKeysValuesChanged() {
+    notifyChange(new PropertyChangeRecord(this, #keys, null, null));
+    _notifyValuesChanged();
+  }
+
+  void _notifyValuesChanged() {
+    notifyChange(new PropertyChangeRecord(this, #values, null, null));
+  }
+}
diff --git a/pub/observable/lib/src/records.dart b/pub/observable/lib/src/records.dart
new file mode 100644
index 0000000..87d7d4f
--- /dev/null
+++ b/pub/observable/lib/src/records.dart
@@ -0,0 +1,28 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library observable.src.records;
+
+import 'package:collection/collection.dart';
+import 'package:quiver/core.dart';
+
+import 'internal.dart';
+
+part 'records/list_change_record.dart';
+part 'records/map_change_record.dart';
+part 'records/property_change_record.dart';
+
+/// Result of a change to an observed object.
+class ChangeRecord {
+  /// Signifies a change occurred, but without details of the specific change.
+  ///
+  /// May be used to produce lower-GC-pressure records where more verbose change
+  /// records will not be used directly.
+  static const List<ChangeRecord> ANY = const [const ChangeRecord()];
+
+  /// Signifies no changes occurred.
+  static const List<ChangeRecord> NONE = const [];
+
+  const ChangeRecord();
+}
diff --git a/pub/observable/lib/src/records/list_change_record.dart b/pub/observable/lib/src/records/list_change_record.dart
new file mode 100644
index 0000000..59867b2
--- /dev/null
+++ b/pub/observable/lib/src/records/list_change_record.dart
@@ -0,0 +1,131 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+part of observable.src.records;
+
+/// A [ChangeRecord] that denotes adding or removing nodes at [index].
+///
+/// It should be assumed that elements are [removed] *before* being added.
+///
+/// A [List<ListChangeRecord>] can be "played back" against the [List] using
+/// the final list positions to figure out which item was added - this removes
+/// the need to incur costly GC on the most common operation (adding).
+class ListChangeRecord<E> implements ChangeRecord {
+  /// How many elements were added at [index] (after removing elements).
+  final int addedCount;
+
+  /// Index of where the change occurred.
+  final int index;
+
+  /// List that changed.
+  final List<E> object;
+
+  /// Elements that were removed starting at [index] (before adding elements).
+  final List<E> removed;
+
+  factory ListChangeRecord(
+    List<E> object,
+    int index, {
+    List<E> removed: const [],
+    int addedCount: 0,
+  }) {
+    return new ListChangeRecord._(object, index, removed, addedCount);
+  }
+
+  /// Records an `add` operation at `object[index]` of [addedCount] elements.
+  ListChangeRecord.add(this.object, this.index, this.addedCount)
+      : removed = const [] {
+    _assertValidState();
+  }
+
+  /// Records a `remove` operation at `object[index]` of [removed] elements.
+  ListChangeRecord.remove(this.object, this.index, List<E> removed)
+      : this.removed = freezeInDevMode/*<E>*/(removed),
+        this.addedCount = 0 {
+    _assertValidState();
+  }
+
+  /// Records a `replace` operation at `object[index]` of [removed] elements.
+  ///
+  /// If [addedCount] is not specified it defaults to `removed.length`.
+  ListChangeRecord.replace(this.object, this.index, List<E> removed,
+      [int addedCount])
+      : this.removed = freezeInDevMode/*<E>*/(removed),
+        this.addedCount = addedCount ?? removed.length {
+    _assertValidState();
+  }
+
+  ListChangeRecord._(
+    this.object,
+    this.index,
+    this.removed,
+    this.addedCount,
+  ) {
+    _assertValidState();
+  }
+
+  /// What elements were added to [object].
+  Iterable<E> get added {
+    return addedCount == 0 ? const [] : object.getRange(index, addedCount);
+  }
+
+  /// Apply this change record to [list].
+  void apply(List<E> list) {
+    list
+      ..removeRange(index, index + removed.length)
+      ..insertAll(index, object.getRange(index, index + addedCount));
+  }
+
+  void _assertValidState() {
+    assert(() {
+      if (object == null) {
+        throw new ArgumentError.notNull('object');
+      }
+      if (index == null) {
+        throw new ArgumentError.notNull('index');
+      }
+      if (removed == null) {
+        throw new ArgumentError.notNull('removed');
+      }
+      if (addedCount == null || addedCount < 0) {
+        throw new ArgumentError('Invalid `addedCount`: $addedCount');
+      }
+      return true;
+    });
+  }
+
+  /// Returns whether [reference] index was changed in this operation.
+  bool indexChanged(int reference) {
+    // If reference was before the change then it wasn't changed.
+    if (reference < index) return false;
+
+    // If this was a shift operation anything after index is changed.
+    if (addedCount != removed.length) return true;
+
+    // Otherwise anything in the update range was changed.
+    return reference < index + addedCount;
+  }
+
+  @override
+  bool operator ==(Object o) {
+    if (o is ListChangeRecord<E>) {
+      return identical(object, o.object) &&
+          index == o.index &&
+          addedCount == o.addedCount &&
+          const ListEquality().equals(removed, o.removed);
+    }
+    return false;
+  }
+
+  @override
+  int get hashCode {
+    return hash4(object, index, addedCount, const ListEquality().hash(removed));
+  }
+
+  @override
+  String toString() => ''
+      '#<$ListChangeRecord index: $index, '
+      'removed: $removed, '
+      'addedCount: $addedCount>';
+}
diff --git a/pub/observable/lib/src/records/map_change_record.dart b/pub/observable/lib/src/records/map_change_record.dart
new file mode 100644
index 0000000..cd37fc9
--- /dev/null
+++ b/pub/observable/lib/src/records/map_change_record.dart
@@ -0,0 +1,82 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+part of observable.src.records;
+
+/// A [ChangeRecord] that denotes adding, removing, or updating a map.
+class MapChangeRecord<K, V> implements ChangeRecord {
+  /// The map key that changed.
+  final K key;
+
+  /// The previous value associated with this key.
+  ///
+  /// Is always `null` if [isInsert].
+  final V oldValue;
+
+  /// The new value associated with this key.
+  ///
+  /// Is always `null` if [isRemove].
+  final V newValue;
+
+  /// True if this key was inserted.
+  final bool isInsert;
+
+  /// True if this key was removed.
+  final bool isRemove;
+
+  /// Create an update record of [key] from [oldValue] to [newValue].
+  const MapChangeRecord(this.key, this.oldValue, this.newValue)
+      : isInsert = false,
+        isRemove = false;
+
+  /// Create an insert record of [key] and [newValue].
+  const MapChangeRecord.insert(this.key, this.newValue)
+      : isInsert = true,
+        isRemove = false,
+        oldValue = null;
+
+  /// Create a remove record of [key] with a former [oldValue].
+  const MapChangeRecord.remove(this.key, this.oldValue)
+      : isInsert = false,
+        isRemove = true,
+        newValue = null;
+
+  /// Apply this change record to [map].
+  void apply(Map<K, V> map) {
+    if (isRemove) {
+      map.remove(key);
+    } else {
+      map[key] = newValue;
+    }
+  }
+
+  @override
+  bool operator ==(Object o) {
+    if (o is MapChangeRecord<K, V>) {
+      return key == o.key &&
+          oldValue == o.oldValue &&
+          newValue == o.newValue &&
+          isInsert == o.isInsert &&
+          isRemove == o.isRemove;
+    }
+    return false;
+  }
+
+  @override
+  int get hashCode {
+    return hashObjects([
+      key,
+      oldValue,
+      newValue,
+      isInsert,
+      isRemove,
+    ]);
+  }
+
+  @override
+  String toString() {
+    final kind = isInsert ? 'insert' : isRemove ? 'remove' : 'set';
+    return '#<MapChangeRecord $kind $key from $oldValue to $newValue';
+  }
+}
diff --git a/pub/observable/lib/src/records/property_change_record.dart b/pub/observable/lib/src/records/property_change_record.dart
new file mode 100644
index 0000000..74566d5
--- /dev/null
+++ b/pub/observable/lib/src/records/property_change_record.dart
@@ -0,0 +1,31 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+part of observable.src.records;
+
+/// A change record to a field of a generic observable object.
+class PropertyChangeRecord<T> implements ChangeRecord {
+  /// Object that changed.
+  final Object object;
+
+  /// Name of the property that changed.
+  final Symbol name;
+
+  /// Previous value of the property.
+  final T oldValue;
+
+  /// New value of the property.
+  final T newValue;
+
+  const PropertyChangeRecord(
+    this.object,
+    this.name,
+    this.oldValue,
+    this.newValue,
+  );
+
+  @override
+  String toString() => ''
+      '#<$PropertyChangeRecord $name from $oldValue to: $newValue';
+}
diff --git a/pub/observable/lib/src/to_observable.dart b/pub/observable/lib/src/to_observable.dart
new file mode 100644
index 0000000..3bf24b6
--- /dev/null
+++ b/pub/observable/lib/src/to_observable.dart
@@ -0,0 +1,51 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library observable.src.to_observable;
+
+import 'dart:collection';
+
+import 'observable.dart' show Observable;
+import 'observable_list.dart' show ObservableList;
+import 'observable_map.dart' show ObservableMap;
+
+/// Converts the [Iterable] or [Map] to an [ObservableList] or [ObservableMap],
+/// respectively. This is a convenience function to make it easier to convert
+/// literals into the corresponding observable collection type.
+///
+/// If [value] is not one of those collection types, or is already [Observable],
+/// it will be returned unmodified.
+///
+/// If [value] is a [Map], the resulting value will use the appropriate kind of
+/// backing map: either [HashMap], [LinkedHashMap], or [SplayTreeMap].
+///
+/// By default this performs a deep conversion, but you can set [deep] to false
+/// for a shallow conversion. This does not handle circular data structures.
+/// If a conversion is peformed, mutations are only observed to the result of
+/// this function. Changing the original collection will not affect it.
+// TODO(jmesserly): ObservableSet?
+toObservable(dynamic value, {bool deep: true}) =>
+    deep ? _toObservableDeep(value) : _toObservableShallow(value);
+
+dynamic _toObservableShallow(dynamic value) {
+  if (value is Observable) return value;
+  if (value is Map) return new ObservableMap.from(value);
+  if (value is Iterable) return new ObservableList.from(value);
+  return value;
+}
+
+dynamic _toObservableDeep(dynamic value) {
+  if (value is Observable) return value;
+  if (value is Map) {
+    var result = new ObservableMap.createFromType(value);
+    value.forEach((k, v) {
+      result[_toObservableDeep(k)] = _toObservableDeep(v);
+    });
+    return result;
+  }
+  if (value is Iterable) {
+    return new ObservableList.from(value.map(_toObservableDeep));
+  }
+  return value;
+}
diff --git a/pub/observable/pubspec.yaml b/pub/observable/pubspec.yaml
new file mode 100644
index 0000000..f326c5d
--- /dev/null
+++ b/pub/observable/pubspec.yaml
@@ -0,0 +1,14 @@
+name: observable
+version: 0.17.0+1
+author: Dart Team <misc@dartlang.org>
+description: Support for marking objects as observable
+homepage: https://github.com/dart-lang/observable
+environment:
+  sdk: '>=1.19.0 <2.0.0'
+dependencies:
+  collection: '^1.11.0'
+  meta: '^1.0.4'
+  quiver: '^0.24.0'
+dev_dependencies:
+  dart_style: '^0.2.0'
+  test: '^0.12.0'
diff --git a/pub/observable/tool/presubmit.sh b/pub/observable/tool/presubmit.sh
new file mode 100755
index 0000000..364bee7
--- /dev/null
+++ b/pub/observable/tool/presubmit.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+
+# Make sure dartfmt is run on everything
+# This assumes you have dart_style as a dev_dependency
+echo "Checking dartfmt..."
+NEEDS_DARTFMT="$(find lib test -name "*.dart" | xargs pub run dart_style:format -n)"
+if [[ ${NEEDS_DARTFMT} != "" ]]
+then
+  echo "FAILED"
+  echo "${NEEDS_DARTFMT}"
+  exit 1
+fi
+echo "PASSED"
+
+# Make sure we pass the analyzer
+echo "Checking dartanalyzer..."
+FAILS_ANALYZER="$(find lib test -name "*.dart" | xargs dartanalyzer --options .analysis_options)"
+if [[ $FAILS_ANALYZER == *"[error]"* ]]
+then
+  echo "FAILED"
+  echo "${FAILS_ANALYZER}"
+  exit 1
+fi
+echo "PASSED"
+
+# Fail on anything that fails going forward.
+set -e
+
+pub run test
diff --git a/pub/observe/.analysis_options b/pub/observe/.analysis_options
new file mode 100644
index 0000000..a10d4c5
--- /dev/null
+++ b/pub/observe/.analysis_options
@@ -0,0 +1,2 @@
+analyzer:
+  strong-mode: true
diff --git a/pub/observe/.gitignore b/pub/observe/.gitignore
new file mode 100644
index 0000000..f20a0d0
--- /dev/null
+++ b/pub/observe/.gitignore
@@ -0,0 +1,18 @@
+# Don’t commit the following directories created by pub.
+.pub
+build/
+packages
+.packages
+
+# Or the files created by dart2js.
+*.dart.js
+*.dart.precompiled.js
+*.js_
+*.js.deps
+*.js.map
+*.sw?
+.idea/
+.pub/
+
+# Include when developing application packages.
+pubspec.lock
diff --git a/pub/observe/.test_config b/pub/observe/.test_config
new file mode 100644
index 0000000..dfc533f
--- /dev/null
+++ b/pub/observe/.test_config
@@ -0,0 +1,5 @@
+{
+  "test_package": {
+    "barback": true
+  }
+}
diff --git a/pub/observe/AUTHORS b/pub/observe/AUTHORS
new file mode 100644
index 0000000..0617765
--- /dev/null
+++ b/pub/observe/AUTHORS
@@ -0,0 +1,9 @@
+# Names should be added to this file with this pattern:
+#
+# For individuals:
+#   Name <email address>
+#
+# For organizations:
+#   Organization <fnmatch pattern>
+#
+Google Inc. <*@google.com>
diff --git a/pub/observe/BUILD.gn b/pub/observe/BUILD.gn
new file mode 100644
index 0000000..aa8261b
--- /dev/null
+++ b/pub/observe/BUILD.gn
@@ -0,0 +1,25 @@
+# This file is generated by importer.py for observe-0.15.0
+
+import("//build/dart/dart_package.gni")
+
+dart_package("observe") {
+  package_name = "observe"
+
+  source_dir = "lib"
+
+  disable_analysis = true
+
+  deps = [
+    "//third_party/dart-pkg/pub/source_maps",
+    "//third_party/dart-pkg/pub/barback",
+    "//third_party/dart-pkg/pub/code_transformers",
+    "//third_party/dart-pkg/pub/logging",
+    "//dart/third_party/pkg/func",
+    "//third_party/dart-pkg/pub/observable",
+    "//dart/pkg/analyzer",
+    "//third_party/dart-pkg/pub/smoke",
+    "//third_party/dart-pkg/pub/source_span",
+    "//third_party/dart-pkg/pub/utf",
+    "//third_party/dart-pkg/pub/path",
+  ]
+}
diff --git a/pub/observe/CHANGELOG.md b/pub/observe/CHANGELOG.md
new file mode 100644
index 0000000..07d54d2
--- /dev/null
+++ b/pub/observe/CHANGELOG.md
@@ -0,0 +1,129 @@
+#### 0.14.0
+
+* Update to be built on top of `package:observable`. Contains the following
+  breaking changes:
+  - `Observable` now lives in `package:observable` and behaves like the old
+    `ChangeNotifier` did (except that it's now the base class) - with subclasses
+    manually notifying listeners of changes via `notifyPropertyChange()`.
+  - `ChangeNotifier` has been removed.
+  - `ObservableList` has been moved to `package:observable`.
+  - `ObservableMap` has been moved to `package:observable`.
+  - `toObservable()` has been moved to `package:observable`.
+  - `Observable` (the one with dirty checking) in `package:observe` has been
+    renamed `AutoObservable`
+
+#### 0.13.5
+
+* Fixed strong mode errors and warnings
+
+#### 0.13.4
+
+* Fixed strong mode errors and warnings
+
+#### 0.13.3+1
+
+* Add support for code_transformers `0.4.x`.
+
+#### 0.13.3
+
+* Update to the `test` package.
+
+#### 0.13.2
+
+* Update to analyzer '^0.27.0'.
+
+#### 0.13.1+3
+
+ * Sorting an already sorted list will no longer yield new change notifications.
+
+#### 0.13.1+2
+
+ * Update to analyzer '<0.27.0'
+
+#### 0.13.1+1
+
+ * Update to logging `<0.12.0`.
+
+#### 0.13.1
+
+ * Update to analyzer `<0.26.0`.
+
+#### 0.13.0+2
+  * Fixed `close` in `PathObserver` so it doesn't leak observers.
+  * Ported the benchmarks from
+    [observe-js](https://github.com/Polymer/observe-js/tree/master/benchmark).
+
+#### 0.13.0+1
+  * Widen the constraint on analyzer.
+
+#### 0.13.0
+  * Don't output log files by default in release mode, and provide option to
+    turn them off entirely.
+  * Changed the api for the ObserveTransformer to use named arguments.
+
+#### 0.12.2+1
+  * Cleanup some method signatures.
+
+#### 0.12.2
+  * Updated to match release 0.5.1
+    [observe-js#d530515](https://github.com/Polymer/observe-js/commit/d530515).
+
+#### 0.12.1+1
+  * Expand stack_trace version constraint.
+
+#### 0.12.1
+  * Upgraded error messages to have a unique and stable identifier.
+
+#### 0.12.0
+  * Old transform.dart file removed. If you weren't use it it, this change is
+    backwards compatible with version 0.11.0.
+
+#### 0.11.0+5
+  * Widen the constraint on analyzer.
+
+#### 0.11.0+4
+  * Raise the lower bound on the source_maps constraint to exclude incompatible
+    versions.
+
+#### 0.11.0+3
+  * Widen the constraint on source_maps.
+
+#### 0.11.0+2
+  * Widen the constraint on barback.
+
+#### 0.11.0+1
+  * Switch from `source_maps`' `Span` class to `source_span`'s `SourceSpan`
+    class.
+
+#### 0.11.0
+  * Updated to match [observe-js#e212e74][e212e74] (release 0.3.4), which also
+    matches [observe-js#fa70c37][fa70c37] (release 0.4.2).
+  * ListPathObserver has been deprecated  (it was deleted a while ago in
+    observe-js). We plan to delete it in a future release. You may copy the code
+    if you still need it.
+  * PropertyPath now uses an expression syntax including indexers. For example,
+    you can write `a.b["m"]` instead of `a.b.m`.
+  * **breaking change**: PropertyPath no longer allows numbers as fields, you
+    need to use indexers instead. For example, you now need to write `a[3].d`
+    instead of `a.3.d`.
+  * **breaking change**: PathObserver.value= no longer discards changes (this is
+    in combination with a change in template_binding and polymer to improve
+    interop with JS custom elements).
+
+#### 0.10.0+3
+  * minor changes to documentation, deprecated `discardListChages` in favor of
+    `discardListChanges` (the former had a typo).
+
+#### 0.10.0
+  * package:observe no longer declares @MirrorsUsed. The package uses mirrors
+    for development time, but assumes frameworks (like polymer) and apps that
+    use it directly will either generate code that replaces the use of mirrors,
+    or add the @MirrorsUsed declaration themselves. For convinience, you can
+    import 'package:observe/mirrors_used.dart', and that will add a @MirrorsUsed
+    annotation that preserves properties and classes labeled with @reflectable
+    and properties labeled with @observable.
+  * Updated to match [observe-js#0152d54][0152d54]
+
+[fa70c37]: https://github.com/Polymer/observe-js/blob/fa70c37099026225876f7c7a26bdee7c48129f1c/src/observe.js
+[0152d54]: https://github.com/Polymer/observe-js/blob/0152d542350239563d0f2cad39d22d3254bd6c2a/src/observe.js
+[e212e74]: https://github.com/Polymer/observe-js/blob/e212e7473962067c099a3d1859595c2f8baa36d7/src/observe.js
diff --git a/pub/observe/LICENSE b/pub/observe/LICENSE
new file mode 100644
index 0000000..ee99930
--- /dev/null
+++ b/pub/observe/LICENSE
@@ -0,0 +1,26 @@
+Copyright 2013, the Dart project authors. 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.
+    * Neither the name of Google Inc. nor the names of its
+      contributors may 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 THE COPYRIGHT
+OWNER OR CONTRIBUTORS 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.
diff --git a/pub/observe/PATENTS b/pub/observe/PATENTS
new file mode 100644
index 0000000..6954196
--- /dev/null
+++ b/pub/observe/PATENTS
@@ -0,0 +1,23 @@
+Additional IP Rights Grant (Patents)
+
+"This implementation" means the copyrightable works distributed by
+Google as part of the Dart Project.
+
+Google hereby grants to you a perpetual, worldwide, non-exclusive,
+no-charge, royalty-free, irrevocable (except as stated in this
+section) patent license to make, have made, use, offer to sell, sell,
+import, transfer, and otherwise run, modify and propagate the contents
+of this implementation of Dart, where such license applies only to
+those patent claims, both currently owned by Google and acquired in
+the future, licensable by Google that are necessarily infringed by
+this implementation of Dart. This grant does not include claims that
+would be infringed only as a consequence of further modification of
+this implementation. If you or your agent or exclusive licensee
+institute or order or agree to the institution of patent litigation
+against any entity (including a cross-claim or counterclaim in a
+lawsuit) alleging that this implementation of Dart or any code
+incorporated within this implementation of Dart constitutes direct or
+contributory patent infringement, or inducement of patent
+infringement, then any patent rights granted to you under this License
+for this implementation of Dart shall terminate as of the date such
+litigation is filed.
diff --git a/pub/observe/README.md b/pub/observe/README.md
new file mode 100644
index 0000000..9e641b5
--- /dev/null
+++ b/pub/observe/README.md
@@ -0,0 +1,65 @@
+# observe
+
+Support for marking objects as observable, and getting notifications when those
+objects are mutated.
+
+This library is used to observe changes to [AutoObservable][] types. It also
+has helpers to make implementing and using [Observable][] objects easy.
+
+You can provide an observable object in two ways. The simplest way is to
+use dirty checking to discover changes automatically:
+
+```dart
+import 'package:observe/observe.dart';
+import 'package:observe/mirrors_used.dart'; // for smaller code
+
+class Monster extends Unit with AutoObservable {
+  @observable int health = 100;
+
+  void damage(int amount) {
+    print('$this takes $amount damage!');
+    health -= amount;
+  }
+
+  toString() => 'Monster with $health hit points';
+}
+
+main() {
+  var obj = new Monster();
+  obj.changes.listen((records) {
+    print('Changes to $obj were: $records');
+  });
+  // No changes are delivered until we check for them
+  obj.damage(10);
+  obj.damage(20);
+  print('dirty checking!');
+  Observable.dirtyCheck();
+  print('done!');
+}
+```
+
+**Note**: by default this package uses mirrors to access getters and setters
+marked with `@reflectable`. Dart2js disables tree-shaking if there are any
+uses of mirrors, unless you declare how mirrors are used (via the
+[MirrorsUsed](https://api.dartlang.org/apidocs/channels/stable/#dart-mirrors.MirrorsUsed)
+annotation).
+
+As of version 0.10.0, this package doesn't declare `@MirrorsUsed`. This is
+because we intend to use mirrors for development time, but assume that
+frameworks and apps that use this pacakge will either generate code that
+replaces the use of mirrors, or add the `@MirrorsUsed` declaration
+themselves.  For convenience, you can import
+`package:observe/mirrors_used.dart` as shown on the first example above.
+That will add a `@MirrorsUsed` annotation that preserves properties and
+classes labeled with `@reflectable` and properties labeled with
+`@observable`.
+
+If you are using the `package:observe/mirrors_used.dart` import, you can
+also make use of `@reflectable` on your own classes and dart2js will
+preserve all of its members for reflection.
+
+[Tools](https://www.dartlang.org/polymer-dart/) exist to convert the first
+form into the second form automatically, to get the best of both worlds.
+
+[AutoObservable]: http://www.dartdocs.org/documentation/observe/latest/index.html#observe/observe.AutoObservable
+[AutoObservable.dirtyCheck]: http://www.dartdocs.org/documentation/observe/latest/index.html#observe/observe.AutoObservable@id_dirtyCheck
diff --git a/pub/observe/codereview.settings b/pub/observe/codereview.settings
new file mode 100644
index 0000000..ade7e33
--- /dev/null
+++ b/pub/observe/codereview.settings
@@ -0,0 +1,3 @@
+CODE_REVIEW_SERVER: http://codereview.chromium.org/
+VIEW_VC: https://github.com/dart-lang/observe/commit/
+CC_LIST: reviews@dartlang.org
diff --git a/pub/observe/lib/html.dart b/pub/observe/lib/html.dart
new file mode 100644
index 0000000..e29b0db
--- /dev/null
+++ b/pub/observe/lib/html.dart
@@ -0,0 +1,83 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// TODO(jmesserly): can we handle this more elegantly?
+// In general, it seems like we want a convenient way to take a Stream plus a
+// getter and convert this into an AutoObservable.
+
+/// Helpers for exposing dart:html as observable data.
+@Deprecated('Parts of Observe used to support Polymer will move out of library')
+library observe.html;
+
+import 'dart:html';
+
+import 'package:observable/observable.dart';
+
+import 'observe.dart';
+
+/// An observable version of [window.location.hash].
+final ObservableLocationHash windowLocation = new ObservableLocationHash._();
+
+class ObservableLocationHash extends PropertyChangeNotifier {
+  Object _currentHash;
+
+  ObservableLocationHash._() {
+    // listen on changes to #hash in the URL
+    // Note: listen on both popState and hashChange, because IE9 doesn't support
+    // history API. See http://dartbug.com/5483
+    // TODO(jmesserly): only listen to these if someone is listening to our
+    // changes.
+    window.onHashChange.listen(_notifyHashChange);
+    window.onPopState.listen(_notifyHashChange);
+
+    _currentHash = hash;
+  }
+
+  @reflectable
+  String get hash => window.location.hash;
+
+  /// Pushes a new URL state, similar to the affect of clicking a link.
+  /// Has no effect if the [value] already equals [window.location.hash].
+  @reflectable
+  void set hash(String value) {
+    if (value == hash) return;
+
+    window.history.pushState(null, '', value);
+    _notifyHashChange(null);
+  }
+
+  void _notifyHashChange(Event _) {
+    var oldValue = _currentHash;
+    _currentHash = hash;
+    notifyPropertyChange(#hash, oldValue, _currentHash);
+  }
+}
+
+/// *Deprecated* use [CssClassSet.toggle] instead.
+///
+/// Add or remove CSS class [className] based on the [value].
+@deprecated
+void updateCssClass(Element element, String className, bool value) {
+  if (value == true) {
+    element.classes.add(className);
+  } else {
+    element.classes.remove(className);
+  }
+}
+
+/// *Deprecated* use `class="{{ binding }}"` in your HTML instead. It will also
+/// work on a `<polymer-element>`.
+///
+/// Bind a CSS class to the observable [object] and property [path].
+@deprecated
+PathObserver bindCssClass(
+    Element element, String className, AutoObservable object, String path) {
+  callback(value) {
+    updateCssClass(element, className, value);
+  }
+
+  var obs = new PathObserver(object, path);
+  callback(obs.open(callback));
+  return obs;
+}
diff --git a/pub/observe/lib/mirrors_used.dart b/pub/observe/lib/mirrors_used.dart
new file mode 100644
index 0000000..3710359
--- /dev/null
+++ b/pub/observe/lib/mirrors_used.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// An empty library that declares what needs to be retained with [MirrorsUsed]
+/// if you were to use observables together with mirrors. By default this is not
+/// included because frameworks using this package also use code generation to
+/// avoid using mirrors at deploy time.
+@Deprecated('Parts of Observe used to support Polymer will move out of library')
+library observe.mirrors_used;
+
+// Note: ObservableProperty is in this list only for the unusual use case of
+// invoking dart2js without running this package's transformers. The
+// transformer in `lib/transformer.dart` will replace @observable with the
+// @reflectable annotation.
+@MirrorsUsed(
+    metaTargets: const [Reflectable, ObservableProperty],
+    override: 'smoke.mirrors')
+import 'dart:mirrors' show MirrorsUsed;
+import 'package:observe/observe.dart' show Reflectable, ObservableProperty;
diff --git a/pub/observe/lib/observe.dart b/pub/observe/lib/observe.dart
new file mode 100644
index 0000000..b43db31
--- /dev/null
+++ b/pub/observe/lib/observe.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library observe;
+
+// This library contains code ported from observe-js:
+// https://github.com/Polymer/observe-js/blob/0152d542350239563d0f2cad39d22d3254bd6c2a/src/observe.js
+// We port what is needed for data bindings. Most of the functionality is
+// ported, except where differences are needed for Dart's AutoObservable type.
+
+export 'package:observable/observable.dart';
+export 'src/auto_observable.dart';
+export 'src/bindable.dart';
+export 'src/bind_property.dart';
+export 'src/list_path_observer.dart';
+export 'src/metadata.dart';
+export 'src/observable_box.dart';
+export 'src/observer_transform.dart';
+export 'src/path_observer.dart' hide getSegmentsOfPropertyPathForTesting;
diff --git a/pub/observe/lib/src/auto_observable.dart b/pub/observe/lib/src/auto_observable.dart
new file mode 100644
index 0000000..2fae812
--- /dev/null
+++ b/pub/observe/lib/src/auto_observable.dart
@@ -0,0 +1,155 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library observe.src.auto_observable;
+
+import 'dart:async';
+import 'dart:collection';
+
+import 'package:observable/observable.dart';
+import 'package:smoke/smoke.dart' as smoke;
+
+import 'dirty_check.dart' show dirtyCheckObservables, registerObservable;
+import 'metadata.dart' show ObservableProperty;
+
+abstract class AutoObservable implements ChangeNotifier {
+  /// Performs dirty checking of objects that inherit from [AutoObservable].
+  /// This scans all observed objects using mirrors and determines if any fields
+  /// have changed. If they have, it delivers the changes for the object.
+  static void dirtyCheck() => dirtyCheckObservables();
+
+  StreamController<List<ChangeRecord>> _changes;
+
+  Map<Symbol, Object> _values;
+  List<ChangeRecord> _records;
+
+  /// The stream of change records to this object. Records will be delivered
+  /// asynchronously.
+  ///
+  /// [deliverChanges] can be called to force synchronous delivery.
+  @override
+  Stream<List<ChangeRecord>> get changes {
+    if (_changes == null) {
+      _changes = new StreamController.broadcast(
+          sync: true, onListen: observed, onCancel: unobserved);
+    }
+    return _changes.stream;
+  }
+
+  /// True if this object has any observers, and should call
+  /// [notifyChange] for changes.
+  @override
+  bool get hasObservers => _changes != null && _changes.hasListener;
+
+  @override
+  void observed() {
+    // Register this object for dirty checking purposes.
+    registerObservable(this);
+
+    var values = new Map<Symbol, Object>();
+
+    // Note: we scan for @observable regardless of whether the base type
+    // actually includes this mixin. While perhaps too inclusive, it lets us
+    // avoid complex logic that walks "with" and "implements" clauses.
+    var queryOptions = new smoke.QueryOptions(
+        includeInherited: true,
+        includeProperties: false,
+        withAnnotations: const [ObservableProperty]);
+    for (var decl in smoke.query(this.runtimeType, queryOptions)) {
+      var name = decl.name;
+      // Note: since this is a field, getting the value shouldn't execute
+      // user code, so we don't need to worry about errors.
+      values[name] = smoke.read(this, name);
+    }
+
+    _values = values;
+  }
+
+  /// Release data associated with observation.
+  @override
+  void unobserved() {
+    // Note: we don't need to explicitly unregister from the dirty check list.
+    // This will happen automatically at the next call to dirtyCheck.
+    if (_values != null) {
+      _values = null;
+    }
+  }
+
+  /// Synchronously deliver pending [changes]. Returns true if any records were
+  /// delivered, otherwise false.
+  // TODO(jmesserly): this is a bit different from the ES Harmony version, which
+  // allows delivery of changes to a particular observer:
+  // http://wiki.ecmascript.org/doku.php?id=harmony:observe#object.deliverchangerecords
+  //
+  // The rationale for that, and for async delivery in general, is the principal
+  // that you shouldn't run code (observers) when it doesn't expect to be run.
+  // If you do that, you risk violating invariants that the code assumes.
+  //
+  // For this reason, we need to match the ES Harmony version. The way we can do
+  // this in Dart is to add a method on StreamSubscription (possibly by
+  // subclassing Stream* types) that immediately delivers records for only
+  // that subscription. Alternatively, we could consider using something other
+  // than Stream to deliver the multicast change records, and provide an
+  // Observable->Stream adapter.
+  //
+  // Also: we should be delivering changes to the observer (subscription) based
+  // on the birth order of the observer. This is for compatibility with ES
+  // Harmony as well as predictability for app developers.
+  @override
+  bool deliverChanges() {
+    if (_values == null || !hasObservers) return false;
+
+    // Start with manually notified records (computed properties, etc),
+    // then scan all fields for additional changes.
+    var records = _records;
+    _records = null;
+
+    _values.forEach((name, oldValue) {
+      var newValue = smoke.read(this, name);
+      if (oldValue != newValue) {
+        if (records == null) records = [];
+        records.add(new PropertyChangeRecord(this, name, oldValue, newValue));
+        _values[name] = newValue;
+      }
+    });
+
+    if (records == null) return false;
+
+    _changes.add(new UnmodifiableListView<ChangeRecord>(records));
+    return true;
+  }
+
+  /// Notify that the field [name] of this object has been changed.
+  ///
+  /// The [oldValue] and [newValue] are also recorded. If the two values are
+  /// equal, no change will be recorded.
+  ///
+  /// For convenience this returns [newValue].
+  @override
+  /*=T*/ notifyPropertyChange/*<T>*/(
+      Symbol field, /*=T*/ oldValue, /*=T*/ newValue) {
+    if (hasObservers && oldValue != newValue) {
+      notifyChange(new PropertyChangeRecord(this, field, oldValue, newValue));
+    }
+    return newValue;
+  }
+
+  /// Notify observers of a change.
+  ///
+  /// For most objects [AutoObservable.notifyPropertyChange] is more
+  /// convenient, but collections sometimes deliver other types of changes
+  /// such as a [ListChangeRecord].
+  ///
+  /// Notes:
+  /// - This is *not* required for fields if you mixin or extend
+  ///   [AutoObservable], but you can use it for computed properties.
+  /// - Unlike [Observable] this will not schedule [deliverChanges]; use
+  ///   [AutoObservable.dirtyCheck] instead.
+  @override
+  void notifyChange([ChangeRecord record]) {
+    if (record == null || !hasObservers) return;
+    if (_records == null) _records = [];
+    _records.add(record);
+  }
+}
diff --git a/pub/observe/lib/src/bind_property.dart b/pub/observe/lib/src/bind_property.dart
new file mode 100644
index 0000000..7f1df91
--- /dev/null
+++ b/pub/observe/lib/src/bind_property.dart
@@ -0,0 +1,38 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library observe.src.bind_property;
+
+import 'dart:async';
+import 'package:observable/observable.dart' as obs;
+
+/// Forwards an observable property from one object to another. For example:
+///
+///     class MyModel extends AutoObservable {
+///       StreamSubscription _sub;
+///       MyOtherModel _otherModel;
+///
+///       MyModel() {
+///         ...
+///         _sub = onPropertyChange(_otherModel, #value,
+///             () => notifyPropertyChange(#prop, oldValue, newValue);
+///       }
+///
+///       String get prop => _otherModel.value;
+///       set prop(String value) { _otherModel.value = value; }
+///     }
+///
+/// See also [notifyPropertyChange].
+// TODO(jmesserly): make this an instance method?
+StreamSubscription onPropertyChange(
+    obs.Observable source, Symbol sourceName, void callback()) {
+  return source.changes.listen((records) {
+    for (var record in records) {
+      if (record is obs.PropertyChangeRecord && record.name == sourceName) {
+        callback();
+        break;
+      }
+    }
+  });
+}
diff --git a/pub/observe/lib/src/bindable.dart b/pub/observe/lib/src/bindable.dart
new file mode 100644
index 0000000..c3314a2
--- /dev/null
+++ b/pub/observe/lib/src/bindable.dart
@@ -0,0 +1,42 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library observe.src.bindable;
+
+/// An object that can be data bound.
+// Normally this is used with 'package:template_binding'.
+// TODO(jmesserly): Node.bind polyfill calls this "observable"
+abstract class Bindable {
+  // Dart note: changed setValue to be "set value" and discardChanges() to
+  // be "get value".
+
+  /// Initiates observation and returns the initial value.
+  /// The callback will be called with the updated [value].
+  ///
+  /// Some subtypes may chose to provide additional arguments, such as
+  /// [PathObserver] providing the old value as the second argument.
+  /// However, they must support callbacks with as few as 0 or 1 argument.
+  /// This can be implemented by performing an "is" type test on the callback.
+  open(callback);
+
+  /// Stops future notifications and frees the reference to the callback passed
+  /// to [open], so its memory can be collected even if this Bindable is alive.
+  void close();
+
+  /// Gets the current value of the bindings.
+  /// Note: once the value of a [Bindable] is fetched, the callback passed to
+  /// [open] should not be called again with this new value.
+  /// In other words, any pending change notifications must be discarded.
+  // TODO(jmesserly): I don't like a getter with side effects. Should we just
+  // rename the getter/setter pair to discardChanges/setValue like they are in
+  // JavaScript?
+  get value;
+
+  /// This can be implemented for two-way bindings. By default does nothing.
+  set value(newValue) {}
+
+  /// Deliver changes. Typically this will perform dirty-checking, if any is
+  /// needed.
+  void deliver() {}
+}
diff --git a/pub/observe/lib/src/dirty_check.dart b/pub/observe/lib/src/dirty_check.dart
new file mode 100644
index 0000000..c60753c
--- /dev/null
+++ b/pub/observe/lib/src/dirty_check.dart
@@ -0,0 +1,136 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// *Warning*: this library is **internal**, and APIs are subject to change.
+///
+/// Tracks observable objects for dirty checking and testing purposes.
+///
+/// It can collect all observed objects, which can be used to trigger
+/// predictable delivery of all pending changes in a test, including objects
+/// allocated internally to another library, such as those in
+/// `package:template_binding`.
+library observe.src.dirty_check;
+
+import 'dart:async';
+
+import 'package:func/func.dart';
+import 'package:logging/logging.dart';
+import 'package:observable/observable.dart' as obs show Observable;
+
+import 'auto_observable.dart' show AutoObservable;
+
+/// The number of active observables in the system.
+int get allObservablesCount => _allObservablesCount;
+
+int _allObservablesCount = 0;
+
+List<obs.Observable> _allObservables = null;
+
+bool _delivering = false;
+
+void registerObservable(obs.Observable obj) {
+  if (_allObservables == null) _allObservables = <obs.Observable>[];
+  _allObservables.add(obj);
+  _allObservablesCount++;
+}
+
+/// Synchronously deliver all change records for known observables.
+///
+/// This will execute [AutoObservable.deliverChanges] on objects that inherit from
+/// [AutoObservable].
+// Note: this is called performMicrotaskCheckpoint in change_summary.js.
+void dirtyCheckObservables() {
+  if (_delivering) return;
+  if (_allObservables == null) return;
+
+  _delivering = true;
+
+  int cycles = 0;
+  bool anyChanged = false;
+  List debugLoop = null;
+  do {
+    cycles++;
+    if (cycles == MAX_DIRTY_CHECK_CYCLES) {
+      debugLoop = [];
+    }
+
+    var toCheck = _allObservables;
+    _allObservables = <obs.Observable>[];
+    anyChanged = false;
+
+    for (int i = 0; i < toCheck.length; i++) {
+      final observer = toCheck[i];
+      if (observer.hasObservers) {
+        if (observer.deliverChanges()) {
+          anyChanged = true;
+          if (debugLoop != null) debugLoop.add([i, observer]);
+        }
+        _allObservables.add(observer);
+      }
+    }
+  } while (cycles < MAX_DIRTY_CHECK_CYCLES && anyChanged);
+
+  if (debugLoop != null && anyChanged) {
+    _logger.warning('Possible loop in AutoObservable.dirtyCheck, stopped '
+        'checking.');
+    for (final info in debugLoop) {
+      _logger.warning('In last iteration AutoObservable changed at index '
+          '${info[0]}, object: ${info[1]}.');
+    }
+  }
+
+  _allObservablesCount = _allObservables.length;
+  _delivering = false;
+}
+
+const MAX_DIRTY_CHECK_CYCLES = 1000;
+
+/// Log for messages produced at runtime by this library. Logging can be
+/// configured by accessing Logger.root from the logging library.
+final Logger _logger = new Logger('AutoObservable.dirtyCheck');
+
+/// Creates a [ZoneSpecification] to set up automatic dirty checking after each
+/// batch of async operations. This ensures that change notifications are always
+/// delivered. Typically used via [dirtyCheckZone].
+ZoneSpecification dirtyCheckZoneSpec() {
+  bool pending = false;
+
+  enqueueDirtyCheck(ZoneDelegate parent, Zone zone) {
+    // Only schedule one dirty check per microtask.
+    if (pending) return;
+
+    pending = true;
+    parent.scheduleMicrotask(zone, () {
+      pending = false;
+      AutoObservable.dirtyCheck();
+    });
+  }
+
+  Func0 wrapCallback(Zone self, ZoneDelegate parent, Zone zone, f()) {
+    // TODO(jmesserly): why does this happen?
+    if (f == null) return f;
+    return () {
+      enqueueDirtyCheck(parent, zone);
+      return f();
+    };
+  }
+
+  Func1 wrapUnaryCallback(Zone self, ZoneDelegate parent, Zone zone, f(x)) {
+    // TODO(jmesserly): why does this happen?
+    if (f == null) return f;
+    return (x) {
+      enqueueDirtyCheck(parent, zone);
+      return f(x);
+    };
+  }
+
+  return new ZoneSpecification(
+      registerCallback: wrapCallback, registerUnaryCallback: wrapUnaryCallback);
+}
+
+/// Forks a [Zone] off the current one that does dirty-checking automatically
+/// after each batch of async operations. Equivalent to:
+///
+///     Zone.current.fork(specification: dirtyCheckZoneSpec());
+Zone dirtyCheckZone() => Zone.current.fork(specification: dirtyCheckZoneSpec());
diff --git a/pub/observe/lib/src/list_path_observer.dart b/pub/observe/lib/src/list_path_observer.dart
new file mode 100644
index 0000000..c329103
--- /dev/null
+++ b/pub/observe/lib/src/list_path_observer.dart
@@ -0,0 +1,73 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library observe.src.list_path_observer;
+
+import 'dart:async';
+import 'package:observable/observable.dart';
+import 'package:observe/observe.dart';
+
+// Inspired by ArrayReduction at:
+// https://raw.github.com/rafaelw/ChangeSummary/master/util/array_reduction.js
+// The main difference is we support anything on the rich Dart Iterable API.
+
+/// Observes a path starting from each item in the list.
+@deprecated
+class ListPathObserver<E, P> extends PropertyChangeNotifier {
+  final ObservableList<E> list;
+  final String _itemPath;
+  final List<PathObserver> _observers = <PathObserver>[];
+  StreamSubscription _sub;
+  bool _scheduled = false;
+  Iterable<P> _value;
+
+  ListPathObserver(this.list, String path) : _itemPath = path {
+    // TODO(jmesserly): delay observation until we are observed.
+    _sub = list.listChanges.listen((records) {
+      for (var record in records) {
+        _observeItems(record.addedCount - record.removed.length);
+      }
+      _scheduleReduce(null);
+    });
+
+    _observeItems(list.length);
+    _reduce();
+  }
+
+  @reflectable
+  Iterable<P> get value => _value;
+
+  void dispose() {
+    if (_sub != null) _sub.cancel();
+    _observers.forEach((o) => o.close());
+    _observers.clear();
+  }
+
+  void _reduce() {
+    _scheduled = false;
+    var newValue = _observers.map((o) => o.value as P);
+    _value = notifyPropertyChange(#value, _value, newValue);
+  }
+
+  void _scheduleReduce(_) {
+    if (_scheduled) return;
+    _scheduled = true;
+    scheduleMicrotask(_reduce);
+  }
+
+  void _observeItems(int lengthAdjust) {
+    if (lengthAdjust > 0) {
+      for (int i = 0; i < lengthAdjust; i++) {
+        int len = _observers.length;
+        var pathObs = new PathObserver(list, '[$len].$_itemPath');
+        pathObs.open(_scheduleReduce);
+        _observers.add(pathObs);
+      }
+    } else if (lengthAdjust < 0) {
+      for (int i = 0; i < -lengthAdjust; i++) {
+        _observers.removeLast().close();
+      }
+    }
+  }
+}
diff --git a/pub/observe/lib/src/messages.dart b/pub/observe/lib/src/messages.dart
new file mode 100644
index 0000000..f2c46f5
--- /dev/null
+++ b/pub/observe/lib/src/messages.dart
@@ -0,0 +1,51 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Contains all warning messages produced by the observe transformer.
+library observe.src.messages;
+
+import 'package:code_transformers/messages/messages.dart';
+
+const NO_OBSERVABLE_ON_LIBRARY = const MessageTemplate(
+    const MessageId('observe', 1),
+    '@observable on a library no longer has any effect. '
+    'Instead, annotate individual fields as @observable.',
+    '`@observable` not supported on libraries',
+    _COMMON_MESSAGE_WHERE_TO_USE_OBSERVABLE);
+
+const NO_OBSERVABLE_ON_TOP_LEVEL = const MessageTemplate(
+    const MessageId('observe', 2),
+    'Top-level fields can no longer be observable. '
+    'Observable fields must be in observable objects.',
+    '`@observable` not supported on top-level fields',
+    _COMMON_MESSAGE_WHERE_TO_USE_OBSERVABLE);
+
+const NO_OBSERVABLE_ON_CLASS = const MessageTemplate(
+    const MessageId('observe', 3),
+    '@observable on a class no longer has any effect. '
+    'Instead, annotate individual fields as @observable.',
+    '`@observable` not supported on classes',
+    _COMMON_MESSAGE_WHERE_TO_USE_OBSERVABLE);
+
+const NO_OBSERVABLE_ON_STATIC_FIELD = const MessageTemplate(
+    const MessageId('observe', 4),
+    'Static fields can no longer be observable. '
+    'Observable fields must be in observable objects.',
+    '`@observable` not supported on static fields',
+    _COMMON_MESSAGE_WHERE_TO_USE_OBSERVABLE);
+
+const REQUIRE_OBSERVABLE_INTERFACE = const MessageTemplate(
+    const MessageId('observe', 5),
+    'Observable fields must be in observable objects. '
+    'Change this class to extend, mix in, or implement AutoObservable.',
+    '`@observable` field not in an `AutoObservable` class',
+    _COMMON_MESSAGE_WHERE_TO_USE_OBSERVABLE);
+
+const String _COMMON_MESSAGE_WHERE_TO_USE_OBSERVABLE = '''
+Only instance fields on `Observable` classes can be observable,
+and you must explicitly annotate each observable field as `@observable`.
+
+Support for using the `@observable` annotation in libraries, classes, and
+elsewhere is deprecated.
+''';
diff --git a/pub/observe/lib/src/metadata.dart b/pub/observe/lib/src/metadata.dart
new file mode 100644
index 0000000..15e7905
--- /dev/null
+++ b/pub/observe/lib/src/metadata.dart
@@ -0,0 +1,51 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library observe.src.metadata;
+
+/// Use `@observable` to make a field automatically observable, or to indicate
+/// that a property is observable. This only works on classes that extend or
+/// mix in `AutoObservable`.
+const ObservableProperty observable = const ObservableProperty();
+
+/// An annotation that is used to make a property observable.
+/// Normally this is used via the [observable] constant, for example:
+///
+///     class Monster extends AutoObservable {
+///       @observable int health;
+///     }
+///
+// TODO(sigmund): re-add this to the documentation when it's really true:
+//     If needed, you can subclass this to create another annotation that will
+//     also be treated as observable.
+// Note: observable properties imply reflectable.
+class ObservableProperty {
+  const ObservableProperty();
+}
+
+/// This can be used to retain any properties that you wish to access with
+/// Dart's mirror system. If you import `package:observe/mirrors_used.dart`, all
+/// classes or members annotated with `@reflectable` wil be preserved by dart2js
+/// during compilation.  This is necessary to make the member visible to
+/// `PathObserver`, or similar systems, once the code is deployed, if you are
+/// not doing a different kind of code-generation for your app. If you are using
+/// polymer, you most likely don't need to use this annotation anymore.
+const Reflectable reflectable = const Reflectable();
+
+/// An annotation that is used to make a type or member reflectable. This makes
+/// it available to `PathObserver` at runtime. For example:
+///
+///     @reflectable
+///     class Monster extends AutoObservable {
+///       int _health;
+///       int get health => _health;
+///       ...
+///     }
+///     ...
+///       // This will work even if the code has been tree-shaken/minified:
+///       final monster = new Monster();
+///       new PathObserver(monster, 'health').changes.listen(...);
+class Reflectable {
+  const Reflectable();
+}
diff --git a/pub/observe/lib/src/observable_box.dart b/pub/observe/lib/src/observable_box.dart
new file mode 100644
index 0000000..5749890
--- /dev/null
+++ b/pub/observe/lib/src/observable_box.dart
@@ -0,0 +1,30 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library observe.src.observable_box;
+
+import 'package:observable/observable.dart';
+import 'package:observe/observe.dart';
+
+// TODO(jmesserly): should the property name be configurable?
+// That would be more convenient.
+/// An observable box that holds a value. Use this if you want to store a single
+/// value. For other cases, it is better to use [AutoObservableList],
+/// [AutoObservableMap], or a custom [AutoObservable] implementation based on
+/// [AutoObservable]. The property name for changes is "value".
+class ObservableBox<T> extends PropertyChangeNotifier {
+  T _value;
+
+  ObservableBox([T initialValue]) : _value = initialValue;
+
+  @reflectable
+  T get value => _value;
+
+  @reflectable
+  void set value(T newValue) {
+    _value = notifyPropertyChange(#value, _value, newValue);
+  }
+
+  String toString() => '#<$runtimeType value: $value>';
+}
diff --git a/pub/observe/lib/src/observer_transform.dart b/pub/observe/lib/src/observer_transform.dart
new file mode 100644
index 0000000..735e3ab
--- /dev/null
+++ b/pub/observe/lib/src/observer_transform.dart
@@ -0,0 +1,85 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library observe.src.observer_transform;
+
+import 'package:observe/observe.dart';
+
+/// ObserverTransform is used to dynamically transform observed value(s).
+///
+///    var obj = new ObservableBox(10);
+///    var observer = new PathObserver(obj, 'value');
+///    var transform = new ObserverTransform(observer,
+///        (x) => x * 2, setValue: (x) => x ~/ 2);
+///
+///    // Open returns the current value of 20.
+///    transform.open((newValue) => print('new: $newValue'));
+///
+///    obj.value = 20; // prints 'new: 40' async
+///    new Future(() {
+///      transform.value = 4; // obj.value will be 2
+///    });
+///
+/// ObserverTransform can also be used to reduce a set of observed values to a
+/// single value:
+///
+///    var obj = new AutoObservableMap.from({'a': 1, 'b': 2, 'c': 3});
+///    var observer = new CompoundObserver()
+///      ..addPath(obj, 'a')
+///      ..addPath(obj, 'b')
+///      ..addPath(obj, 'c');
+///
+///    var transform = new ObserverTransform(observer,
+///        (values) => values.fold(0, (x, y) => x + y));
+///
+///    // Open returns the current value of 6.
+///    transform.open((newValue) => print('new: $newValue'));
+///
+///    obj['a'] = 2;
+///    obj['c'] = 10; // will print 'new 14' asynchronously
+///
+class ObserverTransform extends Bindable {
+  Bindable _bindable;
+  Function _getTransformer, _setTransformer;
+  Function _notifyCallback;
+  var _value;
+
+  ObserverTransform(Bindable bindable, computeValue(value), {setValue(value)})
+      : _bindable = bindable,
+        _getTransformer = computeValue,
+        _setTransformer = setValue;
+
+  open(callback) {
+    _notifyCallback = callback;
+    _value = _getTransformer(_bindable.open(_observedCallback));
+    return _value;
+  }
+
+  _observedCallback(newValue) {
+    final value = _getTransformer(newValue);
+    if (value == _value) return null;
+    _value = value;
+    return _notifyCallback(value);
+  }
+
+  void close() {
+    if (_bindable != null) _bindable.close();
+    _bindable = null;
+    _getTransformer = null;
+    _setTransformer = null;
+    _notifyCallback = null;
+    _value = null;
+  }
+
+  get value => _value = _getTransformer(_bindable.value);
+
+  set value(newValue) {
+    if (_setTransformer != null) {
+      newValue = _setTransformer(newValue);
+    }
+    _bindable.value = newValue;
+  }
+
+  deliver() => _bindable.deliver();
+}
diff --git a/pub/observe/lib/src/path_observer.dart b/pub/observe/lib/src/path_observer.dart
new file mode 100644
index 0000000..a8e7868
--- /dev/null
+++ b/pub/observe/lib/src/path_observer.dart
@@ -0,0 +1,921 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library observe.src.path_observer;
+
+import 'dart:async';
+import 'dart:collection';
+import 'dart:math' show min;
+
+import 'package:logging/logging.dart' show Logger, Level;
+import 'package:observable/observable.dart';
+import 'package:observe/observe.dart';
+import 'package:smoke/smoke.dart' as smoke;
+
+import 'package:utf/utf.dart' show stringToCodepoints;
+
+/// A data-bound path starting from a view-model or model object, for example
+/// `foo.bar.baz`.
+///
+/// When [open] is called, this will observe changes to the object and any
+/// intermediate object along the path, and send updated values accordingly.
+/// When [close] is called it will stop observing the objects.
+///
+/// This class is used to implement `Node.bind` and similar functionality in
+/// the [template_binding](pub.dartlang.org/packages/template_binding) package.
+class PathObserver extends _Observer implements Bindable {
+  PropertyPath _path;
+  Object _object;
+  _ObservedSet _directObserver;
+
+  /// Observes [path] on [object] for changes. This returns an object
+  /// that can be used to get the changes and get/set the value at this path.
+  ///
+  /// The path can be a [PropertyPath], or a [String] used to construct it.
+  ///
+  /// See [open] and [value].
+  PathObserver(Object object, [path])
+      : _object = object,
+        _path = new PropertyPath(path);
+
+  PropertyPath get path => _path;
+
+  /// Sets the value at this path.
+  void set value(newValue) {
+    if (_path != null) _path.setValueFrom(_object, newValue);
+  }
+
+  int get _reportArgumentCount => 2;
+
+  /// Initiates observation and returns the initial value.
+  /// The callback will be passed the updated [value], and may optionally be
+  /// declared to take a second argument, which will contain the previous value.
+  open(callback) => super.open(callback);
+
+  void _connect() {
+    _directObserver = new _ObservedSet(this, _object);
+    _check(skipChanges: true);
+  }
+
+  void _disconnect() {
+    _value = null;
+    if (_directObserver != null) {
+      _directObserver.close(this);
+      _directObserver = null;
+    }
+    // Dart note: the JS impl does not do this, but it seems consistent with
+    // CompoundObserver. After closing the PathObserver can't be reopened.
+    _path = null;
+    _object = null;
+  }
+
+  void _iterateObjects(void observe(obj, prop)) {
+    _path._iterateObjects(_object, observe);
+  }
+
+  bool _check({bool skipChanges: false}) {
+    var oldValue = _value;
+    _value = _path.getValueFrom(_object);
+    if (skipChanges || _value == oldValue) return false;
+
+    _report(_value, oldValue, this);
+    return true;
+  }
+}
+
+/// A dot-delimieted property path such as "foo.bar" or "foo.10.bar".
+///
+/// The path specifies how to get a particular value from an object graph, where
+/// the graph can include arrays and maps. Each segment of the path describes
+/// how to take a single step in the object graph. Properties like 'foo' or
+/// 'bar' are read as properties on objects, or as keys if the object is a [Map]
+/// or a [Indexable], while integer values are read as indexes in a [List].
+// TODO(jmesserly): consider specialized subclasses for:
+// * empty path
+// * "value"
+// * single token in path, e.g. "foo"
+class PropertyPath {
+  /// The segments of the path.
+  final List<Object> _segments;
+
+  /// Creates a new [PropertyPath]. These can be stored to avoid excessive
+  /// parsing of path strings.
+  ///
+  /// The provided [path] should be a String or a List. If it is a list it
+  /// should contain only Symbols and integers. This can be used to avoid
+  /// parsing.
+  ///
+  /// Note that this constructor will canonicalize identical paths in some cases
+  /// to save memory, but this is not guaranteed. Use [==] for comparions
+  /// purposes instead of [identical].
+  // Dart note: this is ported from `function getPath`.
+  factory PropertyPath([path]) {
+    if (path is PropertyPath) return path;
+    if (path == null || (path is List && path.isEmpty)) path = '';
+
+    if (path is List) {
+      var copy = new List.from(path, growable: false);
+      for (var segment in copy) {
+        // Dart note: unlike Javascript, we don't support arbitraty objects that
+        // can be converted to a String.
+        // TODO(sigmund): consider whether we should support that here. It might
+        // be easier to add support for that if we switch first to use strings
+        // for everything instead of symbols.
+        if (segment is! int && segment is! String && segment is! Symbol) {
+          throw new ArgumentError(
+              'List must contain only ints, Strings, and Symbols');
+        }
+      }
+      return new PropertyPath._(copy);
+    }
+
+    var pathObj = _pathCache[path];
+    if (pathObj != null) return pathObj;
+
+    final segments = new _PathParser().parse(path);
+    if (segments == null) return _InvalidPropertyPath._instance;
+
+    // TODO(jmesserly): we could use an UnmodifiableListView here, but that adds
+    // memory overhead.
+    pathObj = new PropertyPath._(segments.toList(growable: false));
+    if (_pathCache.length >= _pathCacheLimit) {
+      _pathCache.remove(_pathCache.keys.first);
+    }
+    _pathCache[path] = pathObj;
+    return pathObj;
+  }
+
+  PropertyPath._(this._segments);
+
+  int get length => _segments.length;
+  bool get isEmpty => _segments.isEmpty;
+  bool get isValid => true;
+
+  String toString() {
+    if (!isValid) return '<invalid path>';
+    var sb = new StringBuffer();
+    bool first = true;
+    for (var key in _segments) {
+      if (key is Symbol) {
+        if (!first) sb.write('.');
+        sb.write(smoke.symbolToName(key));
+      } else {
+        _formatAccessor(sb, key);
+      }
+      first = false;
+    }
+    return sb.toString();
+  }
+
+  _formatAccessor(StringBuffer sb, Object key) {
+    if (key is int) {
+      sb.write('[$key]');
+    } else {
+      sb.write('["${key.toString().replaceAll('"', '\\"')}"]');
+    }
+  }
+
+  bool operator ==(other) {
+    if (identical(this, other)) return true;
+    if (other is! PropertyPath) return false;
+    if (isValid != other.isValid) return false;
+
+    int len = _segments.length;
+    if (len != other._segments.length) return false;
+    for (int i = 0; i < len; i++) {
+      if (_segments[i] != other._segments[i]) return false;
+    }
+    return true;
+  }
+
+  /// This is the [Jenkins hash function][1] but using masking to keep
+  /// values in SMI range.
+  /// [1]: http://en.wikipedia.org/wiki/Jenkins_hash_function
+  // TODO(jmesserly): should reuse this instead, see
+  // https://code.google.com/p/dart/issues/detail?id=11617
+  int get hashCode {
+    int hash = 0;
+    for (int i = 0, len = _segments.length; i < len; i++) {
+      hash = 0x1fffffff & (hash + _segments[i].hashCode);
+      hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
+      hash = hash ^ (hash >> 6);
+    }
+    hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
+    hash = hash ^ (hash >> 11);
+    return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
+  }
+
+  /// Returns the current value of the path from the provided [obj]ect.
+  getValueFrom(Object obj) {
+    if (!isValid) return null;
+    for (var segment in _segments) {
+      if (obj == null) return null;
+      obj = _getObjectProperty(obj, segment);
+    }
+    return obj;
+  }
+
+  /// Attempts to set the [value] of the path from the provided [obj]ect.
+  /// Returns true if and only if the path was reachable and set.
+  bool setValueFrom(Object obj, Object value) {
+    var end = _segments.length - 1;
+    if (end < 0) return false;
+    for (int i = 0; i < end; i++) {
+      if (obj == null) return false;
+      obj = _getObjectProperty(obj, _segments[i]);
+    }
+    return _setObjectProperty(obj, _segments[end], value);
+  }
+
+  void _iterateObjects(Object obj, void observe(obj, prop)) {
+    if (!isValid || isEmpty) return;
+
+    int i = 0, last = _segments.length - 1;
+    while (obj != null) {
+      // _segments[i] is passed to indicate that we are only observing that
+      // property of obj. See observe declaration in _ObservedSet.
+      observe(obj, _segments[i]);
+
+      if (i >= last) break;
+      obj = _getObjectProperty(obj, _segments[i++]);
+    }
+  }
+
+  // Dart note: it doesn't make sense to have compiledGetValueFromFn in Dart.
+}
+
+/// Visible only for testing:
+getSegmentsOfPropertyPathForTesting(p) => p._segments;
+
+class _InvalidPropertyPath extends PropertyPath {
+  static final _instance = new _InvalidPropertyPath();
+
+  bool get isValid => false;
+  _InvalidPropertyPath() : super._([]);
+}
+
+/// Properties in [Map] that need to be read as properties and not as keys in
+/// the map. We exclude methods ('containsValue', 'containsKey', 'putIfAbsent',
+/// 'addAll', 'remove', 'clear', 'forEach') because there is no use in reading
+/// them as part of path-observer segments.
+const _MAP_PROPERTIES = const [#keys, #values, #length, #isEmpty, #isNotEmpty];
+
+_getObjectProperty(object, property) {
+  if (object == null) return null;
+
+  if (property is int) {
+    if (object is List && property >= 0 && property < object.length) {
+      return object[property];
+    }
+  } else if (property is String) {
+    return object[property];
+  } else if (property is Symbol) {
+    // Support indexer if available, e.g. Maps or polymer_expressions Scope.
+    // This is the default syntax used by polymer/nodebind and
+    // polymer/observe-js PathObserver.
+    // TODO(sigmund): should we also support using checking dynamically for
+    // whether the type practically implements the indexer API
+    // (smoke.hasInstanceMethod(type, const Symbol('[]')))?
+    if (object is Indexable ||
+        object is Map && !_MAP_PROPERTIES.contains(property)) {
+      return object[smoke.symbolToName(property)];
+    }
+    try {
+      return smoke.read(object, property);
+    } on NoSuchMethodError catch (_) {
+      // Rethrow, unless the type implements noSuchMethod, in which case we
+      // interpret the exception as a signal that the method was not found.
+      // Dart note: getting invalid properties is an error, unlike in JS where
+      // it returns undefined.
+      if (!smoke.hasNoSuchMethod(object.runtimeType)) rethrow;
+    }
+  }
+
+  if (_logger.isLoggable(Level.FINER)) {
+    _logger.finer("can't get $property in $object");
+  }
+  return null;
+}
+
+bool _setObjectProperty(object, property, value) {
+  if (object == null) return false;
+
+  if (property is int) {
+    if (object is List && property >= 0 && property < object.length) {
+      object[property] = value;
+      return true;
+    }
+  } else if (property is Symbol) {
+    // Support indexer if available, e.g. Maps or polymer_expressions Scope.
+    if (object is Indexable ||
+        object is Map && !_MAP_PROPERTIES.contains(property)) {
+      object[smoke.symbolToName(property)] = value;
+      return true;
+    }
+    try {
+      smoke.write(object, property, value);
+      return true;
+    } on NoSuchMethodError catch (_) {
+      if (!smoke.hasNoSuchMethod(object.runtimeType)) rethrow;
+    }
+  }
+
+  if (_logger.isLoggable(Level.FINER)) {
+    _logger.finer("can't set $property in $object");
+  }
+  return false;
+}
+
+// From: https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js
+
+final RegExp _identRegExp = () {
+  const identStart = '[\$_a-zA-Z]';
+  const identPart = '[\$_a-zA-Z0-9]';
+  return new RegExp('^$identStart+$identPart*\$');
+}();
+
+_isIdent(s) => _identRegExp.hasMatch(s);
+
+// Dart note: refactored to convert to codepoints once and operate on codepoints
+// rather than characters.
+class _PathParser {
+  List<Object> keys = [];
+  int index = -1;
+  String key;
+
+  final Map<String, Map<String, List<String>>> _pathStateMachine = {
+    'beforePath': {
+      'ws': ['beforePath'],
+      'ident': ['inIdent', 'append'],
+      '[': ['beforeElement'],
+      'eof': ['afterPath']
+    },
+    'inPath': {
+      'ws': ['inPath'],
+      '.': ['beforeIdent'],
+      '[': ['beforeElement'],
+      'eof': ['afterPath']
+    },
+    'beforeIdent': {
+      'ws': ['beforeIdent'],
+      'ident': ['inIdent', 'append']
+    },
+    'inIdent': {
+      'ident': ['inIdent', 'append'],
+      '0': ['inIdent', 'append'],
+      'number': ['inIdent', 'append'],
+      'ws': ['inPath', 'push'],
+      '.': ['beforeIdent', 'push'],
+      '[': ['beforeElement', 'push'],
+      'eof': ['afterPath', 'push']
+    },
+    'beforeElement': {
+      'ws': ['beforeElement'],
+      '0': ['afterZero', 'append'],
+      'number': ['inIndex', 'append'],
+      "'": ['inSingleQuote', 'append', ''],
+      '"': ['inDoubleQuote', 'append', '']
+    },
+    'afterZero': {
+      'ws': ['afterElement', 'push'],
+      ']': ['inPath', 'push']
+    },
+    'inIndex': {
+      '0': ['inIndex', 'append'],
+      'number': ['inIndex', 'append'],
+      'ws': ['afterElement'],
+      ']': ['inPath', 'push']
+    },
+    'inSingleQuote': {
+      "'": ['afterElement'],
+      'eof': ['error'],
+      'else': ['inSingleQuote', 'append']
+    },
+    'inDoubleQuote': {
+      '"': ['afterElement'],
+      'eof': ['error'],
+      'else': ['inDoubleQuote', 'append']
+    },
+    'afterElement': {
+      'ws': ['afterElement'],
+      ']': ['inPath', 'push']
+    }
+  };
+
+  /// From getPathCharType: determines the type of a given [code]point.
+  String _getPathCharType(code) {
+    if (code == null) return 'eof';
+    switch (code) {
+      case 0x5B: // [
+      case 0x5D: // ]
+      case 0x2E: // .
+      case 0x22: // "
+      case 0x27: // '
+      case 0x30: // 0
+        return _char(code);
+
+      case 0x5F: // _
+      case 0x24: // $
+        return 'ident';
+
+      case 0x20: // Space
+      case 0x09: // Tab
+      case 0x0A: // Newline
+      case 0x0D: // Return
+      case 0xA0: // No-break space
+      case 0xFEFF: // Byte Order Mark
+      case 0x2028: // Line Separator
+      case 0x2029: // Paragraph Separator
+        return 'ws';
+    }
+
+    // a-z, A-Z
+    if ((0x61 <= code && code <= 0x7A) || (0x41 <= code && code <= 0x5A))
+      return 'ident';
+
+    // 1-9
+    if (0x31 <= code && code <= 0x39) return 'number';
+
+    return 'else';
+  }
+
+  static String _char(int codepoint) => new String.fromCharCodes([codepoint]);
+
+  void push() {
+    if (key == null) return;
+
+    // Dart note: we store the keys with different types, rather than
+    // parsing/converting things later in toString.
+    if (_isIdent(key)) {
+      keys.add(smoke.nameToSymbol(key));
+    } else {
+      var index = int.parse(key, radix: 10, onError: (_) => null);
+      keys.add(index != null ? index : key);
+    }
+    key = null;
+  }
+
+  void append(newChar) {
+    key = (key == null) ? newChar : '$key$newChar';
+  }
+
+  bool _maybeUnescapeQuote(String mode, codePoints) {
+    if (index >= codePoints.length) return false;
+    var nextChar = _char(codePoints[index + 1]);
+    if ((mode == 'inSingleQuote' && nextChar == "'") ||
+        (mode == 'inDoubleQuote' && nextChar == '"')) {
+      index++;
+      append(nextChar);
+      return true;
+    }
+    return false;
+  }
+
+  /// Returns the parsed keys, or null if there was a parse error.
+  List<Object> parse(String path) {
+    var codePoints = stringToCodepoints(path);
+    var mode = 'beforePath';
+
+    while (mode != null) {
+      index++;
+      var c = index >= codePoints.length ? null : codePoints[index];
+
+      if (c != null &&
+          _char(c) == '\\' &&
+          _maybeUnescapeQuote(mode, codePoints)) continue;
+
+      var type = _getPathCharType(c);
+      if (mode == 'error') return null;
+
+      var typeMap = _pathStateMachine[mode];
+      var transition = typeMap[type];
+      if (transition == null) transition = typeMap['else'];
+      if (transition == null) return null; // parse error;
+
+      mode = transition[0];
+      var actionName = transition.length > 1 ? transition[1] : null;
+      if (actionName == 'push' && key != null) push();
+      if (actionName == 'append') {
+        var newChar = transition.length > 2 && transition[2] != null
+            ? transition[2]
+            : _char(c);
+        append(newChar);
+      }
+
+      if (mode == 'afterPath') return keys;
+    }
+    return null; // parse error
+  }
+}
+
+final Logger _logger = new Logger('observe.PathObserver');
+
+/// This is a simple cache. It's like LRU but we don't update an item on a
+/// cache hit, because that would require allocation. Better to let it expire
+/// and reallocate the PropertyPath.
+// TODO(jmesserly): this optimization is from observe-js, how valuable is it in
+// practice?
+final _pathCache = new LinkedHashMap<String, PropertyPath>();
+
+/// The size of a path like "foo.bar" is approximately 160 bytes, so this
+/// reserves ~16Kb of memory for recently used paths. Since paths are frequently
+/// reused, the theory is that this ends up being a good tradeoff in practice.
+// (Note: the 160 byte estimate is from Dart VM 1.0.0.10_r30798 on x64 without
+// using UnmodifiableListView in PropertyPath)
+const int _pathCacheLimit = 100;
+
+/// [CompoundObserver] is a [Bindable] object which knows how to listen to
+/// multiple values (registered via [addPath] or [addObserver]) and invoke a
+/// callback when one or more of the values have changed.
+///
+///    var obj = new ObservableMap.from({'a': 1, 'b': 2});
+///    var otherObj = new ObservableMap.from({'c': 3});
+///
+///    var observer = new CompoundObserver()
+///      ..addPath(obj, 'a');
+///      ..addObserver(new PathObserver(obj, 'b'));
+///      ..addPath(otherObj, 'c');
+///      ..open((values) {
+///        for (int i = 0; i < values.length; i++) {
+///          print('The value at index $i is now ${values[i]}');
+///        }
+///      });
+///
+///   obj['a'] = 10; // print will be triggered async
+///
+class CompoundObserver extends _Observer implements Bindable {
+  _ObservedSet _directObserver;
+  bool _reportChangesOnOpen;
+  List _observed = [];
+
+  CompoundObserver([this._reportChangesOnOpen = false]) {
+    _value = [];
+  }
+
+  int get _reportArgumentCount => 3;
+
+  /// Initiates observation and returns the initial value.
+  /// The callback will be passed the updated [value], and may optionally be
+  /// declared to take a second argument, which will contain the previous value.
+  ///
+  /// Implementation note: a third argument can also be declared, which will
+  /// receive a list of objects and paths, such that `list[2 * i]` will access
+  /// the object and `list[2 * i + 1]` will access the path, where `i` is the
+  /// order of the [addPath] call. This parameter is only used by
+  /// `package:polymer` as a performance optimization, and should not be relied
+  /// on in new code.
+  open(callback) => super.open(callback);
+
+  void _connect() {
+    for (var i = 0; i < _observed.length; i += 2) {
+      var object = _observed[i];
+      if (!identical(object, _observerSentinel)) {
+        _directObserver = new _ObservedSet(this, object);
+        break;
+      }
+    }
+
+    _check(skipChanges: !_reportChangesOnOpen);
+  }
+
+  void _disconnect() {
+    for (var i = 0; i < _observed.length; i += 2) {
+      if (identical(_observed[i], _observerSentinel)) {
+        _observed[i + 1].close();
+      }
+    }
+
+    _observed = null;
+    _value = null;
+
+    if (_directObserver != null) {
+      _directObserver.close(this);
+      _directObserver = null;
+    }
+  }
+
+  /// Adds a dependency on the property [path] accessed from [object].
+  /// [path] can be a [PropertyPath] or a [String]. If it is omitted an empty
+  /// path will be used.
+  void addPath(Object object, [path]) {
+    if (_isOpen || _isClosed) {
+      throw new StateError('Cannot add paths once started.');
+    }
+
+    path = new PropertyPath(path);
+    _observed..add(object)..add(path);
+    if (!_reportChangesOnOpen) return;
+    _value.add(path.getValueFrom(object));
+  }
+
+  void addObserver(Bindable observer) {
+    if (_isOpen || _isClosed) {
+      throw new StateError('Cannot add observers once started.');
+    }
+
+    _observed..add(_observerSentinel)..add(observer);
+    if (!_reportChangesOnOpen) return;
+    _value.add(observer.open((_) => deliver()));
+  }
+
+  void _iterateObjects(void observe(obj, prop)) {
+    for (var i = 0; i < _observed.length; i += 2) {
+      var object = _observed[i];
+      if (!identical(object, _observerSentinel)) {
+        (_observed[i + 1] as PropertyPath)._iterateObjects(object, observe);
+      }
+    }
+  }
+
+  bool _check({bool skipChanges: false}) {
+    bool changed = false;
+    _value.length = _observed.length ~/ 2;
+    var oldValues = null;
+    for (var i = 0; i < _observed.length; i += 2) {
+      var object = _observed[i];
+      var path = _observed[i + 1];
+      var value;
+      if (identical(object, _observerSentinel)) {
+        var observable = path as Bindable;
+        value = _state == _Observer._UNOPENED
+            ? observable.open((_) => this.deliver())
+            : observable.value;
+      } else {
+        value = (path as PropertyPath).getValueFrom(object);
+      }
+
+      if (skipChanges) {
+        _value[i ~/ 2] = value;
+        continue;
+      }
+
+      if (value == _value[i ~/ 2]) continue;
+
+      // don't allocate this unless necessary.
+      if (_notifyArgumentCount >= 2) {
+        if (oldValues == null) oldValues = new Map();
+        oldValues[i ~/ 2] = _value[i ~/ 2];
+      }
+
+      changed = true;
+      _value[i ~/ 2] = value;
+    }
+
+    if (!changed) return false;
+
+    // TODO(rafaelw): Having _observed as the third callback arg here is
+    // pretty lame API. Fix.
+    _report(_value, oldValues, _observed);
+    return true;
+  }
+}
+
+/// An object accepted by [PropertyPath] where properties are read and written
+/// as indexing operations, just like a [Map].
+abstract class Indexable<K, V> {
+  V operator [](K key);
+  operator []=(K key, V value);
+}
+
+const _observerSentinel = const _ObserverSentinel();
+
+class _ObserverSentinel {
+  const _ObserverSentinel();
+}
+
+// Visible for testing
+get observerSentinelForTesting => _observerSentinel;
+
+// A base class for the shared API implemented by PathObserver and
+// CompoundObserver and used in _ObservedSet.
+abstract class _Observer extends Bindable {
+  Function _notifyCallback;
+  int _notifyArgumentCount;
+  var _value;
+
+  // abstract members
+  void _iterateObjects(void observe(obj, prop));
+  void _connect();
+  void _disconnect();
+  bool _check({bool skipChanges: false});
+
+  static int _UNOPENED = 0;
+  static int _OPENED = 1;
+  static int _CLOSED = 2;
+  int _state = _UNOPENED;
+  bool get _isOpen => _state == _OPENED;
+  bool get _isClosed => _state == _CLOSED;
+
+  /// The number of arguments the subclass will pass to [_report].
+  int get _reportArgumentCount;
+
+  open(callback) {
+    if (_isOpen || _isClosed) {
+      throw new StateError('Observer has already been opened.');
+    }
+
+    if (smoke.minArgs(callback) > _reportArgumentCount) {
+      throw new ArgumentError('callback should take $_reportArgumentCount or '
+          'fewer arguments');
+    }
+
+    _notifyCallback = callback;
+    _notifyArgumentCount = min(_reportArgumentCount, smoke.maxArgs(callback));
+
+    _connect();
+    _state = _OPENED;
+    return _value;
+  }
+
+  get value => _discardChanges();
+
+  void close() {
+    if (!_isOpen) return;
+
+    _disconnect();
+    _value = null;
+    _notifyCallback = null;
+    _state = _CLOSED;
+  }
+
+  _discardChanges() {
+    _check(skipChanges: true);
+    return _value;
+  }
+
+  void deliver() {
+    if (_isOpen) _dirtyCheck();
+  }
+
+  bool _dirtyCheck() {
+    var cycles = 0;
+    while (cycles < _MAX_DIRTY_CHECK_CYCLES && _check()) {
+      cycles++;
+    }
+    return cycles > 0;
+  }
+
+  void _report(newValue, oldValue, [extraArg]) {
+    try {
+      switch (_notifyArgumentCount) {
+        case 0:
+          _notifyCallback();
+          break;
+        case 1:
+          _notifyCallback(newValue);
+          break;
+        case 2:
+          _notifyCallback(newValue, oldValue);
+          break;
+        case 3:
+          _notifyCallback(newValue, oldValue, extraArg);
+          break;
+      }
+    } catch (e, s) {
+      // Deliver errors async, so if a single callback fails it doesn't prevent
+      // other things from working.
+      new Completer().completeError(e, s);
+    }
+  }
+}
+
+/// The observedSet abstraction is a perf optimization which reduces the total
+/// number of Object.observe observations of a set of objects. The idea is that
+/// groups of Observers will have some object dependencies in common and this
+/// observed set ensures that each object in the transitive closure of
+/// dependencies is only observed once. The observedSet acts as a write barrier
+/// such that whenever any change comes through, all Observers are checked for
+/// changed values.
+///
+/// Note that this optimization is explicitly moving work from setup-time to
+/// change-time.
+///
+/// TODO(rafaelw): Implement "garbage collection". In order to move work off
+/// the critical path, when Observers are closed, their observed objects are
+/// not Object.unobserve(d). As a result, it's possible that if the observedSet
+/// is kept open, but some Observers have been closed, it could cause "leaks"
+/// (prevent otherwise collectable objects from being collected). At some
+/// point, we should implement incremental "gc" which keeps a list of
+/// observedSets which may need clean-up and does small amounts of cleanup on a
+/// timeout until all is clean.
+class _ObservedSet {
+  /// To prevent sequential [PathObserver]s and [CompoundObserver]s from
+  /// observing the same object, we check if they are observing the same root
+  /// as the most recently created observer, and if so merge it into the
+  /// existing _ObservedSet.
+  ///
+  /// See <https://github.com/Polymer/observe-js/commit/f0990b1> and
+  /// <https://codereview.appspot.com/46780044/>.
+  static _ObservedSet _lastSet;
+
+  /// The root object for a [PathObserver]. For a [CompoundObserver], the root
+  /// object of the first path observed. This is used by the constructor to
+  /// reuse an [_ObservedSet] that starts from the same object.
+  Object _rootObject;
+
+  /// Subset of properties in [_rootObject] that we care about.
+  Set _rootObjectProperties;
+
+  /// Observers associated with this root object, in birth order.
+  final List<_Observer> _observers = [];
+
+  // Dart note: the JS implementation is O(N^2) because Array.indexOf is used
+  // for lookup in this array. We use HashMap to avoid this problem. It
+  // also gives us a nice way of tracking the StreamSubscription.
+  Map<Object, StreamSubscription> _objects;
+
+  factory _ObservedSet(_Observer observer, Object rootObject) {
+    if (_lastSet == null || !identical(_lastSet._rootObject, rootObject)) {
+      _lastSet = new _ObservedSet._(rootObject);
+    }
+    _lastSet.open(observer, rootObject);
+    return _lastSet;
+  }
+
+  _ObservedSet._(rootObject)
+      : _rootObject = rootObject,
+        _rootObjectProperties = rootObject == null ? null : new Set();
+
+  void open(_Observer obs, Object rootObject) {
+    if (_rootObject == null) {
+      _rootObject = rootObject;
+      _rootObjectProperties = new Set();
+    }
+
+    _observers.add(obs);
+    obs._iterateObjects(observe);
+  }
+
+  void close(_Observer obs) {
+    _observers.remove(obs);
+    if (_observers.isNotEmpty) return;
+
+    if (_objects != null) {
+      for (var sub in _objects.values) sub.cancel();
+      _objects = null;
+    }
+    _rootObject = null;
+    _rootObjectProperties = null;
+    if (identical(_lastSet, this)) _lastSet = null;
+  }
+
+  /// Observe now takes a second argument to indicate which property of an
+  /// object is being observed, so we don't trigger change notifications on
+  /// changes to unrelated properties.
+  void observe(Object obj, Object prop) {
+    if (identical(obj, _rootObject)) _rootObjectProperties.add(prop);
+    if (obj is ObservableList) _observeStream(obj.listChanges);
+    if (obj is Observable) _observeStream(obj.changes);
+  }
+
+  void _observeStream(Stream stream) {
+    // TODO(jmesserly): we hash on streams as we have two separate change
+    // streams for ObservableList. Not sure if that is the design we will use
+    // going forward.
+
+    if (_objects == null) _objects = new HashMap();
+    if (!_objects.containsKey(stream)) {
+      _objects[stream] = stream.listen(_callback);
+    }
+  }
+
+  /// Whether we can ignore all change events in [records]. This is true if all
+  /// records are for properties in the [_rootObject] and we are not observing
+  /// any of those properties. Changes on objects other than [_rootObject], or
+  /// changes for properties in [_rootObjectProperties] can't be ignored.
+  // Dart note: renamed from `allRootObjNonObservedProps` in the JS code.
+  bool _canIgnoreRecords(List<ChangeRecord> records) {
+    for (var rec in records) {
+      if (rec is PropertyChangeRecord) {
+        if (!identical(rec.object, _rootObject) ||
+            _rootObjectProperties.contains(rec.name)) {
+          return false;
+        }
+      } else if (rec is ListChangeRecord) {
+        if (!identical(rec.object, _rootObject) ||
+            _rootObjectProperties.contains(rec.index)) {
+          return false;
+        }
+      } else {
+        // TODO(sigmund): consider adding object to MapChangeRecord, and make
+        // this more precise.
+        return false;
+      }
+    }
+    return true;
+  }
+
+  void _callback(List<ChangeRecord> records) {
+    if (_canIgnoreRecords(records)) return;
+    for (var observer in _observers.toList(growable: false)) {
+      if (observer._isOpen) observer._iterateObjects(observe);
+    }
+
+    for (var observer in _observers.toList(growable: false)) {
+      if (observer._isOpen) observer._check();
+    }
+  }
+}
+
+const int _MAX_DIRTY_CHECK_CYCLES = 1000;
diff --git a/pub/observe/lib/transformer.dart b/pub/observe/lib/transformer.dart
new file mode 100644
index 0000000..a7008bd
--- /dev/null
+++ b/pub/observe/lib/transformer.dart
@@ -0,0 +1,419 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Code transform for @observable. The core transformation is relatively
+/// straightforward, and essentially like an editor refactoring.
+@Deprecated('Parts of Observe used to support Polymer will move out of library')
+library observe.transformer;
+
+import 'dart:async';
+
+import 'package:analyzer/analyzer.dart';
+import 'package:analyzer/src/generated/ast.dart';
+import 'package:analyzer/src/generated/scanner.dart';
+import 'package:barback/barback.dart';
+import 'package:code_transformers/messages/build_logger.dart';
+import 'package:source_maps/refactor.dart';
+import 'package:source_span/source_span.dart';
+
+import 'src/messages.dart';
+
+/// A [Transformer] that replaces observables based on dirty-checking with an
+/// implementation based on change notifications.
+///
+/// The transformation adds hooks for field setters and notifies the observation
+/// system of the change.
+class ObservableTransformer extends Transformer {
+  final bool releaseMode;
+  final bool injectBuildLogsInOutput;
+  final List<String> _files;
+
+  ObservableTransformer(
+      {List<String> files, bool releaseMode, bool injectBuildLogsInOutput})
+      : _files = files,
+        releaseMode = releaseMode == true,
+        injectBuildLogsInOutput = injectBuildLogsInOutput == null
+            ? releaseMode != true
+            : injectBuildLogsInOutput;
+
+  ObservableTransformer.asPlugin(BarbackSettings settings)
+      : _files = _readFiles(settings.configuration['files']),
+        releaseMode = settings.mode == BarbackMode.RELEASE,
+        injectBuildLogsInOutput = settings.mode != BarbackMode.RELEASE;
+
+  static List<String> _readFiles(value) {
+    if (value == null) return null;
+    var files = <String>[];
+    bool error;
+    if (value is List) {
+      files = new List<String>.from(value);
+      error = value.any((e) => e is! String);
+    } else if (value is String) {
+      files = [value];
+      error = false;
+    } else {
+      error = true;
+    }
+    if (error) print('Invalid value for "files" in the observe transformer.');
+    return files;
+  }
+
+  // TODO(nweiz): This should just take an AssetId when barback <0.13.0 support
+  // is dropped.
+  Future<bool> isPrimary(Object idOrAsset) {
+    var id = idOrAsset is AssetId ? idOrAsset : (idOrAsset as Asset).id;
+    return new Future.value(id.extension == '.dart' &&
+        (_files == null || _files.contains(id.path)));
+  }
+
+  Future apply(Transform transform) {
+    return transform.primaryInput.readAsString().then((content) {
+      // Do a quick string check to determine if this is this file even
+      // plausibly might need to be transformed. If not, we can avoid an
+      // expensive parse.
+      if (!observableMatcher.hasMatch(content)) return null;
+
+      var id = transform.primaryInput.id;
+      // TODO(sigmund): improve how we compute this url
+      var url = id.path.startsWith('lib/')
+          ? 'package:${id.package}/${id.path.substring(4)}'
+          : id.path;
+      var sourceFile = new SourceFile(content, url: url);
+      var logger = new BuildLogger(transform,
+          convertErrorsToWarnings: !releaseMode,
+          detailsUri: 'http://goo.gl/5HPeuP');
+      var transaction = _transformCompilationUnit(content, sourceFile, logger);
+      if (!transaction.hasEdits) {
+        transform.addOutput(transform.primaryInput);
+      } else {
+        var printer = transaction.commit();
+        // TODO(sigmund): emit source maps when barback supports it (see
+        // dartbug.com/12340)
+        printer.build(url);
+        transform.addOutput(new Asset.fromString(id, printer.text));
+      }
+
+      if (injectBuildLogsInOutput) return logger.writeOutput();
+    });
+  }
+}
+
+TextEditTransaction _transformCompilationUnit(
+    String inputCode, SourceFile sourceFile, BuildLogger logger) {
+  var unit = parseCompilationUnit(inputCode, suppressErrors: true);
+  var code = new TextEditTransaction(inputCode, sourceFile);
+  for (var directive in unit.directives) {
+    if (directive is LibraryDirective && _hasObservable(directive)) {
+      logger.warning(NO_OBSERVABLE_ON_LIBRARY,
+          span: _getSpan(sourceFile, directive));
+      break;
+    }
+  }
+
+  for (var declaration in unit.declarations) {
+    if (declaration is ClassDeclaration) {
+      _transformClass(declaration, code, sourceFile, logger);
+    } else if (declaration is TopLevelVariableDeclaration) {
+      if (_hasObservable(declaration)) {
+        logger.warning(NO_OBSERVABLE_ON_TOP_LEVEL,
+            span: _getSpan(sourceFile, declaration));
+      }
+    }
+  }
+  return code;
+}
+
+_getSpan(SourceFile file, AstNode node) => file.span(node.offset, node.end);
+
+/// True if the node has the `@observable` or `@published` annotation.
+// TODO(jmesserly): it is not good to be hard coding Polymer support here.
+bool _hasObservable(AnnotatedNode node) =>
+    node.metadata.any(_isObservableAnnotation);
+
+// TODO(jmesserly): this isn't correct if the annotation has been imported
+// with a prefix, or cases like that. We should technically be resolving, but
+// that is expensive in analyzer, so it isn't feasible yet.
+bool _isObservableAnnotation(Annotation node) =>
+    _isAnnotationContant(node, 'observable') ||
+    _isAnnotationContant(node, 'published') ||
+    _isAnnotationType(node, 'ObservableProperty') ||
+    _isAnnotationType(node, 'PublishedProperty');
+
+bool _isAnnotationContant(Annotation m, String name) =>
+    m.name.name == name && m.constructorName == null && m.arguments == null;
+
+bool _isAnnotationType(Annotation m, String name) => m.name.name == name;
+
+void _transformClass(ClassDeclaration cls, TextEditTransaction code,
+    SourceFile file, BuildLogger logger) {
+  if (_hasObservable(cls)) {
+    logger.warning(NO_OBSERVABLE_ON_CLASS, span: _getSpan(file, cls));
+  }
+
+  // We'd like to track whether observable was declared explicitly, otherwise
+  // report a warning later below. Because we don't have type analysis (only
+  // syntactic understanding of the code), we only report warnings that are
+  // known to be true.
+  var explicitObservable = false;
+  var implicitObservable = false;
+  if (cls.extendsClause != null) {
+    var id = _getSimpleIdentifier(cls.extendsClause.superclass.name);
+    if (id.name == 'AutoObservable') {
+      code.edit(id.offset, id.end, 'Observable');
+      explicitObservable = true;
+    } else if (id.name == 'Observable') {
+      explicitObservable = true;
+    } else if (id.name != 'HtmlElement' &&
+        id.name != 'CustomElement' &&
+        id.name != 'Object') {
+      // TODO(sigmund): this is conservative, consider using type-resolution to
+      // improve this check.
+      implicitObservable = true;
+    }
+  }
+
+  if (cls.withClause != null) {
+    for (var type in cls.withClause.mixinTypes) {
+      var id = _getSimpleIdentifier(type.name);
+      if (id.name == 'AutoObservable') {
+        code.edit(id.offset, id.end, 'Observable');
+        explicitObservable = true;
+        break;
+      } else if (id.name == 'Observable') {
+        explicitObservable = true;
+        break;
+      } else {
+        // TODO(sigmund): this is conservative, consider using type-resolution
+        // to improve this check.
+        implicitObservable = true;
+      }
+    }
+  }
+
+  if (cls.implementsClause != null) {
+    // TODO(sigmund): consider adding type-resolution to give a more precise
+    // answer.
+    implicitObservable = true;
+  }
+
+  var declaresObservable = explicitObservable || implicitObservable;
+
+  // Track fields that were transformed.
+  var instanceFields = new Set<String>();
+
+  for (var member in cls.members) {
+    if (member is FieldDeclaration) {
+      if (member.isStatic) {
+        if (_hasObservable(member)) {
+          logger.warning(NO_OBSERVABLE_ON_STATIC_FIELD,
+              span: _getSpan(file, member));
+        }
+        continue;
+      }
+      if (_hasObservable(member)) {
+        if (!declaresObservable) {
+          logger.warning(REQUIRE_OBSERVABLE_INTERFACE,
+              span: _getSpan(file, member));
+        }
+        _transformFields(file, member, code, logger);
+
+        var names = member.fields.variables.map((v) => v.name.name);
+
+        if (!_isReadOnly(member.fields)) instanceFields.addAll(names);
+      }
+    }
+  }
+
+  // If nothing was @observable, bail.
+  if (instanceFields.length == 0) return;
+
+  if (!explicitObservable) _mixinObservable(cls, code);
+
+  // Fix initializers, because they aren't allowed to call the setter.
+  for (var member in cls.members) {
+    if (member is ConstructorDeclaration) {
+      _fixConstructor(member, code, instanceFields);
+    }
+  }
+}
+
+/// Adds "with Observable" and associated implementation.
+void _mixinObservable(ClassDeclaration cls, TextEditTransaction code) {
+  // Note: we need to be careful to put the with clause after extends, but
+  // before implements clause.
+  if (cls.withClause != null) {
+    var pos = cls.withClause.end;
+    code.edit(pos, pos, ', Observable');
+  } else if (cls.extendsClause != null) {
+    var pos = cls.extendsClause.end;
+    code.edit(pos, pos, ' with Observable ');
+  } else {
+    var params = cls.typeParameters;
+    var pos = params != null ? params.end : cls.name.end;
+    code.edit(pos, pos, ' extends Observable ');
+  }
+}
+
+SimpleIdentifier _getSimpleIdentifier(Identifier id) =>
+    id is PrefixedIdentifier ? id.identifier : id;
+
+bool _hasKeyword(Token token, Keyword keyword) =>
+    token?.type == TokenType.KEYWORD && token.lexeme == keyword.syntax;
+
+String _getOriginalCode(TextEditTransaction code, AstNode node) =>
+    code.original.substring(node.offset, node.end);
+
+void _fixConstructor(ConstructorDeclaration ctor, TextEditTransaction code,
+    Set<String> changedFields) {
+  // Fix normal initializers
+  for (var initializer in ctor.initializers) {
+    if (initializer is ConstructorFieldInitializer) {
+      var field = initializer.fieldName;
+      if (changedFields.contains(field.name)) {
+        code.edit(field.offset, field.end, '__\$${field.name}');
+      }
+    }
+  }
+
+  // Fix "this." initializer in parameter list. These are tricky:
+  // we need to preserve the name and add an initializer.
+  // Preserving the name is important for named args, and for dartdoc.
+  // BEFORE: Foo(this.bar, this.baz) { ... }
+  // AFTER:  Foo(bar, baz) : __$bar = bar, __$baz = baz { ... }
+
+  var thisInit = [];
+  for (var param in ctor.parameters.parameters) {
+    if (param is DefaultFormalParameter) {
+      param = (param as DefaultFormalParameter).parameter;
+    }
+    if (param is FieldFormalParameter) {
+      var name = param.identifier.name;
+      if (changedFields.contains(name)) {
+        thisInit.add(name);
+        // Remove "this." but keep everything else.
+        code.edit(param.thisKeyword.offset, param.period.end, '');
+      }
+    }
+  }
+
+  if (thisInit.length == 0) return;
+
+  // TODO(jmesserly): smarter formatting with indent, etc.
+  var inserted = thisInit.map((i) => '__\$$i = $i').join(', ');
+
+  int offset;
+  if (ctor.separator != null) {
+    offset = ctor.separator.end;
+    inserted = ' $inserted,';
+  } else {
+    offset = ctor.parameters.end;
+    inserted = ' : $inserted';
+  }
+
+  code.edit(offset, offset, inserted);
+}
+
+bool _isReadOnly(VariableDeclarationList fields) {
+  return _hasKeyword(fields.keyword, Keyword.CONST) ||
+      _hasKeyword(fields.keyword, Keyword.FINAL);
+}
+
+void _transformFields(SourceFile file, FieldDeclaration member,
+    TextEditTransaction code, BuildLogger logger) {
+  final fields = member.fields;
+  if (_isReadOnly(fields)) return;
+
+  // Private fields aren't supported:
+  for (var field in fields.variables) {
+    final name = field.name.name;
+    if (Identifier.isPrivateName(name)) {
+      logger.warning('Cannot make private field $name observable.',
+          span: _getSpan(file, field));
+      return;
+    }
+  }
+
+  // Unfortunately "var" doesn't work in all positions where type annotations
+  // are allowed, such as "var get name". So we use "dynamic" instead.
+  var type = 'dynamic';
+  if (fields.type != null) {
+    type = _getOriginalCode(code, fields.type);
+  } else if (_hasKeyword(fields.keyword, Keyword.VAR)) {
+    // Replace 'var' with 'dynamic'
+    code.edit(fields.keyword.offset, fields.keyword.end, type);
+  }
+
+  // Note: the replacements here are a bit subtle. It needs to support multiple
+  // fields declared via the same @observable, as well as preserving newlines.
+  // (Preserving newlines is important because it allows the generated code to
+  // be debugged without needing a source map.)
+  //
+  // For example:
+  //
+  //     @observable
+  //     @otherMetaData
+  //         Foo
+  //             foo = 1, bar = 2,
+  //             baz;
+  //
+  // Will be transformed into something like:
+  //
+  //     @reflectable @observable
+  //     @OtherMetaData()
+  //         Foo
+  //             get foo => __foo; Foo __foo = 1; @reflectable set foo ...; ...
+  //             @observable @OtherMetaData() Foo get baz => __baz; Foo baz; ...
+  //
+  // Metadata is moved to the getter.
+
+  String metadata = '';
+  if (fields.variables.length > 1) {
+    metadata = member.metadata.map((m) => _getOriginalCode(code, m)).join(' ');
+    metadata = '@reflectable $metadata';
+  }
+
+  for (int i = 0; i < fields.variables.length; i++) {
+    final field = fields.variables[i];
+    final name = field.name.name;
+
+    var beforeInit = 'get $name => __\$$name; $type __\$$name';
+
+    // The first field is expanded differently from subsequent fields, because
+    // we can reuse the metadata and type annotation.
+    if (i == 0) {
+      final begin = member.metadata.first.offset;
+      code.edit(begin, begin, '@reflectable ');
+    } else {
+      beforeInit = '$metadata $type $beforeInit';
+    }
+
+    code.edit(field.name.offset, field.name.end, beforeInit);
+
+    // Replace comma with semicolon
+    final end = _findFieldSeperator(field.endToken.next);
+    if (end.type == TokenType.COMMA) code.edit(end.offset, end.end, ';');
+
+    code.edit(
+        end.end,
+        end.end,
+        ' @reflectable set $name($type value) { '
+        '__\$$name = notifyPropertyChange(#$name, __\$$name, value); }');
+  }
+}
+
+Token _findFieldSeperator(Token token) {
+  while (token != null) {
+    if (token.type == TokenType.COMMA || token.type == TokenType.SEMICOLON) {
+      break;
+    }
+    token = token.next;
+  }
+  return token;
+}
+
+// TODO(sigmund): remove hard coded Polymer support (@published). The proper way
+// to do this would be to switch to use the analyzer to resolve whether
+// annotations are subtypes of ObservableProperty.
+final observableMatcher =
+    new RegExp("@(published|observable|PublishedProperty|ObservableProperty)");
diff --git a/pub/observe/pubspec.yaml b/pub/observe/pubspec.yaml
new file mode 100644
index 0000000..e9a4b36
--- /dev/null
+++ b/pub/observe/pubspec.yaml
@@ -0,0 +1,37 @@
+name: observe
+version: 0.15.0
+author: Polymer.dart Authors <web-ui-dev@dartlang.org>
+description: >
+  Observable properties and objects for use in template_binding.
+  Template Binding extends HTML and the DOM APIs to support a sensible
+  separation between the UI (DOM) of a document or application and its
+  underlying data (model). Updates to the model are reflected in the DOM and
+  user input into the DOM is immediately assigned to the model.
+homepage: https://www.dartlang.org/polymer-dart/
+dependencies:
+  analyzer: ^0.27.0
+  barback: '>=0.14.2 <0.16.0'
+  func: ^0.1.0
+  logging: '>=0.9.0 <0.12.0'
+  observable: '^0.17.0'
+  path: '>=0.9.0 <2.0.0'
+  smoke: '>=0.1.0 <0.4.0'
+  source_maps: '>=0.9.4 <0.11.0'
+  source_span: ^1.0.0
+  utf: ^0.9.0
+  code_transformers: ^0.4.2
+dev_dependencies:
+  benchmark_harness: '>=1.0.0 <2.0.0'
+  browser: any
+  chart: '>=1.0.8 <2.0.0'
+  test: '^0.12.0'
+  stack_trace: '>=0.9.1 <2.0.0'
+environment:
+  sdk: ">=1.9.0 <2.0.0"
+transformers:
+- observe:
+    files:
+      - benchmark/test_observable.dart
+      - benchmark/test_path_observable.dart
+- test/pub_serve:
+    $include: test/**_test.dart
diff --git a/pub/platform/.analysis_options b/pub/platform/.analysis_options
old mode 100644
new mode 100755
diff --git a/pub/platform/.gitignore b/pub/platform/.gitignore
old mode 100644
new mode 100755
diff --git a/pub/platform/.travis.yml b/pub/platform/.travis.yml
old mode 100644
new mode 100755
diff --git a/pub/platform/AUTHORS b/pub/platform/AUTHORS
old mode 100644
new mode 100755
diff --git a/pub/platform/BUILD.gn b/pub/platform/BUILD.gn
index 3062c4f..03587fc 100644
--- a/pub/platform/BUILD.gn
+++ b/pub/platform/BUILD.gn
@@ -1,4 +1,4 @@
-# This file is generated by importer.py for platform-1.1.1
+# This file is generated by importer.py for platform-2.0.0
 
 import("//build/dart/dart_package.gni")
 
diff --git a/pub/platform/CHANGELOG.md b/pub/platform/CHANGELOG.md
old mode 100644
new mode 100755
index 8ec72a7..25d0ae2
--- a/pub/platform/CHANGELOG.md
+++ b/pub/platform/CHANGELOG.md
@@ -1,3 +1,7 @@
+### 2.0.0
+* Added `stdinSupportsAnsi` and `stdinSupportsAnsi`
+* Removed `ansiSupported`
+
 ### 1.1.1
 
 * Updated `LocalPlatform` to use new `dart.io` API for ansi color support queries
diff --git a/pub/platform/CONTRIBUTING.md b/pub/platform/CONTRIBUTING.md
old mode 100644
new mode 100755
diff --git a/pub/platform/LICENSE b/pub/platform/LICENSE
old mode 100644
new mode 100755
diff --git a/pub/platform/README.md b/pub/platform/README.md
old mode 100644
new mode 100755
index 00f0773..0487c5c
--- a/pub/platform/README.md
+++ b/pub/platform/README.md
@@ -2,6 +2,7 @@
 
 [![Build Status -](https://travis-ci.org/google/platform.dart.svg?branch=master)](https://travis-ci.org/google/platform.dart)
 [![Coverage Status -](https://coveralls.io/repos/github/google/platform.dart/badge.svg?branch=master)](https://coveralls.io/github/google/platform.dart?branch=master)
+[![Pub](https://img.shields.io/pub/v/platform.svg)](https://pub.dartlang.org/packages/platform)
 
 A generic platform abstraction for Dart.
 
diff --git a/pub/platform/lib/platform.dart b/pub/platform/lib/platform.dart
old mode 100644
new mode 100755
diff --git a/pub/platform/lib/src/interface/local_platform.dart b/pub/platform/lib/src/interface/local_platform.dart
old mode 100644
new mode 100755
index e9e7bfb..c90256a
--- a/pub/platform/lib/src/interface/local_platform.dart
+++ b/pub/platform/lib/src/interface/local_platform.dart
@@ -2,7 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-import 'dart:io' as io show Platform, stdin;
+import 'dart:io' as io show Platform, stdin, stdout;
 
 import 'platform.dart';
 
@@ -48,5 +48,8 @@
   String get version => io.Platform.version;
 
   @override
-  bool get ansiSupported => io.stdin.supportsAnsiEscapes;
+  bool get stdinSupportsAnsi => io.stdin.supportsAnsiEscapes;
+
+  @override
+  bool get stdoutSupportsAnsi => io.stdout.supportsAnsiEscapes;
 }
diff --git a/pub/platform/lib/src/interface/platform.dart b/pub/platform/lib/src/interface/platform.dart
old mode 100644
new mode 100755
index a603e3c..c750108
--- a/pub/platform/lib/src/interface/platform.dart
+++ b/pub/platform/lib/src/interface/platform.dart
@@ -114,11 +114,11 @@
   /// whitespace and other version and build details.
   String get version;
 
-  /// When stdio is connected to a terminal, whether ANSI codes are supported.
-  ///
-  /// This value is hard-coded to true, except on Windows where only more recent
-  /// versions of Windows 10 support the codes.
-  bool get ansiSupported;
+  /// When stdin is connected to a terminal, whether ANSI codes are supported.
+  bool get stdinSupportsAnsi;
+
+  /// When stdout is connected to a terminal, whether ANSI codes are supported.
+  bool get stdoutSupportsAnsi;
 
   /// Returns a JSON-encoded representation of this platform.
   String toJson() {
@@ -135,7 +135,8 @@
       'packageRoot': packageRoot,
       'packageConfig': packageConfig,
       'version': version,
-      'ansiSupported': ansiSupported,
+      'stdinSupportsAnsi': stdinSupportsAnsi,
+      'stdoutSupportsAnsi': stdoutSupportsAnsi,
     });
   }
 }
diff --git a/pub/platform/lib/src/testing/fake_platform.dart b/pub/platform/lib/src/testing/fake_platform.dart
old mode 100644
new mode 100755
index ceba4cd..16b8092
--- a/pub/platform/lib/src/testing/fake_platform.dart
+++ b/pub/platform/lib/src/testing/fake_platform.dart
@@ -25,7 +25,8 @@
     this.packageRoot,
     this.packageConfig,
     this.version,
-    this.ansiSupported,
+    this.stdinSupportsAnsi,
+    this.stdoutSupportsAnsi,
   });
 
   /// Creates a new [FakePlatform] with properties whose initial values mirror
@@ -44,7 +45,8 @@
         packageRoot = platform.packageRoot,
         packageConfig = platform.packageConfig,
         version = platform.version,
-        ansiSupported = platform.ansiSupported;
+        stdinSupportsAnsi = platform.stdinSupportsAnsi,
+        stdoutSupportsAnsi = platform.stdoutSupportsAnsi;
 
   /// Creates a new [FakePlatform] with properties extracted from the encoded
   /// JSON string.
@@ -66,7 +68,8 @@
       packageRoot: map['packageRoot'],
       packageConfig: map['packageConfig'],
       version: map['version'],
-      ansiSupported: map['ansiSupported'],
+      stdinSupportsAnsi: map['stdinSupportsAnsi'],
+      stdoutSupportsAnsi: map['stdoutSupportsAnsi'],
     );
   }
 
@@ -107,5 +110,8 @@
   String version;
 
   @override
-  bool ansiSupported;
+  bool stdinSupportsAnsi;
+
+  @override
+  bool stdoutSupportsAnsi;
 }
diff --git a/pub/platform/pubspec.yaml b/pub/platform/pubspec.yaml
old mode 100644
new mode 100755
index 74f5564..ab7c8e9
--- a/pub/platform/pubspec.yaml
+++ b/pub/platform/pubspec.yaml
@@ -1,5 +1,5 @@
 name: platform
-version: 1.1.1
+version: 2.0.0
 authors:
 - Todd Volkert <tvolkert@google.com>
 description: A pluggable, mockable platform abstraction for Dart.
diff --git a/pub/process/.analysis_options b/pub/process/.analysis_options
old mode 100644
new mode 100755
diff --git a/pub/process/.gitignore b/pub/process/.gitignore
old mode 100644
new mode 100755
diff --git a/pub/process/.travis.yml b/pub/process/.travis.yml
old mode 100644
new mode 100755
diff --git a/pub/process/AUTHORS b/pub/process/AUTHORS
old mode 100644
new mode 100755
diff --git a/pub/process/BUILD.gn b/pub/process/BUILD.gn
index 9949f05..2ec3241 100644
--- a/pub/process/BUILD.gn
+++ b/pub/process/BUILD.gn
@@ -1,4 +1,4 @@
-# This file is generated by importer.py for process-2.0.1
+# This file is generated by importer.py for process-2.0.3
 
 import("//build/dart/dart_package.gni")
 
diff --git a/pub/process/CHANGELOG.md b/pub/process/CHANGELOG.md
old mode 100644
new mode 100755
index cc43eb1..93d7fa2
--- a/pub/process/CHANGELOG.md
+++ b/pub/process/CHANGELOG.md
@@ -1,3 +1,11 @@
+### 2.0.3
+* relax dependency requirement for `platform`
+
+#### 2.0.2
+
+* Fix a strong mode function expression return type inference bug with Dart
+  1.23.0-dev.10.0.
+
 #### 2.0.1
 
 * Fixed bug in `ReplayProcessManager` whereby it could try to write to `stdout`
diff --git a/pub/process/CONTRIBUTING.md b/pub/process/CONTRIBUTING.md
old mode 100644
new mode 100755
diff --git a/pub/process/LICENSE b/pub/process/LICENSE
old mode 100644
new mode 100755
diff --git a/pub/process/README.md b/pub/process/README.md
old mode 100644
new mode 100755
diff --git a/pub/process/lib/process.dart b/pub/process/lib/process.dart
old mode 100644
new mode 100755
diff --git a/pub/process/lib/record_replay.dart b/pub/process/lib/record_replay.dart
old mode 100644
new mode 100755
diff --git a/pub/process/lib/src/interface/common.dart b/pub/process/lib/src/interface/common.dart
old mode 100644
new mode 100755
diff --git a/pub/process/lib/src/interface/local_process_manager.dart b/pub/process/lib/src/interface/local_process_manager.dart
old mode 100644
new mode 100755
diff --git a/pub/process/lib/src/interface/process_manager.dart b/pub/process/lib/src/interface/process_manager.dart
old mode 100644
new mode 100755
diff --git a/pub/process/lib/src/record_replay/can_run_manifest_entry.dart b/pub/process/lib/src/record_replay/can_run_manifest_entry.dart
old mode 100644
new mode 100755
diff --git a/pub/process/lib/src/record_replay/command_element.dart b/pub/process/lib/src/record_replay/command_element.dart
old mode 100644
new mode 100755
diff --git a/pub/process/lib/src/record_replay/common.dart b/pub/process/lib/src/record_replay/common.dart
old mode 100644
new mode 100755
diff --git a/pub/process/lib/src/record_replay/constants.dart b/pub/process/lib/src/record_replay/constants.dart
old mode 100644
new mode 100755
diff --git a/pub/process/lib/src/record_replay/manifest.dart b/pub/process/lib/src/record_replay/manifest.dart
old mode 100644
new mode 100755
diff --git a/pub/process/lib/src/record_replay/manifest_entry.dart b/pub/process/lib/src/record_replay/manifest_entry.dart
old mode 100644
new mode 100755
diff --git a/pub/process/lib/src/record_replay/recording_process_manager.dart b/pub/process/lib/src/record_replay/recording_process_manager.dart
old mode 100644
new mode 100755
index c1f337c..8b9cd92
--- a/pub/process/lib/src/record_replay/recording_process_manager.dart
+++ b/pub/process/lib/src/record_replay/recording_process_manager.dart
@@ -319,8 +319,9 @@
     void callOnTimeout(int pid) => onTimeout(_manifest.getRunEntry(pid));
     await Future
         .wait(new List<Future<int>>.from(_runningProcesses.values))
-        .timeout(timeout,
-            onTimeout: () => _runningProcesses.keys.forEach(callOnTimeout));
+        .timeout(timeout, onTimeout: () {
+      _runningProcesses.keys.forEach(callOnTimeout);
+    });
   }
 
   /// Writes our process invocation manifest to disk in the destination folder.
diff --git a/pub/process/lib/src/record_replay/replay_process_manager.dart b/pub/process/lib/src/record_replay/replay_process_manager.dart
old mode 100644
new mode 100755
diff --git a/pub/process/lib/src/record_replay/run_manifest_entry.dart b/pub/process/lib/src/record_replay/run_manifest_entry.dart
old mode 100644
new mode 100755
diff --git a/pub/process/pubspec.yaml b/pub/process/pubspec.yaml
old mode 100644
new mode 100755
index 75e0544..d565ac4
--- a/pub/process/pubspec.yaml
+++ b/pub/process/pubspec.yaml
@@ -1,5 +1,5 @@
 name: process
-version: 2.0.1
+version: 2.0.3
 authors:
 - Todd Volkert <tvolkert@google.com>
 - Michael Goderbauer <goderbauer@google.com>
@@ -11,7 +11,7 @@
   intl: '>=0.14.0 <0.15.0'
   meta: ^1.0.4
   path: ^1.4.0
-  platform: ^1.0.1
+  platform: '>=1.0.1 <3.0.0'
 
 dev_dependencies:
   test: ^0.12.10
diff --git a/pub/smoke/.gitignore b/pub/smoke/.gitignore
new file mode 100644
index 0000000..b0f213a
--- /dev/null
+++ b/pub/smoke/.gitignore
@@ -0,0 +1,7 @@
+pubspec.lock
+.pub
+packages
+.idea
+/build/
+.packages
+.analysis_options
diff --git a/pub/smoke/.status b/pub/smoke/.status
new file mode 100644
index 0000000..9a4bb95
--- /dev/null
+++ b/pub/smoke/.status
@@ -0,0 +1,22 @@
+# Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+# Don't run any test-like files that show up in packages directories. It
+# shouldn't be necessary to run "pub install" in these packages, but if you do
+# it shouldn't break the tests.
+*/packages/*/*: Skip
+*/*/packages/*/*: Skip
+*/*/*/packages/*/*: Skip
+*/*/*/*/packages/*/*: Skip
+*/*/*/*/*/packages/*/*: Skip
+
+[ $runtime == vm && $mode == debug]
+test/codegen/end_to_end_test: Skip  # Times out
+test/codegen/recorder_test: Skip  # Times out
+
+[ $browser ]
+build/test/codegen/end_to_end_test: Skip # Uses dart:io.
+build/test/codegen/recorder_test: Skip # Uses dart:io.
+test/codegen/end_to_end_test: Skip # Uses dart:io.
+test/codegen/recorder_test: Skip # Uses dart:io.
diff --git a/pub/smoke/.test_config b/pub/smoke/.test_config
new file mode 100644
index 0000000..2535563
--- /dev/null
+++ b/pub/smoke/.test_config
@@ -0,0 +1,3 @@
+{
+  "test_package": true
+}
diff --git a/pub/smoke/AUTHORS b/pub/smoke/AUTHORS
new file mode 100644
index 0000000..0617765
--- /dev/null
+++ b/pub/smoke/AUTHORS
@@ -0,0 +1,9 @@
+# Names should be added to this file with this pattern:
+#
+# For individuals:
+#   Name <email address>
+#
+# For organizations:
+#   Organization <fnmatch pattern>
+#
+Google Inc. <*@google.com>
diff --git a/pub/smoke/BUILD.gn b/pub/smoke/BUILD.gn
new file mode 100644
index 0000000..a1dbf90
--- /dev/null
+++ b/pub/smoke/BUILD.gn
@@ -0,0 +1,17 @@
+# This file is generated by importer.py for smoke-0.3.6+2
+
+import("//build/dart/dart_package.gni")
+
+dart_package("smoke") {
+  package_name = "smoke"
+
+  source_dir = "lib"
+
+  disable_analysis = true
+
+  deps = [
+    "//third_party/dart-pkg/pub/barback",
+    "//third_party/dart-pkg/pub/logging",
+    "//dart/pkg/analyzer",
+  ]
+}
diff --git a/pub/smoke/CHANGELOG.md b/pub/smoke/CHANGELOG.md
new file mode 100644
index 0000000..4e24e6b
--- /dev/null
+++ b/pub/smoke/CHANGELOG.md
@@ -0,0 +1,60 @@
+#### 0.3.6+2
+  * Eliminate errors reported by analyzer in strong mode.
+
+#### 0.3.6+1
+  * Update to use the `transformer_test` package instead of `code_transformers`
+    for tests.
+
+#### 0.3.6
+  * Update to analyzer '^0.27.0' and update to the test package.
+
+#### 0.3.5
+  * Update to analyzer '<0.27.0'
+
+#### 0.3.4
+  * Add excludeOverriden to QueryOptions which removes declarations that were
+    overriden within the class hierarchy.
+
+#### 0.3.3+1
+  * Update logging package to `<0.12.0`.
+
+#### 0.3.3
+  * Update to analyzer `<0.26.0`.
+
+#### 0.3.2
+  * Work around an issue running Dart analyzer on the generated code, if the
+    `dynamic` type appeared in the output. Smoke will now use `Object` instead.
+
+#### 0.3.1+1
+  * Updated dependency versions.
+
+#### 0.3.1
+  * Add canAcceptNArgs method.
+
+#### 0.3.0
+  * Change SUPPORTED_ARGS limit for minArgs and maxArgs method from 3 to 15.
+
+#### 0.2.1+1
+  * Fix toString calls on Type instances.
+
+#### 0.2.0+3
+  * Widen the constraint on analyzer.
+
+#### 0.2.0+2
+  * Widen the constraint on barback.
+
+#### 0.2.0+1
+  * Switch from `source_maps`' `Span` class to `source_span`'s `SourceSpan`
+    class.
+
+#### 0.2.0
+  * Static configuration can be modified, so code generators can split the
+    static configuration in pieces.
+  * **breaking change**: for codegen call `writeStaticConfiguration` instead of
+    `writeInitCall`.
+
+#### 0.1.0
+  * Initial release: introduces the smoke API, a mirror based implementation, a
+    statically configured implementation that can be declared by hand or be
+    generated by tools, and libraries that help generate the static
+    configurations.
diff --git a/pub/smoke/LICENSE b/pub/smoke/LICENSE
new file mode 100644
index 0000000..95987ba
--- /dev/null
+++ b/pub/smoke/LICENSE
@@ -0,0 +1,27 @@
+// Copyright (c) 2014 The Polymer Authors. 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.
+//    * Neither the name of Google Inc. nor the names of its
+// contributors may 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 THE COPYRIGHT
+// OWNER OR CONTRIBUTORS 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.
diff --git a/pub/smoke/PATENTS b/pub/smoke/PATENTS
new file mode 100644
index 0000000..e120963
--- /dev/null
+++ b/pub/smoke/PATENTS
@@ -0,0 +1,23 @@
+Additional IP Rights Grant (Patents)
+
+"This implementation" means the copyrightable works distributed by
+Google as part of the Polymer project.
+
+Google hereby grants to You a perpetual, worldwide, non-exclusive,
+no-charge, royalty-free, irrevocable (except as stated in this section)
+patent license to make, have made, use, offer to sell, sell, import,
+transfer and otherwise run, modify and propagate the contents of this
+implementation of Polymer, where such license applies only to those
+patent claims, both currently owned or controlled by Google and acquired
+in the future, licensable by Google that are necessarily infringed by
+this implementation of Polymer.  This grant does not include claims
+that would be infringed only as a consequence of further modification of
+this implementation.  If you or your agent or exclusive licensee
+institute or order or agree to the institution of patent litigation
+against any entity (including a cross-claim or counterclaim in a
+lawsuit) alleging that this implementation of Polymer or any code
+incorporated within this implementation of Polymer constitutes
+direct or contributory patent infringement, or inducement of patent
+infringement, then any patent rights granted to you under this License
+for this implementation of Polymer shall terminate as of the date
+such litigation is filed.
diff --git a/pub/smoke/README.md b/pub/smoke/README.md
new file mode 100644
index 0000000..685e1c5
--- /dev/null
+++ b/pub/smoke/README.md
@@ -0,0 +1,40 @@
+Smoke (and mirrors)
+===================
+
+Smoke is a package that exposes a reduced reflective system API. This API
+includes accessing objects in a dynamic fashion (read properties, write
+properties, and call methods), inspecting types (for example, whether a
+method exists), and symbol/string convertion.
+
+The package provides a default implementation of this API that uses the system's
+mirrors, but additionally provides mechanisms for statically generating code
+that can replace the mirror-based implementation.
+
+The intention of this package is to allow frameworks to use mirrors in a way
+that will not impose on their users. The idea is that users will not worry about
+how to preserve symbols when compiling with dart2js (for instance, using the
+[MirrorsUsed][] annotation). Instead, this package provides the building
+blocks to autogenerate whatever is needed for dart2js to be happy and to
+generate reasonable code.
+
+Note this package alone doesn't know how to generate everything, but it provides
+a simple API that different frameworks can use to define what needs to be
+generated.
+
+
+Smoke reflective API
+====================
+
+Use `package:smoke/smoke.dart` in your framework to read and write objects and
+to inspect type information. Read the Dart-docs for more details.
+
+Code Generation
+===============
+
+Use `package:smoke/codegen/generator.dart` and
+`package:smoke/codegen/recorder.dart` in your transformer to create a static
+initialization that can be used by smoke. The test under
+`test/codegen/end_to_end_test.dart` is a good illustrating example to learn how
+to use these APIs.
+
+[MirrorsUsed]: https://api.dartlang.org/apidocs/channels/stable/#dart-mirrors.MirrorsUsed
diff --git a/pub/smoke/codereview.settings b/pub/smoke/codereview.settings
new file mode 100644
index 0000000..c24df00
--- /dev/null
+++ b/pub/smoke/codereview.settings
@@ -0,0 +1,3 @@
+CODE_REVIEW_SERVER: https://codereview.chromium.org/
+VIEW_VC: https://github.com/dart-lang/smoke/commit/
+CC_LIST: reviews@dartlang.org
\ No newline at end of file
diff --git a/pub/smoke/lib/codegen/generator.dart b/pub/smoke/lib/codegen/generator.dart
new file mode 100644
index 0000000..3a67687
--- /dev/null
+++ b/pub/smoke/lib/codegen/generator.dart
@@ -0,0 +1,522 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Library to generate code that can initialize the `StaticConfiguration` in
+/// `package:smoke/static.dart`.
+///
+/// This library doesn't have any specific logic to extract information from
+/// Dart source code. To extract code using the analyzer, take a look at the
+/// `smoke.codegen.recorder` library.
+library smoke.codegen.generator;
+
+import 'dart:collection' show SplayTreeMap, SplayTreeSet;
+
+import 'package:smoke/src/common.dart' show compareLists, compareMaps;
+
+/// Collects the necessary information and generates code to initialize a
+/// `StaticConfiguration`. After setting up the generator by calling
+/// [addGetter], [addSetter], [addSymbol], and [addDeclaration], you can
+/// retrieve the generated code using the following three methods:
+///
+///   * [writeImports] writes a list of imports directives,
+///   * [writeTopLevelDeclarations] writes additional declarations used to
+///     represent mixin classes by name in the generated code.
+///   * [writeStaticConfiguration] writes the actual code that allocates the
+///     static configuration.
+///
+/// You'd need to include all three in your generated code, since the
+/// initialization code refers to symbols that are only available from the
+/// generated imports or the generated top-level declarations.
+class SmokeCodeGenerator {
+  // Note: we use SplayTreeSet/Map here and below to keep generated code sorted.
+  /// Names used as getters via smoke.
+  final Set<String> _getters = new SplayTreeSet();
+
+  /// Names used as setters via smoke.
+  final Set<String> _setters = new SplayTreeSet();
+
+  /// Subclass relations needed to run smoke queries.
+  final Map<TypeIdentifier, TypeIdentifier> _parents = new SplayTreeMap();
+
+  /// Declarations requested via `smoke.getDeclaration or `smoke.query`.
+  final Map<TypeIdentifier, Map<String, _DeclarationCode>> _declarations =
+      new SplayTreeMap();
+
+  /// Static methods used on each type.
+  final Map<TypeIdentifier, Set<String>> _staticMethods = new SplayTreeMap();
+
+  /// Names that are used both as strings and symbols.
+  final Set<String> _names = new SplayTreeSet();
+
+  /// Prefixes associated with imported libraries.
+  final Map<String, String> _libraryPrefix = {};
+
+  /// Register that [name] is used as a getter in the code.
+  void addGetter(String name) {
+    _getters.add(name);
+  }
+
+  /// Register that [name] is used as a setter in the code.
+  void addSetter(String name) {
+    _setters.add(name);
+  }
+
+  /// Register that [name] might be needed as a symbol.
+  void addSymbol(String name) {
+    _names.add(name);
+  }
+
+  /// Register that `cls.name` is used as a static method in the code.
+  void addStaticMethod(TypeIdentifier cls, String name) {
+    var methods =
+        _staticMethods.putIfAbsent(cls, () => new SplayTreeSet<String>());
+    _addLibrary(cls.importUrl);
+    methods.add(name);
+  }
+
+  int _mixins = 0;
+
+  /// Creates a new type to represent a mixin. Use [comment] to help users
+  /// figure out what mixin is being represented.
+  TypeIdentifier createMixinType([String comment = '']) =>
+      new TypeIdentifier(null, '_M${_mixins++}', comment);
+
+  /// Register that we care to know that [child] extends [parent].
+  void addParent(TypeIdentifier child, TypeIdentifier parent) {
+    var existing = _parents[child];
+    if (existing != null) {
+      if (existing == parent) return;
+      throw new StateError('$child already has a different parent associated'
+          '($existing instead of $parent)');
+    }
+    _addLibrary(child.importUrl);
+    _addLibrary(parent.importUrl);
+    _parents[child] = parent;
+  }
+
+  /// Register a declaration of a field, property, or method. Note that one and
+  /// only one of [isField], [isMethod], or [isProperty] should be set at a
+  /// given time.
+  void addDeclaration(TypeIdentifier cls, String name, TypeIdentifier type,
+      {bool isField: false,
+      bool isProperty: false,
+      bool isMethod: false,
+      bool isFinal: false,
+      bool isStatic: false,
+      List<ConstExpression> annotations: const []}) {
+    final count = (isField ? 1 : 0) + (isProperty ? 1 : 0) + (isMethod ? 1 : 0);
+    if (count != 1) {
+      throw new ArgumentError('Declaration must be one (and only one) of the '
+          'following: a field, a property, or a method.');
+    }
+    var kind = isField ? 'FIELD' : isProperty ? 'PROPERTY' : 'METHOD';
+    _declarations.putIfAbsent(
+        cls, () => new SplayTreeMap<String, _DeclarationCode>());
+    _addLibrary(cls.importUrl);
+    var map = _declarations[cls];
+
+    for (var exp in annotations) {
+      for (var lib in exp.librariesUsed) {
+        _addLibrary(lib);
+      }
+    }
+
+    _addLibrary(type.importUrl);
+    var decl =
+        new _DeclarationCode(name, type, kind, isFinal, isStatic, annotations);
+    if (map.containsKey(name) && map[name] != decl) {
+      throw new StateError('$type.$name already has a different declaration'
+          ' (${map[name]} instead of $decl).');
+    }
+    map[name] = decl;
+  }
+
+  /// Register that we might try to read declarations of [type], even if no
+  /// declaration exists. This informs the smoke system that querying for a
+  /// member in this class might be intentional and not an error.
+  void addEmptyDeclaration(TypeIdentifier type) {
+    _addLibrary(type.importUrl);
+    _declarations.putIfAbsent(
+        type, () => new SplayTreeMap<String, _DeclarationCode>());
+  }
+
+  /// Writes to [buffer] a line for each import that is needed by the generated
+  /// code. The code added by [writeStaticConfiguration] depends on these
+  /// imports.
+  void writeImports(StringBuffer buffer) {
+    DEFAULT_IMPORTS.forEach((i) => buffer.writeln(i));
+    _libraryPrefix.forEach((url, prefix) {
+      buffer.writeln("import '$url' as $prefix;");
+    });
+  }
+
+  /// Writes to [buffer] top-level declarations that are used by the code
+  /// generated in [writeStaticConfiguration]. These are typically declarations
+  /// of empty classes that are then used as placeholders for mixin
+  /// superclasses.
+  void writeTopLevelDeclarations(StringBuffer buffer) {
+    var types = new Set()
+      ..addAll(_parents.keys)
+      ..addAll(_parents.values)
+      ..addAll(_declarations.keys)
+      ..addAll(_declarations.values.expand((m) => m.values.map((d) => d.type)))
+      ..removeWhere((t) => t.importUrl != null);
+    for (var type in types) {
+      buffer.write('abstract class ${type.name} {}');
+      if (type.comment != null) buffer.write(' // ${type.comment}');
+      buffer.writeln();
+    }
+  }
+
+  /// Appends to [buffer] code that will create smoke's static configuration.
+  /// For example, the code might be of the form:
+  ///
+  ///    new StaticConfiguration(
+  ///      getters: {
+  ///         #i: (o) => o.i,
+  ///         ...
+  ///      names: {
+  ///         #i: "i",
+  ///      })
+  ///
+  /// Callers of this code can assign this expression to a variable, and should
+  /// generate code that invokes `useGeneratedCode`.
+  ///
+  /// The optional [indent] argument is used for formatting purposes. All
+  /// entries in each map (getters, setters, names, declarations, parents) are
+  /// sorted alphabetically.
+  ///
+  /// **Note**: this code assumes that imports from [writeImports] and top-level
+  /// declarations from [writeTopLevelDeclarations] are included in the same
+  /// library where this code will live.
+  void writeStaticConfiguration(StringBuffer buffer, [int indent = 2]) {
+    final spaces = ' ' * (indent + 4);
+    var args = {};
+
+    if (_getters.isNotEmpty) {
+      args['getters'] = _getters.map((n) => '${_symbol(n)}: (o) => o.$n');
+    }
+    if (_setters.isNotEmpty) {
+      args['setters'] =
+          _setters.map((n) => '${_symbol(n)}: (o, v) { o.$n = v; }');
+    }
+
+    if (_parents.isNotEmpty) {
+      var parentsMap = [];
+      _parents.forEach((child, parent) {
+        var parent = _parents[child];
+        parentsMap.add('${child.asCode(_libraryPrefix)}: '
+            '${parent.asCode(_libraryPrefix)}');
+      });
+      args['parents'] = parentsMap;
+    }
+
+    if (_declarations.isNotEmpty) {
+      var declarations = [];
+      _declarations.forEach((type, members) {
+        final sb = new StringBuffer()
+          ..write(type.asCode(_libraryPrefix))
+          ..write(': ');
+        if (members.isEmpty) {
+          sb.write('{}');
+        } else {
+          sb.write('{\n');
+          members.forEach((name, decl) {
+            var decl = members[name].asCode(_libraryPrefix);
+            sb.write('${spaces}    ${_symbol(name)}: $decl,\n');
+          });
+          sb.write('${spaces}  }');
+        }
+        declarations.add(sb.toString());
+      });
+      args['declarations'] = declarations;
+    }
+
+    if (_staticMethods.isNotEmpty) {
+      var methods = [];
+      _staticMethods.forEach((type, members) {
+        var className = type.asCode(_libraryPrefix);
+        final sb = new StringBuffer()..write(className)..write(': ');
+        if (members.isEmpty) {
+          sb.write('{}');
+        } else {
+          sb.write('{\n');
+          for (var name in members) {
+            sb.write('${spaces}    ${_symbol(name)}: $className.$name,\n');
+          }
+          sb.write('${spaces}  }');
+        }
+        methods.add(sb.toString());
+      });
+      args['staticMethods'] = methods;
+    }
+
+    if (_names.isNotEmpty) {
+      args['names'] = _names.map((n) => "${_symbol(n)}: r'$n'");
+    }
+
+    buffer
+      ..writeln('new StaticConfiguration(')
+      ..write('${spaces}checkedMode: false');
+
+    args.forEach((name, mapContents) {
+      buffer.writeln(',');
+      // TODO(sigmund): use const map when Type can be keys (dartbug.com/17123)
+      buffer.writeln('${spaces}$name: {');
+      for (var entry in mapContents) {
+        buffer.writeln('${spaces}  $entry,');
+      }
+      buffer.write('${spaces}}');
+    });
+    buffer.write(')');
+  }
+
+  /// Adds a library that needs to be imported.
+  void _addLibrary(String url) {
+    if (url == null || url == 'dart:core') return;
+    _libraryPrefix.putIfAbsent(url, () => 'smoke_${_libraryPrefix.length}');
+  }
+}
+
+/// Information used to generate code that allocates a `Declaration` object.
+class _DeclarationCode extends ConstExpression {
+  final String name;
+  final TypeIdentifier type;
+  final String kind;
+  final bool isFinal;
+  final bool isStatic;
+  final List<ConstExpression> annotations;
+
+  _DeclarationCode(this.name, this.type, this.kind, this.isFinal, this.isStatic,
+      this.annotations);
+
+  List<String> get librariesUsed => <String>[]
+    ..addAll(type.librariesUsed)
+    ..addAll(annotations.expand/*<String>*/((a) => a.librariesUsed));
+
+  String asCode(Map<String, String> libraryPrefixes) {
+    var sb = new StringBuffer();
+    sb.write('const Declaration(${_symbol(name)}, '
+        '${type.asCode(libraryPrefixes)}');
+    if (kind != 'FIELD') sb.write(', kind: $kind');
+    if (isFinal) sb.write(', isFinal: true');
+    if (isStatic) sb.write(', isStatic: true');
+    if (annotations != null && annotations.isNotEmpty) {
+      sb.write(', annotations: const [');
+      bool first = true;
+      for (var e in annotations) {
+        if (!first) sb.write(', ');
+        first = false;
+        sb.write(e.asCode(libraryPrefixes));
+      }
+      sb.write(']');
+    }
+    sb.write(')');
+    return sb.toString();
+  }
+
+  String toString() =>
+      '(decl: $type.$name - $kind, $isFinal, $isStatic, $annotations)';
+  operator ==(other) =>
+      other is _DeclarationCode &&
+      name == other.name &&
+      type == other.type &&
+      kind == other.kind &&
+      isFinal == other.isFinal &&
+      isStatic == other.isStatic &&
+      compareLists(annotations, other.annotations);
+  int get hashCode => name.hashCode + (31 * type.hashCode);
+}
+
+/// A constant expression that can be used as an annotation.
+abstract class ConstExpression {
+  /// Returns the library URLs that needs to be imported for this
+  /// [ConstExpression] to be a valid annotation.
+  List<String> get librariesUsed;
+
+  /// Return a string representation of the code in this expression.
+  /// [libraryPrefixes] describes what prefix has been associated with each
+  /// import url mentioned in [libraryUsed].
+  String asCode(Map<String, String> libraryPrefixes);
+
+  ConstExpression();
+
+  /// Create a string expression of the form `'string'`, where [string] is
+  /// normalized so we can correctly wrap it in single quotes.
+  factory ConstExpression.string(String string) {
+    var value = string.replaceAll(r'\', r'\\').replaceAll(r"'", r"\'");
+    return new CodeAsConstExpression("'$value'");
+  }
+
+  /// Create an expression of the form `prefix.variable_name`.
+  factory ConstExpression.identifier(String importUrl, String name) =>
+      new TopLevelIdentifier(importUrl, name);
+
+  /// Create an expression of the form `prefix.Constructor(v1, v2, p3: v3)`.
+  factory ConstExpression.constructor(
+          String importUrl,
+          String name,
+          List<ConstExpression> positionalArgs,
+          Map<String, ConstExpression> namedArgs) =>
+      new ConstructorExpression(importUrl, name, positionalArgs, namedArgs);
+}
+
+/// A constant expression written as a String. Used when the code is self
+/// contained and it doesn't depend on any imported libraries.
+class CodeAsConstExpression extends ConstExpression {
+  String code;
+  List<String> get librariesUsed => const [];
+
+  CodeAsConstExpression(this.code);
+
+  String asCode(Map<String, String> libraryPrefixes) => code;
+
+  String toString() => '(code: $code)';
+  operator ==(other) => other is CodeAsConstExpression && code == other.code;
+  int get hashCode => code.hashCode;
+}
+
+/// Describes a reference to some symbol that is exported from a library. This
+/// is typically used to refer to a type or a top-level variable from that
+/// library.
+class TopLevelIdentifier extends ConstExpression {
+  final String importUrl;
+  final String name;
+  TopLevelIdentifier(this.importUrl, this.name);
+
+  List<String> get librariesUsed => [importUrl];
+  String asCode(Map<String, String> libraryPrefixes) {
+    if (importUrl == 'dart:core' || importUrl == null) {
+      // TODO(jmesserly): analyzer doesn't consider `dynamic` to be a constant,
+      // so use Object as a workaround:
+      // https://code.google.com/p/dart/issues/detail?id=22989
+      return name == 'dynamic' ? 'Object' : name;
+    }
+    return '${libraryPrefixes[importUrl]}.$name';
+  }
+
+  String toString() => '(identifier: $importUrl, $name)';
+  operator ==(other) =>
+      other is TopLevelIdentifier &&
+      name == other.name &&
+      importUrl == other.importUrl;
+  int get hashCode => 31 * importUrl.hashCode + name.hashCode;
+}
+
+/// Represents an expression that invokes a const constructor.
+class ConstructorExpression extends ConstExpression {
+  final String importUrl;
+  final String name;
+  final List<ConstExpression> positionalArgs;
+  final Map<String, ConstExpression> namedArgs;
+  ConstructorExpression(
+      this.importUrl, this.name, this.positionalArgs, this.namedArgs);
+
+  List<String> get librariesUsed => <String>[importUrl]
+    ..addAll(positionalArgs.expand/*<String>*/((e) => e.librariesUsed))
+    ..addAll(namedArgs.values.expand/*<String>*/((e) => e.librariesUsed));
+
+  String asCode(Map<String, String> libraryPrefixes) {
+    var sb = new StringBuffer();
+    sb.write('const ');
+    if (importUrl != 'dart:core' && importUrl != null) {
+      sb.write('${libraryPrefixes[importUrl]}.');
+    }
+    sb.write('$name(');
+    bool first = true;
+    for (var e in positionalArgs) {
+      if (!first) sb.write(', ');
+      first = false;
+      sb.write(e.asCode(libraryPrefixes));
+    }
+    namedArgs.forEach((name, value) {
+      if (!first) sb.write(', ');
+      first = false;
+      sb.write('$name: ');
+      sb.write(value.asCode(libraryPrefixes));
+    });
+    sb.write(')');
+    return sb.toString();
+  }
+
+  String toString() => '(ctor: $importUrl, $name, $positionalArgs, $namedArgs)';
+  operator ==(other) =>
+      other is ConstructorExpression &&
+      name == other.name &&
+      importUrl == other.importUrl &&
+      compareLists(positionalArgs, other.positionalArgs) &&
+      compareMaps(namedArgs, other.namedArgs);
+  int get hashCode => 31 * importUrl.hashCode + name.hashCode;
+}
+
+/// Describes a type identifier, with the library URL where the type is defined.
+// TODO(sigmund): consider adding support for imprecise TypeIdentifiers, which
+// may be used by tools that want to generate code without using the analyzer
+// (they can syntactically tell the type comes from one of N imports).
+class TypeIdentifier extends TopLevelIdentifier
+    implements Comparable<TypeIdentifier> {
+  final String comment;
+  TypeIdentifier(importUrl, typeName, [this.comment])
+      : super(importUrl, typeName);
+
+  // We implement [Comparable] to sort out entries in the generated code.
+  int compareTo(TypeIdentifier other) {
+    if (importUrl == null && other.importUrl != null) return 1;
+    if (importUrl != null && other.importUrl == null) return -1;
+    var c1 = importUrl == null ? 0 : importUrl.compareTo(other.importUrl);
+    return c1 != 0 ? c1 : name.compareTo(other.name);
+  }
+
+  String toString() => '(type-identifier: $importUrl, $name, $comment)';
+  bool operator ==(other) =>
+      other is TypeIdentifier &&
+      importUrl == other.importUrl &&
+      name == other.name &&
+      comment == other.comment;
+  int get hashCode => super.hashCode;
+}
+
+/// Default set of imports added by [SmokeCodeGenerator].
+const DEFAULT_IMPORTS = const [
+  "import 'package:smoke/smoke.dart' show Declaration, PROPERTY, METHOD;",
+  "import 'package:smoke/static.dart' show "
+      "useGeneratedCode, StaticConfiguration;",
+];
+
+_symbol(String name) {
+  if (!_publicSymbolPattern.hasMatch(name)) {
+    throw new StateError('invalid symbol name: "$name"');
+  }
+  return _literalSymbolPattern.hasMatch(name)
+      ? '#$name'
+      : "const Symbol('$name')";
+}
+
+// TODO(sigmund): is this included in some library we can import? I derived the
+// definitions below from sdk/lib/internal/symbol.dart.
+
+/// Reserved words in Dart.
+const String _reservedWordRE =
+    r'(?:assert|break|c(?:a(?:se|tch)|lass|on(?:st|tinue))|d(?:efault|o)|'
+    r'e(?:lse|num|xtends)|f(?:alse|inal(?:ly)?|or)|i[fns]|n(?:ew|ull)|'
+    r'ret(?:hrow|urn)|s(?:uper|witch)|t(?:h(?:is|row)|r(?:ue|y))|'
+    r'v(?:ar|oid)|w(?:hile|ith))';
+
+/// Public identifier: a valid identifier (not a reserved word) that doesn't
+/// start with '_'.
+const String _publicIdentifierRE =
+    r'(?!' '$_reservedWordRE' r'\b(?!\$))[a-zA-Z$][\w$]*';
+
+/// Pattern that matches operators only.
+final RegExp _literalSymbolPattern =
+    new RegExp('^(?:$_publicIdentifierRE(?:\$|[.](?!\$)))+?\$');
+
+/// Operator names allowed as symbols. The name of the oeprators is the same as
+/// the operator itself except for unary minus, where the name is "unary-".
+const String _operatorRE =
+    r'(?:[\-+*/%&|^]|\[\]=?|==|~/?|<[<=]?|>[>=]?|unary-)';
+
+/// Pattern that matches public symbols.
+final RegExp _publicSymbolPattern = new RegExp(
+    '^(?:$_operatorRE\$|$_publicIdentifierRE(?:=?\$|[.](?!\$)))+?\$');
diff --git a/pub/smoke/lib/codegen/recorder.dart b/pub/smoke/lib/codegen/recorder.dart
new file mode 100644
index 0000000..7d41029
--- /dev/null
+++ b/pub/smoke/lib/codegen/recorder.dart
@@ -0,0 +1,415 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Records accesses to Dart program declarations and generates code that will
+/// allow to do the same accesses at runtime using `package:smoke/static.dart`.
+/// Internally, this library relies on the `analyzer` to extract data from the
+/// program, and then uses [SmokeCodeGenerator] to produce the code needed by
+/// the smoke system.
+///
+/// This library only uses the analyzer to consume data previously produced by
+/// running the resolver. This library does not provide any hooks to integrate
+/// running the analyzer itself. See `package:code_transformers` to integrate
+/// the analyzer into pub transformers.
+library smoke.codegen.recorder;
+
+import 'package:analyzer/src/generated/element.dart';
+import 'package:analyzer/src/generated/ast.dart';
+import 'generator.dart';
+
+typedef String ImportUrlResolver(LibraryElement lib);
+
+/// A recorder that tracks how elements are accessed in order to generate code
+/// that replicates those accesses with the smoke runtime.
+class Recorder {
+  /// Underlying code generator.
+  SmokeCodeGenerator generator;
+
+  /// Function that provides the import url for a library element. This may
+  /// internally use the resolver to resolve the import url.
+  ImportUrlResolver importUrlFor;
+
+  Recorder(this.generator, this.importUrlFor);
+
+  /// Stores mixins that have been recorded and associates a type identifier
+  /// with them. Mixins don't have an associated identifier in the code, so we
+  /// generate a unique identifier for them and use it throughout the code.
+  Map<TypeIdentifier, Map<ClassElement, TypeIdentifier>> _mixins = {};
+
+  /// Adds the superclass information of [type] (including intermediate mixins).
+  /// This will not generate data for direct subtypes of Object, as that is
+  /// considered redundant information.
+  void lookupParent(ClassElement type) {
+    var parent = type.supertype;
+    var mixins = type.mixins;
+    if (parent == null && mixins.isEmpty) return; // type is Object
+    var baseType = parent.element;
+    var baseId = _typeFor(baseType);
+    if (mixins.isNotEmpty) {
+      _mixins.putIfAbsent(baseId, () => {});
+      for (var m in mixins) {
+        var mixinType = m.element;
+        var mixinId = _mixins[baseId].putIfAbsent(mixinType, () {
+          var comment = '${baseId.name} & ${mixinType.name}';
+          return generator.createMixinType(comment);
+        });
+        if (!baseType.type.isObject) generator.addParent(mixinId, baseId);
+        baseType = mixinType;
+        baseId = mixinId;
+        _mixins.putIfAbsent(mixinId, () => {});
+      }
+    }
+    if (!baseType.type.isObject) generator.addParent(_typeFor(type), baseId);
+  }
+
+  TypeIdentifier _typeFor(Element type) => new TypeIdentifier(
+      type.library == null ? 'dart:core' : importUrlFor(type.library),
+      type.displayName);
+
+  /// Adds any declaration and superclass information that is needed to answer a
+  /// query on [type] that matches [options]. Also adds symbols, getters, and
+  /// setters if [includeAccessors] is true. If [results] is not null, it will
+  /// be filled up with the members that match the query.
+  void runQuery(ClassElement type, QueryOptions options,
+      {bool includeAccessors: true, List results}) {
+    if (type.type.isObject) return; // We don't include Object in query results.
+    var id = _typeFor(type);
+    var parent = type.supertype != null ? type.supertype.element : null;
+    if (options.includeInherited &&
+        parent != null &&
+        parent != options.includeUpTo) {
+      lookupParent(type);
+      runQuery(parent, options, includeAccessors: includeAccessors);
+      var parentId = _typeFor(parent);
+      for (var m in type.mixins) {
+        var mixinClass = m.element;
+        var mixinId = _mixins[parentId][mixinClass];
+        _runQueryInternal(
+            mixinClass, mixinId, options, includeAccessors, results);
+        parentId = mixinId;
+      }
+    }
+    _runQueryInternal(type, id, options, includeAccessors, results);
+  }
+
+  /// Helper for [runQuery]. This runs the query only on a specific [type],
+  /// which could be a class or a mixin labeled by [id].
+  // TODO(sigmund): currently we materialize mixins in smoke/static.dart,
+  // we should consider to include the mixin declaration information directly,
+  // and remove the duplication we have for mixins today.
+  void _runQueryInternal(ClassElement type, TypeIdentifier id,
+      QueryOptions options, bool includeAccessors, List results) {
+    skipBecauseOfAnnotations(Element e) {
+      if (options.withAnnotations == null) return false;
+      return !_matchesAnnotation(e.metadata, options.withAnnotations);
+    }
+
+    if (options.includeFields) {
+      for (var f in type.fields) {
+        if (f.isStatic) continue;
+        if (f.isSynthetic) continue; // exclude getters
+        if (options.excludeFinal && f.isFinal) continue;
+        var name = f.displayName;
+        if (options.matches != null && !options.matches(name)) continue;
+        if (skipBecauseOfAnnotations(f)) continue;
+        if (results != null) results.add(f);
+        generator.addDeclaration(id, name, _typeFor(f.type.element),
+            isField: true,
+            isFinal: f.isFinal,
+            annotations: _copyAnnotations(f));
+        if (includeAccessors) _addAccessors(name, !f.isFinal);
+      }
+    }
+
+    if (options.includeProperties) {
+      for (var a in type.accessors) {
+        if (a is! PropertyAccessorElement) continue;
+        if (a.isStatic || !a.isGetter) continue;
+        var v = a.variable;
+        if (v is FieldElement && !v.isSynthetic) continue; // exclude fields
+        if (options.excludeFinal && v.isFinal) continue;
+        var name = v.displayName;
+        if (options.matches != null && !options.matches(name)) continue;
+        if (skipBecauseOfAnnotations(a)) continue;
+        if (results != null) results.add(a);
+        generator.addDeclaration(id, name, _typeFor(a.type.returnType.element),
+            isProperty: true,
+            isFinal: v.isFinal,
+            annotations: _copyAnnotations(a));
+        if (includeAccessors) _addAccessors(name, !v.isFinal);
+      }
+    }
+
+    if (options.includeMethods) {
+      for (var m in type.methods) {
+        if (m.isStatic) continue;
+        var name = m.displayName;
+        if (options.matches != null && !options.matches(name)) continue;
+        if (skipBecauseOfAnnotations(m)) continue;
+        if (results != null) results.add(m);
+        generator.addDeclaration(
+            id, name, new TypeIdentifier('dart:core', 'Function'),
+            isMethod: true, annotations: _copyAnnotations(m));
+        if (includeAccessors) _addAccessors(name, false);
+      }
+    }
+  }
+
+  /// Adds the declaration of [name] if it was found in [type]. If [recursive]
+  /// is true, then we continue looking up [name] in the parent classes until we
+  /// find it or we reach [includeUpTo] or Object. Returns whether the
+  /// declaration was found.  When a declaration is found, add also a symbol,
+  /// getter, and setter if [includeAccessors] is true.
+  bool lookupMember(ClassElement type, String name,
+          {bool recursive: false,
+          bool includeAccessors: true,
+          ClassElement includeUpTo}) =>
+      _lookupMemberInternal(
+          type, _typeFor(type), name, recursive, includeAccessors, includeUpTo);
+
+  /// Helper for [lookupMember] that walks up the type hierarchy including mixin
+  /// classes.
+  bool _lookupMemberInternal(ClassElement type, TypeIdentifier id, String name,
+      bool recursive, bool includeAccessors, ClassElement includeUpTo) {
+    // Exclude members from [Object].
+    if (type.type.isObject) return false;
+    generator.addEmptyDeclaration(id);
+    for (var f in type.fields) {
+      if (f.displayName != name) continue;
+      if (f.isSynthetic) continue; // exclude getters
+      generator.addDeclaration(id, name, _typeFor(f.type.element),
+          isField: true,
+          isFinal: f.isFinal,
+          isStatic: f.isStatic,
+          annotations: _copyAnnotations(f));
+      if (includeAccessors && !f.isStatic) _addAccessors(name, !f.isFinal);
+      return true;
+    }
+
+    for (var a in type.accessors) {
+      if (a is! PropertyAccessorElement) continue;
+      // TODO(sigmund): support setters without getters.
+      if (!a.isGetter) continue;
+      if (a.displayName != name) continue;
+      var v = a.variable;
+      if (v is FieldElement && !v.isSynthetic) continue; // exclude fields
+      generator.addDeclaration(id, name, _typeFor(a.type.returnType.element),
+          isProperty: true,
+          isFinal: v.isFinal,
+          isStatic: a.isStatic,
+          annotations: _copyAnnotations(a));
+      if (includeAccessors && !v.isStatic) _addAccessors(name, !v.isFinal);
+      return true;
+    }
+
+    for (var m in type.methods) {
+      if (m.displayName != name) continue;
+      generator.addDeclaration(
+          id, name, new TypeIdentifier('dart:core', 'Function'),
+          isMethod: true,
+          isStatic: m.isStatic,
+          annotations: _copyAnnotations(m));
+      if (includeAccessors) {
+        if (m.isStatic) {
+          generator.addStaticMethod(id, name);
+          generator.addSymbol(name);
+        } else {
+          _addAccessors(name, false);
+        }
+      }
+      return true;
+    }
+
+    if (recursive) {
+      lookupParent(type);
+      var parent = type.supertype != null ? type.supertype.element : null;
+      if (parent == null || parent == includeUpTo) return false;
+      var parentId = _typeFor(parent);
+      for (var m in type.mixins) {
+        var mixinClass = m.element;
+        var mixinId = _mixins[parentId][mixinClass];
+        if (_lookupMemberInternal(
+            mixinClass, mixinId, name, false, includeAccessors, includeUpTo)) {
+          return true;
+        }
+        parentId = mixinId;
+      }
+      return _lookupMemberInternal(
+          parent, parentId, name, true, includeAccessors, includeUpTo);
+    }
+    return false;
+  }
+
+  /// Add information so smoke can invoke the static method [type].[name].
+  void addStaticMethod(ClassElement type, String name) {
+    generator.addStaticMethod(_typeFor(type), name);
+  }
+
+  /// Adds [name] as a symbol, a getter, and optionally a setter in [generator].
+  _addAccessors(String name, bool includeSetter) {
+    generator.addSymbol(name);
+    generator.addGetter(name);
+    if (includeSetter) generator.addSetter(name);
+  }
+
+  /// Copy metadata associated with the declaration of [target].
+  List<ConstExpression> _copyAnnotations(Element target) {
+    var node = target.computeNode();
+    // [node] is the initialization expression, we walk up to get to the actual
+    // member declaration where the metadata is attached to.
+    while (node is! ClassMember) node = node.parent;
+    return (node as ClassMember)
+        .metadata
+        .map/*<ConstExpression>*/(_convertAnnotation)
+        .toList();
+  }
+
+  /// Converts annotations into [ConstExpression]s supported by the codegen
+  /// library.
+  ConstExpression _convertAnnotation(Annotation annotation) {
+    var element = annotation.element;
+    if (element is ConstructorElement) {
+      if (!element.name.isEmpty) {
+        throw new UnimplementedError(
+            'named constructors are not implemented in smoke.codegen.recorder');
+      }
+
+      var positionalArgs = <ConstExpression>[];
+      var namedArgs = <String, ConstExpression>{};
+      for (var arg in annotation.arguments.arguments) {
+        if (arg is NamedExpression) {
+          namedArgs[arg.name.label.name] = _convertExpression(arg.expression);
+        } else {
+          positionalArgs.add(_convertExpression(arg));
+        }
+      }
+
+      return new ConstructorExpression(importUrlFor(element.library),
+          element.enclosingElement.name, positionalArgs, namedArgs);
+    }
+
+    if (element is PropertyAccessorElement) {
+      return new TopLevelIdentifier(
+          importUrlFor(element.library), element.name);
+    }
+
+    throw new UnsupportedError('unsupported annotation $annotation');
+  }
+
+  /// Converts [expression] into a [ConstExpression].
+  ConstExpression _convertExpression(Expression expression) {
+    if (expression is StringLiteral) {
+      return new ConstExpression.string(expression.stringValue);
+    }
+
+    if (expression is BooleanLiteral ||
+        expression is DoubleLiteral ||
+        expression is IntegerLiteral ||
+        expression is NullLiteral) {
+      return new CodeAsConstExpression("${(expression as dynamic).value}");
+    }
+
+    if (expression is Identifier) {
+      var element = expression.bestElement;
+      if (element == null || !element.isPublic) {
+        throw new UnsupportedError('private constants are not supported');
+      }
+
+      var url = importUrlFor(element.library);
+      if (element is ClassElement) {
+        return new TopLevelIdentifier(url, element.name);
+      }
+
+      if (element is PropertyAccessorElement) {
+        var variable = element.variable;
+        if (variable is FieldElement) {
+          var cls = variable.enclosingElement;
+          return new TopLevelIdentifier(url, '${cls.name}.${variable.name}');
+        } else if (variable is TopLevelVariableElement) {
+          return new TopLevelIdentifier(url, variable.name);
+        }
+      }
+    }
+
+    throw new UnimplementedError('expression convertion not implemented in '
+        'smoke.codegen.recorder (${expression.runtimeType} $expression)');
+  }
+}
+
+/// Returns whether [metadata] contains any annotation that is either equal to
+/// an annotation in [queryAnnotations] or whose type is a subclass of a type
+/// listed in [queryAnnotations]. This is equivalent to the check done in
+/// `src/common.dart#matchesAnnotation`, except that this is applied to
+/// static metadata as it was provided by the analyzer.
+bool _matchesAnnotation(
+    Iterable<ElementAnnotation> metadata, Iterable<Element> queryAnnotations) {
+  for (var meta in metadata) {
+    var element = meta.element;
+    var exp;
+    var type;
+    if (element is PropertyAccessorElement) {
+      exp = element.variable;
+      type = exp.evaluationResult.value.type;
+    } else if (element is ConstructorElement) {
+      exp = element;
+      type = element.enclosingElement.type;
+    } else {
+      throw new UnimplementedError('Unsupported annotation: ${meta}');
+    }
+    for (var queryMeta in queryAnnotations) {
+      if (exp == queryMeta) return true;
+      if (queryMeta is ClassElement && type.isSubtypeOf(queryMeta.type)) {
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+/// Options equivalent to `smoke.dart#QueryOptions`, except that type
+/// information and annotations are denoted by resolver's elements.
+class QueryOptions {
+  /// Whether to include fields (default is true).
+  final bool includeFields;
+
+  /// Whether to include getters and setters (default is true). Note that to
+  /// include fields you also need to enable [includeFields].
+  final bool includeProperties;
+
+  /// Whether to include symbols from the given type and its superclasses
+  /// (except [Object]).
+  final bool includeInherited;
+
+  /// If [includeInherited], walk up the type hierarchy up to this type
+  /// (defaults to [Object]).
+  final ClassElement includeUpTo;
+
+  /// Whether to include final fields and getter-only properties.
+  final bool excludeFinal;
+
+  /// Whether to include methods (default is false).
+  final bool includeMethods;
+
+  /// If [withAnnotation] is not null, then it should be a list of types, so
+  /// only symbols that are annotated with instances of those types are
+  /// included.
+  final List<Element> withAnnotations;
+
+  /// If [matches] is not null, then only those fields, properties, or methods
+  /// that match will be included.
+  final NameMatcher matches;
+
+  const QueryOptions(
+      {this.includeFields: true,
+      this.includeProperties: true,
+      this.includeInherited: true,
+      this.includeUpTo: null,
+      this.excludeFinal: false,
+      this.includeMethods: false,
+      this.withAnnotations: null,
+      this.matches: null});
+}
+
+/// Predicate that tells whether [name] should be included in query results.
+typedef bool NameMatcher(String name);
diff --git a/pub/smoke/lib/mirrors.dart b/pub/smoke/lib/mirrors.dart
new file mode 100644
index 0000000..a4ccf86
--- /dev/null
+++ b/pub/smoke/lib/mirrors.dart
@@ -0,0 +1,336 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Implementation of the smoke services using mirrors.
+library smoke.mirrors;
+
+import 'dart:mirrors';
+import 'package:smoke/smoke.dart';
+import 'package:logging/logging.dart';
+import 'src/common.dart';
+
+/// Set up the smoke package to use a mirror-based implementation. To tune what
+/// is preserved by `dart:mirrors`, use a @MirrorsUsed annotation and include
+/// 'smoke.mirrors' in your override arguments.
+useMirrors() {
+  configure(
+      new ReflectiveObjectAccessorService(),
+      new ReflectiveTypeInspectorService(),
+      new ReflectiveSymbolConverterService());
+}
+
+var _logger = new Logger('smoke.mirrors');
+
+/// Implements [ObjectAccessorService] using mirrors.
+class ReflectiveObjectAccessorService implements ObjectAccessorService {
+  read(Object object, Symbol name) => reflect(object).getField(name).reflectee;
+
+  void write(Object object, Symbol name, value) {
+    reflect(object).setField(name, value);
+  }
+
+  invoke(receiver, Symbol methodName, List args,
+      {Map namedArgs, bool adjust: false}) {
+    var receiverMirror;
+    var method;
+    if (receiver is Type && methodName != #toString) {
+      receiverMirror = reflectType(receiver);
+      method = receiverMirror.declarations[methodName];
+    } else {
+      receiverMirror = reflect(receiver);
+      method = _findMethod(receiverMirror.type, methodName);
+    }
+    if (method != null && adjust) {
+      var required = 0;
+      var optional = 0;
+      for (var p in method.parameters) {
+        if (p.isOptional) {
+          if (!p.isNamed) optional++;
+        } else {
+          required++;
+        }
+      }
+      args = adjustList(args, required, required + optional);
+    }
+    return receiverMirror.invoke(methodName, args, namedArgs).reflectee;
+  }
+}
+
+/// Implements [TypeInspectorService] using mirrors.
+class ReflectiveTypeInspectorService implements TypeInspectorService {
+  bool isSubclassOf(Type type, Type supertype) {
+    if (type == supertype || supertype == Object) return true;
+    // TODO(sigmund): change to mirror.isSubclassOf when it gets implemented in
+    // dart2js. (dartbug.com/12439)
+    var mirror = reflectClass(type);
+    var top = reflectClass(supertype);
+    while (mirror != _objectType) {
+      mirror = _safeSuperclass(mirror);
+      if (mirror == top) return true;
+    }
+    return false;
+  }
+
+  bool hasGetter(Type type, Symbol name) {
+    var reflectiveType = reflectType(type);
+    if (reflectiveType is! ClassMirror) return false;
+    var mirror = reflectiveType as ClassMirror;
+    while (mirror != _objectType) {
+      final members = mirror.declarations;
+      if (members.containsKey(name)) return true;
+      mirror = _safeSuperclass(mirror);
+    }
+    return false;
+  }
+
+  bool hasSetter(Type type, Symbol name) {
+    var reflectiveType = reflectType(type);
+    if (reflectiveType is! ClassMirror) return false;
+    var mirror = reflectiveType as ClassMirror;
+    var setterName = _setterName(name);
+    while (mirror != _objectType) {
+      final members = mirror.declarations;
+      var declaration = members[name];
+      if (declaration is VariableMirror && !declaration.isFinal) return true;
+      if (members.containsKey(setterName)) return true;
+      mirror = _safeSuperclass(mirror);
+    }
+    return false;
+  }
+
+  bool hasInstanceMethod(Type type, Symbol name) {
+    var reflectiveType = reflectType(type);
+    if (reflectiveType is! ClassMirror) return false;
+    var mirror = reflectiveType as ClassMirror;
+    while (mirror != _objectType) {
+      final m = mirror.declarations[name];
+      if (m is MethodMirror && m.isRegularMethod && !m.isStatic) return true;
+      mirror = _safeSuperclass(mirror);
+    }
+    return false;
+  }
+
+  bool hasStaticMethod(Type type, Symbol name) {
+    var reflectiveType = reflectType(type);
+    if (reflectiveType is! ClassMirror) return false;
+    var mirror = reflectiveType as ClassMirror;
+    final m = mirror.declarations[name];
+    return m is MethodMirror && m.isRegularMethod && m.isStatic;
+  }
+
+  Declaration getDeclaration(Type type, Symbol name) {
+    var reflectiveType = reflectType(type);
+    if (reflectiveType is! ClassMirror) return null;
+    var mirror = reflectiveType as ClassMirror;
+
+    var declaration;
+    while (mirror != _objectType) {
+      final members = mirror.declarations;
+      if (members.containsKey(name)) {
+        declaration = members[name];
+        break;
+      }
+      mirror = _safeSuperclass(mirror);
+    }
+    if (declaration == null) {
+      _logger.severe("declaration doesn't exists ($type.$name).");
+      return null;
+    }
+    return new _MirrorDeclaration(mirror, declaration);
+  }
+
+  List<Declaration> query(Type type, QueryOptions options) {
+    var mirror = reflectType(type);
+    if (mirror is! ClassMirror) return null;
+    return _query(mirror, options);
+  }
+
+  List<Declaration> _query(ClassMirror cls, QueryOptions options) {
+    final visitParent = options.includeInherited &&
+        cls.superclass != null &&
+        // TODO(sigmund): use _toType(cls.superclass) != options.includeUpTo
+        // when dartbug.com/16925 gets fixed (_toType fails in dart2js if
+        // applied to classes with type-arguments).
+        cls.superclass != reflectClass(options.includeUpTo);
+    var result =
+        visitParent ? _query(cls.superclass, options) : <Declaration>[];
+    for (var member in cls.declarations.values) {
+      if (member is! VariableMirror && member is! MethodMirror) continue;
+      if (member.isPrivate || (member as dynamic).isStatic) continue;
+      var name = member.simpleName;
+      if (member is VariableMirror) {
+        if (!options.includeFields) continue;
+        if (options.excludeFinal && member.isFinal) continue;
+      }
+
+      // TODO(sigmund): what if we have a setter but no getter?
+      if (member is MethodMirror && member.isSetter) continue;
+      if (member is MethodMirror && member.isConstructor) continue;
+
+      if (member is MethodMirror && member.isGetter) {
+        if (!options.includeProperties) continue;
+        if (options.excludeFinal && !_hasSetter(cls, member)) continue;
+      }
+
+      if (member is MethodMirror && member.isRegularMethod) {
+        if (!options.includeMethods) continue;
+      }
+
+      if (options.matches != null && !options.matches(name)) continue;
+
+      var annotations = member.metadata.map((m) => m.reflectee).toList();
+      if (options.withAnnotations != null &&
+          !matchesAnnotation(annotations, options.withAnnotations)) {
+        continue;
+      }
+
+      var declaration = new _MirrorDeclaration(cls, member);
+
+      if (options.excludeOverriden) {
+        result.retainWhere((value) => declaration.name != value.name);
+      }
+
+      // TODO(sigmund): should we cache parts of this declaration so we don't
+      // compute them twice?  For example, this chould be `new Declaration(name,
+      // type, ...)` and we could reuse what we computed above to implement the
+      // query filtering.  Note, when I tried to eagerly compute everything, I
+      // run into trouble with type (`type = _toType(member.type)`), dart2js
+      // failed when the underlying types had type-arguments (see
+      // dartbug.com/16925).
+      result.add(declaration);
+    }
+
+    return result;
+  }
+}
+
+/// Implements [SymbolConverterService] using mirrors.
+class ReflectiveSymbolConverterService implements SymbolConverterService {
+  String symbolToName(Symbol symbol) => MirrorSystem.getName(symbol);
+  Symbol nameToSymbol(String name) => new Symbol(name);
+}
+
+// TODO(jmesserly): workaround for:
+// https://code.google.com/p/dart/issues/detail?id=10029
+Symbol _setterName(Symbol getter) =>
+    new Symbol('${MirrorSystem.getName(getter)}=');
+
+ClassMirror _safeSuperclass(ClassMirror type) {
+  try {
+    var t = type.superclass;
+    // TODO(sigmund): workaround for darbug.com/17779.
+    // Interceptor is leaked by dart2js. It has the same methods as Object
+    // (including noSuchMethod), and our code above assumes that it doesn't
+    // exist. Most queries exclude Object, so they should exclude Interceptor
+    // too. We don't check for t.simpleName == #Interceptor because depending on
+    // dart2js optimizations it may be #Interceptor or #num/Interceptor.
+    // Checking for a private library seems to reliably filter this out.
+    if (t != null && t.owner != null && t.owner.isPrivate) {
+      t = _objectType;
+    }
+    return t;
+  } on UnsupportedError catch (_) {
+    // Note: dart2js throws UnsupportedError when the type is not reflectable.
+    return _objectType;
+  }
+}
+
+MethodMirror _findMethod(ClassMirror type, Symbol name) {
+  do {
+    var member = type.declarations[name];
+    if (member is MethodMirror) return member;
+    type = type.superclass;
+  } while (type != null);
+  return null;
+}
+
+// When recursively looking for symbols up the type-hierarchy it's generally a
+// good idea to stop at Object, since we know it doesn't have what we want.
+// TODO(jmesserly): This is also a workaround for what appears to be a V8
+// bug introduced between Chrome 31 and 32. After 32
+// JsClassMirror.declarations on Object calls
+// JsClassMirror.typeVariables, which tries to get the _jsConstructor's
+// .prototype["<>"]. This ends up getting the "" property instead, maybe
+// because "<>" doesn't exist, and gets ";" which then blows up because
+// the code later on expects a List of ints.
+final _objectType = reflectClass(Object);
+
+bool _hasSetter(ClassMirror cls, MethodMirror getter) {
+  var mirror = cls.declarations[_setterName(getter.simpleName)];
+  return mirror is MethodMirror && mirror.isSetter;
+}
+
+Type _toType(TypeMirror t) {
+  // TODO(sigmund): this line can go away after dartbug.com/16962
+  if (t == _objectType) return Object;
+  if (t is ClassMirror) return t.reflectedType;
+  if (t == null || t.qualifiedName != #dynamic) {
+    _logger.warning('unknown type ($t).');
+  }
+  return dynamic;
+}
+
+class _MirrorDeclaration implements Declaration {
+  final ClassMirror _cls;
+  final _original;
+
+  _MirrorDeclaration(this._cls, DeclarationMirror this._original);
+
+  Symbol get name => _original.simpleName;
+
+  DeclarationKind get kind => isField ? FIELD : isProperty ? PROPERTY : METHOD;
+
+  bool get isField => _original is VariableMirror;
+
+  bool get isProperty =>
+      _original is MethodMirror && !_original.isRegularMethod;
+
+  bool get isMethod => !isField && !isProperty;
+
+  /// If this is a property, whether it's read only (final fields or properties
+  /// with no setter).
+  bool get isFinal =>
+      (_original is VariableMirror && _original.isFinal) ||
+      (_original is MethodMirror &&
+          _original.isGetter &&
+          !_hasSetter(_cls, _original));
+
+  /// If this is a property, it's declared type (including dynamic if it's not
+  /// declared). For methods, the returned type.
+  Type get type {
+    if (_original is MethodMirror && _original.isRegularMethod) {
+      return Function;
+    }
+    var typeMirror =
+        _original is VariableMirror ? _original.type : _original.returnType;
+    return _toType(typeMirror);
+  }
+
+  /// Whether this symbol is static.
+  bool get isStatic => _original.isStatic;
+
+  /// List of annotations in this declaration.
+  List get annotations => _original.metadata.map((a) => a.reflectee).toList();
+
+  int get hashCode => name.hashCode;
+  operator ==(other) =>
+      other is Declaration &&
+      name == other.name &&
+      kind == other.kind &&
+      isFinal == other.isFinal &&
+      type == other.type &&
+      isStatic == other.isStatic &&
+      compareLists(annotations, other.annotations);
+  String toString() => (new StringBuffer()
+        ..write('(mirror-based-declaration ')
+        ..write(name)
+        ..write(isField
+            ? ' (field) '
+            : (isProperty ? ' (property) ' : ' (method) '))
+        ..write(isFinal ? 'final ' : '')
+        ..write(isStatic ? 'static ' : '')
+        ..write(annotations)
+        ..write(')'))
+      .toString();
+}
diff --git a/pub/smoke/lib/smoke.dart b/pub/smoke/lib/smoke.dart
new file mode 100644
index 0000000..afcc006
--- /dev/null
+++ b/pub/smoke/lib/smoke.dart
@@ -0,0 +1,286 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Collects services that can be used to access objects dynamically, inspect
+/// type information, and convert between symbols and strings.
+library smoke;
+
+import 'src/implementation.dart' as implementation;
+
+export 'src/common.dart' show minArgs, maxArgs, canAcceptNArgs, SUPPORTED_ARGS;
+import 'src/common.dart' show compareLists;
+
+/// Configures this library to use [objectAccessor] for all read/write/invoke
+/// APIs, [typeInspector] for all type query APIs, and [symbolConverter] for all
+/// symbol convertion operations.
+///
+/// This function doesn't need to be called during development, but frameworks
+/// should autogenerate a call to this function when running in deployment.
+void configure(
+    ObjectAccessorService objectAccessor,
+    TypeInspectorService typeInspector,
+    SymbolConverterService symbolConverter) {
+  implementation.objectAccessor = objectAccessor;
+  implementation.typeInspector = typeInspector;
+  implementation.symbolConverter = symbolConverter;
+}
+
+/// Return the value of [field] in [object].
+read(Object object, Symbol field) =>
+    implementation.objectAccessor.read(object, field);
+
+/// Update the [value] of [field] in [object].
+void write(Object object, Symbol field, value) =>
+    implementation.objectAccessor.write(object, field, value);
+
+/// Invoke [method] in [receiver] with [args]. The [receiver] can be either an
+/// object (to invoke instance methods) or a type (to invoke static methods).
+/// This function optionally [adjust]s the list of arguments to match the number
+/// of formal parameters by either adding nulls for missing arguments, or by
+/// truncating the list.
+invoke(receiver, Symbol method, List args,
+        {Map namedArgs, bool adjust: false}) =>
+    implementation.objectAccessor
+        .invoke(receiver, method, args, namedArgs: namedArgs, adjust: adjust);
+
+/// Tells whether [type] is transitively a subclass of [supertype].
+bool isSubclassOf(Type type, Type supertype) =>
+    implementation.typeInspector.isSubclassOf(type, supertype);
+
+// TODO(sigmund): consider adding also:
+// * isImplementationOf(type, subtype) to tells whether [type] declares that it
+//   implements the [supertype] interface.
+// * isSubtypeOf(type, subtype): Tells whether [type]'s interface is a sybtype
+//   of [supertype]. That is, whether it is a subclass or if [type] implements
+//   [supertype].
+
+/// Tells whether [type] has a field or getter for [field].
+bool hasGetter(Type type, Symbol field) =>
+    implementation.typeInspector.hasGetter(type, field);
+
+/// Tells whether [type] has a field or setter for [field].
+bool hasSetter(Type type, Symbol field) =>
+    implementation.typeInspector.hasSetter(type, field);
+
+/// Tells whether [type] or a superclass (other than [Object]) defines
+/// `noSuchMethod`.
+bool hasNoSuchMethod(Type type) => hasInstanceMethod(type, #noSuchMethod);
+
+/// Tells whether [type] has or a superclass contains a specific instance
+/// [method] (excluding methods in [Object]).
+bool hasInstanceMethod(Type type, Symbol method) =>
+    implementation.typeInspector.hasInstanceMethod(type, method);
+
+/// Tells whether [type] has a specific static [method].
+bool hasStaticMethod(Type type, Symbol method) =>
+    implementation.typeInspector.hasStaticMethod(type, method);
+
+/// Get the declaration associated with field [name] found in [type] or a
+/// superclass of [type].
+Declaration getDeclaration(Type type, Symbol name) =>
+    implementation.typeInspector.getDeclaration(type, name);
+
+/// Retrieve all symbols of [type] that match [options].
+List<Declaration> query(Type type, QueryOptions options) =>
+    implementation.typeInspector.query(type, options);
+
+/// Returns the name associated with a [symbol].
+String symbolToName(Symbol symbol) =>
+    implementation.symbolConverter.symbolToName(symbol);
+
+/// Returns the symbol associated with a [name].
+Symbol nameToSymbol(String name) =>
+    implementation.symbolConverter.nameToSymbol(name);
+
+/// Establishes the parameters for [query] to search for symbols in a type
+/// hierarchy. For now only public instance symbols can be queried (no private,
+/// no static).
+class QueryOptions {
+  /// Whether to include fields (default is true).
+  final bool includeFields;
+
+  /// Whether to include getters and setters (default is true). Note that to
+  /// include fields you also need to enable [includeFields].
+  final bool includeProperties;
+
+  /// Whether to include symbols from the given type and its superclasses
+  /// (except [Object]).
+  final bool includeInherited;
+
+  /// If [includeInherited], walk up the type hierarchy up to this type
+  /// (defaults to [Object]).
+  final Type includeUpTo;
+
+  /// Whether to include final fields and getter-only properties.
+  final bool excludeFinal;
+
+  /// Whether to include symbols that are overriden within the subclass
+  /// (default is false).
+  final bool excludeOverriden;
+
+  /// Whether to include methods (default is false).
+  final bool includeMethods;
+
+  /// If [withAnnotation] is not null, then it should be a list of types, so
+  /// only symbols that are annotated with instances of those types are
+  /// included.
+  final List withAnnotations;
+
+  /// If [matches] is not null, then include only those fields, properties, or
+  /// methods that match the predicate.
+  final NameMatcher matches;
+
+  const QueryOptions(
+      {this.includeFields: true,
+      this.includeProperties: true,
+      this.includeInherited: true,
+      this.includeUpTo: Object,
+      this.excludeFinal: false,
+      this.excludeOverriden: false,
+      this.includeMethods: false,
+      this.withAnnotations: null,
+      this.matches: null});
+
+  String toString() => (new StringBuffer()
+        ..write('(options:')
+        ..write(includeFields ? 'fields ' : '')
+        ..write(includeProperties ? 'properties ' : '')
+        ..write(includeMethods ? 'methods ' : '')
+        ..write(includeInherited ? 'inherited ' : '_')
+        ..write(excludeFinal ? 'no finals ' : '')
+        ..write(excludeOverriden ? 'no overriden ' : '')
+        ..write('annotations: $withAnnotations')
+        ..write(matches != null ? 'with matcher' : '')
+        ..write(')'))
+      .toString();
+}
+
+/// Used to filter query results based on a predicate on [name]. Returns true if
+/// [name] should be included in the query results.
+typedef bool NameMatcher(Symbol name);
+
+/// Information associated with a symbol declaration (like a property or
+/// method).
+class Declaration {
+  /// Name of the property or method
+  final Symbol name;
+
+  /// Kind of declaration (field, property, or method).
+  final DeclarationKind kind;
+
+  /// Whether the symbol is a field (and not a getter/setter property).
+  bool get isField => kind == FIELD;
+
+  /// Whether the symbol is a getter/setter and not a field.
+  bool get isProperty => kind == PROPERTY;
+
+  /// Whether the symbol is a method.
+  bool get isMethod => kind == METHOD;
+
+  /// For fields, whether they are final, for properties, whether they are
+  /// read-only (they have no setter).
+  final bool isFinal;
+
+  /// If this is a field or property, it's declared type (including dynamic if
+  /// it's not declared). For methods, the returned type.
+  final Type type;
+
+  /// Whether this symbol is static.
+  final bool isStatic;
+
+  /// List of annotations in this declaration.
+  final List annotations;
+
+  const Declaration(this.name, this.type,
+      {this.kind: FIELD,
+      this.isFinal: false,
+      this.isStatic: false,
+      this.annotations: const []});
+
+  int get hashCode => name.hashCode;
+  operator ==(other) =>
+      other is Declaration &&
+      name == other.name &&
+      kind == other.kind &&
+      isFinal == other.isFinal &&
+      type == other.type &&
+      isStatic == other.isStatic &&
+      compareLists(annotations, other.annotations);
+
+  String toString() {
+    return (new StringBuffer()
+          ..write('(declaration ')
+          ..write(name)
+          ..write(isProperty ? ' (property) ' : ' (method) ')
+          ..write(isFinal ? 'final ' : '')
+          ..write(isStatic ? 'static ' : '')
+          ..write(annotations)
+          ..write(')'))
+        .toString();
+  }
+}
+
+/// Enumeration for declaration kinds (field, property, or method)
+class DeclarationKind {
+  final int kind;
+  const DeclarationKind(this.kind);
+}
+
+/// Declaration kind used to denote a raw field.
+const FIELD = const DeclarationKind(0);
+
+/// Declaration kind used to denote a getter/setter.
+const PROPERTY = const DeclarationKind(1);
+
+/// Declaration kind used to denote a method.
+const METHOD = const DeclarationKind(2);
+
+/// A service that provides a way to implement simple operations on objects like
+/// read, write, and invoke.
+abstract class ObjectAccessorService {
+  /// Return the value of [field] in [object].
+  read(Object object, Symbol field);
+
+  /// Update the [value] of [field] in [object].
+  void write(Object object, Symbol field, value);
+
+  /// Invoke [method] in [object] with [args]. It optionally [adjust]s the list
+  /// of arguments to match the number of formal parameters by either adding
+  /// nulls for missing arguments, or by truncating the list.
+  invoke(Object object, Symbol method, List args,
+      {Map namedArgs, bool adjust: false});
+}
+
+/// A service that provides partial inspection into Dart types.
+abstract class TypeInspectorService {
+  /// Tells whether [type] is transitively a subclass of [supertype].
+  bool isSubclassOf(Type type, Type supertype);
+
+  /// Tells whether [type] has a field or getter for [name].
+  bool hasGetter(Type type, Symbol name);
+
+  /// Tells whether [type] has a field or setter for [name].
+  bool hasSetter(Type type, Symbol name);
+
+  /// Tells whether [type] has a specific instance [method].
+  bool hasInstanceMethod(Type type, Symbol method);
+
+  /// Tells whether [type] has a specific static [method].
+  bool hasStaticMethod(Type type, Symbol method);
+
+  /// Get the declaration associated with field [name] in [type].
+  Declaration getDeclaration(Type type, Symbol name);
+
+  /// Retrieve all symbols of [type] that match [options].
+  List<Declaration> query(Type type, QueryOptions options);
+}
+
+/// A service that converts between [Symbol]s and [String]s.
+abstract class SymbolConverterService {
+  /// Returns the name associated with a [symbol].
+  String symbolToName(Symbol symbol);
+
+  /// Returns the symbol associated with a [name].
+  Symbol nameToSymbol(String name);
+}
diff --git a/pub/smoke/lib/src/common.dart b/pub/smoke/lib/src/common.dart
new file mode 100644
index 0000000..69cd8be
--- /dev/null
+++ b/pub/smoke/lib/src/common.dart
@@ -0,0 +1,210 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Some common utilities used by other libraries in this package.
+library smoke.src.common;
+
+import 'package:smoke/smoke.dart' as smoke show isSubclassOf;
+
+/// Returns [input] adjusted to be within [min] and [max] length. Truncating it
+/// if it's longer, or padding it with nulls if it's shorter. The returned list
+/// is a new copy if any modification is needed, otherwise [input] is returned.
+List adjustList(List input, int min, int max) {
+  if (input.length < min) {
+    return new List(min)..setRange(0, input.length, input);
+  }
+
+  if (input.length > max) {
+    return new List(max)..setRange(0, max, input);
+  }
+  return input;
+}
+
+/// Returns whether [metadata] contains any annotation that is either equal to
+/// an annotation in [queryAnnotations] or whose type is listed in
+/// [queryAnnotations].
+bool matchesAnnotation(Iterable metadata, Iterable queryAnnotations) {
+  for (var meta in metadata) {
+    for (var queryMeta in queryAnnotations) {
+      if (meta == queryMeta) return true;
+      if (queryMeta is Type && smoke.isSubclassOf(meta.runtimeType, queryMeta))
+        return true;
+    }
+  }
+  return false;
+}
+
+/// Number of arguments supported by [minArgs] and [maxArgs].
+const SUPPORTED_ARGS = 15;
+
+typedef _Func0();
+typedef _Func1(a);
+typedef _Func2(a, b);
+typedef _Func3(a, b, c);
+typedef _Func4(a, b, c, d);
+typedef _Func5(a, b, c, d, e);
+typedef _Func6(a, b, c, d, e, f);
+typedef _Func7(a, b, c, d, e, f, g);
+typedef _Func8(a, b, c, d, e, f, g, h);
+typedef _Func9(a, b, c, d, e, f, g, h, i);
+typedef _Func10(a, b, c, d, e, f, g, h, i, j);
+typedef _Func11(a, b, c, d, e, f, g, h, i, j, k);
+typedef _Func12(a, b, c, d, e, f, g, h, i, j, k, l);
+typedef _Func13(a, b, c, d, e, f, g, h, i, j, k, l, m);
+typedef _Func14(a, b, c, d, e, f, g, h, i, j, k, l, m, n);
+typedef _Func15(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o);
+
+/// Returns the minimum number of arguments that [f] takes as input, in other
+/// words, the total number of required arguments of [f]. If [f] expects more
+/// than [SUPPORTED_ARGS], this function returns `SUPPORTED_ARGS + 1`.
+///
+/// For instance, the current implementation only supports calculating the
+/// number of arguments between `0` and `3`. If the function takes `4` or more,
+/// this function automatically returns `4`.
+int minArgs(Function f) {
+  if (f is _Func0) return 0;
+  if (f is _Func1) return 1;
+  if (f is _Func2) return 2;
+  if (f is _Func3) return 3;
+  if (f is _Func4) return 4;
+  if (f is _Func5) return 5;
+  if (f is _Func6) return 6;
+  if (f is _Func7) return 7;
+  if (f is _Func8) return 8;
+  if (f is _Func9) return 9;
+  if (f is _Func10) return 10;
+  if (f is _Func11) return 11;
+  if (f is _Func12) return 12;
+  if (f is _Func13) return 13;
+  if (f is _Func14) return 14;
+  if (f is _Func15) return 15;
+  return SUPPORTED_ARGS + 1;
+}
+
+/// Returns the maximum number of arguments that [f] takes as input, which is
+/// the total number of required and optional arguments of [f]. If
+/// [f] may take more than [SUPPORTED_ARGS] required arguments, this function
+/// returns `-1`. However, if it takes less required arguments, but more than
+/// [SUPPORTED_ARGS] arguments including optional arguments, the result will be
+/// [SUPPORTED_ARGS].
+///
+/// For instance, the current implementation only supports calculating the
+/// number of arguments between `0` and [SUPPORTED_ARGS].  If the function
+/// takes more than [SUPPORTED_ARGS] mandatory arguments, this function
+/// returns `-1`, but if the funtion takes
+/// `8` mandatory arguments and `10` optional arguments, this function returns
+/// [SUPPORTED_ARGS].
+int maxArgs(Function f) {
+  // We could perform a full modified binary search but we really only care
+  // about performance for functions with fewer than 4 arguments.
+  if (f is! _Func2) {
+    if (f is _Func1) return 1;
+    if (f is _Func0) return 0;
+    if (f is! _Func4 && f is _Func3) return 3;
+    // Fall through to the slow case as the function has has maxArgs > 3.
+  } else if (f is! _Func4) {
+    return f is _Func3 ? 3 : 2;
+  }
+
+  if (f is _Func15) return 15;
+  if (f is _Func14) return 14;
+  if (f is _Func13) return 13;
+  if (f is _Func12) return 12;
+  if (f is _Func11) return 11;
+  if (f is _Func10) return 10;
+  if (f is _Func9) return 9;
+  if (f is _Func8) return 8;
+  if (f is _Func7) return 7;
+  if (f is _Func6) return 6;
+  if (f is _Func5) return 5;
+  if (f is _Func4) return 4;
+  if (f is _Func3) return 3;
+  if (f is _Func2) return 2;
+  if (f is _Func1) return 1;
+  if (f is _Func0) return 0;
+  return -1;
+}
+
+/// Returns whether [f] can accept [n] arguments.
+/// This is equivalent to
+/// `n >= minArgs(f) && n <= maxArgs(f)`
+/// when [f] accepts at most [SUPPORTED_ARGS].
+bool canAcceptNArgs(Function f, int n) {
+  switch (n) {
+    case 0:
+      return f is _Func0;
+    case 1:
+      return f is _Func1;
+    case 2:
+      return f is _Func2;
+    case 3:
+      return f is _Func3;
+    case 4:
+      return f is _Func4;
+    case 5:
+      return f is _Func5;
+    case 6:
+      return f is _Func6;
+    case 7:
+      return f is _Func7;
+    case 8:
+      return f is _Func8;
+    case 9:
+      return f is _Func9;
+    case 10:
+      return f is _Func10;
+    case 11:
+      return f is _Func11;
+    case 12:
+      return f is _Func12;
+    case 13:
+      return f is _Func13;
+    case 14:
+      return f is _Func14;
+    case 15:
+      return f is _Func15;
+  }
+  return false;
+}
+
+/// Shallow comparison of two lists.
+bool compareLists(List a, List b, {bool unordered: false}) {
+  if (a == null && b != null) return false;
+  if (a != null && b == null) return false;
+  if (a.length != b.length) return false;
+  if (unordered) {
+    var countMap = {};
+    for (var x in b) {
+      var count = countMap[x];
+      if (count == null) count = 0;
+      countMap[x] = count + 1;
+    }
+    for (var x in a) {
+      var count = countMap[x];
+      if (count == null) return false;
+      if (count == 1) {
+        countMap.remove(x);
+      } else {
+        countMap[x] = count - 1;
+      }
+    }
+    return countMap.isEmpty;
+  } else {
+    for (int i = 0; i < a.length; i++) {
+      if (a[i] != b[i]) return false;
+    }
+  }
+  return true;
+}
+
+/// Shallow comparison of two maps.
+bool compareMaps(Map a, Map b) {
+  if (a == null && b != null) return false;
+  if (a != null && b == null) return false;
+  if (a.length != b.length) return false;
+  for (var k in a.keys) {
+    if (!b.containsKey(k) || a[k] != b[k]) return false;
+  }
+  return true;
+}
diff --git a/pub/smoke/lib/src/default_transformer.dart b/pub/smoke/lib/src/default_transformer.dart
new file mode 100644
index 0000000..c5e93cc
--- /dev/null
+++ b/pub/smoke/lib/src/default_transformer.dart
@@ -0,0 +1,47 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Transformer that replaces the default mirror-based implementation of smoke,
+/// so that during deploy smoke doesn't include any dependency on dart:mirrors.
+library smoke.src.default_transformer;
+
+import 'dart:async';
+import 'package:barback/barback.dart';
+
+/// Replaces the default mirror-based implementation of smoke in
+/// `pacakge:smoke/implementation.dart`, so that during deploy smoke doesn't
+/// include any dependency on dart:mirrors.
+// TODO(sigmund): include tests that run this transformation automatically.
+class DefaultTransformer extends Transformer {
+  DefaultTransformer.asPlugin();
+
+  /// Only apply to `lib/src/implementation.dart`.
+  // TODO(nweiz): This should just take an AssetId when barback <0.13.0 support
+  // is dropped.
+  Future<bool> isPrimary(idOrAsset) {
+    assert(idOrAsset is Asset || idOrAsset is AssetId);
+    var id = idOrAsset is AssetId ? idOrAsset : (idOrAsset as Asset).id;
+    return new Future.value(
+        id.package == 'smoke' && id.path == 'lib/src/implementation.dart');
+  }
+
+  Future apply(Transform transform) {
+    var id = transform.primaryInput.id;
+    return transform.primaryInput.readAsString().then((code) {
+      // Note: this rewrite is highly-coupled with how implementation.dart is
+      // written. Make sure both are updated in sync.
+      transform.addOutput(new Asset.fromString(
+          id,
+          code
+              .replaceAll(new RegExp('new Reflective[^;]*;'),
+                  'throwNotConfiguredError();')
+              .replaceAll("import 'package:smoke/mirrors.dart';", '')));
+    });
+  }
+}
+
+/** Transformer phases which should be applied to the smoke package. */
+List<List<Transformer>> get phasesForSmoke => [
+      [new DefaultTransformer.asPlugin()]
+    ];
diff --git a/pub/smoke/lib/src/implementation.dart b/pub/smoke/lib/src/implementation.dart
new file mode 100644
index 0000000..51d8b9c
--- /dev/null
+++ b/pub/smoke/lib/src/implementation.dart
@@ -0,0 +1,34 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// A library that is used to select the default implementation of smoke. During
+/// development we use a default mirror-based implementation, for deployment we
+/// let the main program set programatically what implementation to use (likely
+/// one based on static code generation).
+library smoke.src.implementation;
+
+// IMPORTANT NOTE: This file is edited by a transformer in this package
+// (default_transformer.dart), so any edits here should be coordinated with
+// changes there.
+
+import 'package:smoke/mirrors.dart';
+import 'package:smoke/smoke.dart';
+
+/// Implementation of [ObjectAccessorService] in use, initialized lazily so it
+/// can be replaced at deployment time with an efficient alternative.
+ObjectAccessorService objectAccessor = new ReflectiveObjectAccessorService();
+
+/// Implementation of [TypeInspectorService] in use, initialized lazily so it
+/// can be replaced at deployment time with an efficient alternative.
+TypeInspectorService typeInspector = new ReflectiveTypeInspectorService();
+
+/// Implementation of [SymbolConverterService] in use, initialized lazily so it
+/// can be replaced at deployment time with an efficient alternative.
+SymbolConverterService symbolConverter = new ReflectiveSymbolConverterService();
+
+throwNotConfiguredError() {
+  throw new Exception('The "smoke" library has not been configured. '
+      'Make sure you import and configure one of the implementations ('
+      'package:smoke/mirrors.dart or package:smoke/static.dart).');
+}
diff --git a/pub/smoke/lib/static.dart b/pub/smoke/lib/static.dart
new file mode 100644
index 0000000..40ae158
--- /dev/null
+++ b/pub/smoke/lib/static.dart
@@ -0,0 +1,297 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Static implementation of smoke services using code-generated data.
+library smoke.static;
+
+import 'dart:math' as math;
+
+import 'package:smoke/smoke.dart';
+
+import 'src/common.dart';
+
+typedef T Getter<T>(object);
+typedef void Setter<T>(object, value);
+
+class StaticConfiguration {
+  /// Maps symbol to a function that reads that symbol of an object. For
+  /// instance, `#i: (o) => o.i`.
+  final Map<Symbol, Getter> getters;
+
+  /// Maps symbol to a function that updates that symbol of an object. For
+  /// instance, `#i: (o, v) { o.i = v; }`.
+  final Map<Symbol, Setter> setters;
+
+  /// Maps a type to its super class. For example, String: Object.
+  final Map<Type, Type> parents;
+
+  /// For each type, a map of declarations per symbol (property or method).
+  final Map<Type, Map<Symbol, Declaration>> declarations;
+
+  /// Static methods for each type.
+  // TODO(sigmund): should we add static getters & setters too?
+  final Map<Type, Map<Symbol, Function>> staticMethods;
+
+  /// A map from symbol to strings.
+  final Map<Symbol, String> names;
+
+  /// A map from strings to symbols (the reverse of [names]).
+  final Map<String, Symbol> _symbols = {};
+
+  /// Whether to check for missing declarations, otherwise, return default
+  /// values (for example a missing parent class can be treated as Object)
+  final bool checkedMode;
+
+  StaticConfiguration(
+      {Map<Symbol, Getter> getters,
+      Map<Symbol, Setter> setters,
+      Map<Type, Type> parents,
+      Map<Type, Map<Symbol, Declaration>> declarations,
+      Map<Type, Map<Symbol, Function>> staticMethods,
+      Map<Symbol, String> names,
+      this.checkedMode: true})
+      : getters = getters != null ? getters : {},
+        setters = setters != null ? setters : {},
+        parents = parents != null ? parents : {},
+        declarations = declarations != null ? declarations : {},
+        staticMethods = staticMethods != null ? staticMethods : {},
+        names = names != null ? names : {} {
+    this.names.forEach((k, v) {
+      _symbols[v] = k;
+    });
+  }
+
+  void addAll(StaticConfiguration other) {
+    getters.addAll(other.getters);
+    setters.addAll(other.setters);
+    parents.addAll(other.parents);
+    _nestedAddAll(declarations, other.declarations);
+    _nestedAddAll(staticMethods, other.staticMethods);
+    names.addAll(other.names);
+    other.names.forEach((k, v) {
+      _symbols[v] = k;
+    });
+  }
+
+  static _nestedAddAll(Map a, Map b) {
+    for (var key in b.keys) {
+      a.putIfAbsent(key, () => {});
+      a[key].addAll(b[key]);
+    }
+  }
+}
+
+/// Set up the smoke package to use a static implementation based on the given
+/// [configuration].
+useGeneratedCode(StaticConfiguration configuration) {
+  configure(
+      new GeneratedObjectAccessorService(configuration),
+      new GeneratedTypeInspectorService(configuration),
+      new GeneratedSymbolConverterService(configuration));
+}
+
+/// Implements [ObjectAccessorService] using a static configuration.
+class GeneratedObjectAccessorService implements ObjectAccessorService {
+  final StaticConfiguration _configuration;
+  Map<Symbol, Getter> get _getters => _configuration.getters;
+  Map<Symbol, Setter> get _setters => _configuration.setters;
+  Map<Type, Map<Symbol, Function>> get _staticMethods =>
+      _configuration.staticMethods;
+
+  GeneratedObjectAccessorService(this._configuration);
+
+  read(Object object, Symbol name) {
+    var getter = _getters[name];
+    if (getter == null) {
+      throw new MissingCodeException('getter "$name" in $object');
+    }
+    return getter(object);
+  }
+
+  void write(Object object, Symbol name, value) {
+    var setter = _setters[name];
+    if (setter == null) {
+      throw new MissingCodeException('setter "$name" in $object');
+    }
+    setter(object, value);
+  }
+
+  invoke(object, Symbol name, List args, {Map namedArgs, bool adjust: false}) {
+    var method;
+    if (object is Type && name != #toString) {
+      var classMethods = _staticMethods[object];
+      method = classMethods == null ? null : classMethods[name];
+    } else {
+      var getter = _getters[name];
+      method = getter == null ? null : getter(object);
+    }
+    if (method == null) {
+      throw new MissingCodeException('method "$name" in $object');
+    }
+    var tentativeError;
+    if (adjust) {
+      var min = minArgs(method);
+      if (min > SUPPORTED_ARGS) {
+        tentativeError = 'we tried to adjust the arguments for calling "$name"'
+            ', but we couldn\'t determine the exact number of arguments it '
+            'expects (it is more than $SUPPORTED_ARGS).';
+        // The argument list might be correct, so we still invoke the function
+        // and let the user see the error.
+        args = adjustList(args, min, math.max(min, args.length));
+      } else {
+        var max = maxArgs(method);
+        args = adjustList(args, min, max >= 0 ? max : args.length);
+      }
+    }
+    if (namedArgs != null) {
+      throw new UnsupportedError(
+          'smoke.static doesn\'t support namedArguments in invoke');
+    }
+    try {
+      return Function.apply(method, args);
+    } on NoSuchMethodError catch (_) {
+      // TODO(sigmund): consider whether this should just be in a logger or if
+      // we should wrap `e` as a new exception (what's the best way to let users
+      // know about this tentativeError?)
+      if (tentativeError != null) print(tentativeError);
+      rethrow;
+    }
+  }
+}
+
+/// Implements [TypeInspectorService] using a static configuration.
+class GeneratedTypeInspectorService implements TypeInspectorService {
+  final StaticConfiguration _configuration;
+
+  Map<Type, Type> get _parents => _configuration.parents;
+  Map<Type, Map<Symbol, Declaration>> get _declarations =>
+      _configuration.declarations;
+  bool get _checkedMode => _configuration.checkedMode;
+
+  GeneratedTypeInspectorService(this._configuration);
+
+  bool isSubclassOf(Type type, Type supertype) {
+    if (type == supertype || supertype == Object) return true;
+    while (type != Object) {
+      var parentType = _parents[type];
+      if (parentType == supertype) return true;
+      if (parentType == null) {
+        if (!_checkedMode) return false;
+        throw new MissingCodeException('superclass of "$type" ($parentType)');
+      }
+      type = parentType;
+    }
+    return false;
+  }
+
+  bool hasGetter(Type type, Symbol name) {
+    var decl = _findDeclaration(type, name);
+    // No need to check decl.isProperty because methods are also automatically
+    // considered getters (auto-closures).
+    return decl != null && !decl.isStatic;
+  }
+
+  bool hasSetter(Type type, Symbol name) {
+    var decl = _findDeclaration(type, name);
+    return decl != null && !decl.isMethod && !decl.isFinal && !decl.isStatic;
+  }
+
+  bool hasInstanceMethod(Type type, Symbol name) {
+    var decl = _findDeclaration(type, name);
+    return decl != null && decl.isMethod && !decl.isStatic;
+  }
+
+  bool hasStaticMethod(Type type, Symbol name) {
+    final map = _declarations[type];
+    if (map == null) {
+      if (!_checkedMode) return false;
+      throw new MissingCodeException('declarations for $type');
+    }
+    final decl = map[name];
+    return decl != null && decl.isMethod && decl.isStatic;
+  }
+
+  Declaration getDeclaration(Type type, Symbol name) {
+    var decl = _findDeclaration(type, name);
+    if (decl == null) {
+      if (!_checkedMode) return null;
+      throw new MissingCodeException('declaration for $type.$name');
+    }
+    return decl;
+  }
+
+  List<Declaration> query(Type type, QueryOptions options) {
+    var result = <Declaration>[];
+    if (options.includeInherited) {
+      var superclass = _parents[type];
+      if (superclass == null) {
+        if (_checkedMode) {
+          throw new MissingCodeException('superclass of "$type"');
+        }
+      } else if (superclass != options.includeUpTo) {
+        result = query(superclass, options);
+      }
+    }
+    var map = _declarations[type];
+    if (map == null) {
+      if (!_checkedMode) return result;
+      throw new MissingCodeException('declarations for $type');
+    }
+    for (var decl in map.values) {
+      if (!options.includeFields && decl.isField) continue;
+      if (!options.includeProperties && decl.isProperty) continue;
+      if (options.excludeFinal && decl.isFinal) continue;
+      if (!options.includeMethods && decl.isMethod) continue;
+      if (options.matches != null && !options.matches(decl.name)) continue;
+      if (options.withAnnotations != null &&
+          !matchesAnnotation(decl.annotations, options.withAnnotations)) {
+        continue;
+      }
+      if (options.excludeOverriden) {
+        result.retainWhere((value) => decl.name != value.name);
+      }
+      result.add(decl);
+    }
+    return result;
+  }
+
+  Declaration _findDeclaration(Type type, Symbol name) {
+    while (type != Object) {
+      final declarations = _declarations[type];
+      if (declarations != null) {
+        final declaration = declarations[name];
+        if (declaration != null) return declaration;
+      }
+      var parentType = _parents[type];
+      if (parentType == null) {
+        if (!_checkedMode) return null;
+        throw new MissingCodeException('superclass of "$type"');
+      }
+      type = parentType;
+    }
+    return null;
+  }
+}
+
+/// Implements [SymbolConverterService] using a static configuration.
+class GeneratedSymbolConverterService implements SymbolConverterService {
+  final StaticConfiguration _configuration;
+  Map<Symbol, String> get _names => _configuration.names;
+  Map<String, Symbol> get _symbols => _configuration._symbols;
+
+  GeneratedSymbolConverterService(this._configuration);
+
+  String symbolToName(Symbol symbol) => _names[symbol];
+  Symbol nameToSymbol(String name) => _symbols[name];
+}
+
+/// Exception thrown when trynig to access something that should be there, but
+/// the code generator didn't include it.
+class MissingCodeException implements Exception {
+  final String description;
+  MissingCodeException(this.description);
+
+  String toString() => 'Missing $description. '
+      'Code generation for the smoke package seems incomplete.';
+}
diff --git a/pub/smoke/lib/static_debug.dart b/pub/smoke/lib/static_debug.dart
new file mode 100644
index 0000000..4ebca72
--- /dev/null
+++ b/pub/smoke/lib/static_debug.dart
@@ -0,0 +1,120 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Static implementation of smoke services that uses code-generated data and
+/// verifies that the results match what we would get with a mirror-based
+/// implementation.
+library smoke.static_debug;
+
+export 'package:smoke/static.dart' show StaticConfiguration, Getter, Setter;
+import 'package:smoke/static.dart';
+import 'package:smoke/mirrors.dart';
+import 'package:smoke/smoke.dart';
+
+import 'src/common.dart' show compareLists;
+
+/// Set up the smoke package to use a static implementation based on the given
+/// [configuration].
+useGeneratedCode(StaticConfiguration configuration) {
+  configure(
+      new _DebugObjectAccessorService(configuration),
+      new _DebugTypeInspectorService(configuration),
+      new _DebugSymbolConverterService(configuration));
+}
+
+/// Implements [ObjectAccessorService] using a static configuration.
+class _DebugObjectAccessorService implements ObjectAccessorService {
+  GeneratedObjectAccessorService _static;
+  ReflectiveObjectAccessorService _mirrors;
+
+  _DebugObjectAccessorService(StaticConfiguration configuration)
+      : _static = new GeneratedObjectAccessorService(configuration),
+        _mirrors = new ReflectiveObjectAccessorService();
+
+  read(Object object, Symbol name) => _check('read', [object, name],
+      _static.read(object, name), _mirrors.read(object, name));
+
+  // Note: we can't verify operations with side-effects like write or invoke.
+  void write(Object object, Symbol name, value) =>
+      _static.write(object, name, value);
+
+  invoke(object, Symbol name, List args, {Map namedArgs, bool adjust: false}) =>
+      _static.invoke(object, name, args, namedArgs: namedArgs, adjust: adjust);
+}
+
+/// Implements [TypeInspectorService] using a static configuration.
+class _DebugTypeInspectorService implements TypeInspectorService {
+  GeneratedTypeInspectorService _static;
+  ReflectiveTypeInspectorService _mirrors;
+
+  _DebugTypeInspectorService(StaticConfiguration configuration)
+      : _static = new GeneratedTypeInspectorService(configuration),
+        _mirrors = new ReflectiveTypeInspectorService();
+
+  bool isSubclassOf(Type type, Type supertype) => _check(
+      'isSubclassOf',
+      [type, supertype],
+      _static.isSubclassOf(type, supertype),
+      _mirrors.isSubclassOf(type, supertype));
+
+  bool hasGetter(Type type, Symbol name) => _check('hasGetter', [type, name],
+      _static.hasGetter(type, name), _mirrors.hasGetter(type, name));
+
+  bool hasSetter(Type type, Symbol name) => _check('hasSetter', [type, name],
+      _static.hasSetter(type, name), _mirrors.hasSetter(type, name));
+
+  bool hasInstanceMethod(Type type, Symbol name) => _check(
+      'hasInstanceMethod',
+      [type, name],
+      _static.hasInstanceMethod(type, name),
+      _mirrors.hasInstanceMethod(type, name));
+
+  bool hasStaticMethod(Type type, Symbol name) => _check(
+      'hasStaticMethod',
+      [type, name],
+      _static.hasStaticMethod(type, name),
+      _mirrors.hasStaticMethod(type, name));
+
+  Declaration getDeclaration(Type type, Symbol name) => _check(
+      'getDeclaration',
+      [type, name],
+      _static.getDeclaration(type, name),
+      _mirrors.getDeclaration(type, name));
+
+  List<Declaration> query(Type type, QueryOptions options) => _check(
+      'query',
+      [type, options],
+      _static.query(type, options),
+      _mirrors.query(type, options));
+}
+
+/// Implements [SymbolConverterService] using a static configuration.
+class _DebugSymbolConverterService implements SymbolConverterService {
+  GeneratedSymbolConverterService _static;
+  ReflectiveSymbolConverterService _mirrors;
+
+  _DebugSymbolConverterService(StaticConfiguration configuration)
+      : _static = new GeneratedSymbolConverterService(configuration),
+        _mirrors = new ReflectiveSymbolConverterService();
+
+  String symbolToName(Symbol symbol) => _check('symbolToName', [symbol],
+      _static.symbolToName(symbol), _mirrors.symbolToName(symbol));
+
+  Symbol nameToSymbol(String name) => _check('nameToSymbol', [name],
+      _static.nameToSymbol(name), _mirrors.nameToSymbol(name));
+}
+
+dynamic/*=X*/ _check/*<X>*/(String operation, List arguments,
+    dynamic/*=X*/ staticResult, mirrorResult) {
+  if (staticResult == mirrorResult) return staticResult;
+  if (staticResult is List &&
+      mirrorResult is List &&
+      compareLists(staticResult as List, mirrorResult, unordered: true)) {
+    return staticResult;
+  }
+  print('warning: inconsistent result on $operation(${arguments.join(', ')})\n'
+      'smoke.mirrors result: $mirrorResult\n'
+      'smoke.static result:  $staticResult\n');
+  return staticResult;
+}
diff --git a/pub/smoke/pubspec.yaml b/pub/smoke/pubspec.yaml
new file mode 100644
index 0000000..7e9777b
--- /dev/null
+++ b/pub/smoke/pubspec.yaml
@@ -0,0 +1,24 @@
+name: smoke
+version: 0.3.6+2
+author: Polymer.dart Authors <web-ui-dev@dartlang.org>
+homepage: https://github.com/dart-lang/smoke
+description: >
+  A restricted reflective system that uses mirrors at development time, but that
+  can be replaced with non-reflective calls using code generation. See README.md
+  for mode details.
+dependencies:
+  barback: ">=0.9.0 <0.16.0"
+  logging: ">=0.9.0 <0.12.0"
+  analyzer: "^0.27.0"
+# TODO(sigmund): once we have some easier way to do global app-level
+# transformers, we might want to remove this transformer here and only apply it
+# in apps that need it.
+transformers:
+- smoke/src/default_transformer:
+    $include: lib/src/implementation.dart
+dev_dependencies:
+  test: "^0.12.0"
+  path: "^1.0.0"
+  transformer_test: "^0.2.0"
+environment:
+  sdk: ">=1.12.0 <2.0.0"