Delete TensorBoard Gulp build

This CL uses web_library to build all components in the forked d3v4 packages. The tensorboard command now uses the newly vulcanized binary.

This leaves the CMake build for TensorBoard in a partially broken state. We will need to update it to obtain tensorboard/index.html from a web server.

PiperOrigin-RevId: 156369259
diff --git a/WORKSPACE b/WORKSPACE
index b2d6fb5..edf655f 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -20,7 +20,7 @@
 #android_sdk_repository(
 #    name = "androidsdk",
 #    api_level = 23,
-#    # Ensure that you have the build_tools_version below installed in the 
+#    # Ensure that you have the build_tools_version below installed in the
 #    # SDK manager as it updates periodically.
 #    build_tools_version = "25.0.2",
 #    # Replace with path to Android SDK on your system
@@ -31,7 +31,7 @@
 #android_ndk_repository(
 #    name="androidndk",
 #    path="<PATH_TO_NDK>",
-#    # This needs to be 14 or higher to compile TensorFlow. 
+#    # This needs to be 14 or higher to compile TensorFlow.
 #    # Note that the NDK version is not the API level.
 #    api_level=14)
 
@@ -39,485 +39,31 @@
 tf_workspace()
 
 new_http_archive(
-  name = "inception5h",
-  build_file = "models.BUILD",
-  url = "https://storage.googleapis.com/download.tensorflow.org/models/inception5h.zip",
-  sha256 = "d13569f6a98159de37e92e9c8ec4dae8f674fbf475f69fe6199b514f756d4364"
+    name = "inception5h",
+    build_file = "models.BUILD",
+    sha256 = "d13569f6a98159de37e92e9c8ec4dae8f674fbf475f69fe6199b514f756d4364",
+    urls = [
+        "http://storage.googleapis.com/download.tensorflow.org/models/inception5h.zip",
+        "http://download.tensorflow.org/models/inception5h.zip",
+    ],
 )
 
 new_http_archive(
-  name = "mobile_multibox",
-  build_file = "models.BUILD",
-  url = "https://storage.googleapis.com/download.tensorflow.org/models/mobile_multibox_v1a.zip",
-  sha256 = "859edcddf84dddb974c36c36cfc1f74555148e9c9213dedacf1d6b613ad52b96"
+    name = "mobile_multibox",
+    build_file = "models.BUILD",
+    sha256 = "859edcddf84dddb974c36c36cfc1f74555148e9c9213dedacf1d6b613ad52b96",
+    urls = [
+        "http://storage.googleapis.com/download.tensorflow.org/models/mobile_multibox_v1a.zip",
+        "http://download.tensorflow.org/models/mobile_multibox_v1a.zip",
+    ],
 )
 
 new_http_archive(
-  name = "stylize",
-  build_file = "models.BUILD",
-  url = "https://storage.googleapis.com/download.tensorflow.org/models/stylize_v1.zip",
-  sha256 = "3d374a730aef330424a356a8d4f04d8a54277c425e274ecb7d9c83aa912c6bfa"
-)
-
-# TENSORBOARD_BOWER_AUTOGENERATED_BELOW_THIS_LINE_DO_NOT_EDIT
-
-new_http_archive(
-  name = "d3",
-  build_file = "bower.BUILD",
-  url = "https://github.com/mbostock-bower/d3-bower/archive/v3.5.15.tar.gz",
-  strip_prefix = "d3-bower-3.5.15",
-)
-
-new_http_archive(
-  name = "dagre",
-  build_file = "bower.BUILD",
-  url = "https://github.com/cpettitt/dagre/archive/v0.7.4.tar.gz",
-  strip_prefix = "dagre-0.7.4",
-)
-
-new_http_archive(
-  name = "es6_promise",
-  build_file = "bower.BUILD",
-  url = "https://github.com/components/es6-promise/archive/v2.1.0.tar.gz",
-  strip_prefix = "es6-promise-2.1.0",
-)
-
-new_http_archive(
-  name = "font_roboto",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/font-roboto/archive/v1.0.1.tar.gz",
-  strip_prefix = "font-roboto-1.0.1",
-)
-
-new_http_archive(
-  name = "graphlib",
-  build_file = "bower.BUILD",
-  url = "https://github.com/cpettitt/graphlib/archive/v1.0.7.tar.gz",
-  strip_prefix = "graphlib-1.0.7",
-)
-
-new_http_archive(
-  name = "iron_a11y_announcer",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/iron-a11y-announcer/archive/v1.0.5.tar.gz",
-  strip_prefix = "iron-a11y-announcer-1.0.5",
-)
-
-new_http_archive(
-  name = "iron_a11y_keys_behavior",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/iron-a11y-keys-behavior/archive/v1.1.8.tar.gz",
-  strip_prefix = "iron-a11y-keys-behavior-1.1.8",
-)
-
-new_http_archive(
-  name = "iron_ajax",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/iron-ajax/archive/v1.2.0.tar.gz",
-  strip_prefix = "iron-ajax-1.2.0",
-)
-
-new_http_archive(
-  name = "iron_autogrow_textarea",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/iron-autogrow-textarea/archive/v1.0.12.tar.gz",
-  strip_prefix = "iron-autogrow-textarea-1.0.12",
-)
-
-new_http_archive(
-  name = "iron_behaviors",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/iron-behaviors/archive/v1.0.17.tar.gz",
-  strip_prefix = "iron-behaviors-1.0.17",
-)
-
-new_http_archive(
-  name = "iron_checked_element_behavior",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/iron-checked-element-behavior/archive/v1.0.4.tar.gz",
-  strip_prefix = "iron-checked-element-behavior-1.0.4",
-)
-
-new_http_archive(
-  name = "iron_collapse",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/iron-collapse/archive/v1.0.8.tar.gz",
-  strip_prefix = "iron-collapse-1.0.8",
-)
-
-new_http_archive(
-  name = "iron_dropdown",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/iron-dropdown/archive/v1.4.0.tar.gz",
-  strip_prefix = "iron-dropdown-1.4.0",
-)
-
-new_http_archive(
-  name = "iron_fit_behavior",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/iron-fit-behavior/archive/v1.2.5.tar.gz",
-  strip_prefix = "iron-fit-behavior-1.2.5",
-)
-
-new_http_archive(
-  name = "iron_flex_layout",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/iron-flex-layout/archive/v1.3.0.tar.gz",
-  strip_prefix = "iron-flex-layout-1.3.0",
-)
-
-new_http_archive(
-  name = "iron_form_element_behavior",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/iron-form-element-behavior/archive/v1.0.6.tar.gz",
-  strip_prefix = "iron-form-element-behavior-1.0.6",
-)
-
-new_http_archive(
-  name = "iron_icon",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/iron-icon/archive/v1.0.11.tar.gz",
-  strip_prefix = "iron-icon-1.0.11",
-)
-
-new_http_archive(
-  name = "iron_icons",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/iron-icons/archive/v1.1.3.tar.gz",
-  strip_prefix = "iron-icons-1.1.3",
-)
-
-new_http_archive(
-  name = "iron_iconset_svg",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/iron-iconset-svg/archive/v1.1.0.tar.gz",
-  strip_prefix = "iron-iconset-svg-1.1.0",
-)
-
-new_http_archive(
-  name = "iron_input",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/iron-input/archive/1.0.10.tar.gz",
-  strip_prefix = "iron-input-1.0.10",
-)
-
-new_http_archive(
-  name = "iron_list",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/iron-list/archive/v1.3.9.tar.gz",
-  strip_prefix = "iron-list-1.3.9",
-)
-
-new_http_archive(
-  name = "iron_menu_behavior",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/iron-menu-behavior/archive/v1.1.10.tar.gz",
-  strip_prefix = "iron-menu-behavior-1.1.10",
-)
-
-new_http_archive(
-  name = "iron_meta",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/iron-meta/archive/v1.1.1.tar.gz",
-  strip_prefix = "iron-meta-1.1.1",
-)
-
-new_http_archive(
-  name = "iron_overlay_behavior",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/iron-overlay-behavior/archive/v1.10.1.tar.gz",
-  strip_prefix = "iron-overlay-behavior-1.10.1",
-)
-
-new_http_archive(
-  name = "iron_range_behavior",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/iron-range-behavior/archive/v1.0.4.tar.gz",
-  strip_prefix = "iron-range-behavior-1.0.4",
-)
-
-new_http_archive(
-  name = "iron_resizable_behavior",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/iron-resizable-behavior/archive/v1.0.3.tar.gz",
-  strip_prefix = "iron-resizable-behavior-1.0.3",
-)
-
-new_http_archive(
-  name = "iron_scroll_target_behavior",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/iron-scroll-target-behavior/archive/v1.0.3.tar.gz",
-  strip_prefix = "iron-scroll-target-behavior-1.0.3",
-)
-
-new_http_archive(
-  name = "iron_selector",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/iron-selector/archive/v1.5.2.tar.gz",
-  strip_prefix = "iron-selector-1.5.2",
-)
-
-new_http_archive(
-  name = "iron_validatable_behavior",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/iron-validatable-behavior/archive/v1.1.1.tar.gz",
-  strip_prefix = "iron-validatable-behavior-1.1.1",
-)
-
-new_http_archive(
-  name = "lodash",
-  build_file = "bower.BUILD",
-  url = "https://github.com/lodash/lodash/archive/3.8.0.tar.gz",
-  strip_prefix = "lodash-3.8.0",
-)
-
-new_http_archive(
-  name = "neon_animation",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/neon-animation/archive/v1.2.2.tar.gz",
-  strip_prefix = "neon-animation-1.2.2",
-)
-
-http_file(
-  name = "numericjs_numeric_min_js",
-  url = "https://cdnjs.cloudflare.com/ajax/libs/numeric/1.2.6/numeric.min.js",
-)
-
-new_http_archive(
-  name = "paper_behaviors",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/paper-behaviors/archive/v1.0.12.tar.gz",
-  strip_prefix = "paper-behaviors-1.0.12",
-)
-
-new_http_archive(
-  name = "paper_button",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/paper-button/archive/v1.0.11.tar.gz",
-  strip_prefix = "paper-button-1.0.11",
-)
-
-new_http_archive(
-  name = "paper_checkbox",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/paper-checkbox/archive/v1.4.0.tar.gz",
-  strip_prefix = "paper-checkbox-1.4.0",
-)
-
-new_http_archive(
-  name = "paper_dialog",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/paper-dialog/archive/v1.0.4.tar.gz",
-  strip_prefix = "paper-dialog-1.0.4",
-)
-
-new_http_archive(
-  name = "paper_dialog_behavior",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/paper-dialog-behavior/archive/v1.2.5.tar.gz",
-  strip_prefix = "paper-dialog-behavior-1.2.5",
-)
-
-new_http_archive(
-  name = "paper_dialog_scrollable",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/paper-dialog-scrollable/archive/1.1.5.tar.gz",
-  strip_prefix = "paper-dialog-scrollable-1.1.5",
-)
-
-new_http_archive(
-  name = "paper_dropdown_menu",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/paper-dropdown-menu/archive/v1.4.0.tar.gz",
-  strip_prefix = "paper-dropdown-menu-1.4.0",
-)
-
-new_http_archive(
-  name = "paper_header_panel",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/paper-header-panel/archive/v1.1.4.tar.gz",
-  strip_prefix = "paper-header-panel-1.1.4",
-)
-
-new_http_archive(
-  name = "paper_icon_button",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/paper-icon-button/archive/v1.1.3.tar.gz",
-  strip_prefix = "paper-icon-button-1.1.3",
-)
-
-new_http_archive(
-  name = "paper_input",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/paper-input/archive/v1.1.18.tar.gz",
-  strip_prefix = "paper-input-1.1.18",
-)
-
-new_http_archive(
-  name = "paper_item",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/paper-item/archive/v1.1.4.tar.gz",
-  strip_prefix = "paper-item-1.1.4",
-)
-
-new_http_archive(
-  name = "paper_listbox",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/paper-listbox/archive/v1.1.2.tar.gz",
-  strip_prefix = "paper-listbox-1.1.2",
-)
-
-new_http_archive(
-  name = "paper_material",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/paper-material/archive/v1.0.6.tar.gz",
-  strip_prefix = "paper-material-1.0.6",
-)
-
-new_http_archive(
-  name = "paper_menu",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/paper-menu/archive/v1.2.2.tar.gz",
-  strip_prefix = "paper-menu-1.2.2",
-)
-
-new_http_archive(
-  name = "paper_menu_button",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/paper-menu-button/archive/v1.5.1.tar.gz",
-  strip_prefix = "paper-menu-button-1.5.1",
-)
-
-new_http_archive(
-  name = "paper_progress",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/paper-progress/archive/v1.0.9.tar.gz",
-  strip_prefix = "paper-progress-1.0.9",
-)
-
-new_http_archive(
-  name = "paper_radio_button",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/paper-radio-button/archive/v1.1.2.tar.gz",
-  strip_prefix = "paper-radio-button-1.1.2",
-)
-
-new_http_archive(
-  name = "paper_radio_group",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/paper-radio-group/archive/v1.0.9.tar.gz",
-  strip_prefix = "paper-radio-group-1.0.9",
-)
-
-new_http_archive(
-  name = "paper_ripple",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/paper-ripple/archive/v1.0.5.tar.gz",
-  strip_prefix = "paper-ripple-1.0.5",
-)
-
-new_http_archive(
-  name = "paper_slider",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/paper-slider/archive/v1.0.10.tar.gz",
-  strip_prefix = "paper-slider-1.0.10",
-)
-
-new_http_archive(
-  name = "paper_spinner",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/paper-spinner/archive/v1.1.1.tar.gz",
-  strip_prefix = "paper-spinner-1.1.1",
-)
-
-new_http_archive(
-  name = "paper_styles",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/paper-styles/archive/v1.1.4.tar.gz",
-  strip_prefix = "paper-styles-1.1.4",
-)
-
-new_http_archive(
-  name = "paper_tabs",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/paper-tabs/archive/v1.7.0.tar.gz",
-  strip_prefix = "paper-tabs-1.7.0",
-)
-
-new_http_archive(
-  name = "paper_toast",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/paper-toast/archive/v1.3.0.tar.gz",
-  strip_prefix = "paper-toast-1.3.0",
-)
-
-new_http_archive(
-  name = "paper_toggle_button",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/paper-toggle-button/archive/v1.2.0.tar.gz",
-  strip_prefix = "paper-toggle-button-1.2.0",
-)
-
-new_http_archive(
-  name = "paper_toolbar",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/paper-toolbar/archive/v1.1.4.tar.gz",
-  strip_prefix = "paper-toolbar-1.1.4",
-)
-
-new_http_archive(
-  name = "paper_tooltip",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerelements/paper-tooltip/archive/v1.1.2.tar.gz",
-  strip_prefix = "paper-tooltip-1.1.2",
-)
-
-new_http_archive(
-  name = "plottable",
-  build_file = "bower.BUILD",
-  url = "https://github.com/palantir/plottable/archive/v1.16.1.tar.gz",
-  strip_prefix = "plottable-1.16.1",
-)
-
-new_http_archive(
-  name = "polymer",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymer/polymer/archive/v1.7.0.tar.gz",
-  strip_prefix = "polymer-1.7.0",
-)
-
-new_http_archive(
-  name = "promise_polyfill",
-  build_file = "bower.BUILD",
-  url = "https://github.com/polymerlabs/promise-polyfill/archive/v1.0.0.tar.gz",
-  strip_prefix = "promise-polyfill-1.0.0",
-)
-
-http_file(
-  name = "three_js_three_min_js",
-  url = "https://raw.githubusercontent.com/mrdoob/three.js/r77/build/three.min.js",
-)
-
-http_file(
-  name = "three_js_orbitcontrols_js",
-  url = "https://raw.githubusercontent.com/mrdoob/three.js/r77/examples/js/controls/OrbitControls.js",
-)
-
-new_http_archive(
-  name = "web_animations_js",
-  build_file = "bower.BUILD",
-  url = "https://github.com/web-animations/web-animations-js/archive/2.2.1.tar.gz",
-  strip_prefix = "web-animations-js-2.2.1",
-)
-
-new_http_archive(
-  name = "webcomponentsjs",
-  build_file = "bower.BUILD",
-  url = "https://github.com/webcomponents/webcomponentsjs/archive/v0.7.22.tar.gz",
-  strip_prefix = "webcomponentsjs-0.7.22",
-)
-
-http_file(
-  name = "weblas_weblas_js",
-  url = "https://raw.githubusercontent.com/waylonflinn/weblas/v0.9.0/dist/weblas.js",
+    name = "stylize",
+    build_file = "models.BUILD",
+    sha256 = "3d374a730aef330424a356a8d4f04d8a54277c425e274ecb7d9c83aa912c6bfa",
+    urls = [
+        "http://storage.googleapis.com/download.tensorflow.org/models/stylize_v1.zip",
+        "http://download.tensorflow.org/models/stylize_v1.zip",
+    ],
 )
diff --git a/bower.BUILD b/bower.BUILD
deleted file mode 100644
index eabd1d6..0000000
--- a/bower.BUILD
+++ /dev/null
@@ -1,645 +0,0 @@
-# AUTOGENERATED FILE by tensorboard_bower_dependency_sync.py
-
-package(default_visibility = ["//visibility:public"])
-
-filegroup(
-    name = "d3",
-    srcs = [
-        "d3.js",
-        "d3.min.js",
-        "package.js",
-    ],
-)
-
-filegroup(
-    name = "dagre",
-    srcs = [
-        "dist/dagre.core.js",
-        "dist/dagre.core.min.js",
-    ],
-)
-
-filegroup(
-    name = "es6_promise",
-    srcs = [
-        "promise.js",
-        "promise.min.js",
-    ],
-)
-
-filegroup(
-    name = "font_roboto",
-    srcs = ["roboto.html"],
-)
-
-filegroup(
-    name = "graphlib",
-    srcs = [
-        "dist/graphlib.core.js",
-        "dist/graphlib.core.min.js",
-    ],
-)
-
-filegroup(
-    name = "iron_a11y_announcer",
-    srcs = [
-        "index.html",
-        "iron-a11y-announcer.html",
-    ],
-)
-
-filegroup(
-    name = "iron_a11y_keys_behavior",
-    srcs = [
-        "index.html",
-        "iron-a11y-keys-behavior.html",
-    ],
-)
-
-filegroup(
-    name = "iron_ajax",
-    srcs = [
-        "index.html",
-        "iron-ajax.html",
-        "iron-request.html",
-    ],
-)
-
-filegroup(
-    name = "iron_autogrow_textarea",
-    srcs = [
-        "index.html",
-        "iron-autogrow-textarea.html",
-    ],
-)
-
-filegroup(
-    name = "iron_behaviors",
-    srcs = [
-        "index.html",
-        "iron-button-state.html",
-        "iron-control-state.html",
-    ],
-)
-
-filegroup(
-    name = "iron_checked_element_behavior",
-    srcs = [
-        "index.html",
-        "iron-checked-element-behavior.html",
-    ],
-)
-
-filegroup(
-    name = "iron_collapse",
-    srcs = [
-        "index.html",
-        "iron-collapse.html",
-    ],
-)
-
-filegroup(
-    name = "iron_dropdown",
-    srcs = [
-        "index.html",
-        "iron-dropdown.html",
-        "iron-dropdown-scroll-manager.html",
-    ],
-)
-
-filegroup(
-    name = "iron_fit_behavior",
-    srcs = [
-        "index.html",
-        "iron-fit-behavior.html",
-    ],
-)
-
-filegroup(
-    name = "iron_flex_layout",
-    srcs = [
-        "classes/iron-flex-layout.html",
-        "classes/iron-shadow-flex-layout.html",
-        "index.html",
-        "iron-flex-layout.html",
-        "iron-flex-layout-classes.html",
-    ],
-)
-
-filegroup(
-    name = "iron_form_element_behavior",
-    srcs = [
-        "index.html",
-        "iron-form-element-behavior.html",
-    ],
-)
-
-filegroup(
-    name = "iron_icon",
-    srcs = [
-        "index.html",
-        "iron-icon.html",
-    ],
-)
-
-filegroup(
-    name = "iron_icons",
-    srcs = [
-        "av-icons.html",
-        "communication-icons.html",
-        "device-icons.html",
-        "editor-icons.html",
-        "hardware-icons.html",
-        "image-icons.html",
-        "index.html",
-        "iron-icons.html",
-        "maps-icons.html",
-        "notification-icons.html",
-        "places-icons.html",
-        "social-icons.html",
-    ],
-)
-
-filegroup(
-    name = "iron_iconset_svg",
-    srcs = [
-        "index.html",
-        "iron-iconset-svg.html",
-    ],
-)
-
-filegroup(
-    name = "iron_input",
-    srcs = [
-        "index.html",
-        "iron-input.html",
-    ],
-)
-
-filegroup(
-    name = "iron_list",
-    srcs = [
-        "index.html",
-        "iron-list.html",
-        "test/smoke/avg-worst-case.html",
-        "test/smoke/dummy-data.html",
-        "test/smoke/index.html",
-        "test/smoke/physical-count.html",
-    ],
-)
-
-filegroup(
-    name = "iron_menu_behavior",
-    srcs = [
-        "index.html",
-        "iron-menu-behavior.html",
-        "iron-menubar-behavior.html",
-    ],
-)
-
-filegroup(
-    name = "iron_meta",
-    srcs = [
-        "index.html",
-        "iron-meta.html",
-    ],
-)
-
-filegroup(
-    name = "iron_overlay_behavior",
-    srcs = [
-        "index.html",
-        "iron-focusables-helper.html",
-        "iron-overlay-backdrop.html",
-        "iron-overlay-behavior.html",
-        "iron-overlay-manager.html",
-    ],
-)
-
-filegroup(
-    name = "iron_range_behavior",
-    srcs = [
-        "index.html",
-        "iron-range-behavior.html",
-    ],
-)
-
-filegroup(
-    name = "iron_resizable_behavior",
-    srcs = [
-        "demo/src/x-app.html",
-        "index.html",
-        "iron-resizable-behavior.html",
-    ],
-)
-
-filegroup(
-    name = "iron_scroll_target_behavior",
-    srcs = [
-        "index.html",
-        "iron-scroll-target-behavior.html",
-    ],
-)
-
-filegroup(
-    name = "iron_selector",
-    srcs = [
-        "index.html",
-        "iron-multi-selectable.html",
-        "iron-selectable.html",
-        "iron-selection.html",
-        "iron-selector.html",
-    ],
-)
-
-filegroup(
-    name = "iron_validatable_behavior",
-    srcs = [
-        "index.html",
-        "iron-validatable-behavior.html",
-    ],
-)
-
-filegroup(
-    name = "lodash",
-    srcs = [
-        "lodash.js",
-        "lodash.min.js",
-    ],
-)
-
-filegroup(
-    name = "neon_animation",
-    srcs = [
-        "animations/cascaded-animation.html",
-        "animations/fade-in-animation.html",
-        "animations/fade-out-animation.html",
-        "animations/hero-animation.html",
-        "animations/opaque-animation.html",
-        "animations/reverse-ripple-animation.html",
-        "animations/ripple-animation.html",
-        "animations/scale-down-animation.html",
-        "animations/scale-up-animation.html",
-        "animations/slide-down-animation.html",
-        "animations/slide-from-bottom-animation.html",
-        "animations/slide-from-left-animation.html",
-        "animations/slide-from-right-animation.html",
-        "animations/slide-from-top-animation.html",
-        "animations/slide-left-animation.html",
-        "animations/slide-right-animation.html",
-        "animations/slide-up-animation.html",
-        "animations/transform-animation.html",
-        "demo/card/index.html",
-        "demo/card/x-card.html",
-        "demo/card/x-cards-list.html",
-        "demo/declarative/index.html",
-        "demo/doc/index.html",
-        "demo/doc/my-animatable.html",
-        "demo/doc/my-dialog.html",
-        "demo/dropdown/animated-dropdown.html",
-        "demo/dropdown/index.html",
-        "demo/grid/animated-grid.html",
-        "demo/grid/fullsize-page-with-card.html",
-        "demo/grid/index.html",
-        "demo/list/full-view.html",
-        "demo/list/index.html",
-        "demo/list/list-demo.html",
-        "demo/list/list-view.html",
-        "demo/load/animated-grid.html",
-        "demo/load/full-page.html",
-        "demo/load/index.html",
-        "demo/reprojection/animated-grid.html",
-        "demo/reprojection/fullsize-page-with-card.html",
-        "demo/reprojection/index.html",
-        "demo/reprojection/reprojected-pages.html",
-        "demo/tiles/circles-page.html",
-        "demo/tiles/index.html",
-        "demo/tiles/squares-page.html",
-        "index.html",
-        "neon-animatable.html",
-        "neon-animatable-behavior.html",
-        "neon-animated-pages.html",
-        "neon-animation.html",
-        "neon-animation-behavior.html",
-        "neon-animation-runner-behavior.html",
-        "neon-animations.html",
-        "neon-shared-element-animatable-behavior.html",
-        "neon-shared-element-animation-behavior.html",
-        "web-animations.html",
-    ],
-)
-
-filegroup(
-    name = "paper_behaviors",
-    srcs = [
-        "index.html",
-        "paper-button-behavior.html",
-        "paper-checked-element-behavior.html",
-        "paper-inky-focus-behavior.html",
-        "paper-ripple-behavior.html",
-    ],
-)
-
-filegroup(
-    name = "paper_button",
-    srcs = [
-        "index.html",
-        "paper-button.html",
-    ],
-)
-
-filegroup(
-    name = "paper_checkbox",
-    srcs = [
-        "index.html",
-        "paper-checkbox.html",
-    ],
-)
-
-filegroup(
-    name = "paper_dialog",
-    srcs = [
-        "index.html",
-        "paper-dialog.html",
-    ],
-)
-
-filegroup(
-    name = "paper_dialog_behavior",
-    srcs = [
-        "index.html",
-        "paper-dialog-behavior.html",
-        "paper-dialog-common.css",
-        "paper-dialog-shared-styles.html",
-    ],
-)
-
-filegroup(
-    name = "paper_dialog_scrollable",
-    srcs = [
-        "index.html",
-        "paper-dialog-scrollable.html",
-    ],
-)
-
-filegroup(
-    name = "paper_dropdown_menu",
-    srcs = [
-        "index.html",
-        "paper-dropdown-menu.html",
-        "paper-dropdown-menu-icons.html",
-        "paper-dropdown-menu-light.html",
-        "paper-dropdown-menu-shared-styles.html",
-    ],
-)
-
-filegroup(
-    name = "paper_header_panel",
-    srcs = [
-        "index.html",
-        "paper-header-panel.html",
-    ],
-)
-
-filegroup(
-    name = "paper_icon_button",
-    srcs = [
-        "index.html",
-        "paper-icon-button.html",
-        "paper-icon-button-light.html",
-    ],
-)
-
-filegroup(
-    name = "paper_input",
-    srcs = [
-        "all-imports.html",
-        "index.html",
-        "paper-input.html",
-        "paper-input-addon-behavior.html",
-        "paper-input-behavior.html",
-        "paper-input-char-counter.html",
-        "paper-input-container.html",
-        "paper-input-error.html",
-        "paper-textarea.html",
-    ],
-)
-
-filegroup(
-    name = "paper_item",
-    srcs = [
-        "all-imports.html",
-        "index.html",
-        "paper-icon-item.html",
-        "paper-item.html",
-        "paper-item-behavior.html",
-        "paper-item-body.html",
-        "paper-item-shared-styles.html",
-    ],
-)
-
-filegroup(
-    name = "paper_listbox",
-    srcs = [
-        "index.html",
-        "paper-listbox.html",
-    ],
-)
-
-filegroup(
-    name = "paper_material",
-    srcs = [
-        "index.html",
-        "paper-material.html",
-        "paper-material-shared-styles.html",
-    ],
-)
-
-filegroup(
-    name = "paper_menu",
-    srcs = [
-        "index.html",
-        "paper-menu.html",
-        "paper-menu-shared-styles.html",
-        "paper-submenu.html",
-    ],
-)
-
-filegroup(
-    name = "paper_menu_button",
-    srcs = [
-        "index.html",
-        "paper-menu-button.html",
-        "paper-menu-button-animations.html",
-    ],
-)
-
-filegroup(
-    name = "paper_progress",
-    srcs = [
-        "index.html",
-        "paper-progress.html",
-    ],
-)
-
-filegroup(
-    name = "paper_radio_button",
-    srcs = [
-        "index.html",
-        "paper-radio-button.html",
-    ],
-)
-
-filegroup(
-    name = "paper_radio_group",
-    srcs = [
-        "index.html",
-        "paper-radio-group.html",
-    ],
-)
-
-filegroup(
-    name = "paper_ripple",
-    srcs = [
-        "index.html",
-        "paper-ripple.html",
-    ],
-)
-
-filegroup(
-    name = "paper_slider",
-    srcs = [
-        "index.html",
-        "paper-slider.html",
-    ],
-)
-
-filegroup(
-    name = "paper_spinner",
-    srcs = [
-        "index.html",
-        "paper-spinner.html",
-        "paper-spinner-behavior.html",
-        "paper-spinner-lite.html",
-        "paper-spinner-styles.html",
-    ],
-)
-
-filegroup(
-    name = "paper_styles",
-    srcs = [
-        "classes/global.html",
-        "classes/shadow.html",
-        "classes/shadow-layout.html",
-        "classes/typography.html",
-        "color.html",
-        "default-theme.html",
-        "demo.css",
-        "demo-pages.html",
-        "index.html",
-        "paper-styles.html",
-        "paper-styles-classes.html",
-        "shadow.html",
-        "typography.html",
-    ],
-)
-
-filegroup(
-    name = "paper_tabs",
-    srcs = [
-        "index.html",
-        "paper-tab.html",
-        "paper-tabs.html",
-        "paper-tabs-icons.html",
-    ],
-)
-
-filegroup(
-    name = "paper_toast",
-    srcs = [
-        "index.html",
-        "paper-toast.html",
-    ],
-)
-
-filegroup(
-    name = "paper_toggle_button",
-    srcs = [
-        "index.html",
-        "paper-toggle-button.html",
-    ],
-)
-
-filegroup(
-    name = "paper_toolbar",
-    srcs = [
-        "index.html",
-        "paper-toolbar.html",
-    ],
-)
-
-filegroup(
-    name = "paper_tooltip",
-    srcs = [
-        "index.html",
-        "paper-tooltip.html",
-    ],
-)
-
-filegroup(
-    name = "plottable",
-    srcs = [
-        "plottable.css",
-        "plottable.js",
-        "plottable.min.js",
-    ],
-)
-
-filegroup(
-    name = "polymer",
-    srcs = [
-        "polymer.html",
-        "polymer-micro.html",
-        "polymer-mini.html",
-    ],
-)
-
-filegroup(
-    name = "promise_polyfill",
-    srcs = [
-        "Gruntfile.js",
-        "Promise.js",
-        "Promise.min.js",
-        "Promise-Statics.js",
-        "promise-polyfill.html",
-        "promise-polyfill-lite.html",
-    ],
-)
-
-filegroup(
-    name = "web_animations_js",
-    srcs = [
-        "web-animations.html",
-        "web-animations.min.js",
-        "web-animations-next.min.js",
-        "web-animations-next-lite.min.js",
-    ],
-)
-
-filegroup(
-    name = "webcomponentsjs",
-    srcs = [
-        "CustomElements.js",
-        "CustomElements.min.js",
-        "HTMLImports.js",
-        "HTMLImports.min.js",
-        "MutationObserver.js",
-        "MutationObserver.min.js",
-        "ShadowDOM.js",
-        "ShadowDOM.min.js",
-        "webcomponents.js",
-        "webcomponents.min.js",
-        "webcomponents-lite.js",
-        "webcomponents-lite.min.js",
-    ],
-)
diff --git a/tensorflow/BUILD b/tensorflow/BUILD
index 8f67ae2..acd5c10 100644
--- a/tensorflow/BUILD
+++ b/tensorflow/BUILD
@@ -321,7 +321,6 @@
         "//tensorflow/python/saved_model:all_files",
         "//tensorflow/python/tools:all_files",
         "//tensorflow/tensorboard:all_files",
-        "//tensorflow/tensorboard/app:all_files",
         "//tensorflow/tensorboard/backend:all_files",
         "//tensorflow/tensorboard/backend/event_processing:all_files",
         "//tensorflow/tensorboard/components:all_files",
@@ -404,6 +403,8 @@
         "//tensorflow/tensorboard/components/vz_distribution_chart:all_files",
         "//tensorflow/tensorboard/components/vz_distribution_chart/demo:all_files",
         "//tensorflow/tensorboard/components/vz_distribution_chart_d3v4:all_files",
+        "//tensorflow/tensorboard/components/vz_heatmap:all_files",
+        "//tensorflow/tensorboard/components/vz_heatmap_d3v4:all_files",
         "//tensorflow/tensorboard/components/vz_histogram_timeseries:all_files",
         "//tensorflow/tensorboard/components/vz_histogram_timeseries/demo:all_files",
         "//tensorflow/tensorboard/components/vz_histogram_timeseries_d3v4:all_files",
@@ -419,7 +420,6 @@
         "//tensorflow/tensorboard/components/vz_sorting_d3v4/test:all_files",
         "//tensorflow/tensorboard/demo:all_files",
         "//tensorflow/tensorboard/java/org/tensorflow/tensorboard/vulcanize:all_files",
-        "//tensorflow/tensorboard/lib:all_files",
         "//tensorflow/tensorboard/plugins:all_files",
         "//tensorflow/tensorboard/plugins/projector:all_files",
         "//tensorflow/tensorboard/plugins/text:all_files",
diff --git a/tensorflow/contrib/cmake/CMakeLists.txt b/tensorflow/contrib/cmake/CMakeLists.txt
index 83b405f..62afa94 100644
--- a/tensorflow/contrib/cmake/CMakeLists.txt
+++ b/tensorflow/contrib/cmake/CMakeLists.txt
@@ -261,7 +261,6 @@
 endif()
 include(tf_tools.cmake)
 if(tensorflow_BUILD_PYTHON_BINDINGS)
-  include(tensorboard)
   include(tf_python.cmake)
 endif()
 if(tensorflow_BUILD_SHARED_LIB)
diff --git a/tensorflow/contrib/cmake/external/tensorboard.cmake b/tensorflow/contrib/cmake/external/tensorboard.cmake
deleted file mode 100644
index 457868c..0000000
--- a/tensorflow/contrib/cmake/external/tensorboard.cmake
+++ /dev/null
@@ -1,148 +0,0 @@
-# Copyright 2017 The TensorFlow Authors. All Rights Reserved.
-#
-# 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.
-# ==============================================================================
-include (ExternalProject)
-
-set(tensorboard_dependencies)
-add_custom_target(tensorboard_copy_dependencies)
-
-function(tb_new_http_archive)
-  cmake_parse_arguments(_TB "" "NAME;URL" "FILES" ${ARGN})
-  ExternalProject_Add(${_TB_NAME}
-    PREFIX ${_TB_NAME}
-    URL ${_TB_URL}
-    DOWNLOAD_DIR "${DOWNLOAD_LOCATION}/${_TB_NAME}"
-    CONFIGURE_COMMAND ""
-    BUILD_COMMAND ""
-    INSTALL_COMMAND ""
-  )
-
-  set(src_dir "${CMAKE_CURRENT_BINARY_DIR}/${_TB_NAME}/src/${_TB_NAME}")
-  set(dst_dir "${CMAKE_CURRENT_BINARY_DIR}/tensorboard_external/${_TB_NAME}")
-
-  foreach(src_file ${_TB_FILES})
-    add_custom_command(
-      TARGET tensorboard_copy_dependencies PRE_BUILD
-      COMMAND ${CMAKE_COMMAND} -E copy_if_different ${src_dir}/${src_file} ${dst_dir}/${src_file}
-    )
-  endforeach()
-  
-  set(tensorboard_dependencies ${tensorboard_dependencies} ${_TB_NAME} PARENT_SCOPE)
-endfunction()
-
-function(tb_http_file)
-  cmake_parse_arguments(_TB "" "NAME;URL" "" ${ARGN})
-  get_filename_component(src_file ${_TB_URL} NAME)
-  file(DOWNLOAD ${_TB_URL} "${DOWNLOAD_LOCATION}/${_TB_NAME}/${src_file}")
-  
-  set(src_dir "${DOWNLOAD_LOCATION}/${_TB_NAME}")
-  set(dst_dir "${CMAKE_CURRENT_BINARY_DIR}/tensorboard_external/${_TB_NAME}/file")
-  
-  add_custom_command(
-    TARGET tensorboard_copy_dependencies PRE_BUILD
-    COMMAND ${CMAKE_COMMAND} -E copy_if_different ${src_dir}/${src_file} ${dst_dir}/${src_file}
-  )
-  
-  add_custom_target(${_TB_NAME} DEPENDS ${src_dir}/${src_file})
-  set(tensorboard_dependencies ${tensorboard_dependencies} ${_TB_NAME} PARENT_SCOPE)
-endfunction()
-
-# Parse TensorBoard dependency names and URLs from Bazel's WORKSPACE file.
-set(tb_dep_names)
-file(STRINGS ${PROJECT_SOURCE_DIR}/../../../WORKSPACE workspace_contents)
-foreach(line ${workspace_contents})
-  if(line MATCHES "# TENSORBOARD_BOWER_AUTOGENERATED_BELOW_THIS_LINE_DO_NOT_EDIT")
-    set(tb_deps_started 1)
-  endif()
-
-  if(NOT tb_deps_started)
-    continue()
-  endif()
-
-  if(line MATCHES "new_http_archive\\(")
-    set(tb_dep_is_archive 1)
-    continue()
-  elseif(line MATCHES "http_file\\(")
-    set(tb_dep_is_archive 0)
-    continue()
-  endif()
-
-  string(REGEX MATCH "name.*=.*\"(.*)\"" has_name ${line})
-  if(has_name)
-    set(tb_dep_name ${CMAKE_MATCH_1})
-    continue()
-  endif()
-
-  string(REGEX MATCH "url.*=.*\"(.*)\"" has_url ${line})
-  if(has_url)
-    list(APPEND tb_dep_names ${tb_dep_name})
-    set(${tb_dep_name}_is_archive ${tb_dep_is_archive})
-    set(${tb_dep_name}_url ${CMAKE_MATCH_1})
-  endif()
-endforeach()
-
-# Parse the files needed for each TensorBoard dependency from Bazel's bower.BUILD file.
-# Due to CMAKE quirkiness, cannot use file(strings) with files that contain '[' and ']'.
-file(READ ${PROJECT_SOURCE_DIR}/../../../bower.BUILD bower_build_contents)
-string(REPLACE "\[" "OB" bower_build_contents "${bower_build_contents}")
-string(REPLACE "\]" "CB" bower_build_contents "${bower_build_contents}")
-string(REPLACE ";" "\\\\;" bower_build_contents "${bower_build_contents}")
-string(REPLACE "\n" "E;" bower_build_contents "${bower_build_contents}")
-foreach(line ${bower_build_contents})
-  string(REGEX MATCH "name.*=.*\"(.*)\"" has_name ${line})
-  if(has_name)
-    set(tb_dep_name ${CMAKE_MATCH_1})
-    set(${tb_dep_name}_files)
-    continue()
-  endif()
-
-  string(REGEX MATCH "srcs.*=.*\"(.*)\"CB" has_single_line_src ${line})
-  if(has_single_line_src)
-    list(APPEND ${tb_dep_name}_files ${CMAKE_MATCH_1})
-    continue()
-  endif()
-
-  if(line MATCHES "srcs.*=.*OB")
-    set(inside_files_def 1)
-    continue()
-  elseif(line MATCHES "CB,")
-    set(inside_files_def 0)
-    continue()
-  endif()
-
-  if(inside_files_def)
-   string(REGEX MATCH "\"(.*)\"," has_file ${line})
-   if(has_file)
-     list(APPEND ${tb_dep_name}_files ${CMAKE_MATCH_1})
-   endif()
-  endif()
-endforeach()
-
-# Generate a target for each dependency.
-foreach(tb_dep_name ${tb_dep_names})
-  if (${tb_dep_name}_is_archive)
-    tb_new_http_archive(
-      NAME ${tb_dep_name}
-      URL ${${tb_dep_name}_url}
-      FILES ${${tb_dep_name}_files}
-    )
-  else()
-    tb_http_file(
-      NAME ${tb_dep_name}
-      URL ${${tb_dep_name}_url}
-    )
-  endif()
-endforeach()
-
-add_dependencies(tensorboard_copy_dependencies ${tensorboard_dependencies})
diff --git a/tensorflow/contrib/cmake/tf_python.cmake b/tensorflow/contrib/cmake/tf_python.cmake
index b660508..793f7b2 100755
--- a/tensorflow/contrib/cmake/tf_python.cmake
+++ b/tensorflow/contrib/cmake/tf_python.cmake
@@ -514,13 +514,6 @@
 add_python_module("tensorflow/contrib/util")
 
 
-# Additional directories with no Python sources.
-add_custom_command(TARGET tf_python_touchup_modules PRE_BUILD
-    COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_CURRENT_BINARY_DIR}/tf_python/tensorflow/tensorboard/dist")
-add_custom_command(TARGET tf_python_touchup_modules PRE_BUILD
-    COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_CURRENT_BINARY_DIR}/tf_python/tensorflow/tensorboard/lib/css")
-
-
 ########################################################
 # tf_python_op_gen_main library
 ########################################################
@@ -859,7 +852,6 @@
 add_custom_target(tf_python_build_pip_package)
 add_dependencies(tf_python_build_pip_package
     pywrap_tensorflow_internal
-    tensorboard_copy_dependencies
     tf_python_copy_scripts_to_destination
     tf_python_touchup_modules
     tf_python_ops
@@ -887,23 +879,8 @@
 
 # Copy resources for TensorBoard.
 add_custom_command(TARGET tf_python_build_pip_package POST_BUILD
-  COMMAND ${CMAKE_COMMAND} -E copy ${tensorflow_source_dir}/tensorflow/tensorboard/dist/bazel-html-imports.html
-                                   ${CMAKE_CURRENT_BINARY_DIR}/tf_python/tensorflow/tensorboard/dist/)
-add_custom_command(TARGET tf_python_build_pip_package POST_BUILD
-  COMMAND ${CMAKE_COMMAND} -E copy ${tensorflow_source_dir}/tensorflow/tensorboard/dist/index.html
-                                   ${CMAKE_CURRENT_BINARY_DIR}/tf_python/tensorflow/tensorboard/dist/)
-add_custom_command(TARGET tf_python_build_pip_package POST_BUILD
-  COMMAND ${CMAKE_COMMAND} -E copy ${tensorflow_source_dir}/tensorflow/tensorboard/dist/tf-tensorboard.html
-                                   ${CMAKE_CURRENT_BINARY_DIR}/tf_python/tensorflow/tensorboard/dist/)
-add_custom_command(TARGET tf_python_build_pip_package POST_BUILD
-  COMMAND ${CMAKE_COMMAND} -E copy ${tensorflow_source_dir}/tensorflow/tensorboard/lib/css/global.css
-                                   ${CMAKE_CURRENT_BINARY_DIR}/tf_python/tensorflow/tensorboard/lib/css/)
-add_custom_command(TARGET tf_python_build_pip_package POST_BUILD
   COMMAND ${CMAKE_COMMAND} -E copy ${tensorflow_source_dir}/tensorflow/tensorboard/TAG
-                                   ${CMAKE_CURRENT_BINARY_DIR}/tf_python/tensorflow/tensorboard/)
-add_custom_command(TARGET tf_python_build_pip_package POST_BUILD
-  COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_BINARY_DIR}/tensorboard_external
-                                             ${CMAKE_CURRENT_BINARY_DIR}/tf_python/external)
+  ${CMAKE_CURRENT_BINARY_DIR}/tf_python/tensorflow/tensorboard/)
 
 # Copy datasets for tf.contrib.learn.
 add_custom_command(TARGET tf_python_build_pip_package POST_BUILD
diff --git a/tensorflow/tensorboard/.bowerrc b/tensorflow/tensorboard/.bowerrc
deleted file mode 100644
index 333544e..0000000
--- a/tensorflow/tensorboard/.bowerrc
+++ /dev/null
@@ -1,3 +0,0 @@
-{
-  "directory" : "components"
-}
\ No newline at end of file
diff --git a/tensorflow/tensorboard/.gitignore b/tensorflow/tensorboard/.gitignore
deleted file mode 100644
index 98b9642..0000000
--- a/tensorflow/tensorboard/.gitignore
+++ /dev/null
@@ -1,27 +0,0 @@
-node_modules/*
-typings/*
-build/*
-dist/tf-tensorboard-demo.html
-
-# Since bower components are stored in the same directory as
-# tensorboard components, we ignore everything under components
-# except our own components which start with tf-.
-components/*
-# This rule should always be in sync with TF_COMPONENTS_TYPESCRIPT_GLOB
-# in gulpfile.js
-!components/tf-*
-!components/tf_*
-!components/vz-*
-!components/vz_*
-!components/index.html
-!components/BUILD
-# Ignore the sample graph files since they are too large to
-# be in the repo.
-components/tf-graph/demo/tf_model_zoo/*
-
-# All standalone code for TensorBoard components should be written in
-# typescript, and the compiled javascript code should be ignored.
-components/tf-*/**/*.js
-components/tf_*/**/*.js
-components/vz-*/**/*.js
-components/vz_*/**/*.js
diff --git a/tensorflow/tensorboard/BUILD b/tensorflow/tensorboard/BUILD
index 9772538..1171531 100644
--- a/tensorflow/tensorboard/BUILD
+++ b/tensorflow/tensorboard/BUILD
@@ -1,39 +1,14 @@
 # Description:
 # TensorBoard, a dashboard for investigating TensorFlow
 
-package(
-    default_visibility = ["//tensorflow:internal"],
-    features = [
-        "-layering_check",
-        "-parse_headers",
-    ],
-)
+package(default_visibility = ["//tensorflow:internal"])
 
 licenses(["notice"])  # Apache 2.0
 
-exports_files(["LICENSE"])
-
-load("//tensorflow:tensorflow.bzl", "py_test")
-
-filegroup(
-    name = "frontend",
-    srcs = [
-        "TAG",
-        "dist/bazel-html-imports.html",
-        "dist/index.html",
-        "dist/tf-tensorboard.html",
-        "//tensorflow/tensorboard/bower",
-        "//tensorflow/tensorboard/lib:all_files",
-    ],
-)
-
 py_binary(
     name = "tensorboard",
-    srcs = [
-        "__main__.py",
-        "tensorboard.py",
-    ],
-    data = [":frontend"],
+    srcs = ["tensorboard.py"],
+    data = [":assets"],
     srcs_version = "PY2AND3",
     deps = [
         "//tensorflow/python:platform",
@@ -46,15 +21,22 @@
 )
 
 filegroup(
+    name = "assets",
+    srcs = [
+        "TAG",
+        "//tensorflow/tensorboard/components:index.html",
+    ],
+)
+
+filegroup(
     name = "all_files",
     srcs = glob(
-        ["**/*"],
+        ["**"],
         exclude = [
-            "**/METADATA",
-            "**/OWNERS",
-            "**/node_modules/**",
-            "**/typings/**",
+            "METADATA",
+            "OWNERS",
+            "tensorboard.google.bzl",
         ],
     ),
-    visibility = ["//tensorflow:__subpackages__"],
+    tags = ["notsan"],
 )
diff --git a/tensorflow/tensorboard/DEVELOPMENT.md b/tensorflow/tensorboard/DEVELOPMENT.md
index 3ff2c87..8e86bf0 100644
--- a/tensorflow/tensorboard/DEVELOPMENT.md
+++ b/tensorflow/tensorboard/DEVELOPMENT.md
@@ -2,125 +2,24 @@
 
 ## Launching a Development Instance
 
-The first step is getting a TensorBoard development environment set up. You
-should start by making sure you have [nodejs](https://nodejs.org/en/) and
-[npm](https://www.npmjs.com/). On Ubuntu, `sudo apt-get install -y nodejs
-nodejs-legacy npm`. Ensure your npm version is >=3.0 by running
-'npm --version'. If the version is <3.0, run 'sudo npm install npm -g' to
-update to the latest version. You may need to open a new terminal window after
-updating in order to make use of the newly-installed version.
+Run the following to launch a demo of TensorBoard in raw sources mode:
 
-Next, you'll want to install [gulp](http://gulpjs.com/) and
-[bower](http://bower.io/), which are used for build tooling and dependency
-management respectively. Both must be installed globally: `sudo npm install -g
-gulp bower` will do that.
+```sh
+bazel run third_party/tensorflow/tensorboard/components/tf_tensorboard_d3v4:demo
+```
 
-Then, cd into the TensorBoard directory:
-
-`cd tensorflow/tensorboard`
-
-and install dependencies:
-
-`npm run prep`
-
-Then, run gulp: `gulp`
-
-(Don't worry if there are some linter errors.)
-
-Now you can navigate to
-[http://localhost:8000/demo/index.html](http://localhost:8000/demo/index.html)
-and play with the demo TensorBoard instance. If you make changes to the source
-code, `gulp` should detect it, recompile (if Typescript), and reload your
-browser.
+Now you can navigate to <http://localhost:6006/demo/index.html> and play with
+the demo TensorBoard instance. This will have live source reloading.
 
 This demo TensorBoard will have a small amount of demo data generated by
 [generate_testdata.py](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/tensorboard/scripts/generate_testdata.py).
 You can use [serialize_tensorboard.py](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/tensorboard/scripts/serialize_tensorboard.py)
 to create a realistic demo directory from your own data files.
 
-## Launching TensorBoard with modified source
+## Launching TensorBoard Proper
 
-If you are developing in open source, and have made some changes to TensorBoard
-that you'd like to try out on real data, then you need to regenerate
-`dist/tf-tensorboard.html`.
+Running TensorBoard automatically asks Bazel to create a vulcanized HTML binary:
 
-Run `gulp regenerate`. That will recompile all of the TensorBoard assets, and
-produce a new tf-tensorboard.html with your changes.
-
-Now, you can use `bazel` to launch TensorBoard:
-
-`bazel run //tensorflow/tensorboard:tensorboard -- --logdir=/path/to/logs`.
-
-## Updating the vulcanized HTML file (for linux)
-
-The vulcanized HTML file `dist/tf-tensorboard.html.OPENSOURCE` is the version of
-Tensorboard started up by users who install TensorFlow via pip. Today, updating
-that file involves using gulp. Future efforts will streamline this process.
-
-First, `cd` into the `tensorflow/tensorboard` directory within a git repository
-(a piper client will not work). Run `npm run prepare`.
-
-Next, we build some third party JS dependencies via webfiles targets. Run
-
-    bazel build \
-        tensorflow/tensorboard/components/tf_imports:d3 \
-        tensorflow/tensorboard/components/tf_imports:lodash \
-        tensorflow/tensorboard/components/tf_imports:graphlib \
-        tensorflow/tensorboard/components/tf_imports:dagre \
-        tensorflow/tensorboard/components/tf_imports:plottable
-
-Users internal to Google should use the internal build tool instead. Move the
-output JS binaries into the tf_imports directory.
-
-Run `gulp vulcanize`. If compilation errors arise (such as those related to
-TypeScript), fix them and re-run. This step should update the contents of
-`dist/tf-tensorboard.html.OPENSOURCE`.
-
-Next, we perform some manual find-and-replaces on script `src` paths within
-`dist/tf-tensorboard.html.OPENSOURCE`. Manually replace:
-
-* `<script src="../tf-imports/d3.js"></script>` with `<script src="../d3/d3.js"></script>`
-* `<script src="../tf-imports/dagre.js"></script>` with `<script src="../dagre/dist/dagre.core.js"></script>`
-* `<script src="../tf-imports/graphlib.js"></script>` with `<script src="../graphlib/dist/graphlib.core.js"></script>`
-* `<script src="../tf-imports/lodash.js"></script>` with `<script src="../lodash/lodash.min.js"></script>`
-* `<script src="../tf-imports/plottable.js"></script>` with `<script src="../plottable/plottable.js"></script>`
-
-Also, remove duplicate instances of script includes. Each of those scripts
-should only be included once (the first time) within the vulcanized output.
-
-### Try out the vulcanized Tensorboard HTML output
-
-To test the vulcanized output, prepare a pip package within a virtualized
-environment, and run `tensorboard` after activating the environment.
-
-To do that, we first create and activate a virtual environment called say
-`tf_foo` (Pick your own name.).
-
-    virtualenv --system-site-packages ~/tf_foo
-    source ~/tf_foo/bin/activate
-
-Make sure that you have installed `pip` and `virtualenv` beforehand. If not, run
-
-    sudo easy_install pip
-    sudo pip install --upgrade virtualenv
-
-Next, we run this command from the `tensorflow directory`.
-
-    tools/google/make_tree.sh --pip_dir=/tmp/pip_dir
-
-to create a pip package. If you are running within Google, also provide the
-`--pending_cl` flag. That script will generate a wheel file (.whl) within
-`/tmp/pip_dir`. Lets say that it is
-`tensorflow-1.0.0rc2-cp27-none-linux_x86_64.whl`.
-
-Run
-
-    pip install --upgrade /tmp/pip_dir/tensorflow-1.0.0rc2-cp27-none-linux_x86_64.whl
-
-to update the pip installation of TensorFlow within the virtual environment.
-Verify that the `tensorboard` command defers to the tensorboard instance
-installed within your virtual environment (`tf_foo`) by running
-`which tensorboard`. To run tensorboard, start it up as usual within the virtual
-environment:
-
-    tensorboard --logdir=/tmp/my/logdir
+```sh
+bazel run //tensorflow/tensorboard:tensorboard -- --logdir=/path/to/logs
+```
diff --git a/tensorflow/tensorboard/app/BUILD b/tensorflow/tensorboard/app/BUILD
deleted file mode 100644
index 9afcd23..0000000
--- a/tensorflow/tensorboard/app/BUILD
+++ /dev/null
@@ -1,19 +0,0 @@
-# Description:
-# Build rules for building the HTML/JS necessary for TensorBoard.
-package(default_visibility = ["//tensorflow:internal"])
-
-licenses(["notice"])  # Apache 2.0
-
-exports_files(["LICENSE"])
-
-filegroup(
-    name = "all_files",
-    srcs = glob(
-        ["**/*"],
-        exclude = [
-            "**/METADATA",
-            "**/OWNERS",
-        ],
-    ),
-    visibility = ["//tensorflow:__subpackages__"],
-)
diff --git a/tensorflow/tensorboard/app/analytics.js b/tensorflow/tensorboard/app/analytics.js
deleted file mode 100644
index 497c02c..0000000
--- a/tensorflow/tensorboard/app/analytics.js
+++ /dev/null
@@ -1,16 +0,0 @@
-/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-
-// Nothing to see here. vulcanize doesn't like empty files.
diff --git a/tensorflow/tensorboard/backend/BUILD b/tensorflow/tensorboard/backend/BUILD
index d27a22a..adbdea5 100644
--- a/tensorflow/tensorboard/backend/BUILD
+++ b/tensorflow/tensorboard/backend/BUILD
@@ -57,7 +57,7 @@
 py_library(
     name = "application",
     srcs = ["application.py"],
-    data = ["//tensorflow/tensorboard:frontend"],
+    data = ["//tensorflow/tensorboard:assets"],
     srcs_version = "PY2AND3",
     deps = [
         ":http_util",
diff --git a/tensorflow/tensorboard/backend/application.py b/tensorflow/tensorboard/backend/application.py
index 3c8963e..c38ad92 100644
--- a/tensorflow/tensorboard/backend/application.py
+++ b/tensorflow/tensorboard/backend/application.py
@@ -24,7 +24,6 @@
 
 import csv
 import imghdr
-import mimetypes
 import os
 import re
 import threading
@@ -159,8 +158,6 @@
       reload_multiplexer(self._multiplexer, path_to_run)
 
     self.data_applications = {
-        '/app.js':
-            self._serve_js,
         DATA_PREFIX + AUDIO_ROUTE:
             self._serve_audio,
         DATA_PREFIX + COMPRESSED_HISTOGRAMS_ROUTE:
@@ -545,59 +542,9 @@
   @wrappers.Request.application
   def _serve_index(self, request):
     """Serves the index page (i.e., the tensorboard app itself)."""
-    return self._serve_static_file(request, '/dist/index.html')
-
-  @wrappers.Request.application
-  def _serve_js(self, request):
-    """Serves the JavaScript for the index page."""
-    return self._serve_static_file(request, '/dist/app.js')
-
-  def _serve_static_file(self, request, path):
-    """Serves the static file located at the given path.
-
-    Args:
-      request: A werkzeug Request
-      path: The path of the static file, relative to the tensorboard/ directory.
-
-    Returns:
-      A werkzeug.Response application.
-    """
-    # Strip off the leading forward slash.
-    orig_path = path.lstrip('/')
-    if not self._path_is_safe(orig_path):
-      logging.warning('path not safe: %s', orig_path)
-      return http_util.Respond(request, 'Naughty naughty!', 'text/plain', 400)
-      # Resource loader wants a path relative to //WORKSPACE/tensorflow.
-    path = os.path.join('tensorboard', orig_path)
-    # Open the file and read it.
-    try:
-      contents = resource_loader.load_resource(path)
-    except IOError:
-      # For compatibility with latest version of Bazel, we renamed bower
-      # packages to use '_' rather than '-' in their package name.
-      # This means that the directory structure is changed too.
-      # So that all our recursive imports work, we need to modify incoming
-      # requests to map onto the new directory structure.
-      path = orig_path
-      components = path.split('/')
-      components[0] = components[0].replace('-', '_')
-      path = ('/').join(components)
-      # Bazel keeps all the external dependencies in //WORKSPACE/external.
-      # and resource loader wants a path relative to //WORKSPACE/tensorflow/.
-      path = os.path.join('../external', path)
-      try:
-        contents = resource_loader.load_resource(path)
-      except IOError:
-        logging.warning('path %s not found, sending 404', path)
-        return http_util.Respond(request, 'Not found', 'text/plain', code=404)
-    mimetype, content_encoding = mimetypes.guess_type(path)
-    mimetype = mimetype or 'application/octet-stream'
-    return http_util.Respond(
-        request,
-        contents,
-        mimetype,
-        expires=3600,
-        content_encoding=content_encoding)
+    contents = resource_loader.load_resource(
+        'tensorboard/components/index.html')
+    return http_util.Respond(request, contents, 'text/html', expires=3600)
 
   def __call__(self, environ, start_response):  # pylint: disable=invalid-name
     """Central entry point for the TensorBoard application.
@@ -628,8 +575,9 @@
     elif clean_path in TAB_ROUTES:
       return self._serve_index(environ, start_response)
     else:
-      return self._serve_static_file(request, clean_path)(environ,
-                                                          start_response)
+      logging.warning('path %s not found, sending 404', clean_path)
+      return http_util.Respond(request, 'Not found', 'text/plain', code=404)(
+          environ, start_response)
     # pylint: enable=too-many-function-args
 
 
diff --git a/tensorflow/tensorboard/backend/application_test.py b/tensorflow/tensorboard/backend/application_test.py
index 4ea627d..e8556c0 100644
--- a/tensorflow/tensorboard/backend/application_test.py
+++ b/tensorflow/tensorboard/backend/application_test.py
@@ -154,11 +154,6 @@
     response = self._get('/asdf')
     self.assertEqual(response.status, 404)
 
-  def testDirectoryTraversal(self):
-    """Attempt a directory traversal attack."""
-    response = self._get('/..' * 30 + '/etc/passwd')
-    self.assertEqual(response.status, 400)
-
   def testLogdir(self):
     """Test the format of the data/logdir endpoint."""
     parsed_object = self._getJson('/data/logdir')
diff --git a/tensorflow/tensorboard/bower.json b/tensorflow/tensorboard/bower.json
deleted file mode 100644
index 0a0fac4..0000000
--- a/tensorflow/tensorboard/bower.json
+++ /dev/null
@@ -1,187 +0,0 @@
-{
-  "__autoadded_transitive_dep__": [
-    "font-roboto",
-    "iron-a11y-announcer",
-    "iron-a11y-keys-behavior",
-    "iron-autogrow-textarea",
-    "iron-checked-element-behavior",
-    "iron-dropdown",
-    "iron-fit-behavior",
-    "iron-flex-layout",
-    "iron-form-element-behavior",
-    "iron-icon",
-    "iron-iconset-svg",
-    "iron-input",
-    "iron-menu-behavior",
-    "iron-meta",
-    "iron-overlay-behavior",
-    "iron-range-behavior",
-    "iron-resizable-behavior",
-    "iron-scroll-target-behavior",
-    "iron-validatable-behavior",
-    "neon-animation",
-    "paper-dialog-behavior",
-    "paper-material",
-    "paper-menu-button",
-    "paper-ripple",
-    "promise-polyfill",
-    "web-animations-js",
-    "webcomponentsjs"
-  ],
-  "authors": [
-    "Google"
-  ],
-  "dependencies": {
-    "d3": "3.5.15",
-    "dagre": "0.7.4",
-    "es6-promise": "2.1.0",
-    "font-roboto": "PolymerElements/font-roboto#1.0.1",
-    "graphlib": "1.0.7",
-    "iron-a11y-announcer": "PolymerElements/iron-a11y-announcer#1.0.5",
-    "iron-a11y-keys-behavior": "PolymerElements/iron-a11y-keys-behavior#1.1.8",
-    "iron-ajax": "PolymerElements/iron-ajax#1.2.0",
-    "iron-autogrow-textarea": "PolymerElements/iron-autogrow-textarea#1.0.12",
-    "iron-behaviors": "PolymerElements/iron-behaviors#1.0.17",
-    "iron-checked-element-behavior": "PolymerElements/iron-checked-element-behavior#1.0.4",
-    "iron-collapse": "PolymerElements/iron-collapse#1.0.8",
-    "iron-dropdown": "PolymerElements/iron-dropdown#1.4.0",
-    "iron-fit-behavior": "PolymerElements/iron-fit-behavior#1.2.5",
-    "iron-flex-layout": "PolymerElements/iron-flex-layout#1.3.0",
-    "iron-form-element-behavior": "PolymerElements/iron-form-element-behavior#1.0.6",
-    "iron-icon": "PolymerElements/iron-icon#1.0.11",
-    "iron-icons": "PolymerElements/iron-icons#1.1.3",
-    "iron-iconset-svg": "PolymerElements/iron-iconset-svg#1.1.0",
-    "iron-input": "PolymerElements/iron-input#1.0.10",
-    "iron-list": "PolymerElements/iron-list#1.3.9",
-    "iron-menu-behavior": "PolymerElements/iron-menu-behavior#1.1.10",
-    "iron-meta": "PolymerElements/iron-meta#1.1.1",
-    "iron-overlay-behavior": "PolymerElements/iron-overlay-behavior#1.10.1",
-    "iron-range-behavior": "PolymerElements/iron-range-behavior#1.0.4",
-    "iron-resizable-behavior": "PolymerElements/iron-resizable-behavior#1.0.3",
-    "iron-scroll-target-behavior": "PolymerElements/iron-scroll-target-behavior#1.0.3",
-    "iron-selector": "PolymerElements/iron-selector#1.5.2",
-    "iron-validatable-behavior": "PolymerElements/iron-validatable-behavior#1.1.1",
-    "lodash": "3.8.0",
-    "neon-animation": "PolymerElements/neon-animation#1.2.2",
-    "numericjs": "1.2.6",
-    "paper-behaviors": "PolymerElements/paper-behaviors#1.0.12",
-    "paper-button": "PolymerElements/paper-button#1.0.11",
-    "paper-checkbox": "PolymerElements/paper-checkbox#1.4.0",
-    "paper-dialog": "PolymerElements/paper-dialog#1.0.4",
-    "paper-dialog-behavior": "PolymerElements/paper-dialog-behavior#1.2.5",
-    "paper-dialog-scrollable": "PolymerElements/paper-dialog-scrollable#1.1.5",
-    "paper-dropdown-menu": "PolymerElements/paper-dropdown-menu#1.4.0",
-    "paper-header-panel": "PolymerElements/paper-header-panel#1.1.4",
-    "paper-icon-button": "PolymerElements/paper-icon-button#1.1.3",
-    "paper-input": "PolymerElements/paper-input#1.1.18",
-    "paper-item": "PolymerElements/paper-item#1.1.4",
-    "paper-listbox": "PolymerElements/paper-listbox#1.1.2",
-    "paper-material": "PolymerElements/paper-material#1.0.6",
-    "paper-menu": "PolymerElements/paper-menu#1.2.2",
-    "paper-menu-button": "PolymerElements/paper-menu-button#1.5.1",
-    "paper-progress": "PolymerElements/paper-progress#1.0.9",
-    "paper-radio-button": "PolymerElements/paper-radio-button#1.1.2",
-    "paper-radio-group": "PolymerElements/paper-radio-group#1.0.9",
-    "paper-ripple": "PolymerElements/paper-ripple#1.0.5",
-    "paper-slider": "PolymerElements/paper-slider#1.0.10",
-    "paper-spinner": "PolymerElements/paper-spinner#1.1.1",
-    "paper-styles": "PolymerElements/paper-styles#1.1.4",
-    "paper-tabs": "PolymerElements/paper-tabs#1.7.0",
-    "paper-toast": "PolymerElements/paper-toast#1.3.0",
-    "paper-toggle-button": "PolymerElements/paper-toggle-button#1.2.0",
-    "paper-toolbar": "PolymerElements/paper-toolbar#1.1.4",
-    "paper-tooltip": "PolymerElements/paper-tooltip#1.1.2",
-    "plottable": "1.16.1",
-    "polymer": "1.7.0",
-    "promise-polyfill": "polymerlabs/promise-polyfill#1.0.0",
-    "three.js": "threejs#r77",
-    "web-animations-js": "web-animations/web-animations-js#2.2.1",
-    "webcomponentsjs": "webcomponents/webcomponentsjs#0.7.22",
-    "weblas": "0.9.0"
-  },
-  "description": "TensorBoard: Visualizations for TensorFlow",
-  "devDependencies": {
-    "iron-component-page": "PolymerElements/iron-component-page#^1.1.4",
-    "iron-demo-helpers": "PolymerElements/iron-demo-helpers#^1.2.3",
-    "web-component-tester": "Polymer/web-component-tester"
-  },
-  "ignore": [
-    "**/.*",
-    "node_modules",
-    "bower_components",
-    "test",
-    "tests"
-  ],
-  "license": "Apache-2.0",
-  "name": "tensorboard",
-  "private": true,
-  "resolutions": {
-    "d3": "3.5.15",
-    "dagre": "0.7.4",
-    "es6-promise": "2.1.0",
-    "font-roboto": "1.0.1",
-    "graphlib": "1.0.7",
-    "iron-a11y-announcer": "1.0.5",
-    "iron-a11y-keys-behavior": "1.1.8",
-    "iron-ajax": "1.2.0",
-    "iron-autogrow-textarea": "1.0.12",
-    "iron-behaviors": "1.0.17",
-    "iron-checked-element-behavior": "1.0.4",
-    "iron-collapse": "1.0.8",
-    "iron-dropdown": "1.4.0",
-    "iron-fit-behavior": "1.2.5",
-    "iron-flex-layout": "1.3.0",
-    "iron-form-element-behavior": "1.0.6",
-    "iron-icon": "1.0.11",
-    "iron-icons": "1.1.3",
-    "iron-iconset-svg": "1.1.0",
-    "iron-input": "1.0.10",
-    "iron-list": "1.3.9",
-    "iron-menu-behavior": "1.1.10",
-    "iron-meta": "1.1.1",
-    "iron-overlay-behavior": "1.10.1",
-    "iron-range-behavior": "1.0.4",
-    "iron-resizable-behavior": "1.0.3",
-    "iron-scroll-target-behavior": "1.0.3",
-    "iron-selector": "1.5.2",
-    "iron-validatable-behavior": "1.1.1",
-    "lodash": "3.8.0",
-    "neon-animation": "1.2.2",
-    "numericjs": "1.2.6",
-    "paper-behaviors": "1.0.12",
-    "paper-button": "1.0.11",
-    "paper-checkbox": "1.4.0",
-    "paper-dialog": "1.0.4",
-    "paper-dialog-behavior": "1.2.5",
-    "paper-dialog-scrollable": "1.1.5",
-    "paper-dropdown-menu": "1.4.0",
-    "paper-header-panel": "1.1.4",
-    "paper-icon-button": "1.1.3",
-    "paper-input": "1.1.18",
-    "paper-item": "1.1.4",
-    "paper-listbox": "1.1.2",
-    "paper-material": "1.0.6",
-    "paper-menu": "1.2.2",
-    "paper-menu-button": "1.5.1",
-    "paper-progress": "1.0.9",
-    "paper-radio-button": "1.1.2",
-    "paper-radio-group": "1.0.9",
-    "paper-ripple": "1.0.5",
-    "paper-slider": "1.0.10",
-    "paper-spinner": "1.1.1",
-    "paper-styles": "1.1.4",
-    "paper-tabs": "1.7.0",
-    "paper-toast": "1.3.0",
-    "paper-toggle-button": "1.2.0",
-    "paper-toolbar": "1.1.4",
-    "paper-tooltip": "1.1.2",
-    "plottable": "1.16.1",
-    "polymer": "1.7.0",
-    "promise-polyfill": "1.0.0",
-    "three.js": "threejs#r77",
-    "web-animations-js": "2.2.1",
-    "webcomponentsjs": "0.7.22",
-    "weblas": "0.9.0"
-  },
-  "version": "0.0.0"
-}
diff --git a/tensorflow/tensorboard/bower/BUILD b/tensorflow/tensorboard/bower/BUILD
deleted file mode 100644
index 2c2921d..0000000
--- a/tensorflow/tensorboard/bower/BUILD
+++ /dev/null
@@ -1,76 +0,0 @@
-# AUTOGENERATED FILE by tensorboard_bower_dependency_sync.py
-
-package(default_visibility = ["//visibility:public"])
-
-filegroup(
-    name = "bower",
-    srcs = [
-        "@d3//:d3",
-        "@dagre//:dagre",
-        "@es6_promise//:es6_promise",
-        "@font_roboto//:font_roboto",
-        "@graphlib//:graphlib",
-        "@iron_a11y_announcer//:iron_a11y_announcer",
-        "@iron_a11y_keys_behavior//:iron_a11y_keys_behavior",
-        "@iron_ajax//:iron_ajax",
-        "@iron_autogrow_textarea//:iron_autogrow_textarea",
-        "@iron_behaviors//:iron_behaviors",
-        "@iron_checked_element_behavior//:iron_checked_element_behavior",
-        "@iron_collapse//:iron_collapse",
-        "@iron_dropdown//:iron_dropdown",
-        "@iron_fit_behavior//:iron_fit_behavior",
-        "@iron_flex_layout//:iron_flex_layout",
-        "@iron_form_element_behavior//:iron_form_element_behavior",
-        "@iron_icon//:iron_icon",
-        "@iron_icons//:iron_icons",
-        "@iron_iconset_svg//:iron_iconset_svg",
-        "@iron_input//:iron_input",
-        "@iron_list//:iron_list",
-        "@iron_menu_behavior//:iron_menu_behavior",
-        "@iron_meta//:iron_meta",
-        "@iron_overlay_behavior//:iron_overlay_behavior",
-        "@iron_range_behavior//:iron_range_behavior",
-        "@iron_resizable_behavior//:iron_resizable_behavior",
-        "@iron_scroll_target_behavior//:iron_scroll_target_behavior",
-        "@iron_selector//:iron_selector",
-        "@iron_validatable_behavior//:iron_validatable_behavior",
-        "@lodash//:lodash",
-        "@neon_animation//:neon_animation",
-        "@numericjs_numeric_min_js//file",
-        "@paper_behaviors//:paper_behaviors",
-        "@paper_button//:paper_button",
-        "@paper_checkbox//:paper_checkbox",
-        "@paper_dialog//:paper_dialog",
-        "@paper_dialog_behavior//:paper_dialog_behavior",
-        "@paper_dialog_scrollable//:paper_dialog_scrollable",
-        "@paper_dropdown_menu//:paper_dropdown_menu",
-        "@paper_header_panel//:paper_header_panel",
-        "@paper_icon_button//:paper_icon_button",
-        "@paper_input//:paper_input",
-        "@paper_item//:paper_item",
-        "@paper_listbox//:paper_listbox",
-        "@paper_material//:paper_material",
-        "@paper_menu//:paper_menu",
-        "@paper_menu_button//:paper_menu_button",
-        "@paper_progress//:paper_progress",
-        "@paper_radio_button//:paper_radio_button",
-        "@paper_radio_group//:paper_radio_group",
-        "@paper_ripple//:paper_ripple",
-        "@paper_slider//:paper_slider",
-        "@paper_spinner//:paper_spinner",
-        "@paper_styles//:paper_styles",
-        "@paper_tabs//:paper_tabs",
-        "@paper_toast//:paper_toast",
-        "@paper_toggle_button//:paper_toggle_button",
-        "@paper_toolbar//:paper_toolbar",
-        "@paper_tooltip//:paper_tooltip",
-        "@plottable//:plottable",
-        "@polymer//:polymer",
-        "@promise_polyfill//:promise_polyfill",
-        "@three_js_orbitcontrols_js//file",
-        "@three_js_three_min_js//file",
-        "@web_animations_js//:web_animations_js",
-        "@webcomponentsjs//:webcomponentsjs",
-        "@weblas_weblas_js//file",
-    ],
-)
diff --git a/tensorflow/tensorboard/components/BUILD b/tensorflow/tensorboard/components/BUILD
index 301425d..2d2c2d2 100644
--- a/tensorflow/tensorboard/components/BUILD
+++ b/tensorflow/tensorboard/components/BUILD
@@ -1,23 +1,32 @@
 package(default_visibility = ["//tensorflow:internal"])
 
+load("@io_bazel_rules_closure//closure:defs.bzl", "web_library")
+load("//tensorflow/tensorboard:vulcanize.bzl", "tensorboard_html_binary")
+
 licenses(["notice"])  # Apache 2.0
 
-exports_files(["LICENSE"])
+web_library(
+    name = "tensorboard",
+    srcs = [
+        "analytics.html",
+        "tensorboard.html",
+    ],
+    path = "/",
+    deps = [
+        "//tensorflow/tensorboard/components/tf_tensorboard_d3v4",
+        "@org_polymer_webcomponentsjs",
+    ],
+)
+
+tensorboard_html_binary(
+    name = "index",
+    input_path = "/tensorboard.html",
+    output_path = "/index.html",
+    deps = [":tensorboard"],
+)
 
 filegroup(
     name = "all_files",
-    srcs = glob(
-        [
-            "tf_*/**/*",
-            "vz_*/**/*",
-        ],
-        exclude = [
-            "**/tf_model_zoo/*",
-            "**/METADATA",
-            "**/OWNERS",
-        ],
-    ) + [
-        "BUILD",
-    ],
-    visibility = ["//tensorflow:__subpackages__"],
+    srcs = glob(["**"]),
+    tags = ["notsan"],
 )
diff --git a/tensorflow/tensorboard/components/tf_imports_google/graphlib.html b/tensorflow/tensorboard/components/analytics.html
similarity index 89%
rename from tensorflow/tensorboard/components/tf_imports_google/graphlib.html
rename to tensorflow/tensorboard/components/analytics.html
index 56b37eb..d319f57 100644
--- a/tensorflow/tensorboard/components/tf_imports_google/graphlib.html
+++ b/tensorflow/tensorboard/components/analytics.html
@@ -15,4 +15,4 @@
 limitations under the License.
 -->
 
-<link rel="import" href="../graphlib-library/graphlib.html">
+<!-- TODO(jart): Give users the ability to opt-in to analytics. -->
diff --git a/tensorflow/tensorboard/components/index.html b/tensorflow/tensorboard/components/index.html
deleted file mode 100644
index c790a76..0000000
--- a/tensorflow/tensorboard/components/index.html
+++ /dev/null
@@ -1,39 +0,0 @@
-<!doctype html>
-<!--
-@license
-Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
--->
-
-<html>
-  <head>
-    <meta charset="utf-8">
-    <script src="webcomponentsjs/webcomponents-lite.min.js"></script>
-    <style>
-      html, body {
-        margin: 0;
-        padding: 0;
-        height: 100%;
-        font-family: "RobotoDraft","Roboto",sans-serif;
-      }
-    </style>
-    <link rel="shortcut icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMQAAADECAMAAAD3eH5ZAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAD/UExURfFlKfaELvFmKfNyK/67NvWALf68Nv69NvNxK/20NfyyNP22NfN0K/JrKvqhMv2zNf25Nf24Nf23NfeOL/yzNPyvNPJoKviWMPmeMfN1K/WBLfePL/FnKfeML/qlMvR7LPmcMfeLL/aJLvR5LPFoKfJuKvR3LP66NvywNPeNL/V/LfaILv21Nf26NfNzK/NvK/R6LPmaMfyxNPqfMvV+LfurM/iSMPmbMfJvKvmdMfumM/qiMvmZMfytNPJqKvysNPN2K/iYMPNwK/upM/JtKvJsKviVMPaHLvaGLvJpKvR8LPaKLvqkMvuqM/aFLvR4LPuoM/iTMPWDLfiRMPmYMXS0ngkAAALoSURBVHja7drnctpAFIbhFUISSKJ3MKYa0+y4xTW9937/15JkJhlTjhrSrHRmvuf/as6L0YLFCgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMBJ6njenqspzgnPrsrGX9Zpi2tCrmnc6+dYNthVY5WpMmxQLWPdMsOuYVwzNj3ei2t3mQwaV43BJPDCS2NbJ5aEeuX/+9qcjQOtfFIkIkrvY2g4MVcmOBsFWbowKO/kNyj62gRpJcDaPBlxLr1B0zdG0C/8LzbJiJrshuvy1gzlA9+rD8mIkuyIJjFE3/dqnYwoSm7IUEPoD/wut8iIguSIDjlFxe/yfXL5vuSI21BTZLLhXoOILMO8Hxwa/L8bI0LfmUdhGowb2ZvT0e57pFNDgB06IlVyjmmIBl2T/nl9Rw6SD9GgSG/Q0uQkaW3XhmovKQ3eFQ4N2Uo9OQ1eFZsNerf7vP+rO4rhmY1Lg3vFVoP8+8BXg1sFnwbnCk4NThW8GuiKBDdkVVtTNFvNelVsNqTbyWnIOM2oeTRoyWvwmpJHg/ucXBrcJuXT4DwrpwZi2vy0VCx8YtXg/D2bU4OfiuQ3eFfE2KD4bfCqiLNB993gXsGlwa2CT4NzBacGIVQ6YsipQdh0xEdODUKjIxrSp88onZ8zbbFLg1DoiFO5BXvDGv2My9/JhUT8JUZTI0yDaNHLBzIbvqTDNYhUiVw/kdjQ1kM2CHFDPjKW+KzyRTF0g/ga9w9y+fANQpxvX8CU+Ny7FUWDeF3Y+g3lROIf4k0UDX9eCyvO531PyYhHga9zvPZJU5b73Y/eXj8Hv9D48n6HaF5LbcjRt8TZTtda5M1DfXnbkX1C0SHCFKzQB5Fe8op4GNGNHavvZESbVwT5r6W1xyuCPBY3Y9YgDqzknH/e3YfNzzuL30l0IebrZ5kKtuDIXt1n868ET6kf3/49tLvrCcZyF8Pu215dAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcPIbNrBhOaBXucoAAAAASUVORK5CYII=">
-    <link rel="import" href="tf-tensorboard/tf-tensorboard.html">
-    <title>TensorBoard</title>
-  </head>
-  <body>
-    <tf-tensorboard use-hash></tf-tensorboard>
-    <script src="../app/analytics.js"></script>
-  </body>
-</html>
diff --git a/tensorflow/tensorboard/components/tf_tensorboard_d3v4/dist.html b/tensorflow/tensorboard/components/tensorboard.html
similarity index 89%
rename from tensorflow/tensorboard/components/tf_tensorboard_d3v4/dist.html
rename to tensorflow/tensorboard/components/tensorboard.html
index 89bcfb7..0652902 100644
--- a/tensorflow/tensorboard/components/tf_tensorboard_d3v4/dist.html
+++ b/tensorflow/tensorboard/components/tensorboard.html
@@ -16,17 +16,12 @@
 limitations under the License.
 -->
 
+<meta charset="utf-8">
 <title>TensorBoard</title>
-<style>
-  html, body {
-    margin: 0;
-    padding: 0;
-    height: 100%;
-    font-family: "RobotoDraft", "Roboto", sans-serif;
-  }
-</style>
-<script src="../webcomponentsjs/webcomponents-lite.min.js"></script>
 <link rel="shortcut icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMQAAADECAMAAAD3eH5ZAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAD/UExURfFlKfaELvFmKfNyK/67NvWALf68Nv69NvNxK/20NfyyNP22NfN0K/JrKvqhMv2zNf25Nf24Nf23NfeOL/yzNPyvNPJoKviWMPmeMfN1K/WBLfePL/FnKfeML/qlMvR7LPmcMfeLL/aJLvR5LPFoKfJuKvR3LP66NvywNPeNL/V/LfaILv21Nf26NfNzK/NvK/R6LPmaMfyxNPqfMvV+LfurM/iSMPmbMfJvKvmdMfumM/qiMvmZMfytNPJqKvysNPN2K/iYMPNwK/upM/JtKvJsKviVMPaHLvaGLvJpKvR8LPaKLvqkMvuqM/aFLvR4LPuoM/iTMPWDLfiRMPmYMXS0ngkAAALoSURBVHja7drnctpAFIbhFUISSKJ3MKYa0+y4xTW9937/15JkJhlTjhrSrHRmvuf/as6L0YLFCgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMBJ6njenqspzgnPrsrGX9Zpi2tCrmnc6+dYNthVY5WpMmxQLWPdMsOuYVwzNj3ei2t3mQwaV43BJPDCS2NbJ5aEeuX/+9qcjQOtfFIkIkrvY2g4MVcmOBsFWbowKO/kNyj62gRpJcDaPBlxLr1B0zdG0C/8LzbJiJrshuvy1gzlA9+rD8mIkuyIJjFE3/dqnYwoSm7IUEPoD/wut8iIguSIDjlFxe/yfXL5vuSI21BTZLLhXoOILMO8Hxwa/L8bI0LfmUdhGowb2ZvT0e57pFNDgB06IlVyjmmIBl2T/nl9Rw6SD9GgSG/Q0uQkaW3XhmovKQ3eFQ4N2Uo9OQ1eFZsNerf7vP+rO4rhmY1Lg3vFVoP8+8BXg1sFnwbnCk4NThW8GuiKBDdkVVtTNFvNelVsNqTbyWnIOM2oeTRoyWvwmpJHg/ucXBrcJuXT4DwrpwZi2vy0VCx8YtXg/D2bU4OfiuQ3eFfE2KD4bfCqiLNB993gXsGlwa2CT4NzBacGIVQ6YsipQdh0xEdODUKjIxrSp88onZ8zbbFLg1DoiFO5BXvDGv2My9/JhUT8JUZTI0yDaNHLBzIbvqTDNYhUiVw/kdjQ1kM2CHFDPjKW+KzyRTF0g/ga9w9y+fANQpxvX8CU+Ny7FUWDeF3Y+g3lROIf4k0UDX9eCyvO531PyYhHga9zvPZJU5b73Y/eXj8Hv9D48n6HaF5LbcjRt8TZTtda5M1DfXnbkX1C0SHCFKzQB5Fe8op4GNGNHavvZESbVwT5r6W1xyuCPBY3Y9YgDqzknH/e3YfNzzuL30l0IebrZ5kKtuDIXt1n868ET6kf3/49tLvrCcZyF8Pu215dAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcPIbNrBhOaBXucoAAAAASUVORK5CYII=">
-<link rel="import" href="tf-tensorboard.html">
+<script src="webcomponentsjs/webcomponents-lite.min.js"></script>
+<link rel="import" href="tf-tensorboard/style.html">
+<link rel="import" href="tf-tensorboard/tf-tensorboard.html">
+<link rel="import" href="analytics.html">
 <body>
 <tf-tensorboard use-hash></tf-tensorboard>
diff --git a/tensorflow/tensorboard/components/tf_audio_dashboard/BUILD b/tensorflow/tensorboard/components/tf_audio_dashboard/BUILD
index 9172ebb..010643b 100644
--- a/tensorflow/tensorboard/components/tf_audio_dashboard/BUILD
+++ b/tensorflow/tensorboard/components/tf_audio_dashboard/BUILD
@@ -44,9 +44,9 @@
     ],
     destdir = "tf-audio-dashboard",
     deps = [
-        "//tensorflow/tensorboard/components:tf_imports",
         "//tensorflow/tensorboard/components/tf_backend:legacy",
         "//tensorflow/tensorboard/components/tf_dashboard_common:legacy",
+        "//tensorflow/tensorboard/components/tf_imports_google:lib",
         "//third_party/javascript/polymer/v1/paper-icon-button:lib",
         "//third_party/javascript/polymer/v1/paper-styles:lib",
         "//third_party/javascript/polymer/v1/polymer:lib",
@@ -59,5 +59,4 @@
     srcs = [],
     deps_mgmt = "off",
     runtime = "nodejs",
-    deps = ["//tensorflow/tensorboard/components:common_deps"],
 )
diff --git a/tensorflow/tensorboard/components/tf_backend/BUILD b/tensorflow/tensorboard/components/tf_backend/BUILD
index d9f3a03..14781f3 100644
--- a/tensorflow/tensorboard/components/tf_backend/BUILD
+++ b/tensorflow/tensorboard/components/tf_backend/BUILD
@@ -58,7 +58,7 @@
     visibility = ["//visibility:public"],
     destdir = "tf-backend",
     deps = [
-        "//tensorflow/tensorboard/components:tf_imports",
+        "//tensorflow/tensorboard/components/tf_imports_google:lib",
         "//third_party/javascript/polymer/v1/polymer:lib",
     ],
 )
@@ -75,7 +75,15 @@
     deps_mgmt = "off",
     runtime = "nodejs",
     deps = [
-        "//tensorflow/tensorboard/components:common_deps",
         "//tensorflow/tensorboard/components/vz_sorting:legacy_ts",
+        "//third_party/javascript/node_modules/typescript:es2015.promise",
+        "//third_party/javascript/plottable/v1:typings",
+        "//third_party/javascript/typings/chai",
+        "//third_party/javascript/typings/d3",
+        "//third_party/javascript/typings/lodash",
+        "//third_party/javascript/typings/mocha",
+        "//third_party/javascript/typings/polymer:polymer_without_externs",
+        "//third_party/javascript/typings/sinon",
+        "//third_party/javascript/typings/webcomponents_js",
     ],
 )
diff --git a/tensorflow/tensorboard/components/tf_backend_d3v4/BUILD b/tensorflow/tensorboard/components/tf_backend_d3v4/BUILD
index 0192838..179ca27 100644
--- a/tensorflow/tensorboard/components/tf_backend_d3v4/BUILD
+++ b/tensorflow/tensorboard/components/tf_backend_d3v4/BUILD
@@ -13,6 +13,7 @@
         "tf-backend.html",
     ],
     path = "/tf-backend",
+    visibility = ["//visibility:public"],
     deps = [
         "//tensorflow/tensorboard/components/tf_imports_d3v4:d3",
         "//tensorflow/tensorboard/components/tf_imports_d3v4:lodash",
diff --git a/tensorflow/tensorboard/components/tf_color_scale/BUILD b/tensorflow/tensorboard/components/tf_color_scale/BUILD
index 3e64fcc..f6f1be0 100644
--- a/tensorflow/tensorboard/components/tf_color_scale/BUILD
+++ b/tensorflow/tensorboard/components/tf_color_scale/BUILD
@@ -48,7 +48,7 @@
     ],
     destdir = "tf-color-scale",
     deps = [
-        "//tensorflow/tensorboard/components:tf_imports",
+        "//tensorflow/tensorboard/components/tf_imports_google:lib",
         "//third_party/javascript/polymer/v1/polymer:lib",
     ],
 )
@@ -61,5 +61,15 @@
     ],
     deps_mgmt = "off",
     runtime = "nodejs",
-    deps = ["//tensorflow/tensorboard/components:common_deps"],
+    deps = [
+        "//third_party/javascript/node_modules/typescript:es2015.promise",
+        "//third_party/javascript/plottable/v1:typings",
+        "//third_party/javascript/typings/chai",
+        "//third_party/javascript/typings/d3",
+        "//third_party/javascript/typings/lodash",
+        "//third_party/javascript/typings/mocha",
+        "//third_party/javascript/typings/polymer:polymer_without_externs",
+        "//third_party/javascript/typings/sinon",
+        "//third_party/javascript/typings/webcomponents_js",
+    ],
 )
diff --git a/tensorflow/tensorboard/components/tf_dashboard_common/BUILD b/tensorflow/tensorboard/components/tf_dashboard_common/BUILD
index 321e4a8..5a9e941 100644
--- a/tensorflow/tensorboard/components/tf_dashboard_common/BUILD
+++ b/tensorflow/tensorboard/components/tf_dashboard_common/BUILD
@@ -65,7 +65,7 @@
     srcs = glob(["*.html"]) + [":legacy_ts"],
     destdir = "tf-dashboard-common",
     deps = [
-        "//tensorflow/tensorboard/components:tf_imports",
+        "//tensorflow/tensorboard/components/tf_imports_google:lib",
         "//tensorflow/tensorboard/components/tf_storage:legacy",
         "//tensorflow/tensorboard/components/vz_sorting:legacy",
         "//third_party/javascript/polymer/v1/iron-ajax:lib",
@@ -97,7 +97,15 @@
     deps_mgmt = "off",
     runtime = "nodejs",
     deps = [
-        "//tensorflow/tensorboard/components:common_deps",
         "//tensorflow/tensorboard/components/vz_sorting:legacy_ts",
+        "//third_party/javascript/node_modules/typescript:es2015.promise",
+        "//third_party/javascript/plottable/v1:typings",
+        "//third_party/javascript/typings/chai",
+        "//third_party/javascript/typings/d3",
+        "//third_party/javascript/typings/lodash",
+        "//third_party/javascript/typings/mocha",
+        "//third_party/javascript/typings/polymer:polymer_without_externs",
+        "//third_party/javascript/typings/sinon",
+        "//third_party/javascript/typings/webcomponents_js",
     ],
 )
diff --git a/tensorflow/tensorboard/components/tf_distribution_dashboard/BUILD b/tensorflow/tensorboard/components/tf_distribution_dashboard/BUILD
index 38c3eb6..65341e7 100644
--- a/tensorflow/tensorboard/components/tf_distribution_dashboard/BUILD
+++ b/tensorflow/tensorboard/components/tf_distribution_dashboard/BUILD
@@ -42,9 +42,9 @@
     ],
     destdir = "tf-distribution-dashboard",
     deps = [
-        "//tensorflow/tensorboard/components:tf_imports",
         "//tensorflow/tensorboard/components/tf_backend:legacy",
         "//tensorflow/tensorboard/components/tf_dashboard_common:legacy",
+        "//tensorflow/tensorboard/components/tf_imports_google:lib",
         "//tensorflow/tensorboard/components/vz_distribution_chart:legacy",
         "//third_party/javascript/polymer/v1/iron-collapse:lib",
         "//third_party/javascript/polymer/v1/paper-icon-button:lib",
@@ -59,6 +59,4 @@
     srcs = [],
     deps_mgmt = "off",
     runtime = "nodejs",
-    # Don't list the common_deps here; a src-less ts_library re-exports them,
-    # and they are outside the package where the Fileset will be rooted.
 )
diff --git a/tensorflow/tensorboard/components/tf_graph_common/BUILD b/tensorflow/tensorboard/components/tf_graph_common/BUILD
index 50f7a30..7c0a4a4 100644
--- a/tensorflow/tensorboard/components/tf_graph_common/BUILD
+++ b/tensorflow/tensorboard/components/tf_graph_common/BUILD
@@ -51,7 +51,7 @@
     ],
     destdir = "tf-graph-common",
     deps = [
-        "//tensorflow/tensorboard/components:tf_imports",
+        "//tensorflow/tensorboard/components/tf_imports_google:lib",
         "//third_party/javascript/polymer/v1/polymer:lib",
     ],
 )
@@ -61,5 +61,15 @@
     srcs = glob(["*.ts"]),
     deps_mgmt = "off",
     runtime = "nodejs",
-    deps = ["//tensorflow/tensorboard/components:common_deps"],
+    deps = [
+        "//third_party/javascript/node_modules/typescript:es2015.promise",
+        "//third_party/javascript/plottable/v1:typings",
+        "//third_party/javascript/typings/chai",
+        "//third_party/javascript/typings/d3",
+        "//third_party/javascript/typings/lodash",
+        "//third_party/javascript/typings/mocha",
+        "//third_party/javascript/typings/polymer:polymer_without_externs",
+        "//third_party/javascript/typings/sinon",
+        "//third_party/javascript/typings/webcomponents_js",
+    ],
 )
diff --git a/tensorflow/tensorboard/components/tf_histogram_dashboard/BUILD b/tensorflow/tensorboard/components/tf_histogram_dashboard/BUILD
index 3f17bda..d8e2f67 100644
--- a/tensorflow/tensorboard/components/tf_histogram_dashboard/BUILD
+++ b/tensorflow/tensorboard/components/tf_histogram_dashboard/BUILD
@@ -42,9 +42,9 @@
     ],
     destdir = "tf-histogram-dashboard",
     deps = [
-        "//tensorflow/tensorboard/components:tf_imports",
         "//tensorflow/tensorboard/components/tf_backend:legacy",
         "//tensorflow/tensorboard/components/tf_dashboard_common:legacy",
+        "//tensorflow/tensorboard/components/tf_imports_google:lib",
         "//tensorflow/tensorboard/components/vz_histogram_timeseries:legacy",
         "//third_party/javascript/polymer/v1/iron-collapse:lib",
         "//third_party/javascript/polymer/v1/paper-icon-button:lib",
diff --git a/tensorflow/tensorboard/components/tf_imports/BUILD b/tensorflow/tensorboard/components/tf_imports/BUILD
index ac4c41d..81d1234 100644
--- a/tensorflow/tensorboard/components/tf_imports/BUILD
+++ b/tensorflow/tensorboard/components/tf_imports/BUILD
@@ -24,7 +24,7 @@
     name = "graphlib",
     srcs = [
         "graphlib.html",
-        "@io_github_cpettitt_graphlib",
+        "@io_github_cpettitt_graphlib//:graphlib.core.js",
     ],
     path = "/tf-imports",
     deps = [":lodash"],
@@ -34,7 +34,7 @@
     name = "dagre",
     srcs = [
         "dagre.html",
-        "@io_github_cpettitt_dagre",
+        "@io_github_cpettitt_dagre//:dagre.core.js",
     ],
     path = "/tf-imports",
     deps = [
diff --git a/tensorflow/tensorboard/components/tf_imports_d3v4/BUILD b/tensorflow/tensorboard/components/tf_imports_d3v4/BUILD
index edf7590..abebe34 100644
--- a/tensorflow/tensorboard/components/tf_imports_d3v4/BUILD
+++ b/tensorflow/tensorboard/components/tf_imports_d3v4/BUILD
@@ -26,7 +26,7 @@
     name = "numericjs",
     srcs = [
         "numericjs.html",
-        "@com_numericjs",
+        "@com_numericjs//:numeric.js",
     ],
     path = "/tf-imports",
 )
@@ -35,7 +35,7 @@
     name = "weblas",
     srcs = [
         "weblas.html",
-        "@io_github_waylonflinn_weblas",
+        "@io_github_waylonflinn_weblas//:weblas.js",
     ],
     path = "/tf-imports",
 )
@@ -44,7 +44,7 @@
     name = "graphlib",
     srcs = [
         "graphlib.html",
-        "@io_github_cpettitt_graphlib",
+        "@io_github_cpettitt_graphlib//:graphlib.core.js",
     ],
     path = "/tf-imports",
     deps = [":lodash"],
@@ -54,7 +54,7 @@
     name = "dagre",
     srcs = [
         "dagre.html",
-        "@io_github_cpettitt_dagre",
+        "@io_github_cpettitt_dagre//:dagre.core.js",
     ],
     path = "/tf-imports",
     deps = [
@@ -67,7 +67,7 @@
     name = "d3",
     srcs = [
         "d3.html",
-        "@org_d3js_v4",
+        "@org_d3js_v4//:d3.js",
     ],
     path = "/tf-imports",
 )
diff --git a/tensorflow/tensorboard/components/tf_imports_google/README.md b/tensorflow/tensorboard/components/tf_imports_google/README.md
deleted file mode 100644
index 60d9cce..0000000
--- a/tensorflow/tensorboard/components/tf_imports_google/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-This file acts as import routers for third party javascript libraries,
-e.g. Plottable and D3 from `g3/third_party`; it exists to facilitate development
-inside google.
diff --git a/tensorflow/tensorboard/components/tf_imports_google/d3.html b/tensorflow/tensorboard/components/tf_imports_google/d3.html
deleted file mode 100644
index dbfd11a..0000000
--- a/tensorflow/tensorboard/components/tf_imports_google/d3.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<!--
-@license
-Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
--->
-
-<script src="../d3-library/d3.js"></script>
diff --git a/tensorflow/tensorboard/components/tf_imports_google/dagre.html b/tensorflow/tensorboard/components/tf_imports_google/dagre.html
deleted file mode 100644
index 5b8b981..0000000
--- a/tensorflow/tensorboard/components/tf_imports_google/dagre.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<!--
-@license
-Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
--->
-
-<link rel="import" href="../dagre-library/dagre.html">
diff --git a/tensorflow/tensorboard/components/tf_imports_google/lodash.html b/tensorflow/tensorboard/components/tf_imports_google/lodash.html
deleted file mode 100644
index eb8fef2..0000000
--- a/tensorflow/tensorboard/components/tf_imports_google/lodash.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<!--
-@license
-Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
--->
-
-<link rel="import" href="../lodash-library/lodash-library.html">
diff --git a/tensorflow/tensorboard/components/tf_imports_google/plottable.html b/tensorflow/tensorboard/components/tf_imports_google/plottable.html
deleted file mode 100644
index 6f9678f..0000000
--- a/tensorflow/tensorboard/components/tf_imports_google/plottable.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<!--
-@license
-Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
--->
-
-<link rel="import" href="d3.html">
-<link rel="import" href="../plottable-library/plottable.html">
diff --git a/tensorflow/tensorboard/components/tf_scalar_dashboard/BUILD b/tensorflow/tensorboard/components/tf_scalar_dashboard/BUILD
index c1fbf80..46c91c4 100644
--- a/tensorflow/tensorboard/components/tf_scalar_dashboard/BUILD
+++ b/tensorflow/tensorboard/components/tf_scalar_dashboard/BUILD
@@ -49,10 +49,10 @@
     ],
     destdir = "tf-scalar-dashboard",
     deps = [
-        "//tensorflow/tensorboard/components:tf_imports",
         "//tensorflow/tensorboard/components/tf_backend:legacy",
         "//tensorflow/tensorboard/components/tf_color_scale:legacy",
         "//tensorflow/tensorboard/components/tf_dashboard_common:legacy",
+        "//tensorflow/tensorboard/components/tf_imports_google:lib",
         "//tensorflow/tensorboard/components/vz_line_chart:legacy",
         "//third_party/javascript/polymer/v1/iron-collapse:lib",
         "//third_party/javascript/polymer/v1/paper-checkbox:lib",
@@ -73,5 +73,4 @@
     srcs = [],
     deps_mgmt = "off",
     runtime = "nodejs",
-    deps = ["//tensorflow/tensorboard/components:common_deps"],
 )
diff --git a/tensorflow/tensorboard/components/tf_storage/BUILD b/tensorflow/tensorboard/components/tf_storage/BUILD
index 8b2e006..940d096 100644
--- a/tensorflow/tensorboard/components/tf_storage/BUILD
+++ b/tensorflow/tensorboard/components/tf_storage/BUILD
@@ -52,8 +52,8 @@
     visibility = ["//visibility:public"],
     destdir = "tf-storage",
     deps = [
-        "//tensorflow/tensorboard/components:tf_imports",
         "//tensorflow/tensorboard/components/tf_globals:legacy",
+        "//tensorflow/tensorboard/components/tf_imports_google:lib",
         "//third_party/javascript/polymer/v1/polymer:lib",
     ],
 )
@@ -64,7 +64,15 @@
     deps_mgmt = "off",
     runtime = "nodejs",
     deps = [
-        "//tensorflow/tensorboard/components:common_deps",
         "//tensorflow/tensorboard/components/tf_globals:legacy_ts",
+        "//third_party/javascript/node_modules/typescript:es2015.promise",
+        "//third_party/javascript/plottable/v1:typings",
+        "//third_party/javascript/typings/chai",
+        "//third_party/javascript/typings/d3",
+        "//third_party/javascript/typings/lodash",
+        "//third_party/javascript/typings/mocha",
+        "//third_party/javascript/typings/polymer:polymer_without_externs",
+        "//third_party/javascript/typings/sinon",
+        "//third_party/javascript/typings/webcomponents_js",
     ],
 )
diff --git a/tensorflow/tensorboard/components/tf_tensorboard_d3v4/BUILD b/tensorflow/tensorboard/components/tf_tensorboard_d3v4/BUILD
index 683302b..e17a083 100644
--- a/tensorflow/tensorboard/components/tf_tensorboard_d3v4/BUILD
+++ b/tensorflow/tensorboard/components/tf_tensorboard_d3v4/BUILD
@@ -1,7 +1,6 @@
 package(default_visibility = ["//tensorflow:internal"])
 
 load("@io_bazel_rules_closure//closure:defs.bzl", "web_library")
-load("//tensorflow/tensorboard:vulcanize.bzl", "tensorboard_html_binary")
 load("//tensorflow/tensorboard:defs.bzl", "tensorboard_typescript_genrule")
 
 licenses(["notice"])  # Apache 2.0
@@ -9,11 +8,27 @@
 web_library(
     name = "tf_tensorboard_d3v4",
     srcs = [
+        "style.html",
         "tf-tensorboard.html",
         ":ts",
     ],
     path = "/tf-tensorboard",
+    visibility = ["//visibility:public"],
     deps = [
+        "//tensorflow/tensorboard/components/tf_audio_dashboard_d3v4",
+        "//tensorflow/tensorboard/components/tf_backend_d3v4",
+        "//tensorflow/tensorboard/components/tf_dashboard_common_d3v4",
+        "//tensorflow/tensorboard/components/tf_distribution_dashboard_d3v4",
+        "//tensorflow/tensorboard/components/tf_globals_d3v4",
+        "//tensorflow/tensorboard/components/tf_graph_dashboard_d3v4",
+        "//tensorflow/tensorboard/components/tf_histogram_dashboard_d3v4",
+        "//tensorflow/tensorboard/components/tf_image_dashboard_d3v4",
+        "//tensorflow/tensorboard/components/tf_scalar_dashboard_d3v4",
+        "//tensorflow/tensorboard/components/tf_storage_d3v4",
+        "//tensorflow/tensorboard/components/tf_text_dashboard_d3v4",
+        "//tensorflow/tensorboard/components/vz_projector_d3v4",
+        "@org_polymer",
+        "@org_polymer_font_roboto",
         "@org_polymer_iron_icons",
         "@org_polymer_paper_button",
         "@org_polymer_paper_checkbox",
@@ -22,19 +37,6 @@
         "@org_polymer_paper_icon_button",
         "@org_polymer_paper_tabs",
         "@org_polymer_paper_toolbar",
-        "@org_polymer",
-        "//tensorflow/tensorboard/components/tf_audio_dashboard_d3v4",
-        "//tensorflow/tensorboard/components/tf_backend_d3v4",
-        "//tensorflow/tensorboard/components/tf_dashboard_common_d3v4",
-        "//tensorflow/tensorboard/components/tf_distribution_dashboard_d3v4",
-        "//tensorflow/tensorboard/components/tf_globals_d3v4",
-        # "//tensorflow/tensorboard/components/tf_graph_dashboard_d3v4",
-        "//tensorflow/tensorboard/components/tf_histogram_dashboard_d3v4",
-        "//tensorflow/tensorboard/components/tf_image_dashboard_d3v4",
-        "//tensorflow/tensorboard/components/tf_scalar_dashboard_d3v4",
-        "//tensorflow/tensorboard/components/tf_storage_d3v4",
-        "//tensorflow/tensorboard/components/tf_text_dashboard_d3v4",
-        "//tensorflow/tensorboard/components/vz_projector_d3v4",
     ],
 )
 
@@ -49,25 +51,6 @@
     ],
 )
 
-web_library(
-    name = "dist",
-    srcs = ["dist.html"],
-    path = "/tf-tensorboard",
-    deps = [
-        ":tf_tensorboard_d3v4",
-        "//tensorflow/tensorboard/demo:demo_data",
-        "@org_polymer_webcomponentsjs",
-    ],
-)
-
-tensorboard_html_binary(
-    name = "index",
-    # input_path = "/tf-dashboard-common/tf-chart-scaffold.html",
-    input_path = "/tf-tensorboard/dist.html",
-    output_path = "/index.html",
-    deps = [":dist"],
-)
-
 tensorboard_typescript_genrule(
     name = "ts",
     srcs = ["autoReloadBehavior.ts"],
diff --git a/tensorflow/tensorboard/components/tf_tensorboard_d3v4/demo.html b/tensorflow/tensorboard/components/tf_tensorboard_d3v4/demo.html
index 95f5b71..c8a9238 100644
--- a/tensorflow/tensorboard/components/tf_tensorboard_d3v4/demo.html
+++ b/tensorflow/tensorboard/components/tf_tensorboard_d3v4/demo.html
@@ -16,17 +16,10 @@
 limitations under the License.
 -->
 
-<title>TensorBoard</title>
-<style>
-  html, body {
-    margin: 0;
-    padding: 0;
-    height: 100%;
-    font-family: "RobotoDraft", "Roboto", sans-serif;
-  }
-</style>
+<meta charset="utf-8">
+<title>TensorBoard Demo</title>
 <script src="../webcomponentsjs/webcomponents-lite.min.js"></script>
-<link rel="shortcut icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMQAAADECAMAAAD3eH5ZAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAD/UExURfFlKfaELvFmKfNyK/67NvWALf68Nv69NvNxK/20NfyyNP22NfN0K/JrKvqhMv2zNf25Nf24Nf23NfeOL/yzNPyvNPJoKviWMPmeMfN1K/WBLfePL/FnKfeML/qlMvR7LPmcMfeLL/aJLvR5LPFoKfJuKvR3LP66NvywNPeNL/V/LfaILv21Nf26NfNzK/NvK/R6LPmaMfyxNPqfMvV+LfurM/iSMPmbMfJvKvmdMfumM/qiMvmZMfytNPJqKvysNPN2K/iYMPNwK/upM/JtKvJsKviVMPaHLvaGLvJpKvR8LPaKLvqkMvuqM/aFLvR4LPuoM/iTMPWDLfiRMPmYMXS0ngkAAALoSURBVHja7drnctpAFIbhFUISSKJ3MKYa0+y4xTW9937/15JkJhlTjhrSrHRmvuf/as6L0YLFCgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMBJ6njenqspzgnPrsrGX9Zpi2tCrmnc6+dYNthVY5WpMmxQLWPdMsOuYVwzNj3ei2t3mQwaV43BJPDCS2NbJ5aEeuX/+9qcjQOtfFIkIkrvY2g4MVcmOBsFWbowKO/kNyj62gRpJcDaPBlxLr1B0zdG0C/8LzbJiJrshuvy1gzlA9+rD8mIkuyIJjFE3/dqnYwoSm7IUEPoD/wut8iIguSIDjlFxe/yfXL5vuSI21BTZLLhXoOILMO8Hxwa/L8bI0LfmUdhGowb2ZvT0e57pFNDgB06IlVyjmmIBl2T/nl9Rw6SD9GgSG/Q0uQkaW3XhmovKQ3eFQ4N2Uo9OQ1eFZsNerf7vP+rO4rhmY1Lg3vFVoP8+8BXg1sFnwbnCk4NThW8GuiKBDdkVVtTNFvNelVsNqTbyWnIOM2oeTRoyWvwmpJHg/ucXBrcJuXT4DwrpwZi2vy0VCx8YtXg/D2bU4OfiuQ3eFfE2KD4bfCqiLNB993gXsGlwa2CT4NzBacGIVQ6YsipQdh0xEdODUKjIxrSp88onZ8zbbFLg1DoiFO5BXvDGv2My9/JhUT8JUZTI0yDaNHLBzIbvqTDNYhUiVw/kdjQ1kM2CHFDPjKW+KzyRTF0g/ga9w9y+fANQpxvX8CU+Ny7FUWDeF3Y+g3lROIf4k0UDX9eCyvO531PyYhHga9zvPZJU5b73Y/eXj8Hv9D48n6HaF5LbcjRt8TZTtda5M1DfXnbkX1C0SHCFKzQB5Fe8op4GNGNHavvZESbVwT5r6W1xyuCPBY3Y9YgDqzknH/e3YfNzzuL30l0IebrZ5kKtuDIXt1n868ET6kf3/49tLvrCcZyF8Pu215dAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcPIbNrBhOaBXucoAAAAASUVORK5CYII=">
+<link rel="import" href="style.html">
 <link rel="import" href="tf-tensorboard.html">
-
+<body>
 <tf-tensorboard demo-dir="/data" use-hash></tf-tensorboard>
diff --git a/tensorflow/tensorboard/components/tf_imports_google/graphlib.html b/tensorflow/tensorboard/components/tf_tensorboard_d3v4/style.html
similarity index 75%
copy from tensorflow/tensorboard/components/tf_imports_google/graphlib.html
copy to tensorflow/tensorboard/components/tf_tensorboard_d3v4/style.html
index 56b37eb..575e89e 100644
--- a/tensorflow/tensorboard/components/tf_imports_google/graphlib.html
+++ b/tensorflow/tensorboard/components/tf_tensorboard_d3v4/style.html
@@ -15,4 +15,14 @@
 limitations under the License.
 -->
 
-<link rel="import" href="../graphlib-library/graphlib.html">
+<link rel="import" href="../font-roboto/roboto.html">
+
+<style>
+  html,
+  body {
+    margin: 0;
+    padding: 0;
+    height: 100%;
+    font-family: "RobotoDraft", "Roboto", sans-serif;
+  }
+</style>
diff --git a/tensorflow/tensorboard/components/tf_tensorboard_d3v4/tf-tensorboard.html b/tensorflow/tensorboard/components/tf_tensorboard_d3v4/tf-tensorboard.html
index f3f5d59..3a56535 100644
--- a/tensorflow/tensorboard/components/tf_tensorboard_d3v4/tf-tensorboard.html
+++ b/tensorflow/tensorboard/components/tf_tensorboard_d3v4/tf-tensorboard.html
@@ -30,12 +30,13 @@
 <link rel="import" href="../tf-histogram-dashboard/tf-histogram-dashboard.html">
 <link rel="import" href="../tf-image-dashboard/tf-image-dashboard.html">
 <link rel="import" href="../tf-audio-dashboard/tf-audio-dashboard.html">
-<!-- <link rel="import" href="../tf-graph-dashboard/tf-graph-dashboard.html"> -->
+<link rel="import" href="../tf-graph-dashboard/tf-graph-dashboard.html">
 <link rel="import" href="../tf-text-dashboard/tf-text-dashboard.html">
 <link rel="import" href="../tf-dashboard-common/tensorboard-color.html">
 <link rel="import" href="../tf-backend/tf-backend.html">
 <link rel="import" href="../tf-storage/tf-storage.html">
 <link rel="import" href="../vz-projector/vz-projector-dashboard.html">
+<link rel="import" href="../vz-projector/bundle.html">
 
 <!--
 tf-tensorboard is the frontend entry point for TensorBoard.
diff --git a/tensorflow/tensorboard/components/vz_distribution_chart/BUILD b/tensorflow/tensorboard/components/vz_distribution_chart/BUILD
index 3346805..3b46082 100644
--- a/tensorflow/tensorboard/components/vz_distribution_chart/BUILD
+++ b/tensorflow/tensorboard/components/vz_distribution_chart/BUILD
@@ -51,7 +51,7 @@
     visibility = ["//visibility:public"],
     destdir = "vz-distribution-chart",
     deps = [
-        "//tensorflow/tensorboard/components:tf_imports",
+        "//tensorflow/tensorboard/components/tf_imports_google:lib",
         "//tensorflow/tensorboard/components/vz_sorting:legacy",
         "//third_party/javascript/polymer/v1/polymer:lib",
     ],
@@ -63,7 +63,15 @@
     deps_mgmt = "off",
     runtime = "nodejs",
     deps = [
-        "//tensorflow/tensorboard/components:common_deps",
         "//tensorflow/tensorboard/components/vz_line_chart:legacy_ts",
+        "//third_party/javascript/node_modules/typescript:es2015.promise",
+        "//third_party/javascript/plottable/v1:typings",
+        "//third_party/javascript/typings/chai",
+        "//third_party/javascript/typings/d3",
+        "//third_party/javascript/typings/lodash",
+        "//third_party/javascript/typings/mocha",
+        "//third_party/javascript/typings/polymer:polymer_without_externs",
+        "//third_party/javascript/typings/sinon",
+        "//third_party/javascript/typings/webcomponents_js",
     ],
 )
diff --git a/tensorflow/tensorboard/components/vz_heatmap/BUILD b/tensorflow/tensorboard/components/vz_heatmap/BUILD
new file mode 100644
index 0000000..eccc411
--- /dev/null
+++ b/tensorflow/tensorboard/components/vz_heatmap/BUILD
@@ -0,0 +1,29 @@
+package(default_visibility = ["//tensorflow:internal"])
+
+load("//tensorflow/tensorboard:defs.bzl", "tensorboard_webcomponent_library")
+
+licenses(["notice"])  # Apache 2.0
+
+tensorboard_webcomponent_library(
+    name = "legacy",
+    srcs = [
+        "demo/index.html",
+        "index.html",
+        "vz-heatmap.html",
+    ],
+    visibility = ["//visibility:public"],
+    destdir = "vz-heatmap",
+    deps = [
+        "//tensorflow/tensorboard/components/tf_imports_google:lib",
+        "//tensorflow/tensorboard/components/vz_sorting:legacy",
+        "//third_party/javascript/polymer/v1/iron-component-page:lib",
+        "//third_party/javascript/polymer/v1/polymer:lib",
+        "//third_party/javascript/polymer/v1/webcomponentsjs:lib",
+    ],
+)
+
+filegroup(
+    name = "all_files",
+    srcs = glob(["**"]),
+    tags = ["notsan"],
+)
diff --git a/tensorflow/tensorboard/components/vz_heatmap/demo/index.html b/tensorflow/tensorboard/components/vz_heatmap/demo/index.html
index 63dd044..0ef092f 100644
--- a/tensorflow/tensorboard/components/vz_heatmap/demo/index.html
+++ b/tensorflow/tensorboard/components/vz_heatmap/demo/index.html
@@ -22,6 +22,7 @@
   <title>Heatmap example</title>
   <link rel="import" href="../vz-heatmap.html">
   <link rel="import" href="../../iron-demo-helpers/demo-snippet.html">
+  <link rel="import" href="../../tf-imports/d3.html">
 </head>
 <script>
   function generateRandomMatrix() {
@@ -108,7 +109,7 @@
     setTimeout(function () {
       var heatmapColor = document.getElementById('color_function');
       heatmapColor.colorFunction =
-          d3.scale.linear().range(['white', 'black']).domain([0, 5]);
+          d3.scaleLinear().range(['white', 'black']).domain([0, 5]);
     }, 1500);
   </script>
 </div>
diff --git a/tensorflow/tensorboard/components/vz_heatmap/vz-heatmap.html b/tensorflow/tensorboard/components/vz_heatmap/vz-heatmap.html
index 7acba47..4a00542 100644
--- a/tensorflow/tensorboard/components/vz_heatmap/vz-heatmap.html
+++ b/tensorflow/tensorboard/components/vz_heatmap/vz-heatmap.html
@@ -89,7 +89,7 @@
         _generatedColorScale: {
           type: Object,
           value: (function () {
-            var retFunction = d3.scale.linear();
+            var retFunction = d3.scaleLinear();
             if (this.colors) {
               retFunction.range(this.colors)
             }
@@ -277,7 +277,7 @@
        */
       _updateColorFunction: function () {
         if (Array.isArray(this.colors) && this.colors.length) {
-          this._generatedColorScale = d3.scale.linear().range(this.colors);
+          this._generatedColorScale = d3.scaleLinear().range(this.colors);
         }
 
         if (this.values) {
@@ -294,7 +294,7 @@
         }
       },
       _getLinearInterpolation: function (domain, range) {
-        return d3.scale.linear().domain(domain).range(range);
+        return d3.scaleLinear().domain(domain).range(range);
       },
       /**
        * Find side length of each tile in heat map by dividing current width
diff --git a/tensorflow/tensorboard/components/vz_heatmap_d3v4/BUILD b/tensorflow/tensorboard/components/vz_heatmap_d3v4/BUILD
new file mode 100644
index 0000000..b62629f
--- /dev/null
+++ b/tensorflow/tensorboard/components/vz_heatmap_d3v4/BUILD
@@ -0,0 +1,41 @@
+package(default_visibility = ["//tensorflow:internal"])
+
+load("@io_bazel_rules_closure//closure:defs.bzl", "web_library")
+
+licenses(["notice"])  # Apache 2.0
+
+web_library(
+    name = "vz_heatmap_d3v4",
+    srcs = ["vz-heatmap.html"],
+    path = "/vz-heatmap",
+    visibility = ["//visibility:public"],
+    deps = [
+        "//tensorflow/tensorboard/components/tf_imports_d3v4:d3",
+        "@org_polymer",
+        "@org_polymer_iron_resizable_behavior",
+    ],
+)
+
+web_library(
+    name = "index",
+    srcs = [
+        "demo/index.html",
+        "index.html",
+    ],
+    path = "/vz-heatmap",
+    visibility = ["//visibility:public"],
+    deps = [
+        ":vz_heatmap_d3v4",
+        "//tensorflow/tensorboard/components/tf_imports_d3v4:d3",
+        "@org_polymer",
+        "@org_polymer_iron_component_page",
+        "@org_polymer_iron_demo_helpers",
+        "@org_polymer_webcomponentsjs",
+    ],
+)
+
+filegroup(
+    name = "all_files",
+    srcs = glob(["**"]),
+    tags = ["notsan"],
+)
diff --git a/tensorflow/tensorboard/components/vz_heatmap_d3v4/demo/index.html b/tensorflow/tensorboard/components/vz_heatmap_d3v4/demo/index.html
new file mode 100644
index 0000000..0ef092f
--- /dev/null
+++ b/tensorflow/tensorboard/components/vz_heatmap_d3v4/demo/index.html
@@ -0,0 +1,161 @@
+<!doctype html>
+<!--
+@license
+Copyright 2016 The TensorFlow Authors. All Rights Reserved.
+
+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.
+-->
+
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <title>Heatmap example</title>
+  <link rel="import" href="../vz-heatmap.html">
+  <link rel="import" href="../../iron-demo-helpers/demo-snippet.html">
+  <link rel="import" href="../../tf-imports/d3.html">
+</head>
+<script>
+  function generateRandomMatrix() {
+    var rows = getRandomArbitrary(10, 20);
+    var columns = getRandomArbitrary(10, 20);
+    var data = [];
+    // Generate new data array.
+    for (var row = 0; row < rows; row++) {
+      var currentRow = [];
+      for (var col = 0; col < columns; col++) {
+        currentRow[col] = getRandomArbitrary(0, 20);
+      }
+      data[row] = currentRow;
+    }
+    return data;
+  }
+
+  // Returns a random number between min (inclusive) and max (exclusive)
+  function getRandomArbitrary(min, max) {
+    return Math.random() * (max - min) + min;
+  }
+
+  function getRandomColorRange() {
+    return [getRandomColor(), getRandomColor()];
+  }
+
+  function getRandomColor() {
+    var letters = '0123456789ABCDEF';
+    var color = '#';
+    for (var i = 0; i < 6; i++) {
+      color += letters[Math.floor(Math.random() * 16)];
+    }
+    return color;
+  }
+</script>
+<body>
+<h1>Initialized with data</h1>
+<div style="width: 30%">
+  <demo-snippet>
+    <template>
+      <vz-heatmap data="[[1,2],[3,4]]"></vz-heatmap>
+    </template>
+  </demo-snippet>
+</div>
+
+<h1>Initialized with data and custom data range</h1>
+<div style="width: 30%">
+  <demo-snippet>
+    <template>
+      <vz-heatmap data="[[1,2],[3,4]]" values="[0,10]"></vz-heatmap>
+    </template>
+  </demo-snippet>
+</div>
+
+<h1>Initialized with data and colors</h1>
+<div style="width: 30%">
+  <demo-snippet>
+    <template>
+      <vz-heatmap data="[[1,2],[3,4]]" colors='["yellow", "red"]'></vz-heatmap>
+    </template>
+  </demo-snippet>
+</div>
+
+<h1>Initialized with data and colors and threshold values</h1>
+<div style="width: 30%">
+  <demo-snippet>
+    <template>
+      <vz-heatmap data="[[1,2],[3,4]]"
+                  values="[0, 10]"
+                  colors='["yellow", "red"]'
+      ></vz-heatmap>
+    </template>
+  </demo-snippet>
+</div>
+<h1>Initialized with data and color function</h1>
+<div style="width: 30%">
+  <demo-snippet>
+    <template>
+      <vz-heatmap id="color_function" data="[[1,2],[3,4]]"
+      ></vz-heatmap>
+    </template>
+  </demo-snippet>
+  <script>
+    setTimeout(function () {
+      var heatmapColor = document.getElementById('color_function');
+      heatmapColor.colorFunction =
+          d3.scaleLinear().range(['white', 'black']).domain([0, 5]);
+    }, 1500);
+  </script>
+</div>
+
+<h1>Initialized with data and updated data</h1>
+<h3>Click on the heatmap to create new random data.</h3>
+<div style="width: 30%">
+  <demo-snippet>
+    <template>
+      <vz-heatmap data="[[1,2],[3,4]]"
+                  onclick="this.data = generateRandomMatrix()"
+      >
+      </vz-heatmap>
+    </template>
+  </demo-snippet>
+</div>
+
+<h1>Initialized with data and updated color update</h1>
+<h3>Click on the heatmap to update the color scheme.</h3>
+<div style="width: 30%">
+  <demo-snippet>
+    <template>
+      <vz-heatmap id="data_color_update" data="[[1,2],[3,4]]"
+                  onclick="this.colors = getRandomColorRange();
+                       this.data = generateRandomMatrix()"
+      >
+      </vz-heatmap>
+    </template>
+  </demo-snippet>
+</div>
+
+<h2>Let's try to actually break it... *Puts on fedora*</h2>
+<p>Code below is not meant to be seen, but to ensure that no errors are
+  thrown when invalid data is passed into the Polymer element.</p>
+<demo-snippet>
+  <template>
+    <vz-heatmap id="break_the_heatmap" data="undefined"></vz-heatmap>
+  </template>
+</demo-snippet>
+<script>
+  var bth = document.getElementById('break_the_heatmap');
+  bth.data = []; // Empty 1D array.
+  bth.data = [[]]; // Empty 2D array.
+  bth.colors = ['yellow', 'blue', '']; // More than 2 elements in colors array.
+  bth.values = [1, 2, 3]; // More than 2 elements in values array.
+</script>
+
+</body>
+</html>
diff --git a/tensorflow/tensorboard/dist/bazel-html-imports.html b/tensorflow/tensorboard/components/vz_heatmap_d3v4/index.html
similarity index 61%
rename from tensorflow/tensorboard/dist/bazel-html-imports.html
rename to tensorflow/tensorboard/components/vz_heatmap_d3v4/index.html
index 2268e6d..6563064 100644
--- a/tensorflow/tensorboard/dist/bazel-html-imports.html
+++ b/tensorflow/tensorboard/components/vz_heatmap_d3v4/index.html
@@ -16,8 +16,16 @@
 limitations under the License.
 -->
 
-<!-- TENSORBOARD_BOWER_AUTOGENERATED_BELOW_THIS_LINE_DO_NOT_EDIT -->
-<script src="../numericjs_numeric_min_js/file/numeric.min.js"></script>
-<script src="../three_js_three_min_js/file/three.min.js"></script>
-<script src="../three_js_orbitcontrols_js/file/OrbitControls.js"></script>
-<script src="../weblas_weblas_js/file/weblas.js"></script>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <script src="../webcomponentsjs/webcomponents-lite.js"></script>
+    <link rel="import" href="../iron-component-page/iron-component-page.html">
+  </head>
+  <body>
+
+    <iron-component-page src="vz-heatmap.html"></iron-component-page>
+
+  </body>
+</html>
diff --git a/tensorflow/tensorboard/components/vz_heatmap_d3v4/vz-heatmap.html b/tensorflow/tensorboard/components/vz_heatmap_d3v4/vz-heatmap.html
new file mode 100644
index 0000000..8a35034
--- /dev/null
+++ b/tensorflow/tensorboard/components/vz_heatmap_d3v4/vz-heatmap.html
@@ -0,0 +1,360 @@
+<!--
+@license
+Copyright 2016 The TensorFlow Authors. All Rights Reserved.
+
+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.
+-->
+
+<link rel="import" href="../polymer/polymer.html">
+<link rel="import" href="../iron-resizable-behavior/iron-resizable-behavior.html">
+<link rel="import" href="../tf-imports/d3.html">
+
+<!--
+`vz-heatmap` A simple heatmap to visualize 2D data using predefined or user
+defined colour scheme, with a dependency on d3.js. The heatmap automatically
+fits itself to the width of the container it is placed in.
+
+@element vz-heatmap
+@demo demo/index.html
+-->
+<dom-module id="vz-heatmap">
+  <template>
+    <div>
+      <canvas id="heatmap"
+              style="width: 100%; image-rendering:pixelated"></canvas>
+    </div>
+  </template>
+  <script>
+    Polymer({
+      is: 'vz-heatmap',
+      properties: {
+        /**
+         * An 2-element array which defines the lower and upper bound of the
+         * automatically created color scale for the heatmap.
+         */
+        colors: {
+          type: Array,
+          value: ['white', 'steelblue'],
+          observer: '_onColorsChange'
+        },
+        /**
+         * A function which returns a hex-formatted color string
+         * given a number type.
+         * i.e. (input: number) => string
+         *
+         * Passing a colorFunction deactivates the color
+         * function generated by the `colors` and `values` fields.
+         */
+        colorFunction: {
+          type: Object,
+          observer: '_onColorFunctionChange'
+        },
+        /**
+         * A 2D data array containing the data to be visualized.
+         */
+        data: {
+          type: Array,
+          value: [],
+          observer: '_onDataChange'
+        },
+        /**
+         * A 2-element array containing the domain of values which
+         * maps to the color range defined by the `colors` array.
+         */
+        values: {
+          type: Array,
+          observer: '_onValuesChange'
+        },
+        _currentDataValid: {
+          type: Boolean,
+          value: false
+        },
+        _dataExtent: {
+          type: Array,
+          value: [0, 1]
+        },
+        _debug: {
+          type: Boolean,
+          value: false
+        },
+        _generatedColorScale: {
+          type: Object,
+          value: (function () {
+            var retFunction = d3.scaleLinear();
+            if (this.colors) {
+              retFunction.range(this.colors)
+            }
+
+            return retFunction;
+          })()
+        },
+        _isReady: {
+          type: Boolean,
+          value: false
+        },
+        _height: {
+          type: Number,
+          value: 0
+        },
+        _width: {
+          type: Number,
+          value: 0
+        },
+        _useUserColorPickerFunction: {
+          type: Boolean,
+          value: false
+        }
+      },
+      behaviors: [
+        Polymer.IronResizableBehavior
+      ],
+      listeners: {
+        'iron-resize': '_onWidthChange'
+      },
+      _onWidthChange: function () {
+        // Re-render the chart if the data has already been set.
+        if (this._currentDataValid) {
+          this._renderData();
+        }
+      },
+      ready: function () {
+        this._isReady = true;
+        this._onWidthChange();
+      },
+      attached: function () {
+        this._onWidthChange();
+      },
+      /**
+       * Data change handler, if new data is valid, sets flag to indicate data
+       * now valid, updates the data extent and renders the new data.
+       */
+      _onDataChange: function () {
+        // Validate new data.
+        if (!this._isDataValid(this.data)) {
+          return;
+        }
+
+        // Set flag to indicate data is now valid.
+        this._currentDataValid = true;
+
+        // Calculate new data extent.
+        this._updateDataExtent();
+
+        if (this._isReady) {
+          this._renderData();
+        }
+      },
+      /**
+       * (Re-)Renders the heatmap. Called when data, color mapping, or width
+       * changes.
+       * @private
+       */
+      _renderData: function () {
+        // Ensure dimensions are up-to-date and valid before starting rendering.
+        this._updateDimensions();
+        if (!this._width || !this._height) return;
+
+        // Ensure the color function is up-to-date.
+        this._updateColorFunction();
+        var width = this._width;
+        var rows = this.data.length;
+        var columns = this.data[0].length;
+
+        // Calculate side length of each tile.
+        var sideLength = width / columns;
+
+        // Set domain and range of the axis.
+        var colorScale = (this._useUserColorPickerFunction) ?
+            this.colorFunction : this._generatedColorScale;
+
+        // Clear the canvas.
+        this.resetCanvas();
+
+        // Render heatmap.
+        var ctx = this.$.heatmap.getContext("2d");
+
+        for (var row = 0; row < rows; row++) {
+          for (var column = 0; column < columns; column++) {
+            var value = this.data[row][column];
+            // Set location and dimensions.
+            ctx.fillStyle = colorScale(value);
+            // Preferred way to set color of individual pixels.
+            ctx.fillRect(column, row, 1, 1);
+          }
+        }
+      },
+      /**
+       * Observer for this.colors.
+       * @private
+       */
+      _onColorsChange: function () {
+        if (Array.isArray(this.colors) && this.colors.length == 2) {
+          this._updateColorFunction();
+        }
+        this._useUserColorPickerFunction = false;
+        this._renderData();
+      },
+      /**
+       * Observer for this.values. Updates the
+       * internal color function. If data invalid, internal color scale uses
+       * the extent of the data as domain.
+       * @private
+       */
+      _onValuesChange: function () {
+        // Verify that validity of the new content of values.
+        // If data not valid, set this.values to undefined, as
+        // _updateColorFunction will then use the data's extent as the color
+        // scale's domain.
+        if (!(Array.isArray(this.values) && this.values.length == 2)) {
+          this.values = undefined;
+        }
+
+        this._updateColorFunction();
+        this._useUserColorPickerFunction = false;
+        this._renderData();
+      },
+      /**
+       * Observer for this.colorFunction. This field allows the user to
+       * provide a custom color scale. Updates to this value are ignored, if the new
+       * value is not a function object.
+       * @private
+       */
+      _onColorFunctionChange: function () {
+        if (typeof this.colorFunction === 'function') {
+          this._useUserColorPickerFunction = true;
+          this._renderData();
+        } else if (this._debug) {
+          console.log('The colorFunction provided is not of function type, and was not set as default.')
+        }
+      },
+      /**
+       * Calculates the extent of the data if it is required by the
+       * current color function. This function is called when
+       * properties change which would result in the color scale
+       * changing, or when the computed color scale is started to be used.
+       * @private
+       */
+      _updateDataExtent: function () {
+        if (this._currentDataValid) {
+          var rows = this.data.length;
+          var columns = this.data[0].length;
+
+          var min = this.data[0][0];
+          var max = this.data[0][0];
+
+          for (var row = 0; row < rows; row++) {
+            for (var col = 0; col < columns; col++) {
+              var currentElement = this.data[row][col];
+
+              if (currentElement < min) {
+                min = currentElement;
+              } else if (currentElement > max) {
+                max = currentElement;
+              }
+            }
+          }
+
+          this._dataExtent = [min, max];
+        } else if (!this._dataExtent) {
+          this._dataExtent = [0, 1];
+        }
+      },
+      /**
+       * Updates the internal color function only when the number of
+       * elements in this.colors is equal to the number of elements in
+       * this.values or if this.values is empty, in which case the
+       * extent of the data is used.
+       * @private
+       */
+      _updateColorFunction: function () {
+        if (Array.isArray(this.colors) && this.colors.length) {
+          this._generatedColorScale = d3.scaleLinear().range(this.colors);
+        }
+
+        if (this.values) {
+          // Check that the number of elements in this.values and
+          // this.colors have an identical number of elements.
+          if (this.colors.length === this.values.length) {
+            this._generatedColorScale.domain(this.values);
+          }
+
+          // If values reset, and data field contains valid data, set colour scale
+          // to use the data extent as domain.
+        } else if (this._currentDataValid) {
+          this._generatedColorScale.domain(this._dataExtent);
+        }
+      },
+      _getLinearInterpolation: function (domain, range) {
+        return d3.scaleLinear().domain(domain).range(range);
+      },
+      /**
+       * Find side length of each tile in heat map by dividing current width
+       * by number of columns
+       */
+      _findTileSideLength: function () {
+        if (this._currentDataValid) {
+          var numOfColumns = this.data[0].length;
+
+          // Make sure value is finite.
+          if (numOfColumns === 0) {
+            var returnValue = 0;
+          } else {
+            returnValue = this._width / numOfColumns;
+          }
+          return returnValue;
+
+        } else {
+          if (this._debug == true) {
+            console.log("WARNING: vz-heatmap is reverting to zero height as passed data is invalid.")
+          }
+          return 0; // Fall back to 0 height, if data is invalid.
+        }
+      },
+      /**
+       * Verifies whether input data is valid.
+       * @param newData Number[][]
+       * @private
+       */
+      _isDataValid: function (newData) {
+        return Array.isArray(newData) && newData.length &&
+            Array.isArray(newData[0]);
+      },
+      _updateDimensions: function () {
+        var canvasElement = this.$.heatmap;
+
+        var rows = 0;
+        var columns = 0;
+        if (this._currentDataValid) {
+          rows = this.data.length;
+          columns = this.data[0].length;
+        }
+
+        // Recalculate height and width, and ensure data can be rendered.
+        this._width = canvasElement.parentNode.clientWidth;
+        this._height = this._findTileSideLength() * rows;
+        // Update the attribute height and width.
+        canvasElement.setAttribute('height', rows);
+        canvasElement.setAttribute('width', columns);
+      },
+      /**
+       * Function which clears the canvas.
+       */
+      resetCanvas: function () {
+        // Reset the canvas.
+        var canvas = this.$.heatmap;
+        var ctx = canvas.getContext("2d");
+        ctx.clearRect(0, 0, canvas.width, canvas.height);
+      }
+    });
+  </script>
+</dom-module>
diff --git a/tensorflow/tensorboard/components/vz_histogram_timeseries/BUILD b/tensorflow/tensorboard/components/vz_histogram_timeseries/BUILD
index d36cf2b..3ca3936 100644
--- a/tensorflow/tensorboard/components/vz_histogram_timeseries/BUILD
+++ b/tensorflow/tensorboard/components/vz_histogram_timeseries/BUILD
@@ -37,7 +37,7 @@
     visibility = ["//visibility:public"],
     destdir = "vz-histogram-timeseries",
     deps = [
-        "//tensorflow/tensorboard/components:tf_imports",
+        "//tensorflow/tensorboard/components/tf_imports_google:lib",
         "//third_party/javascript/polymer/v1/polymer:lib",
     ],
 )
diff --git a/tensorflow/tensorboard/components/vz_line_chart/BUILD b/tensorflow/tensorboard/components/vz_line_chart/BUILD
index 130d58d..6b988e5 100644
--- a/tensorflow/tensorboard/components/vz_line_chart/BUILD
+++ b/tensorflow/tensorboard/components/vz_line_chart/BUILD
@@ -54,7 +54,7 @@
     visibility = ["//visibility:public"],
     destdir = "vz-line-chart",
     deps = [
-        "//tensorflow/tensorboard/components:tf_imports",
+        "//tensorflow/tensorboard/components/tf_imports_google:lib",
         "//tensorflow/tensorboard/components/vz_sorting:legacy",
         "//third_party/javascript/polymer/v1/polymer:lib",
     ],
@@ -69,5 +69,15 @@
     ],
     deps_mgmt = "off",
     runtime = "nodejs",
-    deps = ["//tensorflow/tensorboard/components:common_deps"],
+    deps = [
+        "//third_party/javascript/node_modules/typescript:es2015.promise",
+        "//third_party/javascript/plottable/v1:typings",
+        "//third_party/javascript/typings/chai",
+        "//third_party/javascript/typings/d3",
+        "//third_party/javascript/typings/lodash",
+        "//third_party/javascript/typings/mocha",
+        "//third_party/javascript/typings/polymer:polymer_without_externs",
+        "//third_party/javascript/typings/sinon",
+        "//third_party/javascript/typings/webcomponents_js",
+    ],
 )
diff --git a/tensorflow/tensorboard/components/vz_sorting/BUILD b/tensorflow/tensorboard/components/vz_sorting/BUILD
index ab5d7c3..795fc5c 100644
--- a/tensorflow/tensorboard/components/vz_sorting/BUILD
+++ b/tensorflow/tensorboard/components/vz_sorting/BUILD
@@ -46,5 +46,4 @@
     srcs = ["sorting.ts"],
     deps_mgmt = "off",
     runtime = "nodejs",
-    deps = ["//tensorflow/tensorboard/components:common_deps"],
 )
diff --git a/tensorflow/tensorboard/components/vz_sorting/test/BUILD b/tensorflow/tensorboard/components/vz_sorting/test/BUILD
index d1cf5a5..649bfc5 100644
--- a/tensorflow/tensorboard/components/vz_sorting/test/BUILD
+++ b/tensorflow/tensorboard/components/vz_sorting/test/BUILD
@@ -34,7 +34,15 @@
     deps_mgmt = "off",
     runtime = "nodejs",
     deps = [
-        "//tensorflow/tensorboard/components:common_deps",
         "//tensorflow/tensorboard/components/vz_sorting:legacy_ts",
+        "//third_party/javascript/node_modules/typescript:es2015.promise",
+        "//third_party/javascript/plottable/v1:typings",
+        "//third_party/javascript/typings/chai",
+        "//third_party/javascript/typings/d3",
+        "//third_party/javascript/typings/lodash",
+        "//third_party/javascript/typings/mocha",
+        "//third_party/javascript/typings/polymer:polymer_without_externs",
+        "//third_party/javascript/typings/sinon",
+        "//third_party/javascript/typings/webcomponents_js",
     ],
 )
diff --git a/tensorflow/tensorboard/dist/index.html b/tensorflow/tensorboard/dist/index.html
deleted file mode 100644
index 5f09faf..0000000
--- a/tensorflow/tensorboard/dist/index.html
+++ /dev/null
@@ -1,32 +0,0 @@
-<!doctype html>
-<!--
-@license
-Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
--->
-
-<html>
-  <head>
-    <title>TensorBoard</title>
-    <script src="webcomponentsjs/webcomponents-lite.min.js"></script>
-    <link rel="stylesheet" type="text/css" href="lib/css/global.css">
-    <link rel="stylesheet" type="text/css" href="plottable/plottable.css">
-    <link rel="shortcut icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMQAAADECAMAAAD3eH5ZAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAD/UExURfFlKfaELvFmKfNyK/67NvWALf68Nv69NvNxK/20NfyyNP22NfN0K/JrKvqhMv2zNf25Nf24Nf23NfeOL/yzNPyvNPJoKviWMPmeMfN1K/WBLfePL/FnKfeML/qlMvR7LPmcMfeLL/aJLvR5LPFoKfJuKvR3LP66NvywNPeNL/V/LfaILv21Nf26NfNzK/NvK/R6LPmaMfyxNPqfMvV+LfurM/iSMPmbMfJvKvmdMfumM/qiMvmZMfytNPJqKvysNPN2K/iYMPNwK/upM/JtKvJsKviVMPaHLvaGLvJpKvR8LPaKLvqkMvuqM/aFLvR4LPuoM/iTMPWDLfiRMPmYMXS0ngkAAALoSURBVHja7drnctpAFIbhFUISSKJ3MKYa0+y4xTW9937/15JkJhlTjhrSrHRmvuf/as6L0YLFCgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMBJ6njenqspzgnPrsrGX9Zpi2tCrmnc6+dYNthVY5WpMmxQLWPdMsOuYVwzNj3ei2t3mQwaV43BJPDCS2NbJ5aEeuX/+9qcjQOtfFIkIkrvY2g4MVcmOBsFWbowKO/kNyj62gRpJcDaPBlxLr1B0zdG0C/8LzbJiJrshuvy1gzlA9+rD8mIkuyIJjFE3/dqnYwoSm7IUEPoD/wut8iIguSIDjlFxe/yfXL5vuSI21BTZLLhXoOILMO8Hxwa/L8bI0LfmUdhGowb2ZvT0e57pFNDgB06IlVyjmmIBl2T/nl9Rw6SD9GgSG/Q0uQkaW3XhmovKQ3eFQ4N2Uo9OQ1eFZsNerf7vP+rO4rhmY1Lg3vFVoP8+8BXg1sFnwbnCk4NThW8GuiKBDdkVVtTNFvNelVsNqTbyWnIOM2oeTRoyWvwmpJHg/ucXBrcJuXT4DwrpwZi2vy0VCx8YtXg/D2bU4OfiuQ3eFfE2KD4bfCqiLNB993gXsGlwa2CT4NzBacGIVQ6YsipQdh0xEdODUKjIxrSp88onZ8zbbFLg1DoiFO5BXvDGv2My9/JhUT8JUZTI0yDaNHLBzIbvqTDNYhUiVw/kdjQ1kM2CHFDPjKW+KzyRTF0g/ga9w9y+fANQpxvX8CU+Ny7FUWDeF3Y+g3lROIf4k0UDX9eCyvO531PyYhHga9zvPZJU5b73Y/eXj8Hv9D48n6HaF5LbcjRt8TZTtda5M1DfXnbkX1C0SHCFKzQB5Fe8op4GNGNHavvZESbVwT5r6W1xyuCPBY3Y9YgDqzknH/e3YfNzzuL30l0IebrZ5kKtuDIXt1n868ET6kf3/49tLvrCcZyF8Pu215dAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcPIbNrBhOaBXucoAAAAASUVORK5CYII=">
-    <link rel="import" href="dist/bazel-html-imports.html">
-    <link rel="import" href="dist/tf-tensorboard.html">
-  </head>
-  <body>
-    <tf-tensorboard use-hash></tf-tensorboard>
-  </body>
-</html>
diff --git a/tensorflow/tensorboard/dist/tf-tensorboard.html b/tensorflow/tensorboard/dist/tf-tensorboard.html
deleted file mode 100644
index 9f13cdf..0000000
--- a/tensorflow/tensorboard/dist/tf-tensorboard.html
+++ /dev/null
@@ -1,27143 +0,0 @@
-<!-- Copyright 2015 The TensorFlow Authors. All Rights Reserved.
-
-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.
-============================================================================
-
-This file is generated by `gulp` & `vulcanize`. Do not directly change it.
-Instead, use `gulp regenerate` to create a new version with your changes.
--->
-
-<html><head><!--
-@license
-Copyright 2017 The TensorFlow Authors. All Rights Reserved.
-
-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.
---><!--
-@license
-Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
---><meta charset="UTF-8"><link rel="import" href="../polymer/polymer.html">
-<link rel="import" href="../iron-icons/iron-icons.html">
-<link rel="import" href="../paper-tabs/paper-tabs.html">
-<link rel="import" href="../paper-dialog/paper-dialog.html">
-<link rel="import" href="../paper-checkbox/paper-checkbox.html">
-<link rel="import" href="../paper-toolbar/paper-toolbar.html">
-<link rel="import" href="../paper-button/paper-button.html">
-<link rel="import" href="../paper-icon-button/paper-icon-button.html">
-<link rel="import" href="../paper-header-panel/paper-header-panel.html">
-
-
-</head><body><div hidden="" by-vulcanize=""><dom-module id="tf-globals" assetpath="../tf-globals/">
-  <script>/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-/* tslint:disable:no-namespace */
-var TF;
-(function (TF) {
-    var Globals;
-    (function (Globals) {
-        // The names of TensorBoard tabs.
-        Globals.TABS = [
-            'scalars', 'images', 'audio', 'graphs', 'distributions', 'histograms',
-            'embeddings', 'text'
-        ];
-        // If true, TensorBoard stores its hash in the URI state.
-        // If false, tab switching in TensorBoard will not update location hash,
-        // because hash updates interfere with wct_tests.
-        Globals.USE_HASH = false;
-        // If USE_HASH is false, FAKE_HASH holds the hash contents.
-        Globals.FAKE_HASH = '';
-    })(Globals = TF.Globals || (TF.Globals = {}));
-})(TF || (TF = {}));
-</script>
-</dom-module>
-
-<script src="../lodash/lodash.min.js"></script>
-<link rel="import" href="../paper-slider/paper-slider.html">
-<link rel="import" href="../paper-input/paper-input.html">
-
-<dom-module id="tf-smoothing-input" assetpath="../tf-scalar-dashboard/">
-  <template>
-    <h3 class="title">Smoothing</h3>
-    <div class="smoothing-block">
-      <paper-slider id="slider" value="{{weight}}" immediate-value="{{_immediateWeightNumberForPaperSlider}}" type="number" step="[[step]]" min="[[min]]" max="[[max]]"></paper-slider>
-      <paper-input id="input" label="weight" no-label-float="" value="{{_inputWeightStringForPaperInput}}" type="number" step="[[step]]" min="[[min]]" max="[[max]]"></paper-input>
-    </div>
-    <style>
-      .title {
-        color: var(--paper-grey-800);
-        margin: 0;
-        font-weight: normal;
-        font-size: 14px;
-        margin-bottom: 5px;
-      }
-
-      .smoothing-block {
-        display: flex;
-      }
-
-      paper-slider {
-        margin-left: 12px;
-        --paper-slider-knob-color: var(--tb-orange-strong);
-        --paper-slider-active-color: var(--tb-orange-strong);
-        flex-grow: 2;
-      }
-
-      paper-input {
-        --paper-input-container-focus-color: var(--tb-orange-strong);
-        --paper-input-container-input: {
-          font-size: 14px;
-        };
-        --paper-input-container-label: {
-          font-size: 14px;
-        };
-        width: 60px;
-      }
-    </style>
-  </template>
-  <script>
-    Polymer({
-      is: "tf-smoothing-input",
-
-      properties: {
-        step: Number,
-        max: Number,
-        min: Number,
-
-        weight: {
-          type: Number,
-          value: 0.6,
-          notify: true
-        },
-
-        _immediateWeightNumberForPaperSlider: {
-          type: Number,
-          notify: true,
-          observer: '_immediateWeightNumberForPaperSliderChanged'
-        },
-
-        // Paper input treats values as strings even if you specify them as
-        // numbers.
-        _inputWeightStringForPaperInput: {
-          type: String,
-          notify: true,
-          observer: '_inputWeightStringForPaperInputChanged'
-        }
-      },
-
-      _updateWeight: _.debounce(function(val) {
-        this.weight = val;
-      }, 250),
-
-      _immediateWeightNumberForPaperSliderChanged: function() {
-        this._inputWeightStringForPaperInput =
-            this._immediateWeightNumberForPaperSlider.toString();
-        this._updateWeight.call(this, this._immediateWeightNumberForPaperSlider);
-      },
-
-      _inputWeightStringForPaperInputChanged: function() {
-        if (+this._inputWeightStringForPaperInput < 0) {
-          this._inputWeightStringForPaperInput = '0';
-        }
-        else if (+this._inputWeightStringForPaperInput > 1) {
-          this._inputWeightStringForPaperInput = '1';
-        }
-
-        var d = +this._inputWeightStringForPaperInput;
-        if (!isNaN(d)) {
-          this._updateWeight.call(this, d);
-        }
-      }
-    });
-  </script>
-</dom-module>
-<script src="../d3/d3.js"></script>
-<script>/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-var VZ;
-(function (VZ) {
-    var Sorting;
-    (function (Sorting) {
-        /**
-         * Compares tag names asciinumerically broken into components.
-         *
-         * <p>This is the comparison function used for sorting most string values in
-         * TensorBoard. Unlike the standard asciibetical comparator, this function
-         * knows that 'a10b' > 'a2b'. Fixed point and engineering notation are
-         * supported. This function also splits the input by slash and underscore to
-         * perform array comparison. Therefore it knows that 'a/a' < 'a+/a' even
-         * though '+' < '/' in the ASCII table.
-         */
-        function compareTagNames(a, b) {
-            var ai = 0;
-            var bi = 0;
-            while (true) {
-                if (ai === a.length) {
-                    return bi === b.length ? 0 : -1;
-                }
-                if (bi === b.length) {
-                    return 1;
-                }
-                if (isDigit(a[ai]) && isDigit(b[bi])) {
-                    var ais = ai;
-                    var bis = bi;
-                    ai = consumeNumber(a, ai + 1);
-                    bi = consumeNumber(b, bi + 1);
-                    var an = parseFloat(a.slice(ais, ai));
-                    var bn = parseFloat(b.slice(bis, bi));
-                    if (an < bn) {
-                        return -1;
-                    }
-                    if (an > bn) {
-                        return 1;
-                    }
-                    continue;
-                }
-                if (isBreak(a[ai])) {
-                    if (!isBreak(b[bi])) {
-                        return -1;
-                    }
-                }
-                else if (isBreak(b[bi])) {
-                    return 1;
-                }
-                else if (a[ai] < b[bi]) {
-                    return -1;
-                }
-                else if (a[ai] > b[bi]) {
-                    return 1;
-                }
-                ai++;
-                bi++;
-            }
-        }
-        Sorting.compareTagNames = compareTagNames;
-        function consumeNumber(s, i) {
-            var State;
-            (function (State) {
-                State[State["NATURAL"] = 0] = "NATURAL";
-                State[State["REAL"] = 1] = "REAL";
-                State[State["EXPONENT_SIGN"] = 2] = "EXPONENT_SIGN";
-                State[State["EXPONENT"] = 3] = "EXPONENT";
-            })(State || (State = {}));
-            var state = State.NATURAL;
-            for (; i < s.length; i++) {
-                if (state === State.NATURAL) {
-                    if (s[i] === '.') {
-                        state = State.REAL;
-                    }
-                    else if (s[i] === 'e' || s[i] === 'E') {
-                        state = State.EXPONENT_SIGN;
-                    }
-                    else if (!isDigit(s[i])) {
-                        break;
-                    }
-                }
-                else if (state === State.REAL) {
-                    if (s[i] === 'e' || s[i] === 'E') {
-                        state = State.EXPONENT_SIGN;
-                    }
-                    else if (!isDigit(s[i])) {
-                        break;
-                    }
-                }
-                else if (state === State.EXPONENT_SIGN) {
-                    if (isDigit(s[i]) || s[i] === '+' || s[i] === '-') {
-                        state = State.EXPONENT;
-                    }
-                    else {
-                        break;
-                    }
-                }
-                else if (state === State.EXPONENT) {
-                    if (!isDigit(s[i])) {
-                        break;
-                    }
-                }
-            }
-            return i;
-        }
-        function isDigit(c) { return '0' <= c && c <= '9'; }
-        function isBreak(c) {
-            // TODO(jart): Remove underscore when people stop using it like a slash.
-            return c === '/' || c === '_' || isDigit(c);
-        }
-    })(Sorting = VZ.Sorting || (VZ.Sorting = {}));
-})(VZ || (VZ = {}));
-</script>
-<script>/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-var __extends = (this && this.__extends) || (function () {
-    var extendStatics = Object.setPrototypeOf ||
-        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
-        function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
-    return function (d, b) {
-        extendStatics(d, b);
-        function __() { this.constructor = d; }
-        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
-    };
-})();
-var TF;
-(function (TF) {
-    var Backend;
-    (function (Backend) {
-        /**
-         * Manages many fetch requests. Launches up to nSimultaneousRequests
-         * simultaneously, and maintains a LIFO queue of requests to process when
-         * more urls are requested than can be handled at once. The queue can be
-         * cleared.
-         *
-         * When a request is made, a Promise is returned which resolves with the
-         * parsed JSON result from the request.
-         */
-        var RequestCancellationError = (function (_super) {
-            __extends(RequestCancellationError, _super);
-            function RequestCancellationError() {
-                var _this = _super !== null && _super.apply(this, arguments) || this;
-                _this.name = 'RequestCancellationError';
-                return _this;
-            }
-            return RequestCancellationError;
-        }(Error));
-        Backend.RequestCancellationError = RequestCancellationError;
-        var RequestNetworkError = (function (_super) {
-            __extends(RequestNetworkError, _super);
-            function RequestNetworkError(req, url) {
-                var _this = _super.call(this) || this;
-                _this.message = "RequestNetworkError: " + req.status + " at " + url;
-                _this.name = 'RequestNetworkError';
-                _this.req = req;
-                _this.url = url;
-                return _this;
-            }
-            return RequestNetworkError;
-        }(Error));
-        Backend.RequestNetworkError = RequestNetworkError;
-        var RequestManager = (function () {
-            function RequestManager(nSimultaneousRequests, maxRetries) {
-                if (nSimultaneousRequests === void 0) { nSimultaneousRequests = 10; }
-                if (maxRetries === void 0) { maxRetries = 3; }
-                this._queue = [];
-                this._nActiveRequests = 0;
-                this._nSimultaneousRequests = nSimultaneousRequests;
-                this._maxRetries = maxRetries;
-            }
-            /**
-             * Gives a promise that loads assets from given url (respects queuing). If
-             * postData is provided, this request will use POST, not GET. This is an
-             * object mapping POST keys to string values.
-             */
-            RequestManager.prototype.request = function (url, postData) {
-                var _this = this;
-                var promise = new Promise(function (resolve, reject) {
-                    var resolver = { resolve: resolve, reject: reject };
-                    _this._queue.push(resolver);
-                    _this.launchRequests();
-                })
-                    .then(function () {
-                    return _this.promiseWithRetries(url, _this._maxRetries, postData);
-                })
-                    .then(function (response) {
-                    // Success - Let's free space for another active
-                    // reqest, and launch it
-                    _this._nActiveRequests--;
-                    _this.launchRequests();
-                    return response;
-                }, function (rejection) {
-                    if (rejection.name === 'RequestNetworkError') {
-                        // If we failed due to network error, we should
-                        // decrement
-                        // _nActiveRequests because this request was
-                        // active
-                        _this._nActiveRequests--;
-                        _this.launchRequests();
-                    }
-                    return Promise.reject(rejection);
-                });
-                return promise;
-            };
-            RequestManager.prototype.clearQueue = function () {
-                while (this._queue.length > 0) {
-                    this._queue.pop().reject(new RequestCancellationError('Request cancelled by clearQueue'));
-                }
-            };
-            /* Return number of currently pending requests */
-            RequestManager.prototype.activeRequests = function () {
-                return this._nActiveRequests;
-            };
-            /* Return total number of outstanding requests (includes queue) */
-            RequestManager.prototype.outstandingRequests = function () {
-                return this._nActiveRequests + this._queue.length;
-            };
-            RequestManager.prototype.launchRequests = function () {
-                while (this._nActiveRequests < this._nSimultaneousRequests &&
-                    this._queue.length > 0) {
-                    this._nActiveRequests++;
-                    this._queue.pop().resolve();
-                }
-            };
-            /**
-             * Try to request a given URL using overwritable _promiseFromUrl method.
-             * If the request fails for any reason, we will retry up to maxRetries
-             * times. In practice, this will help us paper over transient network issues
-             * like '502 Bad Gateway'.
-             * By default, Chrome displays network errors in console, so
-             * the user will be able to tell when the requests are failing. I think this
-             * is a feature, if the request failures and retries are causing any
-             * pain to users, they can see it and file issues.
-             */
-            RequestManager.prototype.promiseWithRetries = function (url, maxRetries, postData) {
-                var _this = this;
-                var success = function (x) { return x; };
-                var failure = function (x) {
-                    if (maxRetries > 0) {
-                        return _this.promiseWithRetries(url, maxRetries - 1, postData);
-                    }
-                    else {
-                        return Promise.reject(x);
-                    }
-                };
-                return this._promiseFromUrl(url, postData).then(success, failure);
-            };
-            /* Actually get promise from url using XMLHttpRequest */
-            RequestManager.prototype._promiseFromUrl = function (url, postData) {
-                return new Promise(function (resolve, reject) {
-                    var req = new XMLHttpRequest();
-                    req.open(postData ? 'POST' : 'GET', url);
-                    var formData;
-                    if (postData) {
-                        // We are to make a POST request.
-                        formData = new FormData();
-                        for (var postKey in postData) {
-                            if (postKey) {
-                                // The linter requires 'for in' loops to be filtered by an if
-                                // condition.
-                                formData.append(postKey, postData[postKey]);
-                            }
-                        }
-                    }
-                    req.onload = function () {
-                        if (req.status === 200) {
-                            resolve(JSON.parse(req.responseText));
-                        }
-                        else {
-                            reject(new RequestNetworkError(req, url));
-                        }
-                    };
-                    req.onerror = function () {
-                        reject(new RequestNetworkError(req, url));
-                    };
-                    req.send(formData);
-                });
-            };
-            return RequestManager;
-        }());
-        Backend.RequestManager = RequestManager;
-    })(Backend = TF.Backend || (TF.Backend = {}));
-})(TF || (TF = {}));
-</script><script>/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-var TF;
-(function (TF) {
-    var Backend;
-    (function (Backend) {
-        Backend.BAD_CHARACTERS = '#%&{}\\/<>*? $!\'":@+`|=() ';
-        /** Cleanup a url so that it can be loaded from a filesystem. */
-        function demoify(s) {
-            // for consistency with python's urllib.urlencode
-            s = s.replace(new RegExp('%20', 'g'), '+');
-            for (var i = 0; i < Backend.BAD_CHARACTERS.length; i++) {
-                var c = Backend.BAD_CHARACTERS[i];
-                s = s.replace(new RegExp('\\' + c, 'g'), '_');
-            }
-            return s;
-        }
-        Backend.demoify = demoify;
-        function queryEncoder(params) {
-            // It's important that the keys be sorted, so we always grab the right file
-            // if we are talking to the backend generated by serialze_tensorboard.py
-            if (params == null) {
-                return '';
-            }
-            var components = _.keys(params)
-                .sort()
-                .filter(function (k) { return params[k] !== undefined; })
-                .map(function (k) { return k + '=' + encodeURIComponent(params[k]); });
-            var result = components.length ? '?' + components.join('&') : '';
-            // Replace parens for consistency with urllib.urlencode
-            return result.replace(/\(/g, '%28').replace(/\)/g, '%29');
-        }
-        Backend.queryEncoder = queryEncoder;
-    })(Backend = TF.Backend || (TF.Backend = {}));
-})(TF || (TF = {}));
-</script><script>/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-var TF;
-(function (TF) {
-    var Backend;
-    (function (Backend) {
-        ;
-        /**
-         * The standard router for communicating with the TensorBoard backend
-         * @param dataDir {string} The base prefix for finding data on server.
-         * @param demoMode {boolean} Whether to modify urls for filesystem demo usage.
-         */
-        function router(dataDir, demoMode) {
-            if (dataDir === void 0) { dataDir = 'data'; }
-            if (demoMode === void 0) { demoMode = false; }
-            var clean = demoMode ? Backend.demoify : function (x) { return x; };
-            if (dataDir[dataDir.length - 1] === '/') {
-                dataDir = dataDir.slice(0, dataDir.length - 1);
-            }
-            function standardRoute(route, demoExtension) {
-                if (demoExtension === void 0) { demoExtension = '.json'; }
-                return function (tag, run) {
-                    var url = dataDir + '/' + route + clean(Backend.queryEncoder({ tag: tag, run: run }));
-                    if (demoMode) {
-                        url += demoExtension;
-                    }
-                    return url;
-                };
-            }
-            function individualImageUrl(query, wallTime) {
-                var url = dataDir + '/' + clean('individualImage?' + query);
-                // Include wall_time just to disambiguate the URL and force the browser
-                // to reload the image when the URL changes. The backend doesn't care
-                // about the value.
-                url += demoMode ? '.png' : '&ts=' + wallTime;
-                return url;
-            }
-            function individualAudioUrl(query) {
-                var url = dataDir + '/' + clean('individualAudio?' + query);
-                if (demoMode) {
-                    url += '.wav';
-                }
-                return url;
-            }
-            function graphUrl(run, limit_attr_size, large_attrs_key) {
-                var query_params = [['run', clean(run)]];
-                if (limit_attr_size != null && !demoMode) {
-                    query_params.push(['limit_attr_size', String(limit_attr_size)]);
-                }
-                if (large_attrs_key != null && !demoMode) {
-                    query_params.push(['large_attrs_key', large_attrs_key]);
-                }
-                var query = query_params
-                    .map(function (param) {
-                    return param[0] + '=' + encodeURIComponent(param[1]);
-                })
-                    .join('&');
-                var url = dataDir + '/graph' + clean('?' + query);
-                if (demoMode) {
-                    url += '.pbtxt';
-                }
-                return url;
-            }
-            return {
-                logdir: function () { return dataDir + '/logdir'; },
-                runs: function () { return dataDir + '/runs' + (demoMode ? '.json' : ''); },
-                individualImage: individualImageUrl,
-                individualAudio: individualAudioUrl,
-                graph: graphUrl,
-                scalars: standardRoute('scalars'),
-                histograms: standardRoute('histograms'),
-                compressedHistograms: standardRoute('compressedHistograms'),
-                images: standardRoute('images'),
-                audio: standardRoute('audio'),
-                runMetadata: standardRoute('run_metadata', '.pbtxt'),
-                healthPills: function () { return dataDir + '/plugin/debugger/health_pills'; },
-                textRuns: function () { return dataDir + '/plugin/text/runs' + (demoMode ? '.json' : ''); },
-                text: standardRoute('plugin/text/text'),
-            };
-        }
-        Backend.router = router;
-        ;
-    })(Backend = TF.Backend || (TF.Backend = {}));
-})(TF || (TF = {}));
-</script><script>/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-var TF;
-(function (TF) {
-    var Backend;
-    (function (Backend_1) {
-        ;
-        ;
-        Backend_1.TYPES = [
-            'scalar', 'histogram', 'compressedHistogram', 'graph', 'image', 'audio',
-            'runMetadata', 'text'
-        ];
-        /**
-         * The Backend class provides a convenient and typed interface to the backend.
-         *
-         * It provides methods corresponding to the different data sources on the
-         * TensorBoard backend. These methods return a promise containing the data
-         * from the backend. This class does some post-processing on the data; for
-         * example, converting data elements tuples into js objects so that they can
-         * be accessed in a more convenient and clearly-documented fashion.
-         */
-        var Backend = (function () {
-            /**
-             * Construct a Backend instance.
-             * @param router the Router with info on what urls to get data from
-             * @param requestManager The RequestManager, overwritable so you may
-             * manually clear request queue, etc. Defaults to a new RequestManager.
-             */
-            function Backend(router, requestManager) {
-                this.router = router;
-                this.requestManager = requestManager || new Backend_1.RequestManager();
-            }
-            /**
-             * Returns a promise for requesting the logdir string.
-             */
-            Backend.prototype.logdir = function () {
-                return this.requestManager.request(this.router.logdir());
-            };
-            /**
-             * Returns a listing of all the available data in the TensorBoard backend.
-             */
-            Backend.prototype.runs = function () {
-                return this.requestManager.request(this.router.runs());
-            };
-            /**
-             * Return a promise showing the Run-to-Tag mapping for scalar data.
-             */
-            Backend.prototype.scalarRuns = function () {
-                return this.runs().then(function (x) { return _.mapValues(x, 'scalars'); });
-            };
-            /**
-             * Return a promise showing the Run-to-Tag mapping for histogram data.
-             */
-            Backend.prototype.histogramRuns = function () {
-                return this.runs().then(function (x) { return _.mapValues(x, 'histograms'); });
-            };
-            /**
-             * Return a promise showing the Run-to-Tag mapping for image data.
-             */
-            Backend.prototype.imageRuns = function () {
-                return this.runs().then(function (x) { return _.mapValues(x, 'images'); });
-            };
-            /**
-             * Return a promise showing the Run-to-Tag mapping for audio data.
-             */
-            Backend.prototype.audioRuns = function () {
-                return this.runs().then(function (x) { return _.mapValues(x, 'audio'); });
-            };
-            /**
-             * Return a promise showing the Run-to-Tag mapping for compressedHistogram
-             * data.
-             */
-            Backend.prototype.compressedHistogramRuns = function () {
-                return this.runs().then(function (x) { return _.mapValues(x, 'compressedHistograms'); });
-            };
-            /**
-             * Return a promise showing list of runs that contain graphs.
-             */
-            Backend.prototype.graphRuns = function () {
-                return this.runs().then(function (x) { return _.keys(x).filter(function (k) { return x[k].graph; }); });
-            };
-            /**
-             * Return a promise showing the Run-to-Tag mapping for run_metadata objects.
-             */
-            Backend.prototype.runMetadataRuns = function () {
-                return this.runs().then(function (x) { return _.mapValues(x, 'run_metadata'); });
-            };
-            /**
-             * Returns a promise showing the Run-to-Tag mapping for text data.
-             */
-            Backend.prototype.textRuns = function () {
-                return this.requestManager.request(this.router.textRuns());
-            };
-            /**
-             * Returns a promise containing TextDatums for given run and tag.
-             */
-            Backend.prototype.text = function (tag, run) {
-                var url = this.router.text(tag, run);
-                // tslint:disable-next-line:no-any it's convenient and harmless here
-                return this.requestManager.request(url).then(map(function (x) {
-                    x.wall_time = timeToDate(x.wall_time);
-                    return x;
-                }));
-            };
-            /**
-             * Return a promise of a graph string from the backend.
-             */
-            Backend.prototype.graph = function (tag, limit_attr_size, large_attrs_key) {
-                var url = this.router.graph(tag, limit_attr_size, large_attrs_key);
-                return this.requestManager.request(url);
-            };
-            /**
-             * Return a promise containing ScalarDatums for given run and tag.
-             */
-            Backend.prototype.scalar = function (tag, run) {
-                var p;
-                var url = this.router.scalars(tag, run);
-                p = this.requestManager.request(url);
-                return p.then(map(detupler(createScalar)));
-            };
-            /**
-             * Returns a promise for requesting the health pills for a list of nodes.
-             */
-            Backend.prototype.healthPills = function (nodeNames, step) {
-                var postData = { 'node_names': JSON.stringify(nodeNames) };
-                if (step !== undefined) {
-                    // The user requested health pills for a specific step. This request
-                    // might be slow since the backend reads events sequentially from disk.
-                    postData['step'] = step;
-                }
-                return this.requestManager.request(this.router.healthPills(), postData);
-            };
-            /**
-             * Return a promise containing HistogramDatums for given run and tag.
-             */
-            Backend.prototype.histogram = function (tag, run) {
-                var p;
-                var url = this.router.histograms(tag, run);
-                p = this.requestManager.request(url);
-                return p.then(map(detupler(createHistogram))).then(function (histos) {
-                    // Get the minimum and maximum values across all histograms so that the
-                    // visualization is aligned for all timesteps.
-                    var min = d3.min(histos, function (d) { return d.min; });
-                    var max = d3.max(histos, function (d) { return d.max; });
-                    return histos.map(function (histo, i) {
-                        return {
-                            wall_time: histo.wall_time,
-                            step: histo.step,
-                            bins: convertBins(histo, min, max)
-                        };
-                    });
-                });
-            };
-            /**
-             * Return a promise containing ImageDatums for given run and tag.
-             */
-            Backend.prototype.image = function (tag, run) {
-                var url = this.router.images(tag, run);
-                var p;
-                p = this.requestManager.request(url);
-                return p.then(map(this.createImage.bind(this)));
-            };
-            /**
-             * Return a promise containing AudioDatums for given run and tag.
-             */
-            Backend.prototype.audio = function (tag, run) {
-                var url = this.router.audio(tag, run);
-                var p;
-                p = this.requestManager.request(url);
-                return p.then(map(this.createAudio.bind(this)));
-            };
-            /**
-             * Returns a promise to load the string RunMetadata for given run/tag.
-             */
-            Backend.prototype.runMetadata = function (tag, run) {
-                var url = this.router.runMetadata(tag, run);
-                return this.requestManager.request(url);
-            };
-            /**
-             * Get compressedHistogram data.
-             * Unlike other methods, don't bother reprocessing this data into a nicer
-             * format. This is because we will deprecate this route.
-             */
-            Backend.prototype.compressedHistogram = function (tag, run) {
-                var url = this.router.compressedHistograms(tag, run);
-                var p;
-                p = this.requestManager.request(url);
-                return p.then(map(detupler(function (x) { return x; })));
-            };
-            Backend.prototype.createImage = function (x) {
-                return {
-                    width: x.width,
-                    height: x.height,
-                    wall_time: timeToDate(x.wall_time),
-                    step: x.step,
-                    url: this.router.individualImage(x.query, x.wall_time),
-                };
-            };
-            Backend.prototype.createAudio = function (x) {
-                return {
-                    content_type: x.content_type,
-                    wall_time: timeToDate(x.wall_time),
-                    step: x.step,
-                    url: this.router.individualAudio(x.query),
-                };
-            };
-            return Backend;
-        }());
-        Backend_1.Backend = Backend;
-        /** Given a RunToTag, return sorted array of all runs */
-        function getRuns(r) {
-            return _.keys(r).sort(VZ.Sorting.compareTagNames);
-        }
-        Backend_1.getRuns = getRuns;
-        /** Given a RunToTag, return array of all tags (sorted + dedup'd) */
-        function getTags(r) {
-            return _.union.apply(null, _.values(r)).sort(VZ.Sorting.compareTagNames);
-        }
-        Backend_1.getTags = getTags;
-        /**
-         * Given a RunToTag and an array of runs, return every tag that appears for
-         * at least one run.
-         * Sorted, deduplicated.
-         */
-        function filterTags(r, runs) {
-            var result = [];
-            runs.forEach(function (x) { return result = result.concat(r[x]); });
-            return _.uniq(result).sort(VZ.Sorting.compareTagNames);
-        }
-        Backend_1.filterTags = filterTags;
-        function timeToDate(x) { return new Date(x * 1000); }
-        ;
-        /**  Just a curryable map to make things cute and tidy. */
-        function map(f) {
-            return function (arr) { return arr.map(f); };
-        }
-        ;
-        /**
-         * This is a higher order function that takes a function that transforms a
-         * T into a G, and returns a function that takes TupleData<T>s and converts
-         * them into the intersection of a G and a Datum.
-         */
-        function detupler(xform) {
-            return function (x) {
-                // Create a G, assert it has type <G & Datum>
-                var obj = xform(x[2]);
-                // ... patch in the properties of datum
-                obj.wall_time = timeToDate(x[0]);
-                obj.step = x[1];
-                return obj;
-            };
-        }
-        ;
-        function createScalar(x) { return { scalar: x }; }
-        ;
-        function createHistogram(x) {
-            return {
-                min: x[0],
-                max: x[1],
-                nItems: x[2],
-                sum: x[3],
-                sumSquares: x[4],
-                bucketRightEdges: x[5],
-                bucketCounts: x[6],
-            };
-        }
-        ;
-        /**
-         * Takes histogram data as stored by tensorboard backend and converts it to
-         * the standard d3 histogram data format to make it more compatible and easier
-         * to visualize. When visualizing histograms, having the left edge and width
-         * makes things quite a bit easier. The bins are also converted to have an
-         * uniform width, what makes the visualization easier to understand.
-         *
-         * @param histogram A histogram from tensorboard backend.
-         * @param min The leftmost edge. The binning will start on it.
-         * @param max The rightmost edge. The binning will end on it.
-         * @param numBins The number of bins of the converted data. The default of 30
-         * is a sensible default, using more starts to get artifacts because the event
-         * data is stored in buckets, and you start being able to see the aliased
-         * borders between each bucket.
-         * @return A histogram bin. Each bin has an x (left edge), a dx (width),
-         *     and a y (count).
-         *
-         * If given rightedges are inclusive, then these left edges (x) are exclusive.
-         */
-        function convertBins(histogram, min, max, numBins) {
-            if (numBins === void 0) { numBins = 30; }
-            if (histogram.bucketRightEdges.length !== histogram.bucketCounts.length) {
-                throw (new Error('Edges and counts are of different lengths.'));
-            }
-            if (max === min) {
-                // Create bins even if all the data has a single value.
-                max = min * 1.1 + 1;
-                min = min / 1.1 - 1;
-            }
-            var binWidth = (max - min) / numBins;
-            var bucketLeft = min; // Use the min as the starting point for the bins.
-            var bucketPos = 0;
-            return d3.range(min, max, binWidth).map(function (binLeft) {
-                var binRight = binLeft + binWidth;
-                // Take the count of each existing bucket, multiply it by the proportion
-                // of overlap with the new bin, then sum and store as the count for the
-                // new bin. If no overlap, will add to zero, if 100% overlap, will include
-                // the full count into new bin.
-                var binY = 0;
-                while (bucketPos < histogram.bucketRightEdges.length) {
-                    // Clip the right edge because right-most edge can be infinite-sized.
-                    var bucketRight = Math.min(max, histogram.bucketRightEdges[bucketPos]);
-                    var intersect = Math.min(bucketRight, binRight) - Math.max(bucketLeft, binLeft);
-                    var count = (intersect / (bucketRight - bucketLeft)) *
-                        histogram.bucketCounts[bucketPos];
-                    binY += intersect > 0 ? count : 0;
-                    // If bucketRight is bigger than binRight, than this bin is finished and
-                    // there is data for the next bin, so don't increment bucketPos.
-                    if (bucketRight > binRight) {
-                        break;
-                    }
-                    bucketLeft = Math.max(min, bucketRight);
-                    bucketPos++;
-                }
-                ;
-                return { x: binLeft, dx: binWidth, y: binY };
-            });
-        }
-        Backend_1.convertBins = convertBins;
-    })(Backend = TF.Backend || (TF.Backend = {}));
-})(TF || (TF = {}));
-</script><script>/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-var TF;
-(function (TF) {
-    var Backend;
-    (function (Backend) {
-        Backend.Behavior = {
-            properties: {
-                /** *** Required properties *** */
-                /** Data type. One of TF.Backend.TYPES */
-                dataType: {
-                    type: String,
-                    observer: '_throwErrorOnUnrecognizedType',
-                },
-                /** TF.Backend.Backend for data loading. */
-                backend: {
-                    type: Object,
-                },
-                /** Should it automatically load when configured ready? Default true. */
-                autoLoad: {
-                    type: Boolean,
-                    value: true,
-                },
-                /** *** Component-provided properties *** */
-                /** Every tag available for data type (sorted, dedpulicated) */
-                tags: {
-                    type: Array,
-                    readOnly: true,
-                    notify: true,
-                },
-                /** Every run available for data type (sorted) */
-                runs: {
-                    type: Array,
-                    readOnly: true,
-                    notify: true,
-                },
-                /** Mapping from runs to tags for the data type */
-                run2tag: {
-                    type: Object,
-                    readOnly: true,
-                    notify: true,
-                },
-                /** Promise provider for the data. Useful for passing to subcomponents */
-                dataProvider: { type: Function, computed: '_getDataProvider(dataType, backend)' },
-                /** Has the dashboard loaded yet? */
-                loadState: {
-                    type: String,
-                    value: 'noload',
-                    readOnly: true,
-                },
-                /**
-                 * True if dashboard has loaded, and no tags were found.
-                 * Persists through subsequent reloads (ie. still true while
-                 * next load is pending) so warning won't flash away every reload
-                 * when there is no data.
-                 */
-                dataNotFound: {
-                    type: Boolean,
-                    value: false,
-                    readOnly: true,
-                }
-            },
-            observers: ['_do_autoLoad(dataType, backend, autoLoad)'],
-            /**
-             * Reloading works in two steps:
-             * Backend reload, which gets metadata on available runs, tags, etc from
-             *   the backend.
-             * Frontend reload, which loads new data for each chart or visual display.
-             * Backend reload logic is provided by this behaivor. The frontend reload
-             *   logic should be provided elsewhere, since it is component-specific.
-             * To keep things simple and consistent, we do the backend reload first,
-             *   and the frontend reload afterwards.
-             */
-            reload: function () {
-                var _this = this;
-                return this.backendReload().then(function (x) { return _this.frontendReload(); });
-            },
-            /**
-             * Load data from backend and then set run2tag, tags, runs, and loadState.
-             * Returns a promise that resolves/rejects when data is loaded.
-             */
-            backendReload: function () {
-                var _this = this;
-                if (this.dataType == null) {
-                    throw new Error('TF.Backend.Behavior: Need a dataType to reload.');
-                }
-                if (this.backend == null) {
-                    throw new Error('TF.Backend.Behavior: Need a backend to reload.');
-                }
-                var runsRoute = this.backend[this.dataType + 'Runs'].bind(this.backend);
-                this._setLoadState('pending');
-                return runsRoute().then(function (x) {
-                    _this._setLoadState('loaded');
-                    if (_.isEqual(x, _this.run2tag)) {
-                        // If x and run2tag are equal, let's avoid updating everything
-                        // since that can needlessly trigger run changes, reloads, etc
-                        return x;
-                    }
-                    _this._setRun2tag(x);
-                    var tags = TF.Backend.getTags(x);
-                    _this._setDataNotFound(tags.length === 0);
-                    _this._setTags(tags);
-                    _this._setRuns(TF.Backend.getRuns(x));
-                    return x;
-                }, function (fail) {
-                    _this._setLoadState('failure');
-                    return fail;
-                });
-            },
-            _do_autoLoad: function (type, backend, autoLoad) {
-                if (autoLoad) {
-                    this.reload();
-                }
-                ;
-            },
-            _getDataProvider: function (dataType, backend) {
-                return this.backend[this.dataType].bind(this.backend);
-            },
-            _throwErrorOnUnrecognizedType: function (dataType) {
-                if (TF.Backend.TYPES.indexOf(dataType) === -1) {
-                    throw new Error('TF.Backend.Behavior: Unknown dataType ' + dataType);
-                }
-            },
-        };
-    })(Backend = TF.Backend || (TF.Backend = {}));
-})(TF || (TF = {}));
-</script>
-
-
-
-
-
-<dom-module id="tf-color-scale" assetpath="../tf-color-scale/">
-  <script>/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-var TF;
-(function (TF) {
-    TF.palettes = {
-        googleStandard: [
-            '#db4437',
-            '#ff7043',
-            '#f4b400',
-            '#0f9d58',
-            '#00796b',
-            '#00acc1',
-            '#4285f4',
-            '#5c6bc0',
-            '#ab47bc' // purple 400
-        ],
-        googleCool: [
-            '#9e9d24',
-            '#0f9d58',
-            '#00796b',
-            '#00acc1',
-            '#4285f4',
-            '#5c6bc0',
-            '#607d8b' // blue gray 500
-        ],
-        googleWarm: [
-            '#795548',
-            '#ab47bc',
-            '#f06292',
-            '#c2185b',
-            '#db4437',
-            '#ff7043',
-            '#f4b400' // google yellow 700
-        ],
-        googleColorBlindAssist: [
-            '#ff7043',
-            '#00ACC1',
-            '#AB47BC',
-            '#2A56C6',
-            '#0b8043',
-            '#F7CB4D',
-            '#c0ca33',
-            '#5e35b1',
-            '#A52714',
-        ],
-        // These palettes try to be better for color differentiation.
-        // https://personal.sron.nl/~pault/
-        colorBlindAssist1: ['#4477aa', '#44aaaa', '#aaaa44', '#aa7744', '#aa4455', '#aa4488'],
-        colorBlindAssist2: [
-            '#88ccee', '#44aa99', '#117733', '#999933', '#ddcc77', '#cc6677',
-            '#882255', '#aa4499'
-        ],
-        colorBlindAssist3: [
-            '#332288', '#6699cc', '#88ccee', '#44aa99', '#117733', '#999933',
-            '#ddcc77', '#cc6677', '#aa4466', '#882255', '#661100', '#aa4499'
-        ],
-        // based on this palette: http://mkweb.bcgsc.ca/biovis2012/
-        colorBlindAssist4: [
-            '#FF6DB6', '#920000', '#924900', '#DBD100', '#24FF24', '#006DDB',
-            '#490092'
-        ],
-        mldash: [
-            '#E47EAD', '#F4640D', '#FAA300', '#F5E636', '#00A077', '#0077B8',
-            '#00B7ED'
-        ]
-    };
-})(TF || (TF = {}));
-</script>
-  <script>/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-// Example usage:
-// runs = ["train", "test", "test1", "test2"]
-// ccs = new TF.ColorScale();
-// ccs.domain(runs);
-// ccs.getColor("train");
-// ccs.getColor("test1");
-var TF;
-(function (TF) {
-    var ColorScale = (function () {
-        /**
-         * Creates a color scale with optional custom palette.
-         *  @param {string[]} [palette=TF.palettes.googleColorBlind] - The color
-         *                 palette you want as an Array of hex strings.
-         */
-        function ColorScale(palette) {
-            if (palette === void 0) { palette = TF.palettes.googleColorBlindAssist; }
-            this.identifiers = d3.map();
-            this.palette = palette;
-        }
-        /**
-         * Set the domain of strings.
-         * @param {string[]} strings - An array of possible strings to use as the
-         *                             domain for your scale.
-         */
-        ColorScale.prototype.domain = function (strings) {
-            var _this = this;
-            this.identifiers = d3.map();
-            strings.forEach(function (s, i) {
-                _this.identifiers.set(s, _this.palette[i % _this.palette.length]);
-            });
-            return this;
-        };
-        /**
-         * Use the color scale to transform an element in the domain into a color.
-         * @param {string} The input string to map to a color.
-         * @return {string} The color corresponding to that input string.
-         * @throws Will error if input string is not in the scale's domain.
-         */
-        ColorScale.prototype.scale = function (s) {
-            if (!this.identifiers.has(s)) {
-                throw new Error('String was not in the domain.');
-            }
-            return this.identifiers.get(s);
-        };
-        return ColorScale;
-    }());
-    TF.ColorScale = ColorScale;
-})(TF || (TF = {}));
-</script>
-  <script>
-    (function() {
-      Polymer({
-        is: "tf-color-scale",
-        properties: {
-          runs: {
-            type: Array,
-          },
-          outColorScale: {
-            type: Object,
-            readOnly: true,
-            notify: true,
-            value: function() {
-              return new TF.ColorScale();
-            },
-          },
-        },
-        observers: ['updateColorScale(runs.*)'],
-        updateColorScale: function(runsChange) {
-          this.outColorScale.domain(this.runs);
-        },
-      });
-    })();
-  </script>
-</dom-module>
-<link rel="import" href="../paper-styles/paper-styles.html">
-
-<dom-module id="scrollbar-style" assetpath="../tf-dashboard-common/">
-  <template>
-    <style>
-      .scrollbar::-webkit-scrollbar-track
-      {
-        visibility: hidden;
-      }
-
-      .scrollbar::-webkit-scrollbar
-      {
-        width: 10px;
-      }
-
-      .scrollbar::-webkit-scrollbar-thumb
-      {
-        border-radius: 10px;
-        -webkit-box-shadow: inset 0 0 2px rgba(0,0,0,.3);
-        background-color: var(--paper-grey-500);
-        color: var(--paper-grey-900);
-      }
-      .scrollbar {
-        box-sizing: border-box;
-      }
-    </style>
-  </template>
-</dom-module>
-<style is="custom-style">
-
-  :root {
-    --tb-orange-weak: #ffa726;
-    --tb-orange-strong: #f57c00;
-    --tb-grey-darker: #e2e2e2;
-    --tb-grey-lighter: #f3f3f3;
-    --tb-ui-dark-accent: #757575;
-    --tb-ui-light-accent: #e0e0e0;
-    --tb-graph-faded: #e0d4b3;
-  }
-
-</style>
-
-<dom-module id="tf-dashboard-layout" assetpath="../tf-dashboard-common/">
-  <template>
-    <div id="sidebar">
-      <content select=".sidebar"></content>
-    </div>
-
-    <div id="center" class="scrollbar">
-      <content select=".center"></content>
-    </div>
-    <style include="scrollbar-style"></style>
-    <style>
-      #sidebar {
-        width: inherit;
-        height: 100%;
-        overflow: ellipsis;
-        flex-grow: 0;
-        flex-shrink: 0;
-      }
-
-      #center {
-        height: 100%;
-        overflow-y: auto;
-        flex-grow: 1;
-        flex-shrink: 1;
-      }
-
-      .tf-graph-dashboard #center {
-        background: white;
-      }
-
-      :host {
-        display: flex;
-        flex-direction: row;
-        height: 100%;
-      }
-    </style>
-  </template>
-  <script>
-    Polymer({
-      is: "tf-dashboard-layout",
-    });
-  </script>
-</dom-module>
-<dom-module id="dashboard-style" assetpath="../tf-dashboard-common/">
-  <template>
-    <style>
-      .sidebar {
-        display: flex;
-        flex-direction: column;
-        height: 100%;
-        margin-right: 20px;
-      }
-
-      .sidebar-section {
-        border-top: solid 1px rgba(0, 0, 0, 0.12);
-        padding: 15px 0px 15px 30px;
-      }
-
-      .sidebar-section:first-child {
-        border: none;
-      }
-
-      .sidebar-section:last-child {
-        flex-grow: 1;
-        display: flex;
-      }
-
-      paper-checkbox {
-        --paper-checkbox-checked-color: var(--tb-ui-dark-accent);
-        --paper-checkbox-unchecked-color: var(--tb-ui-dark-accent);
-        font-size: 14px;
-        margin-top: 5px;
-      }
-    </style>
-  </template>
-</dom-module>
-<link rel="import" href="../paper-dropdown-menu/paper-dropdown-menu.html">
-<link rel="import" href="../paper-menu/paper-menu.html">
-<link rel="import" href="../paper-item/paper-item.html">
-
-<dom-module id="tf-downloader" assetpath="../tf-dashboard-common/">
-  <template>
-    <paper-dropdown-menu no-label-float="true" label="run to download" selected-item-label="{{_run}}">
-      <paper-menu class="dropdown-content">
-        <template is="dom-repeat" items="[[runs]]">
-          <paper-item no-label-float="true">[[item]]</paper-item>
-        </template>
-      </paper-menu>
-    </paper-dropdown-menu>
-    <div class="center">
-      <span>
-        <a download="[[_csvName(_run)]]" href="[[_csvUrl(_run, urlFn)]]">CSV</a>
-        <a download="[[_jsonName(_run)]]" href="[[_jsonUrl(_run, urlFn)]]">JSON</a>
-      </span>
-    </div>
-    <style>
-      :host {
-        display: flex;
-        height: 32px;
-      }
-      .center {
-        display: flex;
-        align-self: center;
-      }
-      paper-dropdown-menu {
-        width: 100px;
-        --paper-input-container-label: {
-          font-size: 10px;
-        }
-        --paper-input-container-input: {
-          font-size: 10px;
-        }
-      }
-      a {
-        font-size: 10px;
-        border-radius: 3px;
-        border: 1px solid #EEE;
-      }
-      paper-input {
-        font-size: 22px;
-      }
-    </style>
-  </template>
-  <script>
-    Polymer({
-      is: "tf-downloader",
-      properties: {
-        _run: String,
-        runs: Array,
-        tag: String,
-        urlFn: Function,
-      },
-      _csvUrl: function(_run, urlFn) {
-        return urlFn(this.tag, _run) + "&format=csv";
-      },
-      _jsonUrl: function(_run, urlFn) {
-        return urlFn(this.tag, _run);
-      },
-      _csvName: function(_run) {
-        return "run_" + _run + ",tag_" + this.tag + ".csv";
-      },
-      _jsonName: function(_run) {
-        return "run-" + _run + "-tag-" + this.tag + ".json";
-      },
-    });
-  </script>
-</dom-module>
-
-<dom-module id="tf-no-data-warning" assetpath="../tf-dashboard-common/">
-  <template>
-    <template is="dom-if" if="[[showWarning]]">
-      <div class="warning">
-        <template is="dom-if" if="[[_isGraph(dataType)]]">
-          <h3>
-            No graph definition files were found.
-          </h3>
-          <p>
-            To store a graph, create a
-            <code>tf.summary.FileWriter</code>
-            and pass the graph either via the constructor, or by calling its
-            <code>add_graph()</code> method.
-            You may want to check out the
-            <a href="https://www.tensorflow.org/get_started/graph_viz">
-              graph visualizer tutorial
-            </a>.
-          </p>
-        </template>
-        <template is="dom-if" if="[[_isProjector(dataType)]]">
-          <h3>
-            No checkpoint was found.
-          </h3>
-          <p>
-            Probable causes:
-            </p><ul>
-              <li>
-                No checkpoint has been saved yet. Please refresh the page periodically.
-              </li>
-              <li>
-                You are not saving any checkpoint. To save your model,
-                create a
-                <a href="https://www.tensorflow.org/api_docs/python/tf/train/Saver">
-                  <code>tf.train.Saver</code>
-                </a>
-                and save your model periodically
-                by calling <code>saver.save(session, LOG_DIR/model.ckpt, step)</code>.
-              </li>
-            </ul>
-          <p></p>
-        </template>
-        <template is="dom-if" if="[[_isOther(dataType)]]">
-          <h3>
-            No <span>[[dataType]]</span> data was found.
-          </h3>
-          <p>
-            Probable causes:
-            </p><ul>
-              <li>
-                You haven't written any <span>[[dataType]]</span> data
-                to your event files.
-              </li>
-              <li>
-                TensorBoard can't find your event files.
-              </li>
-            </ul>
-          <p></p>
-        </template>
-        <p>
-          If you're new to using TensorBoard, and want to find out how to add
-          data and set up your event files, check out the
-          <a href="https://github.com/tensorflow/tensorflow/blob/master/tensorflow/tensorboard/README.md">
-            README
-          </a>
-          and perhaps the
-          <a href="https://www.tensorflow.org/get_started/summaries_and_tensorboard">
-            TensorBoard tutorial
-          </a>.
-        </p>
-
-        <p>
-          If you think TensorBoard is configured properly, please see the
-          <a href="https://github.com/tensorflow/tensorflow/blob/master/tensorflow/tensorboard/README.md#my-tensorboard-isnt-showing-any-data-whats-wrong">
-            section of the README devoted to missing data problems
-          </a>
-          and consider filing an issue on GitHub.
-        </p>
-
-      </div>
-    </template>
-    <style>
-      .warning {
-        max-width: 540px;
-        margin: 80px auto 0 auto;
-      }
-    </style>
-  </template>
-
-  <script>
-    Polymer({
-      is: "tf-no-data-warning",
-      properties: {
-        dataType: String,
-        showWarning: Boolean
-      },
-      _isGraph: function(dataType) {
-        return dataType === "graph";
-      },
-      _isProjector: function(dataType) {
-        return dataType === "projector";
-      },
-      _isOther: function(dataType) {
-        return !this._isGraph(dataType) && !this._isProjector(dataType);
-      }
-    });
-  </script>
-</dom-module>
-<script>/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-var TF;
-(function (TF) {
-    var Dashboard;
-    (function (Dashboard) {
-        /**
-         * A behavior that TensorBoard dashboards must implement. This behavior serves
-         * the purpose of an interface.
-         */
-        function DashboardBehavior(dashboardName) {
-            return {
-                properties: {
-                    name: {
-                        type: String,
-                        value: dashboardName,
-                        readOnly: true,
-                    },
-                },
-                // This method is called when the dashboard reloads, either when the
-                // dashboard is first visited, periodically reloaded, or manually reloaded
-                // via the user clicking the button. Note that dashboard custom elements
-                // that use TF.Dashboard.ReloadBehavior already implement a reload method.
-                reload: function () {
-                    throw Error('The ' + dashboardName + ' dashboard does not implement reload.');
-                },
-            };
-        }
-        Dashboard.DashboardBehavior = DashboardBehavior;
-    })(Dashboard = TF.Dashboard || (TF.Dashboard = {}));
-})(TF || (TF = {}));
-</script><script>/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-var TF;
-(function (TF) {
-    var Dashboard;
-    (function (Dashboard) {
-        /**
-         * ReloadBehavior: A simple behavior for dashboards where the
-         * frontendReload() function should find every child element with a
-         * given tag name (e.g. "tf-line-chart" or "tf-image-loader")
-         * and call a `reload` method on that child.
-         * May later extend it so it has more sophisticated logic, e.g. reloading
-         * only tags that are in view.
-         */
-        function ReloadBehavior(tagName) {
-            return {
-                properties: {
-                    reloadTag: {
-                        type: String,
-                        value: tagName,
-                    },
-                },
-                frontendReload: function () {
-                    var elements = this.getElementsByTagName(this.reloadTag);
-                    Array.prototype.forEach.call(elements, function (x) { x.reload(); });
-                },
-            };
-        }
-        Dashboard.ReloadBehavior = ReloadBehavior;
-    })(Dashboard = TF.Dashboard || (TF.Dashboard = {}));
-})(TF || (TF = {}));
-</script>
-
-
-<dom-module id="tf-option-selector" assetpath="../tf-dashboard-common/">
-  <template>
-    <div id="wrap">
-      <h3>[[name]]</h3>
-      <div class="content-wrapper"><content></content></div>
-    </div>
-    <style>
-      .content-wrapper ::content > * {
-        width: 30%;
-        font-size: 13px;
-        background: none;
-        margin-top: 10px;
-        color: var(--tb-ui-dark-accent);
-      }
-
-      .content-wrapper ::content :first-of-type {
-        margin-left: 0;
-      }
-
-      .content-wrapper ::content .selected {
-        background-color: var(--tb-ui-dark-accent);
-        color: white!important;
-      }
-
-      h3 {
-        color: var(--paper-grey-800);
-        margin: 0;
-        font-weight: normal;
-        font-size: 14px;
-        margin-bottom: 5px;
-        display: block;
-        pointer-events: none;
-      }
-    </style>
-  </template>
-  <script>
-    Polymer({
-      is: "tf-option-selector",
-      properties: {
-        name: String,
-        selectedId: {
-          type: String,
-          notify: true,
-          observer: '_selectedIdChanged'
-        }
-      },
-      attached: function() {
-        this.async(function() {
-          this.getEffectiveChildren().forEach(function(node) {
-            this.listen(node, 'tap', '_selectTarget');
-          }.bind(this));
-        });
-      },
-      _selectTarget: function(e) {
-        this.selectedId = e.currentTarget.id;
-      },
-      _selectedIdChanged: function() {
-        var selected = this.queryEffectiveChildren('#' + this.selectedId);
-        if (!selected) {
-          return;
-        }
-
-        this.getEffectiveChildren().forEach(function(node) {
-          node.classList.remove("selected");
-        });
-        selected.classList.add("selected");
-      }
-    });
-  </script>
-</dom-module>
-<link rel="import" href="../iron-collapse/iron-collapse.html">
-
-<dom-module id="tf-collapsable-pane" assetpath="../tf-dashboard-common/">
-  <template>
-    <button class="heading" on-tap="togglePane" open-button$="[[opened]]">
-    <span class="name">[[name]]</span>
-    <span class="count">
-      <span>[[count]]</span>
-    </span>
-  </button>
-    <iron-collapse opened="[[opened]]">
-      <div class="content">
-        <template is="dom-if" if="[[opened]]" restamp="[[restamp]]">
-          <content></content>
-        </template>
-      </div>
-    </iron-collapse>
-    <style>
-      :host {
-        display: block;
-        margin: 0 5px 1px 10px;
-      }
-
-      :host:first-of-type {
-        margin-top: 20px;
-      }
-      
-      :host:last-of-type {
-        margin-bottom: 20px;
-      }
-
-      .heading {
-        background-color: white;
-        border: none;
-        cursor: pointer;
-        width: 100%;
-        font-size: 15px;
-        line-height: 1;
-        box-shadow: 0 1px 5px rgba(0,0,0,0.2);
-        padding: 10px 15px;
-      }
-
-      .content {
-        padding: 15px;
-        border: 1px solid #dedede;
-        border-top: none;
-        border-bottom-left-radius: 2px;
-        border-bottom-right-radius: 2px;
-        background: white;
-      }
-
-      [open-button] {
-        border-bottom-left-radius: 0px !important;
-        border-bottom-right-radius: 0px !important;
-      }
-
-      .name {
-        float: left;
-      }
-
-      .count {
-        float: right;
-        margin-right: 5px;
-        font-size: 12px;
-        color: var(--paper-grey-500);
-      }
-    </style>
-  </template>
-  <script>
-    Polymer({
-      is: "tf-collapsable-pane",
-      properties: {
-        opened: {type: Boolean, value: false},
-        restamp: {type: Boolean, value: true},
-        name: {type: String, observer: "hide"},
-        count: {type: Number},
-      },
-      hide: function() {
-        this.opened = false;
-      },
-      togglePane: function() {
-        this.opened = !this.opened;
-      }
-    });
-  </script>
-
-</dom-module>
-<script src="../plottable/plottable.js"></script>
-<style>
-/**
- * @license
- * The MIT License (MIT)
- *
- * Copyright (c) 2014-2015 Palantir Technologies, Inc.
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- * Plottable 1.16.1 (https://github.com/palantir/plottable)
- * Copyright 2014-2015 Palantir Technologies
- * Licensed under MIT (https://github.com/palantir/plottable/blob/master/LICENSE)
-*/
-.plottable-colors-0 {
-  background-color: #5279c7; /* INDIGO */
-}
-
-.plottable-colors-1 {
-  background-color: #fd373e; /* CORAL_RED */
-}
-
-.plottable-colors-2 {
-  background-color: #63c261; /* FERN */
-}
-
-.plottable-colors-3 {
-  background-color: #fad419; /* BRIGHT_SUN */
-}
-
-.plottable-colors-4 {
-  background-color: #2c2b6f; /* JACARTA */
-}
-
-.plottable-colors-5 {
-  background-color: #ff7939; /* BURNING_ORANGE */
-}
-
-.plottable-colors-6 {
-  background-color: #db2e65; /* CERISE_RED */
-}
-
-.plottable-colors-7 {
-  background-color: #99ce50; /* CONIFER */
-}
-
-.plottable-colors-8 {
-  background-color: #962565; /* ROYAL_HEATH */
-}
-
-.plottable-colors-9 {
-  background-color: #06cccc; /* ROBINS_EGG_BLUE */
-}
-
-svg.plottable {
-  display : block; /* SVGs must be block elements for width/height calculations to work in Firefox. */
-  pointer-events: visibleFill;
-}
-
-.plottable .background-fill {
-  fill: none;
-  pointer-events: none;
-}
-
-.plottable .bounding-box {
-  /* Invisible pink bounding-box to allow for collision testing */
-  fill: pink;
-  visibility: hidden;
-}
-
-.plottable .label text {
-  font-family: "Helvetica Neue", sans-serif;
-  fill: #32313F;
-}
-
-.plottable .bar-label-text-area text {
-  font-family: "Helvetica Neue", sans-serif;
-  font-size: 14px;
-}
-
-.plottable .label-area text {
-  fill: #32313F;
-  font-family: "Helvetica Neue", sans-serif;
-  font-size: 14px;
-}
-
-.plottable .light-label text {
-  fill: white;
-}
-
-.plottable .dark-label text {
-  fill: #32313F;
-}
-
-.plottable .off-bar-label text {
-  fill: #32313F;
-}
-
-.plottable .stacked-bar-plot .off-bar-label {
-  /* HACKHACK #2795: correct off-bar label logic to be implemented on StackedBar */
-  visibility: hidden !important;
-}
-
-.plottable .axis-label text {
-  font-size: 10px;
-  font-weight: bold;
-  letter-spacing: 1px;
-  line-height: normal;
-  text-transform: uppercase;
-}
-
-.plottable .title-label text {
-  font-size: 20px;
-  font-weight: bold;
-}
-
-.plottable .axis line.baseline {
-  stroke: #CCC;
-  stroke-width: 1px;
-}
-
-.plottable .axis line.tick-mark {
-  stroke: #CCC;
-  stroke-width: 1px;
-}
-
-.plottable .axis text {
-  fill: #32313F;
-  font-family: "Helvetica Neue", sans-serif;
-  font-size: 12px;
-  font-weight: 200;
-  line-height: normal;
-}
-
-.plottable .axis .annotation-circle {
-  fill: white;
-  stroke-width: 1px;
-  stroke: #CCC;
-}
-
-.plottable .axis .annotation-line {
-  stroke: #CCC;
-  stroke-width: 1px;
-}
-
-.plottable .axis .annotation-rect {
-  stroke: #CCC;
-  stroke-width: 1px;
-  fill: white;
-}
-
-.plottable .bar-plot .baseline {
-  stroke: #999;
-}
-
-.plottable .gridlines line {
-  stroke: #3C3C3C; /* hackhack: gridlines should be solid; see #820 */
-  opacity: 0.25;
-  stroke-width: 1px;
-}
-
-.plottable .selection-box-layer .selection-area {
-  fill: black;
-  fill-opacity: 0.03;
-  stroke: #CCC;
-}
-/* DragBoxLayer */
-.plottable .drag-box-layer.x-resizable .drag-edge-lr {
-  cursor: ew-resize;
-}
-.plottable .drag-box-layer.y-resizable .drag-edge-tb {
-  cursor: ns-resize;
-}
-
-.plottable .drag-box-layer.x-resizable.y-resizable .drag-corner-tl {
-  cursor: nwse-resize;
-}
-.plottable .drag-box-layer.x-resizable.y-resizable .drag-corner-tr {
-  cursor: nesw-resize;
-}
-.plottable .drag-box-layer.x-resizable.y-resizable .drag-corner-bl {
-  cursor: nesw-resize;
-}
-.plottable .drag-box-layer.x-resizable.y-resizable .drag-corner-br {
-  cursor: nwse-resize;
-}
-
-.plottable .drag-box-layer.movable .selection-area {
-  cursor: move; /* IE fallback */
-  cursor: -moz-grab;
-  cursor: -webkit-grab;
-  cursor: grab;
-}
-
-.plottable .drag-box-layer.movable .selection-area:active {
-  cursor: -moz-grabbing;
-  cursor: -webkit-grabbing;
-  cursor: grabbing;
-}
-/* /DragBoxLayer */
-
-.plottable .guide-line-layer line.guide-line {
-  stroke: #CCC;
-  stroke-width: 1px;
-}
-
-.plottable .drag-line-layer.enabled.vertical line.drag-edge {
-  cursor: ew-resize;
-}
-
-.plottable .drag-line-layer.enabled.horizontal line.drag-edge {
-  cursor: ns-resize;
-}
-
-.plottable .legend text {
-  fill: #32313F;
-  font-family: "Helvetica Neue", sans-serif;
-  font-size: 12px;
-  font-weight: bold;
-  line-height: normal;
-}
-
-.plottable .interpolated-color-legend rect.swatch-bounding-box {
-  fill: none;
-  stroke: #CCC;
-  stroke-width: 1px;
-  pointer-events: none;
-}
-
-.plottable .waterfall-plot line.connector {
-  stroke: #CCC;
-  stroke-width: 1px;
-}
-
-.plottable .pie-plot .arc.outline {
-  stroke-linejoin: round;
-}
-
-</style>
-
-<dom-module id="tf-chart-scaffold" assetpath="../tf-dashboard-common/">
-  <template>
-    <content></content>
-    <style>
-      :host {
-        -webkit-user-select: none;
-        -moz-user-select: none;
-        display: flex;
-        flex-direction: column;
-        flex-grow: 1;
-        flex-shrink: 1;
-        position: relative;
-      }
-    </style>
-  </template>
-  <script>
-    Polymer({
-      is: "tf-chart-scaffold",
-      properties: {
-        tag: String,
-        dataProvider: Function,
-        visibleSeries: Array,
-        _attached: {
-          type: Boolean,
-          value: false
-        },
-
-        // Storing the update ID of the previous request for data enables us to determine if a
-        // data response is outdated. We rely on an increasing ID instead of timestamp because
-        // successive updates often fire within the same millisecond.
-        _dataUpdateIdOfLastRequest: Number,
-        _nextAvailableDataUpdateId: {
-          type: Number,
-          value: 1,
-        },
-      },
-      observers: [
-        "reload(tag, dataProvider)",
-        "_changeSeries(visibleSeries.*)"
-      ],
-      ready: function() {
-        this.fire('ready');
-      },
-      attached: function() {
-        this._attached = true;
-        this._changeSeries();
-      },
-      detached: function() {
-        this._attached = false;
-      },
-      reload: function() {
-        if (!this._attached) {
-          return;
-        }
-        else if (!this.dataProvider) {
-          throw new Error('tf-chart-scaffold requires a dataProvider.');
-        }
-        else if (!this.tag) {
-          throw new Error('tf-chart-scaffold requires a tag.');
-        }
-
-        // TODO(chizeng): At this point, notify effective children that the previous data has been
-        // invalidated. For instance, the image dashboard may want to clear its images. Today, the
-        // chart scaffold only informs children when the new image URLs response finishes loading.
-
-        const dataUpdateId = this._nextAvailableDataUpdateId++;
-        this._dataUpdateIdOfLastRequest = dataUpdateId;
-
-        this.visibleSeries.forEach(function(name) {
-          this.dataProvider(this.tag, name).then(function(data) {
-            if (dataUpdateId != this._dataUpdateIdOfLastRequest) {
-              // This response is outdated. Ignore it.
-              // TODO(chizeng): Explore canceling an outdated request before we even receive its
-              // response. This involves creating hooks into the request manager and might introduce
-              // some complexity that may not be worth it; Tensorboard frankly does not seem
-              // bottlenecked by the network (It is often run in fast corp networks or locally.).
-              return;
-            }
-            this.chart().setSeriesData(name, data);
-          }.bind(this));
-        }.bind(this));
-      },
-      _changeSeries: function() {
-        if (!this._attached) {
-           return;
-        }
-        else if (!this.visibleSeries) {
-          throw new Error('tf-chart-scaffold requires a visibleSeries.');
-        }
-
-        this.chart().setVisibleSeries(this.visibleSeries);
-        this.reload();
-      },
-      chart: function() {
-        var children = this.getEffectiveChildren();
-        if (!children.length) {
-          throw new Error('tf-chart-scaffold has no children');
-        }
-
-        var child = children[0];
-        if (!child.setVisibleSeries || !child.setSeriesData) {
-          throw new Error("tf-chart-scaffold's content doesn't implement the " +
-              "required interface");
-        }
-        return child;
-      }
-    });
-  </script>
-</dom-module>
-
-<dom-module id="tf-panes-helper" assetpath="../tf-dashboard-common/">
-  <template>
-    <content></content> 
-    <tf-no-data-warning data-type="[[dataType]]" show-warning="[[dataNotFound]]"></tf-no-data-warning>
-
-    <template is="dom-repeat" items="[[categories]]" as="category">
-      <tf-collapsable-pane name="[[category.name]]" count="[[_count(category.tags, selectedRuns.*)]]">
-        <div class="layout horizontal wrap">
-          <template is="dom-repeat" items="[[_categoryCards(category, selectedRuns.*, run2tag.*)]]">
-              <div class="card">
-                <div class="card-title-container" style="border-color: [[_titleBorderColor(item.run)]]">
-                  <div class="card-title" inner-h-t-m-l="[[_break(item.tag)]]"></div>
-                  <template is="dom-if" if="[[repeatForRuns]]">
-                    <div class="card-subtitle" title="[[item.run]]">[[item.run]]</div>
-                  </template>
-                </div>
-                <div class="card-content">
-                  <tf-chart-scaffold tag="[[item.tag]]" data-provider="[[dataProvider]]" visible-series="[[item.runs]]" on-ready="_instantiateTemplate">
-                    
-                  </tf-chart-scaffold>
-                </div>
-                <div class="card-bottom-row">
-                  <paper-icon-button class="expand-button" icon="fullscreen" on-tap="_toggleExpanded"></paper-icon-button>
-                  <template is="dom-if" if="[[showDownloadLinks]]">
-                    <tf-downloader runs="[[item.runs]]" tag="[[item.tag]]" url-fn="[[downloadLinkUrlFunction]]">
-                    </tf-downloader>
-                  </template>
-                </div>
-              </div>
-          </template>
-        </div>
-      </tf-collapsable-pane>
-    </template>
-
-    <style>
-      .card {
-        height: var(--card-height, 200px);
-        width: var(--card-width, 300px);
-        display: flex;
-        flex-direction: column;
-        margin: 5px;
-        padding: var(--card-padding, 0 30px 35px 0);
-        -webkit-user-select: none;
-        -moz-user-select: none;
-        position: relative;
-      }
-
-      .card-expanded {
-        height: var(--card-expanded-height, 400px);
-        width: var(--card-expanded-width, 100%);
-      }
-
-      .card-title, .card-subtitle {
-        flex-grow: 0;
-        flex-shrink: 0;
-        font-size: 14px;
-        text-overflow: ellipsis;
-        overflow: hidden;
-      }
-
-      .card-subtitle {
-        font-size: 12px;
-      }
-
-      .card-content {
-        flex-grow: 1;
-        flex-shrink: 1;
-        display: flex;
-        margin-top: 10px;
-      }
-
-      .card-bottom-row {
-        position: absolute;
-        left: 0px;
-        bottom: 0px;
-        width: 100%;
-        display: flex;
-        flex-direction: row;
-        justify-content: space-between;
-        pointer-events: none;
-      }
-
-      .card-title-container {
-        border-left: 4px solid;
-        padding-left: 5px;
-      }
-
-      .expand-button {
-        color: #2196F3;
-        width: 32px;
-        height: 32px;
-        padding: 4px;
-        border-radius: 100%;
-        pointer-events: auto;
-        display: var(--show-expand-button, block);
-      }
-
-      .card-expanded .expand-button {
-        background: var(--tb-ui-light-accent);
-      }
-
-      tf-downloader {
-        margin-right: 30px;
-        pointer-events: auto;
-      }
-
-    </style>
-  </template>
-  <script>
-    Polymer({
-      is: "tf-panes-helper",
-      properties: {
-        /**
-         * Categories that separate the template instances. Each category will
-         * be given its own collapsible pane. The category must be an array of
-         * objects, each with a 'name' property and a 'tags' array of strings.
-         */
-        categories: Array,
-
-        /**
-         * Input of the colors that are used for the user's runs.
-         */
-        colorScale: Object,
-
-        /**
-         * The name of the data type that is used by this dashboard. This will
-         * be used to display what is missing when there is no data available.
-         */
-        dataType: String,
-
-        /**
-         * The function that requests and returns a promise with the data of the
-         * required type for the templates from the backend.
-         */
-        dataProvider: Object,
-
-        /**
-         * If false, instantiates one template for each tag and calls
-         * setVisibleSeries on the first element of the template with all valid
-         * runs the tag has. If true, instantiates one template for each run of
-         * each tag, and calls setVisibleSeries of the first element of the
-         * instantiated template with just the one run.
-         */
-        repeatForRuns: {
-          type: Boolean,
-          value: false
-        },
-
-        /**
-         * Map from runs to the valid tags that have them.
-         */
-        run2tag: Object,
-
-        /**
-         * Array with the runs that are selected by the user (i.e. valid to be
-         * displayed).
-         */
-        selectedRuns: Array,
-
-        /**
-         * If true, shows a menu with download links for the template data.
-         * If this is set to true, urlFn must also be provided.
-         */
-        showDownloadLinks: Boolean,
-
-        /**
-         * Function that returns the route to get data to download. Must be
-         * provided if showDownloadLinks is enabled.
-         */
-        downloadLinkUrlFunction: Function,
-        _contentTemplate: {
-          type: Object,
-          value: null
-        },
-        _stampedTemplates: {
-          type: Array,
-          value: function() { return [] }
-        }
-      },
-      behaviors: [
-        Polymer.Templatizer,
-      ],
-
-      /**
-       * Initializes the Polymer.Templatizer behavior with the template supplied
-       * by the user. With this, all calls to this.stamp() will produce an
-       * instance of the user template.
-       */
-      _initTemplatizer: function() {
-        if (!this._contentTemplate) {
-          // First template is used as the content.
-          this._contentTemplate = Polymer.dom(this).querySelector('template');
-          this.templatize(this._contentTemplate);
-        }
-      },
-
-      /**
-       * Called every time a tf-chart-scaffold is ready, stamps the user
-       * template inside the scaffold element (before it is attached) and
-       * stores the stamped template in an array to use for data binding
-       * (forwardParentProp/Path).
-       */
-      _instantiateTemplate: function(e) {
-        var scaffold = e.target;
-        this._initTemplatizer();
-        var instance = this.stamp();
-        this._stampedTemplates.push(instance);
-        Polymer.dom(scaffold).appendChild(instance.root);
-      },
-      _toggleExpanded: function(e) {
-        var currentTarget = Polymer.dom(e.currentTarget);
-        var card = currentTarget.node.closest('.card');
-        var scaffold = card.querySelector('tf-chart-scaffold');
-        card.classList.toggle('card-expanded');
-        scaffold.chart().redraw();
-      },
-      _count: function(tags) {
-        if (!this.repeatForRuns) {
-          return tags.length;
-        }
-
-        var targetTags = d3.set(tags);
-        var count = 0;
-        this.selectedRuns.forEach(function(r) {
-          this.run2tag[r].forEach(function(t) {
-            if (targetTags.has(t)) {
-              count++;
-            }
-          });
-        }.bind(this));
-        return count;
-      },
-      _categoryCards: function(category) {
-        var cards = [];
-        category.tags.forEach(function(tag) {
-          var runs = this.selectedRuns.filter(function(r) {
-            return this.run2tag[r] && this.run2tag[r].indexOf(tag) !== -1;
-          }.bind(this));
-
-          if (this.repeatForRuns) {
-            runs.forEach(function(run) {
-              cards.push({tag: tag, run: run, runs: [run]});
-            });
-          } else {
-            cards.push({tag: tag, runs: runs});
-          }
-        }.bind(this));
-
-        return cards;
-      },
-      _titleBorderColor: function(run) {
-        return this.repeatForRuns ? this.colorScale.scale(run) : 'white';
-      },
-
-      /*
-       * Polymer data binding forwarding functions. Check the
-       * Polymer.Templatizer documentation for more information.
-       */
-
-      _forwardParentProp: function(property, value) {
-        this._stampedTemplates.forEach(function(instance) {
-          instance[property] = value;
-        });
-      },
-      _forwardParentPath: function(path, value) {
-        this._stampedTemplates.forEach(function(instance) {
-          instance.notifyPath(path, value, true);
-        });
-      },
-      // TODO(renatoutsch): implement the instance forwarding for two-way data
-      // binding.
-      // Add breaks to input so it will wrap nicely
-      _break: function(ipt) {
-        return ipt.replace(/([\/_-])/g, "$1<wbr>")
-      },
-    });
-  </script>
-</dom-module>
-<dom-module id="tf-storage" assetpath="../tf-storage/">
- <script>/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-/* tslint:disable:no-namespace variable-name */
-/**
- * The Storage Module provides storage for URL parameters, and an API for
- * getting and setting TensorBoard's stateful URI.
- *
- * It generates URI components like: events&runPrefix=train*
- * which TensorBoard uses after like localhost:8000/#events&runPrefix=train*
- * to store state in the URI.
- *
- * It also allows saving the values to localStorage for long-term persistance.
- */
-var TF;
-(function (TF) {
-    var URIStorage;
-    (function (URIStorage) {
-        /**
-         * A key that users cannot use, since TensorBoard uses this to store info
-         * about the active tab.
-         */
-        URIStorage.TAB = '__tab__';
-        /**
-         * The name of the property for users to set on a Polymer component
-         * in order for its stored properties to be stored in the URI unambiguously.
-         * (No need to set this if you want mutliple instances of the component to
-         * share URI state)
-         *
-         * Example:
-         * <my-component disambiguator="0"></my-component>
-         *
-         * The disambiguator should be set to any unique value so that multiple
-         * instances of the component can store properties in URI storage.
-         *
-         * Because it's hard to dereference this variable in HTML property bindings,
-         * it is NOT safe to change the disambiguator string without find+replace
-         * across the codebase.
-         */
-        URIStorage.DISAMBIGUATOR = 'disambiguator';
-        /**
-         * Return a string stored in URI or localStorage.
-         * Undefined if not found.
-         */
-        function getString(key, useLocalStorage) {
-            if (useLocalStorage) {
-                return window.localStorage.getItem(key);
-            }
-            else {
-                return _componentToDict(_readComponent())[key];
-            }
-        }
-        URIStorage.getString = getString;
-        /**
-         * Set a string in URI or localStorage.
-         */
-        function setString(key, value, useLocalStorage) {
-            if (useLocalStorage) {
-                window.localStorage.setItem(key, value);
-            }
-            else {
-                var items = _componentToDict(_readComponent());
-                items[key] = value;
-                _writeComponent(_dictToComponent(items));
-            }
-        }
-        URIStorage.setString = setString;
-        /**
-         * Return a boolean stored in stored in URI or localStorage.
-         * Undefined if not found.
-         */
-        function getBoolean(key, useLocalStorage) {
-            var item = getString(key, useLocalStorage);
-            return item === 'true' ? true : item === 'false' ? false : undefined;
-        }
-        URIStorage.getBoolean = getBoolean;
-        /**
-         * Store a boolean in URI or localStorage.
-         */
-        function setBoolean(key, value, useLocalStorage) {
-            if (useLocalStorage === void 0) { useLocalStorage = false; }
-            setString(key, value.toString(), useLocalStorage);
-        }
-        URIStorage.setBoolean = setBoolean;
-        /**
-         * Return a number stored in stored in URI or localStorage.
-         * Undefined if not found.
-         */
-        function getNumber(key, useLocalStorage) {
-            var item = getString(key, useLocalStorage);
-            return item === undefined ? undefined : +item;
-        }
-        URIStorage.getNumber = getNumber;
-        /**
-         * Store a number in URI or localStorage.
-         */
-        function setNumber(key, value, useLocalStorage) {
-            setString(key, '' + value, useLocalStorage);
-        }
-        URIStorage.setNumber = setNumber;
-        /**
-         * Return an object stored in stored in URI or localStorage.
-         * Undefined if not found.
-         */
-        function getObject(key, useLocalStorage) {
-            var item = getString(key, useLocalStorage);
-            return item === undefined ? undefined : JSON.parse(atob(item));
-        }
-        URIStorage.getObject = getObject;
-        /**
-         * Store an object in URI or localStorage.
-         */
-        function setObject(key, value, useLocalStorage) {
-            setString(key, btoa(JSON.stringify(value)), useLocalStorage);
-        }
-        URIStorage.setObject = setObject;
-        /**
-         * Get a unique storage name for a (Polymer component, propertyName) tuple.
-         *
-         * DISAMBIGUATOR must be set on the component, if other components use the
-         * same propertyName.
-         */
-        function getURIStorageName(component, propertyName) {
-            var d = component[URIStorage.DISAMBIGUATOR];
-            var components = d == null ? [propertyName] : [d, propertyName];
-            return components.join('.');
-        }
-        URIStorage.getURIStorageName = getURIStorageName;
-        /**
-         * Return a function that:
-         * (1) Initializes a Polymer boolean property with a default value, if its
-         *     value is not already set
-         * (2) Sets up listener that updates Polymer property on hash change.
-         */
-        function getBooleanInitializer(propertyName, defaultVal, useLocalStorage) {
-            if (useLocalStorage === void 0) { useLocalStorage = false; }
-            return _getInitializer(getBoolean, propertyName, defaultVal, useLocalStorage);
-        }
-        URIStorage.getBooleanInitializer = getBooleanInitializer;
-        /**
-         * Return a function that:
-         * (1) Initializes a Polymer string property with a default value, if its
-         *     value is not already set
-         * (2) Sets up listener that updates Polymer property on hash change.
-         */
-        function getStringInitializer(propertyName, defaultVal, useLocalStorage) {
-            if (useLocalStorage === void 0) { useLocalStorage = false; }
-            return _getInitializer(getString, propertyName, defaultVal, useLocalStorage);
-        }
-        URIStorage.getStringInitializer = getStringInitializer;
-        /**
-         * Return a function that:
-         * (1) Initializes a Polymer number property with a default value, if its
-         *     value is not already set
-         * (2) Sets up listener that updates Polymer property on hash change.
-         */
-        function getNumberInitializer(propertyName, defaultVal, useLocalStorage) {
-            if (useLocalStorage === void 0) { useLocalStorage = false; }
-            return _getInitializer(getNumber, propertyName, defaultVal, useLocalStorage);
-        }
-        URIStorage.getNumberInitializer = getNumberInitializer;
-        /**
-         * Return a function that:
-         * (1) Initializes a Polymer Object property with a default value, if its
-         *     value is not already set
-         * (2) Sets up listener that updates Polymer property on hash change.
-         *
-         * Generates a deep clone of the defaultVal to avoid mutation issues.
-         */
-        function getObjectInitializer(propertyName, defaultVal, useLocalStorage) {
-            if (useLocalStorage === void 0) { useLocalStorage = false; }
-            return _getInitializer(getObject, propertyName, defaultVal, useLocalStorage);
-        }
-        URIStorage.getObjectInitializer = getObjectInitializer;
-        /**
-         * Return a function that updates URIStorage when a string property changes.
-         */
-        function getBooleanObserver(propertyName, defaultVal, useLocalStorage) {
-            if (useLocalStorage === void 0) { useLocalStorage = false; }
-            return _getObserver(getBoolean, setBoolean, propertyName, defaultVal, useLocalStorage);
-        }
-        URIStorage.getBooleanObserver = getBooleanObserver;
-        /**
-         * Return a function that updates URIStorage when a string property changes.
-         */
-        function getStringObserver(propertyName, defaultVal, useLocalStorage) {
-            if (useLocalStorage === void 0) { useLocalStorage = false; }
-            return _getObserver(getString, setString, propertyName, defaultVal, useLocalStorage);
-        }
-        URIStorage.getStringObserver = getStringObserver;
-        /**
-         * Return a function that updates URIStorage when a number property changes.
-         */
-        function getNumberObserver(propertyName, defaultVal, useLocalStorage) {
-            if (useLocalStorage === void 0) { useLocalStorage = false; }
-            return _getObserver(getNumber, setNumber, propertyName, defaultVal, useLocalStorage);
-        }
-        URIStorage.getNumberObserver = getNumberObserver;
-        /**
-         * Return a function that updates URIStorage when an object property changes.
-         * Generates a deep clone of the defaultVal to avoid mutation issues.
-         */
-        function getObjectObserver(propertyName, defaultVal, useLocalStorage) {
-            if (useLocalStorage === void 0) { useLocalStorage = false; }
-            var clone = _.cloneDeep(defaultVal);
-            return _getObserver(getObject, setObject, propertyName, clone, useLocalStorage);
-        }
-        URIStorage.getObjectObserver = getObjectObserver;
-        /**
-         * Read component from URI (e.g. returns "events&runPrefix=train*").
-         */
-        function _readComponent() {
-            return TF.Globals.USE_HASH ? window.location.hash.slice(1) :
-                TF.Globals.FAKE_HASH;
-        }
-        /**
-         * Write component to URI.
-         */
-        function _writeComponent(component) {
-            if (TF.Globals.USE_HASH) {
-                window.location.hash = component;
-            }
-            else {
-                TF.Globals.FAKE_HASH = component;
-            }
-        }
-        /**
-         * Convert dictionary of strings into a URI Component.
-         * All key value entries get added as key value pairs in the component,
-         * with the exception of a key with the TAB value, which if present
-         * gets prepended to the URI Component string for backwards comptability
-         * reasons.
-         */
-        function _dictToComponent(items) {
-            var component = '';
-            // Add the tab name e.g. 'events', 'images', 'histograms' as a prefix
-            // for backwards compatbility.
-            if (items[URIStorage.TAB] !== undefined) {
-                component += items[URIStorage.TAB];
-            }
-            // Join other strings with &key=value notation
-            var nonTab = _.pairs(items)
-                .filter(function (pair) { return pair[0] !== URIStorage.TAB; })
-                .map(function (pair) {
-                return encodeURIComponent(pair[0]) + '=' +
-                    encodeURIComponent(pair[1]);
-            })
-                .join('&');
-            return nonTab.length > 0 ? (component + '&' + nonTab) : component;
-        }
-        /**
-         * Convert a URI Component into a dictionary of strings.
-         * Component should consist of key-value pairs joined by a delimiter
-         * with the exception of the tabName.
-         * Returns dict consisting of all key-value pairs and
-         * dict[TAB] = tabName
-         */
-        function _componentToDict(component) {
-            var items = {};
-            var tokens = component.split('&');
-            tokens.forEach(function (token) {
-                var kv = token.split('=');
-                // Special backwards compatibility for URI components like #events
-                if (kv.length === 1 && _.contains(TF.Globals.TABS, kv[0])) {
-                    items[URIStorage.TAB] = kv[0];
-                }
-                else if (kv.length === 2) {
-                    items[decodeURIComponent(kv[0])] = decodeURIComponent(kv[1]);
-                }
-            });
-            return items;
-        }
-        /**
-         * Return a function that:
-         * (1) Initializes a Polymer property with a default value, if its
-         *     value is not already set
-         * (2) Sets up listener that updates Polymer property on hash change.
-         */
-        function _getInitializer(get, propertyName, defaultVal, useLocalStorage) {
-            return function () {
-                var _this = this;
-                var URIStorageName = getURIStorageName(this, propertyName);
-                // setComponentValue will be called every time the hash changes, and is
-                // responsible for ensuring that new state in the hash will be propagated
-                // to the component with that property.
-                // It is important that this function does not re-assign needlessly,
-                // to avoid Polymer observer churn.
-                var setComponentValue = function () {
-                    var uriValue = get(URIStorageName, false);
-                    var currentValue = _this[propertyName];
-                    // if uriValue is undefined, we will ensure that the property has the
-                    // default value
-                    if (uriValue === undefined) {
-                        var valueToSet = void 0;
-                        // if we are using localStorage, we will set the value to the value
-                        // from localStorage. Then, the corresponding observer will proxy
-                        // the localStorage value into URI storage.
-                        // in this way, localStorage takes precedence over the default val
-                        // but not over the URI value.
-                        if (useLocalStorage) {
-                            var useLocalStorageValue = get(URIStorageName, true);
-                            valueToSet = useLocalStorageValue === undefined ?
-                                defaultVal :
-                                useLocalStorageValue;
-                        }
-                        else {
-                            valueToSet = defaultVal;
-                        }
-                        if (!_.isEqual(currentValue, valueToSet)) {
-                            // If we don't have an explicit URI value, then we need to ensure
-                            // the property value is equal to the default value.
-                            // We will assign a clone rather than the canonical default, because
-                            // the component receiving this property may mutate it, and we need
-                            // to keep a pristine copy of the default.
-                            _this[propertyName] = _.clone(valueToSet);
-                        }
-                        // In this case, we have an explicit URI value, so we will ensure that
-                        // the component has an equivalent value.
-                    }
-                    else {
-                        if (!_.isEqual(uriValue, currentValue)) {
-                            _this[propertyName] = uriValue;
-                        }
-                    }
-                };
-                // Set the value on the property.
-                setComponentValue();
-                // Update it when the hashchanges.
-                window.addEventListener('hashchange', setComponentValue);
-            };
-        }
-        /**
-         * Return a function that updates URIStorage when a property changes.
-         */
-        function _getObserver(get, set, propertyName, defaultVal, useLocalStorage) {
-            return function () {
-                var URIStorageName = getURIStorageName(this, propertyName);
-                var newVal = this[propertyName];
-                // if this is a localStorage property, we always synchronize the value
-                // in localStorage to match the one currently in the URI.
-                if (useLocalStorage) {
-                    set(URIStorageName, newVal, true);
-                }
-                if (!_.isEqual(newVal, get(URIStorageName, false))) {
-                    if (_.isEqual(newVal, defaultVal)) {
-                        _unsetFromURI(URIStorageName);
-                    }
-                    else {
-                        set(URIStorageName, newVal, false);
-                    }
-                }
-            };
-        }
-        /**
-         * Delete a key from the URI.
-         */
-        function _unsetFromURI(key) {
-            var items = _componentToDict(_readComponent());
-            delete items[key];
-            _writeComponent(_dictToComponent(items));
-        }
-    })(URIStorage = TF.URIStorage || (TF.URIStorage = {}));
-})(TF || (TF = {}));
-</script>
-</dom-module>
-
-<dom-module id="tf-regex-group" assetpath="../tf-dashboard-common/">
-  <template>
-    <div class="regex-list">
-      <template is="dom-repeat" items="{{rawRegexes}}">
-        <div class="regex-line">
-          <paper-input id="text-input" class="regex-input" label="Write a regex to create a tag group" no-label-float="" value="{{item.regex}}" invalid="[[!item.valid]]" on-keyup="moveFocus"></paper-input>
-          <paper-icon-button icon="close" class="delete-button" aria-label="Delete Regex" tabindex="0" on-tap="deleteRegex"></paper-icon-button>
-        </div>
-        <style>
-          .regex-input {
-            width: 250px;
-            display: inline-block;
-            margin-left: -3px;
-          }
-
-          .delete-button {
-            color: var(--paper-grey-700);
-            width: 40px;
-            height: 40px;
-            margin-right: -10px;
-          }
-
-          .regex-list {
-            margin-bottom: 10px;
-          }
-
-          paper-input {
-            --paper-input-container-focus-color: var(--tb-orange-strong);
-            --paper-input-container-input: {
-              font-size: 14px;
-            };
-            --paper-input-container-label: {
-              font-size: 14px;
-            };
-          }
-        </style>
-      </template>
-    </div>
-  </template>
-  <script>
-    Polymer({
-      is: "tf-regex-group",
-      properties: {
-        rawRegexes: {
-          type: Array,
-          value: TF.URIStorage.getObjectInitializer('rawRegexes', [{regex: "", valid: true}]),
-        },
-        regexes: {type: Array, computed: "usableRegexes(rawRegexes.*)", notify: true},
-      },
-      observers: [
-        "addNewRegexIfNeeded(rawRegexes.*)",
-        "checkValidity(rawRegexes.*)",
-        "_uriStoreRegexes(rawRegexes.*)",
-      ],
-      _uriStoreRegexes: TF.URIStorage.getObjectObserver('rawRegexes', [{regex: "", valid: true}]),
-      checkValidity: function(x) {
-        var match = x.path.match(/rawRegexes\.(\d+)\.regex/);
-        if (match) {
-          var idx = match[1];
-          this.set("rawRegexes." + idx + ".valid", this.isValid(x.value));
-        }
-      },
-      isValid: function(s) {
-        try {
-          new RegExp(s);
-          return true;
-        } catch (e) {
-          return false;
-        }
-      },
-      usableRegexes: function(regexes) {
-        var isValid = this.isValid;
-        return regexes.base.filter(function (r) {
-          // Checking validity here (rather than using the data property)
-          // is necessary because otherwise we might send invalid regexes due
-          // to the fact that this function can call before the observer does
-          return r.regex !== "" && isValid(r.regex);
-        }).map(function(r) {
-          return r.regex;
-        });
-      },
-      addNewRegexIfNeeded: function() {
-        var last = this.rawRegexes[this.rawRegexes.length - 1];
-        if (last.regex !== "") {
-          this.push("rawRegexes", {regex: "", valid: true});
-        }
-      },
-      deleteRegex: function(e) {
-        if (this.rawRegexes.length > 1) {
-          this.splice("rawRegexes", e.model.index, 1);
-        }
-      },
-      moveFocus: function(e) {
-        if (e.keyCode === 13) {
-          var idx = e.model.index;
-          var inputs = Polymer.dom(this.root).querySelectorAll(".regex-input");
-          if (idx < this.rawRegexes.length - 1) {
-            inputs[idx+1].$.input.focus();
-          } else {
-            document.activeElement.blur();
-          }
-        }
-      }
-    });
-  </script>
-</dom-module>
-<link rel="import" href="../paper-toggle-button/paper-toggle-button.html">
-
-<dom-module id="tf-categorizer" assetpath="../tf-dashboard-common/">
-  <template>
-    <div class="inputs">
-      <tf-regex-group id="regexGroup" regexes="{{regexes}}"></tf-regex-group>
-    </div>
-    <style>
-      :host {
-        display: block;
-        padding-bottom: 5px;
-      }
-      paper-checkbox {
-        --paper-checkbox-checked-color: var(--paper-grey-600);
-        --paper-checkbox-unchecked-color: var(--paper-grey-600);
-        font-size: 14px;
-      }
-    </style>
-  </template>
-  <script>/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-var Categorizer;
-(function (Categorizer) {
-    /**
-     * This module contains methods that allow sorting tags into 'categories'.
-     * A category contains a name and a list of tags.
-     * The sorting strategy is defined by a 'CustomCategorization', which contains
-     * 'categoryDefinitions' which are regex rules used to construct a category.
-     * E.g. the regex rule 'xent' will create a category called 'xent' that
-     * contains values whose tags match the regex.
-     *
-     * After custom categories are evaluated, the tags are sorted by a hardcoded
-     * fallback categorizer, which may, for example, group tags into categories
-     * based on their top namespace.
-     */
-    /* Canonical TensorFlow ops are namespaced using forward slashes.
-     * This fallback categorizer categorizes by the top-level namespace.
-     */
-    Categorizer.topLevelNamespaceCategorizer = splitCategorizer(/\//);
-    function fallbackCategorizer(s) {
-        switch (s) {
-            case 'TopLevelNamespaceCategorizer':
-                return Categorizer.topLevelNamespaceCategorizer;
-            default:
-                throw new Error('Unrecognized categorization strategy: ' + s);
-        }
-    }
-    Categorizer.fallbackCategorizer = fallbackCategorizer;
-    /* An 'extractor' is a function that takes a tag name, and 'extracts' a
-     * category name.
-     * This function takes an extractor, and produces a categorizer.
-     * Currently, it is just used for the fallbackCategorizer, but we may want to
-     * refactor the general categorization logic to use the concept of extractors.
-     */
-    function extractorToCategorizer(extractor) {
-        return function (tags) {
-            if (tags.length === 0) {
-                return [];
-            }
-            var sortedTags = tags.slice().sort(VZ.Sorting.compareTagNames);
-            var categories = [];
-            var currentCategory = {
-                name: extractor(sortedTags[0]),
-                tags: [],
-            };
-            sortedTags.forEach(function (t) {
-                var topLevel = extractor(t);
-                if (currentCategory.name !== topLevel) {
-                    categories.push(currentCategory);
-                    currentCategory = {
-                        name: topLevel,
-                        tags: [],
-                    };
-                }
-                currentCategory.tags.push(t);
-            });
-            categories.push(currentCategory);
-            return categories;
-        };
-    }
-    function splitCategorizer(r) {
-        var extractor = function (t) {
-            return t.split(r)[0];
-        };
-        return extractorToCategorizer(extractor);
-    }
-    function defineCategory(ruledef) {
-        var r = new RegExp(ruledef);
-        var f = function (tag) {
-            return r.test(tag);
-        };
-        return { name: ruledef, matches: f };
-    }
-    Categorizer.defineCategory = defineCategory;
-    function _categorizer(rules, fallback) {
-        return function (tags) {
-            var remaining = d3.set(tags);
-            var userSpecified = rules.map(function (def) {
-                var tags = [];
-                remaining.forEach(function (t) {
-                    if (def.matches(t)) {
-                        tags.push(t);
-                    }
-                });
-                var cat = { name: def.name, tags: tags.sort(VZ.Sorting.compareTagNames) };
-                return cat;
-            });
-            var defaultCategories = fallback(remaining.values());
-            return userSpecified.concat(defaultCategories);
-        };
-    }
-    Categorizer._categorizer = _categorizer;
-    function categorizer(s) {
-        var rules = s.categoryDefinitions.map(defineCategory);
-        var fallback = fallbackCategorizer(s.fallbackCategorizer);
-        return _categorizer(rules, fallback);
-    }
-    Categorizer.categorizer = categorizer;
-    ;
-})(Categorizer || (Categorizer = {}));
-</script>
-  <script>
-    Polymer({
-      is: "tf-categorizer",
-      properties: {
-        regexes: {type: Array},
-        tags: {type: Array},
-        categoriesAreExclusive: {type: Boolean, value: true},
-        fallbackCategorizer: {
-          type: String,
-          value: "TopLevelNamespaceCategorizer",
-        },
-        categorizer: {
-          type: Object,
-          computed: "computeCategorization(regexes.*, categoriesAreExclusive, fallbackCategorizer)",
-        },
-        categories: {type: Array, value: function() {return [];}, notify: true, readOnly: true},
-      },
-      observers: ['recategorize(tags.*, categorizer)'],
-      computeCategorization: function(regexes, categoriesAreExclusive, fallbackCategorizer) {
-        var categorizationStrategy = {
-          categoryDefinitions: regexes.base,
-          categoriesAreExclusive: categoriesAreExclusive,
-          fallbackCategorizer: fallbackCategorizer,
-        };
-        return Categorizer.categorizer(categorizationStrategy);
-      },
-      recategorize: function() {
-        this.debounce("tf-categorizer-recategorize", function (){
-          var categories = this.categorizer(this.tags);
-          this._setCategories(categories);
-        })
-      },
-    });
-  </script>
-</dom-module>
-<dom-module id="run-color-style" assetpath="../tf-dashboard-common/">
-  <template>
-    <style>
-    [color-class="light-blue"] paper-checkbox {
-      --paper-checkbox-checked-color: var(--paper-light-blue-500);
-      --paper-checkbox-checked-ink-color: var(--paper-light-blue-500);
-      --paper-checkbox-unchecked-color: var(--paper-light-blue-900);
-      --paper-checkbox-unchecked-ink-color: var(--paper-light-blue-900);
-    }
-    [color-class="red"] paper-checkbox {
-      --paper-checkbox-checked-color: var(--paper-red-500);
-      --paper-checkbox-checked-ink-color: var(--paper-red-500);
-      --paper-checkbox-unchecked-color: var(--paper-red-900);
-      --paper-checkbox-unchecked-ink-color: var(--paper-red-900);
-    }
-    [color-class="green"] paper-checkbox {
-      --paper-checkbox-checked-color: var(--paper-green-500);
-      --paper-checkbox-checked-ink-color: var(--paper-green-500);
-      --paper-checkbox-unchecked-color: var(--paper-green-900);
-      --paper-checkbox-unchecked-ink-color: var(--paper-green-900);
-    }
-    [color-class="purple"] paper-checkbox {
-      --paper-checkbox-checked-color: var(--paper-purple-500);
-      --paper-checkbox-checked-ink-color: var(--paper-purple-500);
-      --paper-checkbox-unchecked-color: var(--paper-purple-900);
-      --paper-checkbox-unchecked-ink-color: var(--paper-purple-900);
-    }
-    [color-class="teal"] paper-checkbox {
-      --paper-checkbox-checked-color: var(--paper-teal-500);
-      --paper-checkbox-checked-ink-color: var(--paper-teal-500);
-      --paper-checkbox-unchecked-color: var(--paper-teal-900);
-      --paper-checkbox-unchecked-ink-color: var(--paper-teal-900);
-    }
-    [color-class="pink"] paper-checkbox {
-      --paper-checkbox-checked-color: var(--paper-pink-500);
-      --paper-checkbox-checked-ink-color: var(--paper-pink-500);
-      --paper-checkbox-unchecked-color: var(--paper-pink-900);
-      --paper-checkbox-unchecked-ink-color: var(--paper-pink-900);
-    }
-    [color-class="orange"] paper-checkbox {
-      --paper-checkbox-checked-color: var(--paper-orange-500);
-      --paper-checkbox-checked-ink-color: var(--paper-orange-500);
-      --paper-checkbox-unchecked-color: var(--paper-orange-900);
-      --paper-checkbox-unchecked-ink-color: var(--paper-orange-900);
-    }
-    [color-class="brown"] paper-checkbox {
-      --paper-checkbox-checked-color: var(--paper-brown-500);
-      --paper-checkbox-checked-ink-color: var(--paper-brown-500);
-      --paper-checkbox-unchecked-color: var(--paper-brown-900);
-      --paper-checkbox-unchecked-ink-color: var(--paper-brown-900);
-    }
-    [color-class="indigo"] paper-checkbox {
-      --paper-checkbox-checked-color: var(--paper-indigo-500);
-      --paper-checkbox-checked-ink-color: var(--paper-indigo-500);
-      --paper-checkbox-unchecked-color: var(--paper-indigo-900);
-      --paper-checkbox-unchecked-ink-color: var(--paper-indigo-900);
-    }
-    </style>
-  </template>
-</dom-module>
-
-<dom-module id="tf-multi-checkbox" assetpath="../tf-dashboard-common/">
-  <style include="scrollbar-style"></style>
-  <style include="run-color-style"></style>
-
-  <template>
-      <paper-input id="runs-regex" no-label-float="" label="Write a regex to filter runs" value="[[regexInput]]" on-bind-value-changed="_debouncedRegexChange"></paper-input>
-    <div id="outer-container" class="scrollbar">
-      <template is="dom-repeat" items="[[namesMatchingRegex]]">
-        <div class="run-row">
-          <div class="icon-container checkbox-container vertical-align-container">
-            <paper-checkbox class="checkbox vertical-align-center" name="[[item]]" checked$="[[_isChecked(item, runSelectionState.*)]]" on-change="_checkboxChange"></paper-checkbox>
-
-          </div>
-          <div class="icon-container isolator-container vertical-align-container">
-            <paper-icon-button icon="radio-button-unchecked" class="isolator vertical-align-center" on-tap="_isolateRun" name="[[item]]"></paper-icon-button>
-          </div>
-          <div class="item-label-container">
-            <span>[[item]]</span>
-          </div>
-        </div>
-      </template>
-    </div>
-  <style>
-    paper-input {
-      --paper-input-container-focus-color: var(--tb-orange-strong);
-      --paper-input-container-input: {
-        font-size: 14px;
-      };
-      --paper-input-container-label: {
-        font-size: 14px;
-      };
-    }
-    :host {
-      display: flex;
-      flex-direction: column;
-      height: 100%;
-    }
-    #outer-container {
-      overflow-y: auto;
-      overflow-x: hidden;
-      width: 100%;
-      height: 0; /* Quirk to make firefox add scrolling instead of expand div */
-      flex-grow: 1;
-      flex-shrink: 1;
-      word-wrap: break-word;
-    }
-    .run-row {
-      padding-top: 5px;
-      padding-bottom: 5px;
-      display: flex;
-      flex-direction: row;
-      font-size: 13px;
-    }
-    .icon-container {
-      flex-grow: 0;
-      flex-shrink: 0;
-      padding-left: 2px;
-    }
-    .checkbox {
-      padding-left: 2px;
-      width: 18px;
-      height: 18px;
-    }
-    .isolator {
-      width: 18px;
-      height: 18px;
-      padding: 0px;
-    }
-    .isolator-container {
-      padding-left: 6px;
-      padding-right: 3px;
-    }
-    .checkbox-container {
-      padding-left: 2px;
-    }
-    .item-label-container {
-      padding-left: 5px;
-      flex-grow: 1;
-      flex-shrink: 1;
-      width: 0px; /* hack to get the flex-grow to work properly */
-    }
-    .tooltip-value-container {
-      display: flex;
-      justify-content: center;
-      flex-grow: 0;
-      flex-shrink: 0;
-      text-align:right;
-      padding-left: 2px;
-    }
-    .vertical-align-container {
-      display: flex;
-      justify-content: center;
-    }
-    .vertical-align-container .vertical-align-center {
-      align-self: center;
-    }
-    .vertical-align-container .vertical-align-top {
-      align-self: start;
-    }
-  </style>
-  </template>
-
-  <script>
-  Polymer({
-    is: "tf-multi-checkbox",
-    properties: {
-      names: {
-        type: Array,
-        value: function() {return [];},
-      }, // All the runs in consideration
-      regexInput: {
-        type: String,
-        value: TF.URIStorage.getStringInitializer("regexInput", ""),
-        observer: "_regexInputObserver",
-      }, // Regex for filtering the runs
-      regex: {
-        type: Object,
-        computed: "_makeRegex(regexInput)"
-      },
-      namesMatchingRegex: {
-        type: Array,
-        computed: "computeNamesMatchingRegex(names.*, regex)"
-      }, // Runs that match the regex
-      runSelectionState: {
-      // if a run is explicitly enabled, True, if explicitly disabled, False.
-      // if undefined, default value (enable for first k runs, disable after).
-        type: Object,
-        value: TF.URIStorage.getObjectInitializer('runSelectionState', {}),
-        observer: "_storeRunToIsCheckedMapping",
-      },
-      // (Allows state to persist across regex filtering)
-      outSelected: {
-        type: Array,
-        notify: true,
-        computed: 'computeOutSelected(namesMatchingRegex.*, runSelectionState.*)'
-      },
-      colorScale: {
-        type: Object,
-        observer: "synchronizeColors",
-      }, // map from run name to css class
-      maxRunsToEnableByDefault: {
-        // When TB first loads, if it has k or fewer runs, they are all enabled
-        // by default. If there are more, then they are all disabled.
-        type: Number,
-        value: 40,
-      },
-      _debouncedRegexChange: {
-        type: Function,
-        // Updating the regex can be slow, because it involves updating styles
-        // on a large number of Polymer paper-checkboxes. We don't want to do
-        // this while the user is typing, as it may make a bad, laggy UI.
-        // So we debounce the updates that come from user typing.
-        value: function() {
-          _this = this;
-          var debounced = _.debounce(function(r) {
-            _this.regexInput = r;
-          }, 150, {leading: false});
-          return function() {
-            var r = this.$$("#runs-regex").value;
-            if (r == "") {
-              // If the user cleared the field, they may be done typing, so
-              // update more quickly.
-              this.async(function() {
-                _this.regexInput = r;
-              }, 30);
-            } else {
-              debounced(r);
-            };
-          };
-        },
-      },
-    },
-    listeners: {
-      'dom-change': 'synchronizeColors',
-    },
-    observers: [
-      "_setIsolatorIcon(runSelectionState, names)",
-    ],
-    _storeRunToIsCheckedMapping: TF.URIStorage.getObjectObserver('runSelectionState', {}),
-    _makeRegex: function(regex) {
-      try {
-        return new RegExp(regex)
-      } catch (e) {
-        return null;
-      }
-    },
-    _setIsolatorIcon: function() {
-      var runMap = this.runSelectionState;
-      var numChecked = _.filter(_.values(runMap)).length;
-      var buttons = Array.prototype.slice.call(this.querySelectorAll(".isolator"));
-
-      buttons.forEach(function(b) {
-        if (numChecked === 1 && runMap[b.name]) {
-          b.icon = "radio-button-checked";
-        } else {
-          b.icon = "radio-button-unchecked";
-        }
-      });
-    },
-    computeNamesMatchingRegex: function(__, ___) {
-      var regex = this.regex;
-      return this.names.filter(function(n) {
-        return regex == null || regex.test(n);
-      });
-    },
-    computeOutSelected: function(__, ___) {
-      var runSelectionState = this.runSelectionState;
-      var num = this.maxRunsToEnableByDefault;
-      var allEnabled = this.namesMatchingRegex.length <= num;
-      return this.namesMatchingRegex.filter(function(n, i) {
-        return runSelectionState[n] == null ? allEnabled : runSelectionState[n];
-      });
-    },
-    synchronizeColors: function(e) {
-      if (!this.colorScale) return;
-
-      this._setIsolatorIcon();
-
-      var checkboxes = Array.prototype.slice.call(this.querySelectorAll("paper-checkbox"));
-      var scale = this.colorScale;
-      checkboxes.forEach(function(p) {
-        var color = scale.scale(p.name);
-        p.customStyle['--paper-checkbox-checked-color'] = color;
-        p.customStyle['--paper-checkbox-checked-ink-color'] = color;
-        p.customStyle['--paper-checkbox-unchecked-color'] = color;
-        p.customStyle['--paper-checkbox-unchecked-ink-color'] = color;
-      });
-      var buttons = Array.prototype.slice.call(this.querySelectorAll(".isolator"));
-      buttons.forEach(function(p) {
-        var color = scale.scale(p.name);
-        p.style['color'] = color;
-      });
-      // The updateStyles call fails silently if the browser doesn't have focus,
-      // e.g. if TensorBoard was opened into a new tab that isn't visible.
-      // So we wait for requestAnimationFrame.
-      var _this = this;
-      window.requestAnimationFrame(function() {_this.updateStyles();});
-    },
-    _isolateRun: function(e) {
-      // If user clicks on the label for one run, enable it and disable all other runs.
-
-      var name = Polymer.dom(e).localTarget.name;
-      var selectionState = {};
-      this.names.forEach(function(n) {
-        selectionState[n] = n == name;
-      })
-      this.runSelectionState = selectionState;
-    },
-    _checkboxChange: function(e) {
-      var target = Polymer.dom(e).localTarget;
-      this.runSelectionState[target.name] = target.checked;
-      // n.b. notifyPath won't work because run names may have periods.
-      this.runSelectionState = _.clone(this.runSelectionState);
-    },
-    _isChecked: function(item, outSelectedChange) {
-      return this.outSelected.indexOf(item) != -1;
-    },
-    _regexInputObserver: TF.URIStorage.getStringObserver("regexInput", ""),
-    toggleAll: function() {
-      var _this = this;
-      var anyToggledOn = this.namesMatchingRegex
-                    .some(function(n) {return _this.runSelectionState[n]});
-
-
-      var runSelectionStateIsDefault = Object.keys(this.runSelectionState).length == 0;
-
-      var defaultOff = this.namesMatchingRegex.length > this.maxRunsToEnableByDefault;
-      // We have runs toggled either if some were explicitly toggled on, or if
-      // we are in the default state, and there are few enough that we default
-      // to toggling on.
-      anyToggledOn = anyToggledOn || runSelectionStateIsDefault && !defaultOff;
-
-      // If any are toggled on, we turn everything off. Or, if none are toggled
-      // on, we turn everything on.
-
-      var newRunsDisabled = {};
-      this.names.forEach(function(n) {
-        newRunsDisabled[n] = !anyToggledOn;
-      })
-      this.runSelectionState = newRunsDisabled;
-    },
-  });
-  </script>
-</dom-module>
-
-<dom-module id="tf-run-selector" assetpath="../tf-dashboard-common/">
-  <template>
-    <paper-dialog with-backdrop="" id="logdir-dialog">
-      <h2>logdir</h2>
-      <div inner-h-t-m-l="{{_breakString(logdir)}}"></div>
-    </paper-dialog>
-    <div id="top-text">
-      <h3 id="tooltip-help" class="tooltip-container">
-        Runs
-      </h3>
-    </div>
-    <tf-multi-checkbox id="multiCheckbox" names="[[runs]]" out-selected="{{outSelected}}" color-scale="[[colorScale]]"></tf-multi-checkbox>
-    <paper-button class="x-button" id="toggle-all" on-tap="_toggleAll">
-    Toggle All Runs
-    </paper-button>
-    <template is="dom-if" if="[[logdir]]">
-      <div id="logdir">
-        <span id="clipped-logdir" inner-h-t-m-l="[[_clippedLogdir]]"></span><template is="dom-if" if="[[_shouldShowExpandLogdirButton(logdir, _logdirClipLength)]]"><a href="" on-click="_openLogdirDialog">…</a>
-        </template>
-      </div>
-    </template>
-    <style>
-      :host {
-        display: flex;
-        flex-direction: column;
-        padding-bottom: 10px;
-        box-sizing: border-box;
-      }
-      #top-text {
-        width: 100%;
-        flex-grow: 0;
-        flex-shrink: 0;
-        padding-right: 16px;
-        box-sizing: border-box;
-        color: var(--paper-grey-800);
-      }
-      tf-multi-checkbox {
-        display: flex;
-        flex-grow: 1;
-        flex-shrink: 1;
-      }
-      .x-button {
-        font-size: 13px;
-        background-color: var(--tb-ui-light-accent);
-        color: var(--tb-ui-dark-accent);
-      }
-      #tooltip-help {
-        color: var(--paper-grey-800);
-        margin: 0;
-        font-weight: normal;
-        font-size: 14px;
-        margin-bottom: 5px;
-      }
-      paper-button {
-        margin-left: 0;
-      }
-      #logdir {
-        color: var(--tb-ui-dark-accent);
-        font-size: 13px;
-        margin: 5px 0 0 0;
-        max-width: 288px;
-      }
-    </style>
-  </template>
-  <script>
-  Polymer({
-    is: "tf-run-selector",
-    properties: {
-      backend: Object,
-      outSelected: {type: Array, notify: true},
-      // runs: an array of strings, representing the run names that may be chosen
-      runs: Array,
-      colorScale: Object, // TF.ColorScale
-      logdir: {
-        type: String,
-        notify: true,
-      },
-      // This is the potentially clipped portion of the logdir we show at the bottom of the sidebar.
-      _clippedLogdir: {
-        type: String,
-      },
-      _logdirClipLength: {
-        type: Number,
-        value: 250,
-        readOnly: true,
-      },
-    },
-    observers: [
-      "_onBackendUpdate(backend)",
-      "_logdirSet(logdir)",
-    ],
-    _toggleAll: function() {
-      this.$.multiCheckbox.toggleAll();
-    },
-    // Break the string at natural points, including commas, equals, and slashes
-    _breakString: function(originalString) {
-      return originalString.replace(/([\/=\-_,])/g, "$1<wbr>");
-    },
-    _onBackendUpdate: function(backend) {
-      if (backend === undefined) {
-        return;
-      }
-
-      // When the backend is set, the selector can request the logdir.
-      backend.logdir().then(logdirObject => {
-        this.set('logdir', logdirObject.logdir);
-      }).catch(e => {
-        // Fetching the logdir failed. Prevent the exception from logging to
-        // console. The console already logs a 404 network event.
-      });
-    },
-    _logdirSet: function(logdir) {
-      if (logdir === undefined) {
-        // The logdir has not been set yet.
-        return;
-      }
-
-      var lineBrokenText;
-      if (logdir.length > this._logdirClipLength) {
-        // Clip the logdir to avoid blocking the runs selector. Let the user view a more full
-        // version of the logdir.
-        lineBrokenText = this._breakString(logdir.substring(0, this._logdirClipLength));
-      } else {
-        lineBrokenText = this._breakString(logdir);
-      }
-      this.set('_clippedLogdir', lineBrokenText);
-    },
-    _openLogdirDialog: function(event) {
-      event.preventDefault();
-      this.$$('#logdir-dialog').open();
-    },
-    _shouldShowExpandLogdirButton(logdir, _logdirClipLength) {
-      return logdir && logdir.length > _logdirClipLength;
-    },
-  });
-  </script>
-</dom-module>
-
-<dom-module id="tf-sidebar-helper" assetpath="../tf-dashboard-common/">
-  <template>
-    <div class="sidebar-section">
-      <tf-categorizer id="categorizer" tags="[[tags]]" categories="{{categories}}"></tf-categorizer>
-      <content select=".extend-first-section"></content>
-    </div>
-    <content></content>
-    <div class="sidebar-section">
-      <tf-run-selector id="runSelector" backend="[[backend]]" runs="[[runs]]" color-scale="[[colorScale]]" out-selected="{{selectedRuns}}"></tf-run-selector>
-    </div>
-    <style include="dashboard-style"></style>
-    <style>
-      :host {
-        display: flex;
-        flex-direction: column;
-        height: 100%;
-      }
-
-      #categorizer {
-        flex-shrink: 0;
-      }
-
-      #runSelector {
-        flex-shrink: 1;
-        flex-grow: 1;
-      }
-
-      .sidebar-section {
-        border-top: solid 1px rgba(0, 0, 0, 0.12);
-        padding: 20px 0px 20px 30px;
-      }
-
-      .sidebar-section:first-child {
-        border: none;
-      }
-
-      .sidebar-section:last-child {
-        flex-grow: 1;
-        display: flex;
-      }
-
-      paper-checkbox {
-        --paper-checkbox-checked-color: var(--tb-ui-dark-accent);
-        --paper-checkbox-unchecked-color: var(--tb-ui-dark-accent);
-        font-size: 14px;
-      }
-    </style>
-  </template>
-  <script>
-    Polymer({
-      is: "tf-sidebar-helper",
-      properties: {
-        /**
-         * The backend object used to issue requests.
-         */
-        backend: Object,
-
-        /**
-         * This is an output of the categories that the user selected to
-         * separate the different tags. Each category here should be given its
-         * own collapsible pane.
-         */
-        categories: {
-          type: Array,
-          notify: true,
-        },
-
-        /**
-         * Input of the colors that are used for the user's runs.
-         */
-        colorScale: Object,
-
-        /**
-         * Map from runs to the valid tags that have them.
-         */
-        run2tag: Object,
-
-        /**
-         * Input of all valid runs that can be selected by the user.
-         */
-        runs: Array,
-
-        /**
-         * Outputs an array with the runs that are selected by the user (i.e.
-         * valid to be displayed).
-         */
-        selectedRuns: {
-          type: Array,
-          notify: true,
-        },
-
-        tags: {
-          type: Array,
-          computed: "_getTags(run2tag.*)"
-        },
-      },
-      _getTags: function() {
-        return _.union.apply(null, _.values(this.run2tag));
-      },
-    })
-  </script>
-</dom-module>
-
-<dom-module id="vz-line-chart" assetpath="../vz-line-chart/">
-  <template>
-    <div id="tooltip">
-      <table>
-        <thead>
-          <tr>
-            <th></th>
-            <th>Name</th>
-            <template is="dom-if" if="{{smoothingEnabled}}">
-              <th>Smoothed</th>
-            </template>
-            <th>Value</th>
-            <th>Step</th>
-            <th>Time</th>
-            <th>Relative</th>
-          </tr>
-        </thead>
-        <tbody>
-        </tbody>
-      </table>
-    </div>
-    <svg id="chartsvg"></svg>
-    <style>
-      :host {
-        -webkit-user-select: none;
-        -moz-user-select: none;
-        display: flex;
-        flex-direction: column;
-        flex-grow: 1;
-        flex-shrink: 1;
-        position: relative;
-        outline: none;
-      }
-      svg {
-        -webkit-user-select: none;
-        -moz-user-select: none;
-        flex-grow: 1;
-        flex-shrink: 1;
-      }
-      td {
-        padding-left: 5px;
-        padding-right: 5px;
-        font-size: 13px;
-        opacity: 1;
-      }
-      #tooltip {
-        pointer-events: none;
-        position: absolute;
-        opacity: 0;
-        box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
-        font-size: 14px;
-        background: rgba(0, 0, 0, 0.8);
-        color: white;
-        border-radius: 4px;
-        line-height: 1.4em;
-        padding: 8px;
-        z-index: 5;
-        cursor: none;
-        margin-top: 10px;
-      }
-      .swatch {
-        border-radius: 50%;
-        width: 14px;
-        height: 14px;
-        display: block;
-        border: 2px solid rgba(0,0,0,0);
-      }
-      .closest .swatch {
-        border: 2px solid white;
-      }
-      th {
-        padding-left: 5px;
-        padding-right: 5px;
-        text-align: left;
-      }
-      .distant td {
-        opacity: 0.8;
-      }
-
-      .distant td.swatch {
-        opacity: 1;
-      }
-
-      .ghost {
-        opacity: 0.2;
-        stroke-width: 1px;
-      }
-
-      #chartsvg line.guide-line {
-        stroke: #999;
-        stroke-width: 1.5px;
-      }
-
-    </style>
-  </template>
-  <script>/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-var __extends = (this && this.__extends) || (function () {
-    var extendStatics = Object.setPrototypeOf ||
-        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
-        function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
-    return function (d, b) {
-        extendStatics(d, b);
-        function __() { this.constructor = d; }
-        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
-    };
-})();
-var Plottable;
-(function (Plottable) {
-    var DragZoomLayer = (function (_super) {
-        __extends(DragZoomLayer, _super);
-        /**
-         * Constructs a SelectionBoxLayer with an attached DragInteraction and
-         * ClickInteraction. On drag, it triggers an animated zoom into the box
-         * that was dragged. On double click, it zooms back out to the original
-         * view, before any zooming.
-         * The zoom animation uses an easing function (default
-         * d3.ease('cubic-in-out')) and is customizable.
-         * Usage: Construct the selection box layer and attach x and y scales,
-         * and then add the layer over the plot you are zooming on using a
-         * Component Group.
-         * TODO(danmane) - merge this into Plottable
-         */
-        function DragZoomLayer(xScale, yScale, unzoomMethod) {
-            var _this = _super.call(this) || this;
-            _this.isZoomed = false;
-            _this.easeFn = d3.ease('cubic-in-out');
-            _this._animationTime = 750;
-            _this.xScale(xScale);
-            _this.yScale(yScale);
-            _this._dragInteraction = new Plottable.Interactions.Drag();
-            _this._dragInteraction.attachTo(_this);
-            _this._doubleClickInteraction = new Plottable.Interactions.DoubleClick();
-            _this._doubleClickInteraction.attachTo(_this);
-            _this.setupCallbacks();
-            _this.unzoomMethod = unzoomMethod;
-            return _this;
-        }
-        /**
-         * Register a method that calls when the DragZoom interaction starts.
-         */
-        DragZoomLayer.prototype.interactionStart = function (cb) { this.onStart = cb; };
-        /**
-         * Register a method that calls when the DragZoom interaction ends.
-         */
-        DragZoomLayer.prototype.interactionEnd = function (cb) { this.onEnd = cb; };
-        DragZoomLayer.prototype.setupCallbacks = function () {
-            var _this = this;
-            var dragging = false;
-            this._dragInteraction.onDragStart(function (startPoint) {
-                _this.bounds({
-                    topLeft: startPoint,
-                    bottomRight: startPoint,
-                });
-                _this.onStart();
-            });
-            this._dragInteraction.onDrag(function (startPoint, endPoint) {
-                _this.bounds({ topLeft: startPoint, bottomRight: endPoint });
-                _this.boxVisible(true);
-                dragging = true;
-            });
-            this._dragInteraction.onDragEnd(function (startPoint, endPoint) {
-                _this.boxVisible(false);
-                _this.bounds({ topLeft: startPoint, bottomRight: endPoint });
-                if (dragging) {
-                    _this.zoom();
-                }
-                else {
-                    _this.onEnd();
-                }
-                dragging = false;
-            });
-            this._doubleClickInteraction.onDoubleClick(this.unzoom.bind(this));
-        };
-        DragZoomLayer.prototype.animationTime = function (animationTime) {
-            if (animationTime == null) {
-                return this._animationTime;
-            }
-            if (animationTime < 0) {
-                throw new Error('animationTime cannot be negative');
-            }
-            this._animationTime = animationTime;
-            return this;
-        };
-        /**
-         * Set the easing function, which determines how the zoom interpolates
-         * over time.
-         */
-        DragZoomLayer.prototype.ease = function (fn) {
-            if (typeof (fn) !== 'function') {
-                throw new Error('ease function must be a function');
-            }
-            if (fn(0) !== 0 || fn(1) !== 1) {
-                Plottable.Utils.Window.warn('Easing function does not maintain invariant ' +
-                    'f(0)==0 && f(1)==1. Bad behavior may result.');
-            }
-            this.easeFn = fn;
-            return this;
-        };
-        // Zoom into extent of the selection box bounds
-        DragZoomLayer.prototype.zoom = function () {
-            var x0 = this.xExtent()[0].valueOf();
-            var x1 = this.xExtent()[1].valueOf();
-            var y0 = this.yExtent()[1].valueOf();
-            var y1 = this.yExtent()[0].valueOf();
-            if (x0 === x1 || y0 === y1) {
-                return;
-            }
-            if (!this.isZoomed) {
-                this.isZoomed = true;
-            }
-            this.interpolateZoom(x0, x1, y0, y1);
-        };
-        // Restore the scales to their state before any zoom
-        DragZoomLayer.prototype.unzoom = function () {
-            if (!this.isZoomed) {
-                return;
-            }
-            this.isZoomed = false;
-            var xScale = this.xScale();
-            xScale._domainMin = null;
-            xScale._domainMax = null;
-            var xDomain = xScale._getExtent();
-            this.xScale().domain(xDomain);
-            this.unzoomMethod();
-        };
-        // If we are zooming, disable interactions, to avoid contention
-        DragZoomLayer.prototype.isZooming = function (isZooming) {
-            this._dragInteraction.enabled(!isZooming);
-            this._doubleClickInteraction.enabled(!isZooming);
-        };
-        DragZoomLayer.prototype.interpolateZoom = function (x0f, x1f, y0f, y1f) {
-            var _this = this;
-            var x0s = this.xScale().domain()[0].valueOf();
-            var x1s = this.xScale().domain()[1].valueOf();
-            var y0s = this.yScale().domain()[0].valueOf();
-            var y1s = this.yScale().domain()[1].valueOf();
-            // Copy a ref to the ease fn, so that changing ease wont affect zooms in
-            // progress.
-            var ease = this.easeFn;
-            var interpolator = function (a, b, p) {
-                return d3.interpolateNumber(a, b)(ease(p));
-            };
-            this.isZooming(true);
-            var start = Date.now();
-            var draw = function () {
-                var now = Date.now();
-                var passed = now - start;
-                var p = _this._animationTime === 0 ?
-                    1 :
-                    Math.min(1, passed / _this._animationTime);
-                var x0 = interpolator(x0s, x0f, p);
-                var x1 = interpolator(x1s, x1f, p);
-                var y0 = interpolator(y0s, y0f, p);
-                var y1 = interpolator(y1s, y1f, p);
-                _this.xScale().domain([x0, x1]);
-                _this.yScale().domain([y0, y1]);
-                if (p < 1) {
-                    Plottable.Utils.DOM.requestAnimationFramePolyfill(draw);
-                }
-                else {
-                    _this.onEnd();
-                    _this.isZooming(false);
-                }
-            };
-            draw();
-        };
-        return DragZoomLayer;
-    }(Plottable.Components.SelectionBoxLayer));
-    Plottable.DragZoomLayer = DragZoomLayer;
-})(Plottable || (Plottable = {}));
-</script>
-  <script>/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-/* tslint:disable:no-namespace variable-name */
-var VZ;
-(function (VZ) {
-    var LineChart = (function () {
-        function LineChart(xType, yScaleType, colorScale, tooltip) {
-            this.seriesNames = [];
-            this.name2datasets = {};
-            this.colorScale = colorScale;
-            this.tooltip = tooltip;
-            this.datasets = [];
-            this._ignoreYOutliers = false;
-            // lastPointDataset is a dataset that contains just the last point of
-            // every dataset we're currently drawing.
-            this.lastPointsDataset = new Plottable.Dataset();
-            this.nanDataset = new Plottable.Dataset();
-            // need to do a single bind, so we can deregister the callback from
-            // old Plottable.Datasets. (Deregistration is done by identity checks.)
-            this.onDatasetChanged = this._onDatasetChanged.bind(this);
-            this.buildChart(xType, yScaleType);
-        }
-        LineChart.prototype.buildChart = function (xType, yScaleType) {
-            if (this.outer) {
-                this.outer.destroy();
-            }
-            var xComponents = VZ.ChartHelpers.getXComponents(xType);
-            this.xAccessor = xComponents.accessor;
-            this.xScale = xComponents.scale;
-            this.xAxis = xComponents.axis;
-            this.xAxis.margin(0).tickLabelPadding(3);
-            this.yScale = LineChart.getYScaleFromType(yScaleType);
-            this.yAxis = new Plottable.Axes.Numeric(this.yScale, 'left');
-            var yFormatter = VZ.ChartHelpers.multiscaleFormatter(VZ.ChartHelpers.Y_AXIS_FORMATTER_PRECISION);
-            this.yAxis.margin(0).tickLabelPadding(5).formatter(yFormatter);
-            this.yAxis.usesTextWidthApproximation(true);
-            this.dzl = new Plottable.DragZoomLayer(this.xScale, this.yScale, this.updateSpecialDatasets.bind(this));
-            var center = this.buildPlot(this.xAccessor, this.xScale, this.yScale);
-            this.gridlines =
-                new Plottable.Components.Gridlines(this.xScale, this.yScale);
-            var xZeroLine = new Plottable.Components.GuideLineLayer('horizontal');
-            xZeroLine.scale(this.yScale).value(0);
-            var yZeroLine = new Plottable.Components.GuideLineLayer('vertical');
-            yZeroLine.scale(this.xScale).value(0);
-            this.center = new Plottable.Components.Group([this.gridlines, xZeroLine, yZeroLine, center, this.dzl]);
-            this.outer = new Plottable.Components.Table([
-                [this.yAxis, this.center],
-                [null, this.xAxis]
-            ]);
-        };
-        LineChart.prototype.buildPlot = function (xAccessor, xScale, yScale) {
-            var _this = this;
-            this.scalarAccessor = function (d) { return d.scalar; };
-            this.smoothedAccessor = function (d) { return d.smoothed; };
-            var linePlot = new Plottable.Plots.Line();
-            linePlot.x(xAccessor, xScale);
-            linePlot.y(this.scalarAccessor, yScale);
-            linePlot.attr('stroke', function (d, i, dataset) {
-                return _this.colorScale.scale(dataset.metadata().name);
-            });
-            this.linePlot = linePlot;
-            var group = this.setupTooltips(linePlot);
-            var smoothLinePlot = new Plottable.Plots.Line();
-            smoothLinePlot.x(xAccessor, xScale);
-            smoothLinePlot.y(this.smoothedAccessor, yScale);
-            smoothLinePlot.attr('stroke', function (d, i, dataset) {
-                return _this.colorScale.scale(dataset.metadata().name);
-            });
-            this.smoothLinePlot = smoothLinePlot;
-            // The scatterPlot will display the last point for each dataset.
-            // This way, if there is only one datum for the series, it is still
-            // visible. We hide it when tooltips are active to keep things clean.
-            var scatterPlot = new Plottable.Plots.Scatter();
-            scatterPlot.x(xAccessor, xScale);
-            scatterPlot.y(this.scalarAccessor, yScale);
-            scatterPlot.attr('fill', function (d) { return _this.colorScale.scale(d.name); });
-            scatterPlot.attr('opacity', 1);
-            scatterPlot.size(VZ.ChartHelpers.TOOLTIP_CIRCLE_SIZE * 2);
-            scatterPlot.datasets([this.lastPointsDataset]);
-            this.scatterPlot = scatterPlot;
-            var nanDisplay = new Plottable.Plots.Scatter();
-            nanDisplay.x(xAccessor, xScale);
-            nanDisplay.y(function (x) { return x.displayY; }, yScale);
-            nanDisplay.attr('fill', function (d) { return _this.colorScale.scale(d.name); });
-            nanDisplay.attr('opacity', 1);
-            nanDisplay.size(VZ.ChartHelpers.NAN_SYMBOL_SIZE * 2);
-            nanDisplay.datasets([this.nanDataset]);
-            nanDisplay.symbol(Plottable.SymbolFactories.triangleUp);
-            this.nanDisplay = nanDisplay;
-            return new Plottable.Components.Group([nanDisplay, scatterPlot, smoothLinePlot, group]);
-        };
-        /** Updates the chart when a dataset changes. Called every time the data of
-         * a dataset changes to update the charts.
-         */
-        LineChart.prototype._onDatasetChanged = function (dataset) {
-            if (this.smoothingEnabled) {
-                this.resmoothDataset(dataset);
-            }
-            this.updateSpecialDatasets();
-        };
-        LineChart.prototype.ignoreYOutliers = function (ignoreYOutliers) {
-            if (ignoreYOutliers !== this._ignoreYOutliers) {
-                this._ignoreYOutliers = ignoreYOutliers;
-                this.updateSpecialDatasets();
-            }
-        };
-        LineChart.prototype.updateSpecialDatasets = function () {
-            if (this.smoothingEnabled) {
-                this.updateSpecialDatasetsWithAccessor(this.smoothedAccessor);
-            }
-            else {
-                this.updateSpecialDatasetsWithAccessor(this.scalarAccessor);
-            }
-        };
-        /** Constructs special datasets. Each special dataset contains exceptional
-         * values from all of the regular datasets, e.g. last points in series, or
-         * NaN values. Those points will have a `name` and `relative` property added
-         * (since usually those are context in the surrounding dataset).
-         * The accessor will point to the correct data to access.
-         */
-        LineChart.prototype.updateSpecialDatasetsWithAccessor = function (accessor) {
-            var lastPointsData = this.datasets
-                .map(function (d) {
-                var datum = null;
-                // filter out NaNs to ensure last point is a clean one
-                var nonNanData = d.data().filter(function (x) { return !isNaN(accessor(x, -1, d)); });
-                if (nonNanData.length > 0) {
-                    var idx = nonNanData.length - 1;
-                    datum = nonNanData[idx];
-                    datum.name = d.metadata().name;
-                    datum.relative =
-                        VZ.ChartHelpers.relativeAccessor(datum, -1, d);
-                }
-                return datum;
-            })
-                .filter(function (x) { return x != null; });
-            this.lastPointsDataset.data(lastPointsData);
-            // Take a dataset, return an array of NaN data points
-            // the NaN points will have a "displayY" property which is the
-            // y-value of a nearby point that was not NaN (0 if all points are NaN)
-            var datasetToNaNData = function (d) {
-                var displayY = null;
-                var data = d.data();
-                var i = 0;
-                while (i < data.length && displayY == null) {
-                    if (!isNaN(accessor(data[i], -1, d))) {
-                        displayY = accessor(data[i], -1, d);
-                    }
-                    i++;
-                }
-                if (displayY == null) {
-                    displayY = 0;
-                }
-                var nanData = [];
-                for (i = 0; i < data.length; i++) {
-                    if (!isNaN(accessor(data[i], -1, d))) {
-                        displayY = accessor(data[i], -1, d);
-                    }
-                    else {
-                        data[i].name = d.metadata().name;
-                        data[i].displayY = displayY;
-                        data[i].relative = VZ.ChartHelpers.relativeAccessor(data[i], -1, d);
-                        nanData.push(data[i]);
-                    }
-                }
-                return nanData;
-            };
-            var nanData = _.flatten(this.datasets.map(datasetToNaNData));
-            this.nanDataset.data(nanData);
-            var datasetToValues = function (d) {
-                return d.data().map(function (x) { return accessor(x, -1, d); });
-            };
-            var vals = _.flatten(this.datasets.map(datasetToValues));
-            vals = vals.filter(function (x) { return x === x && x !== Infinity && x !== -Infinity; });
-            var domain = VZ.ChartHelpers.computeDomain(vals, this._ignoreYOutliers);
-            this.yScale.domain(domain);
-        };
-        LineChart.prototype.setupTooltips = function (plot) {
-            var _this = this;
-            var pi = new Plottable.Interactions.Pointer();
-            pi.attachTo(plot);
-            // PointsComponent is a Plottable Component that will hold the little
-            // circles we draw over the closest data points
-            var pointsComponent = new Plottable.Component();
-            var group = new Plottable.Components.Group([plot, pointsComponent]);
-            var hideTooltips = function () {
-                _this.tooltip.style('opacity', 0);
-                _this.scatterPlot.attr('opacity', 1);
-                pointsComponent.content().selectAll('.point').remove();
-            };
-            var enabled = true;
-            var disableTooltips = function () {
-                enabled = false;
-                hideTooltips();
-            };
-            var enableTooltips = function () { enabled = true; };
-            this.dzl.interactionStart(disableTooltips);
-            this.dzl.interactionEnd(enableTooltips);
-            pi.onPointerMove(function (p) {
-                if (!enabled) {
-                    return;
-                }
-                var target = {
-                    x: p.x,
-                    y: p.y,
-                    datum: null,
-                    dataset: null,
-                };
-                var bbox = _this.gridlines.content().node().getBBox();
-                // pts is the closets point to the tooltip for each dataset
-                var pts = plot.datasets()
-                    .map(function (dataset) { return _this.findClosestPoint(target, dataset); })
-                    .filter(function (x) { return x != null; });
-                var intersectsBBox = Plottable.Utils.DOM.intersectsBBox;
-                // We draw tooltips for points that are NaN, or are currently visible
-                var ptsForTooltips = pts.filter(function (p) { return intersectsBBox(p.x, p.y, bbox) || isNaN(p.datum.scalar); });
-                // Only draw little indicator circles for the non-NaN points
-                var ptsToCircle = ptsForTooltips.filter(function (p) { return !isNaN(p.datum.scalar); });
-                var ptsSelection = pointsComponent.content().selectAll('.point').data(ptsToCircle, function (p) { return p.dataset.metadata().name; });
-                if (pts.length !== 0) {
-                    ptsSelection.enter().append('circle').classed('point', true);
-                    ptsSelection.attr('r', VZ.ChartHelpers.TOOLTIP_CIRCLE_SIZE)
-                        .attr('cx', function (p) { return p.x; })
-                        .attr('cy', function (p) { return p.y; })
-                        .style('stroke', 'none')
-                        .attr('fill', function (p) { return _this.colorScale.scale(p.dataset.metadata().name); });
-                    ptsSelection.exit().remove();
-                    _this.drawTooltips(ptsForTooltips, target);
-                }
-                else {
-                    hideTooltips();
-                }
-            });
-            pi.onPointerExit(hideTooltips);
-            return group;
-        };
-        LineChart.prototype.drawTooltips = function (points, target) {
-            var _this = this;
-            // Formatters for value, step, and wall_time
-            this.scatterPlot.attr('opacity', 0);
-            var valueFormatter = VZ.ChartHelpers.multiscaleFormatter(VZ.ChartHelpers.Y_TOOLTIP_FORMATTER_PRECISION);
-            var dist = function (p) {
-                return Math.pow(p.x - target.x, 2) + Math.pow(p.y - target.y, 2);
-            };
-            var closestDist = _.min(points.map(dist));
-            var valueSortMethod = this.scalarAccessor;
-            if (this.smoothingEnabled) {
-                valueSortMethod = this.smoothedAccessor;
-            }
-            if (this.tooltipSortingMethod === 'ascending') {
-                points =
-                    _.sortBy(points, function (d) { return valueSortMethod(d.datum, -1, d.dataset); });
-            }
-            else if (this.tooltipSortingMethod === 'descending') {
-                points =
-                    _.sortBy(points, function (d) { return valueSortMethod(d.datum, -1, d.dataset); })
-                        .reverse();
-            }
-            else if (this.tooltipSortingMethod === 'nearest') {
-                points = _.sortBy(points, dist);
-            }
-            else {
-                // The 'default' sorting method maintains the order of names passed to
-                // setVisibleSeries(). However we reverse that order when defining the
-                // datasets. So we must call reverse again to restore the order.
-                points = points.slice(0).reverse();
-            }
-            var rows = this.tooltip.select('tbody')
-                .html('')
-                .selectAll('tr')
-                .data(points)
-                .enter()
-                .append('tr');
-            // Grey out the point if any of the following are true:
-            // - The cursor is outside of the x-extent of the dataset
-            // - The point's y value is NaN
-            rows.classed('distant', function (d) {
-                var firstPoint = d.dataset.data()[0];
-                var lastPoint = _.last(d.dataset.data());
-                var firstX = _this.xScale.scale(_this.xAccessor(firstPoint, 0, d.dataset));
-                var lastX = _this.xScale.scale(_this.xAccessor(lastPoint, 0, d.dataset));
-                var s = _this.smoothingEnabled ? d.datum.smoothed : d.datum.scalar;
-                return target.x < firstX || target.x > lastX || isNaN(s);
-            });
-            rows.classed('closest', function (p) { return dist(p) === closestDist; });
-            // It is a bit hacky that we are manually applying the width to the swatch
-            // and the nowrap property to the text here. The reason is as follows:
-            // the style gets updated asynchronously by Polymer scopeSubtree observer.
-            // Which means we would get incorrect sizing information since the text
-            // would wrap by default. However, we need correct measurements so that
-            // we can stop the text from falling off the edge of the screen.
-            // therefore, we apply the size-critical styles directly.
-            rows.style('white-space', 'nowrap');
-            rows.append('td')
-                .append('span')
-                .classed('swatch', true)
-                .style('background-color', function (d) { return _this.colorScale.scale(d.dataset.metadata().name); });
-            rows.append('td').text(function (d) { return d.dataset.metadata().name; });
-            if (this.smoothingEnabled) {
-                rows.append('td').text(function (d) { return isNaN(d.datum.smoothed) ? 'NaN' :
-                    valueFormatter(d.datum.smoothed); });
-            }
-            rows.append('td').text(function (d) {
-                return isNaN(d.datum.scalar) ? 'NaN' : valueFormatter(d.datum.scalar);
-            });
-            rows.append('td').text(function (d) { return VZ.ChartHelpers.stepFormatter(d.datum.step); });
-            rows.append('td').text(function (d) { return VZ.ChartHelpers.timeFormatter(d.datum.wall_time); });
-            rows.append('td').text(function (d) { return VZ.ChartHelpers.relativeFormatter(VZ.ChartHelpers.relativeAccessor(d.datum, -1, d.dataset)); });
-            // compute left position
-            var documentWidth = document.body.clientWidth;
-            var node = this.tooltip.node();
-            var parentRect = node.parentElement.getBoundingClientRect();
-            var nodeRect = node.getBoundingClientRect();
-            // prevent it from falling off the right side of the screen
-            var left = documentWidth - parentRect.left - nodeRect.width - 60, top = 0;
-            if (this.tooltipPosition === 'right') {
-                left = Math.min(parentRect.width, left);
-            }
-            else {
-                left = Math.min(0, left);
-                top = parentRect.height + VZ.ChartHelpers.TOOLTIP_Y_PIXEL_OFFSET;
-            }
-            this.tooltip.style('transform', 'translate(' + left + 'px,' + top + 'px)');
-            this.tooltip.style('opacity', 1);
-        };
-        LineChart.prototype.findClosestPoint = function (target, dataset) {
-            var _this = this;
-            var points = dataset.data().map(function (d, i) {
-                var x = _this.xAccessor(d, i, dataset);
-                var y = _this.smoothingEnabled ? _this.smoothedAccessor(d, i, dataset) :
-                    _this.scalarAccessor(d, i, dataset);
-                return {
-                    x: _this.xScale.scale(x),
-                    y: _this.yScale.scale(y),
-                    datum: d,
-                    dataset: dataset,
-                };
-            });
-            var idx = _.sortedIndex(points, target, function (p) { return p.x; });
-            if (idx === points.length) {
-                return points[points.length - 1];
-            }
-            else if (idx === 0) {
-                return points[0];
-            }
-            else {
-                var prev = points[idx - 1];
-                var next = points[idx];
-                var prevDist = Math.abs(prev.x - target.x);
-                var nextDist = Math.abs(next.x - target.x);
-                return prevDist < nextDist ? prev : next;
-            }
-        };
-        LineChart.prototype.resmoothDataset = function (dataset) {
-            var data = dataset.data();
-            var smoothingWeight = this.smoothingWeight;
-            var last = data.length > 0 ? data[0].scalar : NaN;
-            data.forEach(function (d) {
-                if (!_.isFinite(last)) {
-                    d.smoothed = d.scalar;
-                }
-                else {
-                    // 1st-order IIR low-pass filter to attenuate the higher-
-                    // frequency components of the time-series.
-                    d.smoothed =
-                        last * smoothingWeight + (1 - smoothingWeight) * d.scalar;
-                }
-                last = d.smoothed;
-            });
-        };
-        LineChart.prototype.getDataset = function (name) {
-            if (this.name2datasets[name] === undefined) {
-                this.name2datasets[name] = new Plottable.Dataset([], { name: name });
-            }
-            return this.name2datasets[name];
-        };
-        LineChart.getYScaleFromType = function (yScaleType) {
-            if (yScaleType === 'log') {
-                return new Plottable.Scales.ModifiedLog();
-            }
-            else if (yScaleType === 'linear') {
-                return new Plottable.Scales.Linear();
-            }
-            else {
-                throw new Error('Unrecognized yScale type ' + yScaleType);
-            }
-        };
-        /**
-         * Update the selected series on the chart.
-         */
-        LineChart.prototype.setVisibleSeries = function (names) {
-            var _this = this;
-            names = names.sort();
-            this.seriesNames = names;
-            names.reverse(); // draw first series on top
-            this.datasets.forEach(function (d) { return d.offUpdate(_this.onDatasetChanged); });
-            this.datasets = names.map(function (r) { return _this.getDataset(r); });
-            this.datasets.forEach(function (d) { return d.onUpdate(_this.onDatasetChanged); });
-            this.linePlot.datasets(this.datasets);
-            if (this.smoothingEnabled) {
-                this.smoothLinePlot.datasets(this.datasets);
-            }
-            this.updateSpecialDatasets();
-        };
-        /**
-         * Set the data of a series on the chart.
-         */
-        LineChart.prototype.setSeriesData = function (name, data) {
-            this.getDataset(name).data(data);
-        };
-        LineChart.prototype.smoothingUpdate = function (weight) {
-            var _this = this;
-            this.smoothingWeight = weight;
-            this.datasets.forEach(function (d) { return _this.resmoothDataset(d); });
-            if (!this.smoothingEnabled) {
-                this.linePlot.addClass('ghost');
-                this.scatterPlot.y(this.smoothedAccessor, this.yScale);
-                this.smoothingEnabled = true;
-                this.smoothLinePlot.datasets(this.datasets);
-            }
-            this.updateSpecialDatasetsWithAccessor(this.smoothedAccessor);
-        };
-        LineChart.prototype.smoothingDisable = function () {
-            if (this.smoothingEnabled) {
-                this.linePlot.removeClass('ghost');
-                this.scatterPlot.y(this.scalarAccessor, this.yScale);
-                this.smoothLinePlot.datasets([]);
-                this.smoothingEnabled = false;
-                this.updateSpecialDatasetsWithAccessor(this.scalarAccessor);
-            }
-        };
-        LineChart.prototype.setTooltipSortingMethod = function (method) {
-            this.tooltipSortingMethod = method;
-        };
-        LineChart.prototype.setTooltipPosition = function (position) {
-            this.tooltipPosition = position;
-        };
-        LineChart.prototype.renderTo = function (targetSVG) {
-            this.targetSVG = targetSVG;
-            this.setViewBox();
-            this.outer.renderTo(targetSVG);
-        };
-        /** There's an issue in Chrome where the svg overflow is a bit
-         * "flickery". There is a border on the gridlines on the extreme edge of the
-         * chart, which behaves inconsistently and causes the screendiffing tests to
-         * flake. We can solve this by creating 1px effective margin for the svg by
-         * setting the viewBox on the containing svg.
-         */
-        LineChart.prototype.setViewBox = function () {
-            // There's an issue in Firefox where if we measure with the old viewbox
-            // set, we get horrible results.
-            this.targetSVG.attr('viewBox', null);
-            var parent = this.targetSVG.node().parentNode;
-            var w = parent.clientWidth;
-            var h = parent.clientHeight;
-            this.targetSVG.attr({
-                'height': h,
-                'viewBox': "0 0 " + (w + 1) + " " + (h + 1),
-            });
-        };
-        LineChart.prototype.redraw = function () {
-            this.outer.redraw();
-            this.setViewBox();
-        };
-        LineChart.prototype.destroy = function () { this.outer.destroy(); };
-        return LineChart;
-    }());
-    VZ.LineChart = LineChart;
-})(VZ || (VZ = {}));
-</script>
-  <script>/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-/* tslint:disable:no-namespace variable-name */
-var VZ;
-(function (VZ) {
-    var ChartHelpers;
-    (function (ChartHelpers) {
-        ChartHelpers.Y_TOOLTIP_FORMATTER_PRECISION = 4;
-        ChartHelpers.STEP_FORMATTER_PRECISION = 4;
-        ChartHelpers.Y_AXIS_FORMATTER_PRECISION = 3;
-        ChartHelpers.TOOLTIP_Y_PIXEL_OFFSET = 20;
-        ChartHelpers.TOOLTIP_CIRCLE_SIZE = 4;
-        ChartHelpers.NAN_SYMBOL_SIZE = 6;
-        /* Create a formatter function that will switch between exponential and
-         * regular display depending on the scale of the number being formatted,
-         * and show `digits` significant digits.
-         */
-        function multiscaleFormatter(digits) {
-            return function (v) {
-                var absv = Math.abs(v);
-                if (absv < 1E-15) {
-                    // Sometimes zero-like values get an annoying representation
-                    absv = 0;
-                }
-                var f;
-                if (absv >= 1E4) {
-                    f = d3.format('.' + digits + 'e');
-                }
-                else if (absv > 0 && absv < 0.01) {
-                    f = d3.format('.' + digits + 'e');
-                }
-                else {
-                    f = d3.format('.' + digits + 'g');
-                }
-                return f(v);
-            };
-        }
-        ChartHelpers.multiscaleFormatter = multiscaleFormatter;
-        /* Compute an appropriate domain given an array of all the values that are
-         * going to be displayed. If ignoreOutliers is true, it will ignore the
-         * lowest 10% and highest 10% of the data when computing a domain.
-         * It has n log n performance when ignoreOutliers is true, as it needs to
-         * sort the data.
-         */
-        function computeDomain(values, ignoreOutliers) {
-            if (values.length === 0) {
-                return [-0.1, 1.1];
-            }
-            var a;
-            var b;
-            if (ignoreOutliers) {
-                var sorted = _.sortBy(values);
-                a = d3.quantile(sorted, 0.05);
-                b = d3.quantile(sorted, 0.95);
-            }
-            else {
-                a = d3.min(values);
-                b = d3.max(values);
-            }
-            var padding;
-            var span = b - a;
-            if (span === 0) {
-                // If b===a, we would create an empty range. We instead select the range
-                // [0, 2*a] if a > 0, or [-2*a, 0] if a < 0, plus a little bit of
-                // extra padding on the top and bottom of the plot.
-                padding = Math.abs(a) * 1.1 + 1.1;
-            }
-            else {
-                padding = span * 0.2;
-            }
-            var lower;
-            if (a >= 0 && a < span) {
-                // We include the intercept (y = 0) if doing so less than doubles the span
-                // of the y-axis. (We actually select a lower bound that's slightly less
-                // than 0 so that 0.00 will clearly be written on the lower edge of the
-                // chart. The label on the lowest tick is often filtered out.)
-                lower = -0.1 * b;
-            }
-            else {
-                lower = a - padding;
-            }
-            var domain = [lower, b + padding];
-            domain = d3.scale.linear().domain(domain).nice().domain();
-            return domain;
-        }
-        ChartHelpers.computeDomain = computeDomain;
-        function accessorize(key) {
-            return function (d, index, dataset) { return d[key]; };
-        }
-        ChartHelpers.accessorize = accessorize;
-        ChartHelpers.stepFormatter = Plottable.Formatters.siSuffix(ChartHelpers.STEP_FORMATTER_PRECISION);
-        function stepX() {
-            var scale = new Plottable.Scales.Linear();
-            var axis = new Plottable.Axes.Numeric(scale, 'bottom');
-            axis.formatter(ChartHelpers.stepFormatter);
-            return {
-                scale: scale,
-                axis: axis,
-                accessor: function (d) { return d.step; },
-            };
-        }
-        ChartHelpers.stepX = stepX;
-        ChartHelpers.timeFormatter = Plottable.Formatters.time('%a %b %e, %H:%M:%S');
-        function wallX() {
-            var scale = new Plottable.Scales.Time();
-            return {
-                scale: scale,
-                axis: new Plottable.Axes.Time(scale, 'bottom'),
-                accessor: function (d) { return d.wall_time; },
-            };
-        }
-        ChartHelpers.wallX = wallX;
-        ChartHelpers.relativeAccessor = function (d, index, dataset) {
-            // We may be rendering the final-point datum for scatterplot.
-            // If so, we will have already provided the 'relative' property
-            if (d.relative != null) {
-                return d.relative;
-            }
-            var data = dataset.data();
-            // I can't imagine how this function would be called when the data is
-            // empty (after all, it iterates over the data), but lets guard just
-            // to be safe.
-            var first = data.length > 0 ? +data[0].wall_time : 0;
-            return (+d.wall_time - first) / (60 * 60 * 1000); // ms to hours
-        };
-        ChartHelpers.relativeFormatter = function (n) {
-            // we will always show 2 units of precision, e.g days and hours, or
-            // minutes and seconds, but not hours and minutes and seconds
-            var ret = '';
-            var days = Math.floor(n / 24);
-            n -= (days * 24);
-            if (days) {
-                ret += days + 'd ';
-            }
-            var hours = Math.floor(n);
-            n -= hours;
-            n *= 60;
-            if (hours || days) {
-                ret += hours + 'h ';
-            }
-            var minutes = Math.floor(n);
-            n -= minutes;
-            n *= 60;
-            if (minutes || hours || days) {
-                ret += minutes + 'm ';
-            }
-            var seconds = Math.floor(n);
-            return ret + seconds + 's';
-        };
-        function relativeX() {
-            var scale = new Plottable.Scales.Linear();
-            return {
-                scale: scale,
-                axis: new Plottable.Axes.Numeric(scale, 'bottom'),
-                accessor: ChartHelpers.relativeAccessor,
-            };
-        }
-        ChartHelpers.relativeX = relativeX;
-        // a very literal definition of NaN: true for NaN for a non-number type
-        // or null, etc. False for Infinity or -Infinity
-        ChartHelpers.isNaN = function (x) { return +x !== x; };
-        function getXComponents(xType) {
-            switch (xType) {
-                case 'step':
-                    return stepX();
-                case 'wall_time':
-                    return wallX();
-                case 'relative':
-                    return relativeX();
-                default:
-                    throw new Error('invalid xType: ' + xType);
-            }
-        }
-        ChartHelpers.getXComponents = getXComponents;
-    })(ChartHelpers = VZ.ChartHelpers || (VZ.ChartHelpers = {}));
-})(VZ || (VZ = {}));
-</script>
-  <script>
-    Polymer({
-      is: "vz-line-chart",
-      properties: {
-        /**
-         * Scale that maps series names to colors. The default colors are from
-         * d3.scale.category10() scale. Use this property to replace the default
-         * line colors with colors of your own choice.
-         * @type {Plottable.Scales.Color}
-         * @required
-         */
-        colorScale: {
-          type: Object,
-          value: function() {
-            return new Plottable.Scales.Color()
-                .range(d3.scale.category10().range());
-          }
-        },
-
-        /**
-         * Whether smoothing is enabled or not. If true, smoothed lines will be
-         * plotted in the chart while the unsmoothed lines will be ghosted in
-         * the background.
-         *
-         * The smoothing algorithm is a simple moving average, which, given a
-         * point p and a window w, replaces p with a simple average of the
-         * points in the [p - floor(w/2), p + floor(w/2)] range.  If there
-         * aren't enough points to cover the entire window to the left, the
-         * window is reduced to fit exactly the amount of elements available.
-         * This means that the smoothed line will be less in and gradually
-         * become more smooth until the desired window is reached. However when
-         * there aren't enough points on the right, the line stops being
-         * rendered at all.
-         */
-        smoothingEnabled: {
-          type: Boolean,
-          value: false
-        },
-
-        /**
-         * Weight (between 0.0 and 1.0) of the smoothing. This weight controls
-         * the window size, and a weight of 1.0 means using 50% of the entire
-         * dataset as the window, while a weight of 0.0 means using a window of
-         * 0 (and thus replacing each point with themselves).
-         *
-         * The growth between 0.0 and 1.0 is not linear though. Because
-         * changing the window from 0% to 30% of the dataset smooths the line a
-         * lot more than changing the window from 70% to 100%, an exponential
-         * function is used instead: http://i.imgur.com/bDrhEZU.png. This
-         * function increases the size of the window slowly at the beginning
-         * and gradually speeds up the growth, but 0.0 still means a window of
-         * 0 and 1.0 still means a window of the dataset's length.
-         */
-        smoothingWeight: {
-          type: Number,
-          value: 0.6
-        },
-
-        /**
-         * The way to display the X values. Allows:
-         * - "step" - Linear scale using the  "step" property of the datum.
-         * - "wall_time" - Temporal scale using the "wall_time" property of the
-         * datum.
-         * - "relative" - Temporal scale using the "relative" property of the
-         * datum if it is present or calculating from "wall_time" if it isn't.
-         */
-        xType: {
-          type: String,
-          value: 'step'
-        },
-
-        /**
-         * The scale for the y-axis. Allows:
-         * - "linear" - linear scale (Plottable.Scales.Linear)
-         * - "log" - modified-log scale (Plottable.Scales.ModifiedLog)
-         */
-        yScaleType: {
-          type: String,
-          value: 'linear'
-        },
-
-        /**
-         * Whether to ignore outlier data when computing the yScale domain.
-         */
-
-        ignoreYOutliers: {
-          type: Boolean,
-          value: false,
-        },
-
-        /**
-         * Change how the tooltip is sorted. Allows:
-         * - "default" - Sort the tooltip by input order.
-         * - "ascending" - Sort the tooltip by ascending value.
-         * - "descending" - Sort the tooltip by descending value.
-         * - "nearest" - Sort the tooltip by closest to cursor.
-         */
-        tooltipSortingMethod: {
-          type: String,
-          value: 'default'
-        },
-
-        /**
-         * Change how the tooltip is positioned. Allows:
-         * - "bottom" - Position the tooltip on the bottom of the chart.
-         * - "right" - Position the tooltip to the right of the chart.
-         */
-        tooltipPosition: {
-          type: String,
-          value: 'bottom'
-        },
-
-        _attached: Boolean,
-        _chart: Object,
-        _visibleSeriesCache: {
-          type: Array,
-          value: function() { return [] }
-        },
-        _seriesDataCache: {
-          type: Object,
-          value: function() { return {} }
-        },
-        _makeChartAsyncCallbackId: {
-          type: Number,
-          value: null
-        }
-      },
-      observers: [
-        "_makeChart(xType, yScaleType, colorScale, _attached)",
-        "_reloadFromCache(_chart)",
-        "_smoothingChanged(smoothingEnabled, smoothingWeight, _chart)",
-        "_tooltipSortingMethodChanged(tooltipSortingMethod, _chart)",
-        "_tooltipPositionChanged(tooltipPosition, _chart)",
-        "_outliersChanged(ignoreYOutliers, _chart)"
-      ],
-
-      /**
-       * Sets the series that the chart displays. Series with other names will
-       * not be displayed.
-       *
-       * @param {String[]} names Array with the names of the series to
-       * display.
-       */
-      setVisibleSeries: function(names) {
-        this._visibleSeriesCache = names;
-        if (this._chart) {
-          this._chart.setVisibleSeries(names);
-          this.redraw();
-        }
-      },
-
-      /**
-       * Sets the data of one of the series. Note that to display this series
-       * its name must be in the setVisibleSeries() array.
-       *
-       * @param {String} name Name of the series.
-       * @param {VZ.ChartHelpers.ScalarDatum[]} data Data of the series. This is
-       * an array of objects with at least the following properties:
-       * - step: (Number) - index of the datum.
-       * - wall_time: (Date) - Date object with the datum's time.
-       * - scalar: (Number) - Value of the datum.
-       */
-      setSeriesData: function(name, data) {
-        this._seriesDataCache[name] = data;
-        if (this._chart) {
-          this._chart.setSeriesData(name, data);
-        }
-      },
-
-      /**
-       * Re-renders the chart. Useful if e.g. the container size changed.
-       */
-      redraw: function() {
-        this._chart.redraw();
-      },
-      attached: function() {
-        this._attached = true;
-      },
-      detached: function() {
-        this._attached = false;
-      },
-      ready: function() {
-        this.scopeSubtree(this.$.tooltip, true);
-        this.scopeSubtree(this.$.chartsvg, true);
-      },
-      _makeChart: function(xType, yScaleType, colorScale, _attached) {
-        if (this._makeChartAsyncCallbackId !== null) {
-          this.cancelAsync(this._makeChartAsyncCallbackId);
-          this._makeChartAsyncCallbackId = null;
-        }
-
-        this._makeChartAsyncCallbackId = this.async(function() {
-          this._makeChartAsyncCallbackId = null;
-          if (!this._attached) return;
-          if (this._chart) this._chart.destroy();
-          var tooltip = d3.select(this.$.tooltip);
-          var chart = new VZ.LineChart(xType, yScaleType, colorScale, tooltip);
-          var svg = d3.select(this.$.chartsvg);
-          chart.renderTo(svg);
-          this._chart = chart;
-        }, 350);
-      },
-      _reloadFromCache: function() {
-        if(this._chart) {
-          this._chart.setVisibleSeries(this._visibleSeriesCache);
-          this._visibleSeriesCache.forEach(function(name) {
-            this._chart.setSeriesData(name, this._seriesDataCache[name] || []);
-          }.bind(this));
-        }
-      },
-      _smoothingChanged: function() {
-        if(!this._chart) {
-          return;
-        }
-        if(this.smoothingEnabled) {
-          this._chart.smoothingUpdate(this.smoothingWeight);
-        }
-        else {
-          this._chart.smoothingDisable();
-        }
-      },
-      _outliersChanged: function() {
-        if (!this._chart) {
-          return;
-        }
-        this._chart.ignoreYOutliers(this.ignoreYOutliers);
-      },
-      _tooltipSortingMethodChanged: function() {
-        if(this._chart) {
-          this._chart.setTooltipSortingMethod(this.tooltipSortingMethod);
-        }
-      },
-      _tooltipPositionChanged: function() {
-        if (this._chart) {
-          this._chart.setTooltipPosition(this.tooltipPosition);
-        }
-      }
-    });
-  </script>
-</dom-module>
-
-<dom-module id="tf-scalar-dashboard" assetpath="../tf-scalar-dashboard/">
-  <template>
-    <div id="plumbing">
-      <tf-color-scale id="colorScale" runs="[[runs]]" out-color-scale="{{_colorScale}}"></tf-color-scale>
-    </div>
-
-    <tf-dashboard-layout>
-      <div class="sidebar">
-        <tf-sidebar-helper backend="[[backend]]" categories="{{_categories}}" color-scale="[[_colorScale]]" run2tag="[[run2tag]]" runs="[[runs]]" selected-runs="{{_selectedRuns}}">
-          <div class="extend-first-section">
-            <div class="line-item">
-              <paper-checkbox id="download-option" checked="{{_showDownloadLinks}}">Show data download links</paper-checkbox>
-            </div>
-              <div class="line-item">
-                <paper-checkbox id="outliersCheckbox" checked="{{_ignoreYOutliers}}">Ignore outliers in chart scaling</paper-checkbox>
-              </div>
-            <div id="tooltip-sorting">
-              <div id="tooltip-sorting-label">Tooltip sorting method:</div>
-              <paper-dropdown-menu no-label-float="" selected-item-label="{{_tooltipSortingMethod}}">
-                <paper-menu class="dropdown-content" selected="0">
-                  <paper-item>default</paper-item>
-                  <paper-item>descending</paper-item>
-                  <paper-item>ascending</paper-item>
-                  <paper-item>nearest</paper-item>
-                </paper-menu>
-              </paper-dropdown-menu>
-            </div>
-          </div>
-          <div class="sidebar-section">
-            <tf-smoothing-input weight="{{_smoothingWeight}}" step="0.001" min="0" max="1"></tf-smoothing-input>
-          </div>
-          <div class="sidebar-section">
-            <tf-option-selector id="xTypeSelector" name="Horizontal Axis" selected-id="{{_xType}}">
-              <paper-button id="step">step</paper-button>
-              <paper-button id="relative">relative</paper-button>
-              <paper-button id="wall_time">wall</paper-button>
-            </tf-option-selector>
-          </div>
-        </tf-sidebar-helper>
-      </div>
-      <div class="center">
-        <tf-panes-helper categories="[[_categories]]" color-scale="[[_colorScale]]" data-type="[[dataType]]" data-provider="[[dataProvider]]" data-not-found="[[dataNotFound]]" run2tag="[[run2tag]]" selected-runs="[[_selectedRuns]]" show-download-links="[[_showDownloadLinks]]" download-link-url-function="[[scalarUrl]]">
-          <template>
-            <vz-line-chart x-type="[[_xType]]" color-scale="[[_colorScale]]" smoothing-enabled="[[_smoothingEnabled]]" smoothing-weight="[[_smoothingWeight]]" tooltip-sorting-method="[[_tooltipSortingMethod]]" ignore-y-outliers="[[_ignoreYOutliers]]"></vz-line-chart>
-            <paper-icon-button class="log-button" icon="line-weight" on-tap="toggleLogScale" title="Toggle y-axis log scale"></paper-icon-button>
-          </template>
-        </tf-panes-helper>
-      </div>
-    </tf-dashboard-layout>
-
-    <style include="dashboard-style"></style>
-    <style>
-      .log-button {
-        position: absolute;
-        left: 35px;
-        bottom: -35px;
-        color: #2196F3;
-        background: #fff;
-        width: 32px;
-        height: 32px;
-        padding: 4px;
-        border-radius: 100%;
-      }
-
-      .log-button-selected {
-        background: var(--tb-ui-light-accent);
-      }
-
-      #tooltip-sorting {
-        display: flex;
-        font-size: 14px;
-        margin-top: 5px;
-      }
-
-      #tooltip-sorting-label {
-        margin-top: 13px;
-        margin-left: 28px;
-      }
-
-      #tooltip-sorting paper-dropdown-menu {
-        margin-left: 10px;
-        --paper-input-container-focus-color: var(--tb-orange-strong);
-        width: 105px;
-      }
-      .line-item {
-        display: block;
-        padding-top: 5px;
-      }
-    </style>
-
-  </template>
-
-  <script>
-    TF.Dashboard.TfScalarDashboard = Polymer({
-      is: "tf-scalar-dashboard",
-      factoryImpl: function(backend, router) {
-        this.backend = backend;
-        this.router = router;
-      },
-      behaviors: [
-        TF.Dashboard.DashboardBehavior("scalars"),
-        TF.Dashboard.ReloadBehavior("tf-chart-scaffold"),
-        TF.Backend.Behavior,
-      ],
-      properties: {
-        backend: Object,
-        dataType: {
-          type: String,
-          value: "scalar"
-        },
-        router: Object,
-        scalarUrl: {
-          type: Function,
-          computed: "_getScalarUrl(router)"
-        },
-        _showDownloadLinks: {
-          type: Boolean,
-          notify: true,
-          value: TF.URIStorage.getBooleanInitializer('_showDownloadLinks',
-              false, true),
-          observer: '_showDownloadLinksObserver'
-        },
-        _smoothingWeight: {
-          type: Number,
-          notify: true,
-          value: TF.URIStorage.getNumberInitializer('_smoothingWeight', 0.6),
-          observer: '_smoothingWeightObserver'
-        },
-        _smoothingEnabled: {
-          type: Boolean,
-          computed: '_computeSmoothingEnabled(_smoothingWeight)'
-        },
-        _ignoreYOutliers: {
-          type: Boolean,
-          value: TF.URIStorage.getBooleanInitializer('_ignoreYOutliers', true, true),
-          observer: '_ignoreYOutliersObserver',
-        },
-        _xType: {
-          type: String,
-          value: "step"
-        }
-      },
-      attached: function() {
-        this.async(function() {
-          this.fire("rendered");
-        });
-      },
-      _getScalarUrl: function() {
-        return this.router.scalars;
-      },
-      _showDownloadLinksObserver: TF.URIStorage.getBooleanObserver(
-          '_showDownloadLinks', /*default=*/ false, /*useLocalStorage=*/ true),
-      _smoothingWeightObserver: TF.URIStorage.getNumberObserver(
-          '_smoothingWeight', 0.6),
-      _ignoreYOutliersObserver: TF.URIStorage.getBooleanObserver(
-          '_ignoreYOutliers', /*default=*/ true, /*useLocalStorage=*/true),
-      _computeSmoothingEnabled: function(_smoothingWeight) {
-        return _smoothingWeight > 0;
-      },
-      toggleLogScale: function(e) {
-        var currentTarget = Polymer.dom(e.currentTarget);
-        var button = currentTarget.parentNode.querySelector('.log-button');
-        var chart = currentTarget.parentNode.querySelector('vz-line-chart');
-
-        button.classList.toggle("log-button-selected");
-        chart.yScaleType = chart.yScaleType === 'log' ? 'linear' : 'log';
-        chart.redraw();
-      },
-    });
-  </script>
-</dom-module>
-<dom-module id="vz-distribution-chart" assetpath="../vz-distribution-chart/">
-  <template>
-    <svg id="chartsvg"></svg>
-    <style>
-      :host {
-        -webkit-user-select: none;
-        -moz-user-select: none;
-        display: flex;
-        flex-direction: column;
-        flex-grow: 1;
-        flex-shrink: 1;
-        position: relative;
-      }
-      svg {
-        -webkit-user-select: none;
-        -moz-user-select: none;
-        flex-grow: 1;
-        flex-shrink: 1;
-      }
-
-    </style>
-  </template>
-  <script>/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-/* tslint:disable:no-namespace variable-name */
-var VZ;
-(function (VZ) {
-    var DistributionChart = (function () {
-        function DistributionChart(xType, colorScale) {
-            this.run2datasets = {};
-            this.colorScale = colorScale;
-            this.buildChart(xType);
-        }
-        DistributionChart.prototype.getDataset = function (run) {
-            if (this.run2datasets[run] === undefined) {
-                this.run2datasets[run] = new Plottable.Dataset([], { run: run });
-            }
-            return this.run2datasets[run];
-        };
-        DistributionChart.prototype.buildChart = function (xType) {
-            if (this.outer) {
-                this.outer.destroy();
-            }
-            var xComponents = VZ.ChartHelpers.getXComponents(xType);
-            this.xAccessor = xComponents.accessor;
-            this.xScale = xComponents.scale;
-            this.xAxis = xComponents.axis;
-            this.xAxis.margin(0).tickLabelPadding(3);
-            this.yScale = new Plottable.Scales.Linear();
-            this.yAxis = new Plottable.Axes.Numeric(this.yScale, 'left');
-            var yFormatter = VZ.ChartHelpers.multiscaleFormatter(VZ.ChartHelpers.Y_AXIS_FORMATTER_PRECISION);
-            this.yAxis.margin(0).tickLabelPadding(5).formatter(yFormatter);
-            this.yAxis.usesTextWidthApproximation(true);
-            var center = this.buildPlot(this.xAccessor, this.xScale, this.yScale);
-            this.gridlines =
-                new Plottable.Components.Gridlines(this.xScale, this.yScale);
-            this.center = new Plottable.Components.Group([this.gridlines, center]);
-            this.outer = new Plottable.Components.Table([[this.yAxis, this.center], [null, this.xAxis]]);
-        };
-        DistributionChart.prototype.buildPlot = function (xAccessor, xScale, yScale) {
-            var _this = this;
-            var percents = [0, 228, 1587, 3085, 5000, 6915, 8413, 9772, 10000];
-            var opacities = _.range(percents.length - 1)
-                .map(function (i) { return (percents[i + 1] - percents[i]) / 2500; });
-            var accessors = percents.map(function (p, i) { return function (datum) { return datum[i][1]; }; });
-            var median = 4;
-            var medianAccessor = accessors[median];
-            var plots = _.range(accessors.length - 1).map(function (i) {
-                var p = new Plottable.Plots.Area();
-                p.x(xAccessor, xScale);
-                var y0 = i > median ? accessors[i] : accessors[i + 1];
-                var y = i > median ? accessors[i + 1] : accessors[i];
-                p.y(y, yScale);
-                p.y0(y0);
-                p.attr('fill', function (d, i, dataset) {
-                    return _this.colorScale.scale(dataset.metadata().run);
-                });
-                p.attr('stroke', function (d, i, dataset) {
-                    return _this.colorScale.scale(dataset.metadata().run);
-                });
-                p.attr('stroke-weight', function (d, i, m) { return '0.5px'; });
-                p.attr('stroke-opacity', function () { return opacities[i]; });
-                p.attr('fill-opacity', function () { return opacities[i]; });
-                return p;
-            });
-            var medianPlot = new Plottable.Plots.Line();
-            medianPlot.x(xAccessor, xScale);
-            medianPlot.y(medianAccessor, yScale);
-            medianPlot.attr('stroke', function (d, i, m) { return _this.colorScale.scale(m.run); });
-            this.plots = plots;
-            return new Plottable.Components.Group(plots);
-        };
-        DistributionChart.prototype.setVisibleSeries = function (runs) {
-            var _this = this;
-            this.runs = runs;
-            var datasets = runs.map(function (r) { return _this.getDataset(r); });
-            this.plots.forEach(function (p) { return p.datasets(datasets); });
-        };
-        /**
-         * Set the data of a series on the chart.
-         */
-        DistributionChart.prototype.setSeriesData = function (name, data) {
-            this.getDataset(name).data(data);
-        };
-        DistributionChart.prototype.renderTo = function (targetSVG) {
-            this.targetSVG = targetSVG;
-            this.setViewBox();
-            this.outer.renderTo(targetSVG);
-        };
-        /** There's an issue in Chrome where the svg overflow is a bit
-         * "flickery". There is a border on the gridlines on the extreme edge of the
-         * chart, which behaves inconsistently and causes the screendiffing tests to
-         * flake. We can solve this by creating 1px effective margin for the svg by
-         * setting the viewBox on the containing svg.
-         */
-        DistributionChart.prototype.setViewBox = function () {
-            // There's an issue in Firefox where if we measure with the old viewbox
-            // set, we get horrible results.
-            this.targetSVG.attr('viewBox', null);
-            var parent = this.targetSVG.node().parentNode;
-            var w = parent.clientWidth;
-            var h = parent.clientHeight;
-            this.targetSVG.attr({
-                'height': h,
-                'viewBox': "0 0 " + (w + 1) + " " + (h + 1),
-            });
-        };
-        DistributionChart.prototype.redraw = function () {
-            this.outer.redraw();
-            this.setViewBox();
-        };
-        DistributionChart.prototype.destroy = function () { this.outer.destroy(); };
-        return DistributionChart;
-    }());
-    VZ.DistributionChart = DistributionChart;
-})(VZ || (VZ = {}));
-</script>
-  <script>/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-/* tslint:disable:no-namespace variable-name */
-var VZ;
-(function (VZ) {
-    var ChartHelpers;
-    (function (ChartHelpers) {
-        ChartHelpers.Y_TOOLTIP_FORMATTER_PRECISION = 4;
-        ChartHelpers.STEP_FORMATTER_PRECISION = 4;
-        ChartHelpers.Y_AXIS_FORMATTER_PRECISION = 3;
-        ChartHelpers.TOOLTIP_Y_PIXEL_OFFSET = 20;
-        ChartHelpers.TOOLTIP_CIRCLE_SIZE = 4;
-        ChartHelpers.NAN_SYMBOL_SIZE = 6;
-        /* Create a formatter function that will switch between exponential and
-         * regular display depending on the scale of the number being formatted,
-         * and show `digits` significant digits.
-         */
-        function multiscaleFormatter(digits) {
-            return function (v) {
-                var absv = Math.abs(v);
-                if (absv < 1E-15) {
-                    // Sometimes zero-like values get an annoying representation
-                    absv = 0;
-                }
-                var f;
-                if (absv >= 1E4) {
-                    f = d3.format('.' + digits + 'e');
-                }
-                else if (absv > 0 && absv < 0.01) {
-                    f = d3.format('.' + digits + 'e');
-                }
-                else {
-                    f = d3.format('.' + digits + 'g');
-                }
-                return f(v);
-            };
-        }
-        ChartHelpers.multiscaleFormatter = multiscaleFormatter;
-        /* Compute an appropriate domain given an array of all the values that are
-         * going to be displayed. If ignoreOutliers is true, it will ignore the
-         * lowest 10% and highest 10% of the data when computing a domain.
-         * It has n log n performance when ignoreOutliers is true, as it needs to
-         * sort the data.
-         */
-        function computeDomain(values, ignoreOutliers) {
-            if (values.length === 0) {
-                return [-0.1, 1.1];
-            }
-            var a;
-            var b;
-            if (ignoreOutliers) {
-                var sorted = _.sortBy(values);
-                a = d3.quantile(sorted, 0.05);
-                b = d3.quantile(sorted, 0.95);
-            }
-            else {
-                a = d3.min(values);
-                b = d3.max(values);
-            }
-            var padding;
-            var span = b - a;
-            if (span === 0) {
-                // If b===a, we would create an empty range. We instead select the range
-                // [0, 2*a] if a > 0, or [-2*a, 0] if a < 0, plus a little bit of
-                // extra padding on the top and bottom of the plot.
-                padding = Math.abs(a) * 1.1 + 1.1;
-            }
-            else {
-                padding = span * 0.2;
-            }
-            var lower;
-            if (a >= 0 && a < span) {
-                // We include the intercept (y = 0) if doing so less than doubles the span
-                // of the y-axis. (We actually select a lower bound that's slightly less
-                // than 0 so that 0.00 will clearly be written on the lower edge of the
-                // chart. The label on the lowest tick is often filtered out.)
-                lower = -0.1 * b;
-            }
-            else {
-                lower = a - padding;
-            }
-            var domain = [lower, b + padding];
-            domain = d3.scale.linear().domain(domain).nice().domain();
-            return domain;
-        }
-        ChartHelpers.computeDomain = computeDomain;
-        function accessorize(key) {
-            return function (d, index, dataset) { return d[key]; };
-        }
-        ChartHelpers.accessorize = accessorize;
-        ChartHelpers.stepFormatter = Plottable.Formatters.siSuffix(ChartHelpers.STEP_FORMATTER_PRECISION);
-        function stepX() {
-            var scale = new Plottable.Scales.Linear();
-            var axis = new Plottable.Axes.Numeric(scale, 'bottom');
-            axis.formatter(ChartHelpers.stepFormatter);
-            return {
-                scale: scale,
-                axis: axis,
-                accessor: function (d) { return d.step; },
-            };
-        }
-        ChartHelpers.stepX = stepX;
-        ChartHelpers.timeFormatter = Plottable.Formatters.time('%a %b %e, %H:%M:%S');
-        function wallX() {
-            var scale = new Plottable.Scales.Time();
-            return {
-                scale: scale,
-                axis: new Plottable.Axes.Time(scale, 'bottom'),
-                accessor: function (d) { return d.wall_time; },
-            };
-        }
-        ChartHelpers.wallX = wallX;
-        ChartHelpers.relativeAccessor = function (d, index, dataset) {
-            // We may be rendering the final-point datum for scatterplot.
-            // If so, we will have already provided the 'relative' property
-            if (d.relative != null) {
-                return d.relative;
-            }
-            var data = dataset.data();
-            // I can't imagine how this function would be called when the data is
-            // empty (after all, it iterates over the data), but lets guard just
-            // to be safe.
-            var first = data.length > 0 ? +data[0].wall_time : 0;
-            return (+d.wall_time - first) / (60 * 60 * 1000); // ms to hours
-        };
-        ChartHelpers.relativeFormatter = function (n) {
-            // we will always show 2 units of precision, e.g days and hours, or
-            // minutes and seconds, but not hours and minutes and seconds
-            var ret = '';
-            var days = Math.floor(n / 24);
-            n -= (days * 24);
-            if (days) {
-                ret += days + 'd ';
-            }
-            var hours = Math.floor(n);
-            n -= hours;
-            n *= 60;
-            if (hours || days) {
-                ret += hours + 'h ';
-            }
-            var minutes = Math.floor(n);
-            n -= minutes;
-            n *= 60;
-            if (minutes || hours || days) {
-                ret += minutes + 'm ';
-            }
-            var seconds = Math.floor(n);
-            return ret + seconds + 's';
-        };
-        function relativeX() {
-            var scale = new Plottable.Scales.Linear();
-            return {
-                scale: scale,
-                axis: new Plottable.Axes.Numeric(scale, 'bottom'),
-                accessor: ChartHelpers.relativeAccessor,
-            };
-        }
-        ChartHelpers.relativeX = relativeX;
-        // a very literal definition of NaN: true for NaN for a non-number type
-        // or null, etc. False for Infinity or -Infinity
-        ChartHelpers.isNaN = function (x) { return +x !== x; };
-        function getXComponents(xType) {
-            switch (xType) {
-                case 'step':
-                    return stepX();
-                case 'wall_time':
-                    return wallX();
-                case 'relative':
-                    return relativeX();
-                default:
-                    throw new Error('invalid xType: ' + xType);
-            }
-        }
-        ChartHelpers.getXComponents = getXComponents;
-    })(ChartHelpers = VZ.ChartHelpers || (VZ.ChartHelpers = {}));
-})(VZ || (VZ = {}));
-</script>
-  <script>
-    Polymer({
-      is: "vz-distribution-chart",
-      properties: {
-        /**
-         * Scale that maps series names to colors. The default colors are from
-         * d3.scale.category10() scale. Use this property to replace the default
-         * line colors with colors of your own choice.
-         * @type {Plottable.Scales.Color}
-         * @required
-         */
-        colorScale: {
-          type: Object,
-          value: function() {
-            return new Plottable.Scales.Color()
-                .range(d3.scale.category10().range());
-          }
-        },
-        /**
-         * The way to display the X values. Allows:
-         * - "step" - Linear scale using the  "step" property of the datum.
-         * - "wall_time" - Temporal scale using the "wall_time" property of the
-         * datum.
-         * - "relative" - Temporal scale using the "relative" property of the
-         * datum if it is present or calculating from "wall_time" if it isn't.
-         */
-        xType: {
-          type: String,
-          value: 'step'
-        },
-        _attached: Boolean,
-        _chart: Object,
-        _visibleSeriesCache: {
-          type: Array,
-          value: function() { return [] }
-        },
-        _seriesDataCache: {
-          type: Object,
-          value: function() { return {} }
-        },
-        _makeChartAsyncCallbackId: { type: Number, value: null }
-      },
-      observers: [
-        "_makeChart(xType, colorScale, _attached)",
-        "_reloadFromCache(_chart)",
-      ],
-      setVisibleSeries: function(names) {
-        this._visibleSeriesCache = names;
-        if (this._chart) {
-          this._chart.setVisibleSeries(names);
-          this.redraw();
-        }
-      },
-      setSeriesData: function(name, data) {
-        this._seriesDataCache[name] = data;
-        if (this._chart) {
-          this._chart.setSeriesData(name, data);
-        }
-      },
-      redraw: function() {
-        this._chart.redraw();
-      },
-      ready: function() {
-        this.scopeSubtree(this.$.chartsvg, true);
-      },
-      _makeChart: function(xType, colorScale, _attached) {
-        if (this._makeChartAsyncCallbackId === null) {
-          this.cancelAsync(this._makeChartAsyncCallbackId);
-        }
-
-        this._makeChartAsyncCallbackId = this.async(function() {
-          this._makeChartAsyncCallbackId = null;
-          if (!_attached) return;
-          if (this._chart) this._chart.destroy();
-          var chart = new VZ.DistributionChart(xType, colorScale);
-          var svg = d3.select(this.$.chartsvg);
-          chart.renderTo(svg);
-          this._chart = chart;
-        }, 350);
-      },
-      _reloadFromCache: function() {
-        if(this._chart) {
-          this._chart.setVisibleSeries(this._visibleSeriesCache);
-          this._visibleSeriesCache.forEach(function(name) {
-            this._chart.setSeriesData(name, this._seriesDataCache[name] || []);
-          }.bind(this));
-        }
-      },
-      attached: function() {
-        this._attached = true;
-      },
-      detached: function() {
-        this._attached = false;
-      }
-    });
-  </script>
-</dom-module>
-
-<dom-module id="tf-distribution-dashboard" assetpath="../tf-distribution-dashboard/">
-  <template>
-    <div id="plumbing">
-      <tf-color-scale id="colorScale" runs="[[runs]]" out-color-scale="{{_colorScale}}"></tf-color-scale>
-    </div>
-
-    <tf-dashboard-layout>
-      <div class="sidebar">
-        <tf-sidebar-helper backend="[[backend]]" categories="{{_categories}}" color-scale="[[_colorScale]]" run2tag="[[run2tag]]" runs="[[runs]]" selected-runs="{{_selectedRuns}}">
-        <div class="sidebar-section">
-          <tf-option-selector id="xTypeSelector" name="Horizontal Axis" selected-id="{{_xType}}">
-            <paper-button id="step">step</paper-button>
-            <paper-button id="relative">relative</paper-button>
-            <paper-button id="wall_time">wall</paper-button>
-          </tf-option-selector>
-        </div>
-        </tf-sidebar-helper>
-      </div>
-
-      <div class="center">
-        <tf-panes-helper categories="[[_categories]]" color-scale="[[_colorScale]]" data-type="[[dataType]]" data-provider="[[dataProvider]]" data-not-found="[[dataNotFound]]" run2tag="[[run2tag]]" selected-runs="[[_selectedRuns]]" repeat-for-runs="">
-          <template>
-            <vz-distribution-chart x-type="[[_xType]]" color-scale="[[_colorScale]]"></vz-distribution-chart>
-          </template>
-        </tf-panes-helper>
-      </div>
-    </tf-dashboard-layout>
-
-    <style include="dashboard-style"></style>
-  </template>
-
-  <script>
-    TF.Dashboard.TfDistributionDashboard = Polymer({
-      is: "tf-distribution-dashboard",
-      factoryImpl: function(backend) {
-        this.backend = backend;
-      },
-      behaviors: [
-        TF.Dashboard.DashboardBehavior("distributions"),
-        TF.Dashboard.ReloadBehavior("tf-chart-scaffold"),
-        TF.Backend.Behavior,
-      ],
-      properties: {
-        backend: Object,
-        _xType: {
-          type: String,
-          value: "step"
-        },
-        dataType: {value: "compressedHistogram"},
-      },
-    });
-  </script>
-</dom-module>
-
-<dom-module id="vz-histogram-timeseries" assetpath="../vz-histogram-timeseries/">
-    <template>
-      <div id="tooltip"><span></span></div>
-      <svg id="svg">
-        <g>
-          <g class="axis x"></g>
-          <g class="axis y"></g>
-          <g class="axis y slice"></g>
-          <g class="stage">
-            <rect class="background"></rect>
-          </g>
-          <g class="x-axis-hover"></g>
-          <g class="y-axis-hover"></g>
-          <g class="y-slice-axis-hover"></g>
-        </g>
-      </svg>
-
-      <style>
-        :host {
-          display: flex;
-          flex-direction: column;
-          flex-grow: 1;
-          flex-shrink: 1;
-          position: relative;
-        }
-
-        svg {
-          font-family: roboto, sans-serif;
-          overflow: visible;
-          display: block;
-          width: 100%;
-          flex-grow: 1;
-          flex-shrink: 1;
-        }
-
-        #tooltip {
-          position: absolute;
-          display: block;
-          opacity: 0;
-          font-weight: bold;
-          font-size: 11px;
-        }
-
-        .background {
-          fill-opacity: 0;
-          fill: red;
-        }
-
-        .histogram {
-          pointer-events: none;
-        }
-
-        .hover {
-          font-size: 9px;
-          dominant-baseline: middle;
-          opacity: 0;
-        }
-
-        .hover circle {
-          stroke: white;
-          stroke-opacity: 0.5;
-          stroke-width: 1px;
-        }
-
-        .hover text {
-          fill: black;
-          opacity: 0;
-        }
-
-        .hover.hover-closest circle {
-          fill: black!important;
-        }
-
-        .hover.hover-closest text {
-          opacity: 1;
-        }
-
-        .baseline {
-          stroke: black;
-          stroke-opacity: 0.1;
-        }
-
-        .outline {
-          fill: none;
-          stroke: white;
-          stroke-opacity: 0.5;
-        }
-
-        .outline.outline-hover {
-          stroke: black!important;
-          stroke-opacity: 1;
-        }
-
-        .x-axis-hover,
-        .y-axis-hover,
-        .y-slice-axis-hover {
-          pointer-events: none;
-        }
-
-        .x-axis-hover .label,
-        .y-axis-hover .label,
-        .y-slice-axis-hover .label {
-          opacity: 0;
-          font-weight: bold;
-          font-size: 11px;
-          text-anchor: end;
-        }
-
-        .x-axis-hover text {
-          text-anchor: middle;
-        }
-
-        .y-axis-hover text,
-        .y-slice-axis-hover text {
-          text-anchor: start;
-        }
-
-        .x-axis-hover line,
-        .y-axis-hover line,
-        .y-slice-axis-hover line {
-          stroke: black;
-        }
-
-        .x-axis-hover rect,
-        .y-axis-hover rect,
-        .y-slice-axis-hover rect {
-          fill: white;
-        }
-
-        .axis {
-          font-size: 10px;
-          fill: #aaa;
-        }
-
-        .axis path.domain {
-          fill: none;
-        }
-
-        .axis .tick line {
-          stroke: #ddd;
-        }
-
-        .axis.slice {
-          opacity: 0;
-        }
-
-        .axis.slice .tick line {
-          stroke-dasharray: 2;
-        }
-
-        .small .axis text { display: none; }
-        .small .axis .tick:first-of-type text { display: block; }
-        .small .axis .tick:last-of-type text { display: block; }
-        .medium .axis text { display: none; }
-        .medium .axis .tick:nth-child(2n + 1) text { display: block; }
-        .large .axis text { display: none; }
-        .large .axis .tick:nth-child(2n + 1) text { display: block; }
-
-      </style>
-    </template>
-
-    <script>
-    Polymer({
-      is: "vz-histogram-timeseries",
-      properties: {
-        /**
-         * Defines which view mode is being used by the chart. Supported values
-         * are:
-         * - "offset" - Offset view of the data showing all timesteps.
-         * - "overlay" - Overlays all timesteps into one 2D view, with the
-         * brighter lines representing the newer timesteps.
-         */
-        mode: {
-          type: String,
-          value: "offset"
-        },
-
-        /*
-         * The name of the datum's property that contains the time values.
-         * Allows:
-         * - "step" - Linear scale using the "step" property of the datum.
-         * - "wall_time" - Temporal scale using the "wall_time" property of the
-         * datum.
-         * - "relative" - Temporal scale starting at 0 created by using
-         * the "wall_time" property of the datum.
-         */
-        timeProperty: {
-          type: String,
-          value: "step"
-        },
-
-        /**
-         * The name of the data's property that contains the bins.
-         */
-        bins: {
-          type: String,
-          value: "bins"
-        },
-
-        /**
-         * The name of the datum's property that contains the x values.
-         */
-        x: {
-          type: String,
-          value: "x"
-        },
-
-        /**
-         * The name of the datum's property that contains the bin width values.
-         */
-        dx: {
-          type: String,
-          value: "dx"
-        },
-
-        /**
-         * The name of the datum's property that contains the bin height.
-         */
-        y: {
-          type: String,
-          value: "y"
-        },
-
-        /**
-         * Scale that maps series names to colors. The default colors are from
-         * d3.scale.category10() scale. Use this property to replace the default
-         * line colors with colors of your own choice.
-         */
-        colorScale: {
-          type: Object,
-          value: function() {
-            return d3.scale.category10();
-          }
-        },
-
-        /**
-         * Duration of the transition between histogram modes.
-         */
-        modeTransitionDuration: {
-          type: Number,
-          value: 500
-        },
-
-        _attached: Boolean,
-        _name: { type: String, value: null },
-        _data: { type: Array, value: null },
-      },
-      observers: [
-        'redraw(timeProperty, _attached)',
-        '_modeRedraw(mode)'
-      ],
-      ready: function() {
-        // Polymer's way of scoping styles on nodes that d3 created
-        this.scopeSubtree(this.$.svg, true);
-      },
-      attached: function() {
-        this._attached = true;
-      },
-      detached: function() {
-        this._attached = false;
-      },
-      setVisibleSeries: function(names) {
-        // Do nothing.
-      },
-      setSeriesData: function(name, data) {
-        this._name = name;
-        this._data = data;
-        this.redraw();
-      },
-
-      /**
-       * Redraws the chart. This is only called if the chart is attached to the
-       * screen and if the chart has data.
-       */
-      redraw: function() {
-        this._draw(0);
-      },
-
-      _modeRedraw: function() {
-        this._draw(this.modeTransitionDuration);
-      },
-
-      _draw: function(duration) {
-        if (!this._attached || !this._data) {
-          return;
-        }
-
-        //
-        // Data verification
-        //
-        if (duration === undefined) throw(new Error("vz-histogram-timeseries _draw needs duration"));
-        if (this._data.length <= 0) throw(new Error("Not enough steps in the data"));
-        if (!this._data[0].hasOwnProperty(this.bins)) throw(new Error("No bins property of '" + this.bins + "' in data"));
-        if (this._data[0][this.bins].length <= 0) throw(new Error("Must have at least one bin in bins in data"));
-        if (!this._data[0][this.bins][0].hasOwnProperty(this.x)) throw(new Error("No x property '" + this.x + "' on bins data"));
-        if (!this._data[0][this.bins][0].hasOwnProperty(this.dx)) throw(new Error("No dx property '" + this.dx + "' on bins data"));
-        if (!this._data[0][this.bins][0].hasOwnProperty(this.y)) throw(new Error("No y property '" + this.y + "' on bins data"));
-
-        //
-        // Initialization
-        //
-        var timeProp = this.timeProperty;
-        var xProp = this.x;
-        var binsProp = this.bins;
-        var dxProp = this.dx;
-        var yProp = this.y;
-
-        var data = this._data;
-        var name = this._name;
-        var mode = this.mode;
-        var color = d3.hcl(this.colorScale(name));
-        var tooltip = d3.select(this.$.tooltip);
-
-        var xAccessor = function(d) { return d[xProp] };
-        var yAccessor = function(d) { return d[yProp] };
-        var dxAccessor = function(d) { return d[dxProp] };
-        var xRightAccessor = function(d) { return d[xProp] + d[dxProp] };
-        var timeAccessor = function(d) { return d[timeProp] };
-
-        if (timeProp === "relative") {
-          timeAccessor = function(d) { return d.wall_time - data[0].wall_time };
-        }
-
-        var brect = this.$.svg.getBoundingClientRect();
-        var outerWidth = brect.width,
-            outerHeight = brect.height;
-
-        var sliceHeight,
-            margin = {top: 5, right: 60, bottom: 20, left: 24};
-
-        if (mode === "offset") {
-          sliceHeight = outerHeight / 2.5;
-          margin.top = sliceHeight + 5;
-        } else {
-          sliceHeight = outerHeight - margin.top - margin.bottom;
-        }
-
-        var width = outerWidth - margin.left - margin.right,
-            height = outerHeight - margin.top - margin.bottom;
-
-        var leftMin = d3.min(data, xAccessor),
-            rightMax = d3.max(data, xRightAccessor);
-
-        //
-        // Text formatters
-        //
-        var format = d3.format(".3n");
-        var yAxisFormat = d3.format(".0f");
-
-        if (timeProp === "wall_time") {
-          yAxisFormat = d3.time.format("%m/%d %X");
-        } else if (timeProp === "relative") {
-          yAxisFormat = function(d) {
-            return d3.format(".1r")(d / 3.6e6) + 'h'; // Convert to hours.
-          };
-        }
-
-        //
-        // Calculate the extents
-        //
-        var xExtents = data.map(function(d, i) {
-          return [
-            d3.min(d[binsProp], xAccessor),
-            d3.max(d[binsProp], xRightAccessor)
-          ];
-        });
-        var yExtents = data.map(function(d) {
-          return d3.extent(d[binsProp], yAccessor);
-        });
-
-        //
-        // Scales and axis
-        //
-        var outlineCanvasSize = 500;
-
-        var extent = d3.extent(data, timeAccessor);
-
-        var yScale = (timeProp === "wall_time" ? d3.time.scale() : d3.scale.linear())
-            .domain(extent)
-            .range([0, (mode === "offset" ? height : 0)]);
-
-        var ySliceScale = d3.scale.linear()
-            .domain([0, d3.max(data, function(d, i) { return yExtents[i][1]; })])
-            .range([sliceHeight, 0]);
-
-        var yLineScale = d3.scale.linear()
-            .domain(ySliceScale.domain())
-            .range([outlineCanvasSize, 0]);
-
-        var xScale = d3.scale.linear()
-            .domain([
-              d3.min(data, function(d, i) { return xExtents[i][0]; }),
-              d3.max(data, function(d, i) { return xExtents[i][1]; })
-            ])
-            .nice()
-            .range([0, width]);
-
-        var xLineScale = d3.scale.linear()
-            .domain(xScale.domain())
-            .range([0, outlineCanvasSize]);
-
-        var outlineColor = d3.scale.linear()
-            .domain(d3.extent(data, timeAccessor))
-            .range([color.darker(), color.brighter()])
-            .interpolate(d3.interpolateHcl);
-
-        var xAxis = d3.svg.axis()
-            .scale(xScale)
-            .ticks(Math.max(2, width / 20))
-            .orient("bottom");
-
-        var yAxis = d3.svg.axis()
-            .scale(yScale)
-            .ticks(Math.max(2, height / 15))
-            .tickFormat(yAxisFormat)
-            .orient("right");
-
-        var ySliceAxis = d3.svg.axis()
-            .scale(ySliceScale)
-            .ticks(Math.max(2, height / 15))
-            .tickSize(width + 5)
-            .tickFormat(format)
-            .orient("right");
-
-        var xBinCentroid = function(d) {
-          return d[xProp] + d[dxProp] / 2;
-        };
-
-        var linePath = d3.svg.line()
-            .interpolate("linear")
-            .x(function(d) { return xLineScale(xBinCentroid(d)); })
-            .y(function(d) { return yLineScale(d[yProp]); });
-
-        var path = function(d) {
-          // Draw a line from 0 to the first point and from the last point to 0.
-          return 'M' + xLineScale(xBinCentroid(d[0])) + ',' + yLineScale(0) +
-              'L' + linePath(d).slice(1) +
-              "L" + xLineScale(xBinCentroid(d[d.length - 1])) + "," + yLineScale(0);
-        };
-
-        //
-        // Render
-        //
-        var svgNode = this.$.svg;
-
-        var svg = d3.select(svgNode)
-
-        var svgTransition = svg.transition().duration(duration);
-
-        var g = svg.select("g")
-            .classed("small", function() { return (width > 0 && width <= 150); })
-            .classed("medium", function() { return (width > 150 && width <= 300); })
-            .classed("large", function() { return (width > 300); })
-
-        var gTransition = svgTransition.select("g")
-            .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
-
-        var bisect = d3.bisector(xRightAccessor).left;
-        var stage = g.select(".stage")
-            .on("mouseover", function() {
-              hoverUpdate.style("opacity", 1);
-              xAxisHoverUpdate.style("opacity", 1);
-              yAxisHoverUpdate.style("opacity", 1);
-              ySliceAxisHoverUpdate.style("opacity", 1);
-              tooltip.style("opacity", 1);
-            })
-            .on("mouseout", function() {
-              hoverUpdate.style("opacity", 0);
-              xAxisHoverUpdate.style("opacity", 0);
-              yAxisHoverUpdate.style("opacity", 0);
-              ySliceAxisHoverUpdate.style("opacity", 0);
-              hoverUpdate.classed("hover-closest", false);
-              outlineUpdate.classed("outline-hover", false);
-              tooltip.style("opacity", 0);
-            })
-            .on("mousemove", onMouseMove);
-
-        var background = stage.select(".background")
-            .attr("transform", "translate(" + -margin.left + "," + -margin.top + ")")
-            .attr("width", outerWidth)
-            .attr("height", outerHeight);
-
-        var histogram = stage.selectAll(".histogram").data(data),
-            histogramExit = histogram.exit().remove(),
-            histogramEnter = histogram.enter().append("g").attr("class", "histogram"),
-            histogramUpdate = histogram
-                .sort(function(a, b) { return timeAccessor(a) - timeAccessor(b); }),
-            histogramTransition = gTransition.selectAll(".histogram")
-                .attr("transform", function(d) {
-                  return "translate(0, " +
-                    (mode === "offset" ? (yScale(timeAccessor(d)) - sliceHeight) : 0) + ")";
-                });
-
-        var baselineEnter = histogramEnter.append("line").attr("class", "baseline"),
-            baselineUpdate = histogramTransition.select(".baseline")
-                .style("stroke-opacity", function(d) { return (mode === "offset" ? 0.1 : 0); })
-                .attr("y1", sliceHeight)
-                .attr("y2", sliceHeight)
-                .attr("x2", width);
-
-        var outlineEnter = histogramEnter.append("path").attr("class", "outline"),
-            outlineUpdate = histogramUpdate.select(".outline")
-                .attr("vector-effect", "non-scaling-stroke")
-                .attr("d", function(d) { return path(d[binsProp]); })
-                .style("stroke-width", 1),
-            outlineTransition = histogramTransition.select(".outline")
-                .attr("transform", "scale(" + width / outlineCanvasSize + ", " +
-                      sliceHeight / outlineCanvasSize + ")")
-                .style("stroke", function(d) {
-                  return (mode === "offset" ? "white" : outlineColor(timeAccessor(d)));
-                })
-                .style("fill-opacity", function(d) { return (mode === "offset" ? 1 : 0); })
-                .style("fill", function(d) { return outlineColor(timeAccessor(d)); });
-
-        var hoverEnter = histogramEnter.append("g")
-                .attr("class", "hover")
-                .style("fill", function(d) { return outlineColor(timeAccessor(d)); }),
-            hoverUpdate = histogramUpdate.select(".hover");
-
-        hoverEnter.append("circle")
-            .attr("r", 2);
-
-        hoverEnter.append("text")
-            .style("display", "none")
-            .attr("dx", 4);
-
-        var xAxisHover = g.select(".x-axis-hover").selectAll(".label").data(["x"]),
-            xAxisHoverEnter = xAxisHover.enter().append("g").attr("class", "label"),
-            xAxisHoverUpdate = xAxisHover;
-
-        xAxisHoverEnter.append("rect")
-            .attr("x", -20)
-            .attr("y", 6)
-            .attr("width", 40)
-            .attr("height", 14)
-
-        xAxisHoverEnter.append("line")
-            .attr("x1", 0)
-            .attr("x2", 0)
-            .attr("y1", 0)
-            .attr("y2", 6);
-
-        xAxisHoverEnter.append("text")
-            .attr("dy", 18);
-
-        var yAxisHover = g.select(".y-axis-hover").selectAll(".label").data(["y"]),
-            yAxisHoverEnter = yAxisHover.enter().append("g").attr("class", "label"),
-            yAxisHoverUpdate = yAxisHover;
-
-        yAxisHoverEnter.append("rect")
-            .attr("x", 8)
-            .attr("y", -6)
-            .attr("width", 40)
-            .attr("height", 14)
-
-        yAxisHoverEnter.append("line")
-            .attr("x1", 0)
-            .attr("x2", 6)
-            .attr("y1", 0)
-            .attr("y2", 0);
-
-        yAxisHoverEnter.append("text")
-            .attr("dx", 8)
-            .attr("dy", 4);
-
-        var ySliceAxisHover = g.select(".y-slice-axis-hover").selectAll(".label").data(["y"]),
-            ySliceAxisHoverEnter = ySliceAxisHover.enter().append("g").attr("class", "label"),
-            ySliceAxisHoverUpdate = ySliceAxisHover;
-
-        ySliceAxisHoverEnter.append("rect")
-            .attr("x", 8)
-            .attr("y", -6)
-            .attr("width", 40)
-            .attr("height", 14)
-
-        ySliceAxisHoverEnter.append("line")
-            .attr("x1", 0)
-            .attr("x2", 6)
-            .attr("y1", 0)
-            .attr("y2", 0);
-
-        ySliceAxisHoverEnter.append("text")
-            .attr("dx", 8)
-            .attr("dy", 4);
-
-        gTransition.select(".y.axis.slice")
-            .style("opacity", mode === "offset" ? 0 : 1)
-            .attr("transform", "translate(0, " + (mode === "offset" ? -sliceHeight : 0) + ")")
-            .call(ySliceAxis);
-
-        gTransition.select(".x.axis")
-            .attr("transform", "translate(0, " + height + ")")
-            .call(xAxis);
-
-        gTransition.select(".y.axis")
-            .style("opacity", mode === "offset" ? 1 : 0)
-            .attr("transform", "translate(" + width + ", " + (mode === "offset" ? 0 : height) + ")")
-            .call(yAxis);
-
-        function onMouseMove() {
-          var m = d3.mouse(this),
-              v = xScale.invert(m[0]),
-              t = yScale.invert(m[1]);
-
-          function hoverXIndex(d) {
-            return Math.min(d[binsProp].length - 1, bisect(d[binsProp], v));
-          }
-          var closestSliceData;
-          var closestSliceDistance = Infinity;
-          var lastSliceData;
-          hoverUpdate
-            .attr("transform", function(d, i) {
-              var index = hoverXIndex(d);
-              lastSliceData = d;
-              var x = xScale(d[binsProp][index][xProp] + d[binsProp][index][dxProp] / 2);
-              var y = ySliceScale(d[binsProp][index][yProp]);
-              var globalY = (mode === "offset" ? yScale(timeAccessor(d)) - (sliceHeight - y) : y);
-              var dist = Math.abs(m[1] - globalY);
-              if (dist < closestSliceDistance) {
-                closestSliceDistance = dist;
-                closestSliceData = d;
-              }
-              return "translate(" + x + "," + y + ")";
-            });
-          hoverUpdate.select("text").text(function(d) {
-            var index = hoverXIndex(d);
-            return d[binsProp][index][yProp];
-          })
-          hoverUpdate.classed("hover-closest", function(d) { return d === closestSliceData; });
-          outlineUpdate.classed("outline-hover", function(d) { return d === closestSliceData; });
-
-          var index = hoverXIndex(lastSliceData);
-
-          xAxisHoverUpdate
-              .attr("transform", function(d) {
-                return "translate(" +
-                  xScale(lastSliceData[binsProp][index][xProp] +
-                         lastSliceData[binsProp][index][dxProp] / 2) + ", " +
-                  height + ")";
-              })
-            .select("text")
-              .text(function(d) { return format(lastSliceData[binsProp][index][xProp] +
-                                                lastSliceData[binsProp][index][dxProp] / 2); });
-
-          var fy = yAxis.tickFormat();
-          yAxisHoverUpdate
-              .attr("transform", function(d) {
-                return "translate(" + width + ", " +
-                  (mode === "offset" ? yScale(timeAccessor(closestSliceData)) : 0) + ")";
-              })
-              .style("display", mode === "offset" ? "" : "none")
-            .select("text")
-              .text(function(d) { return fy(timeAccessor(closestSliceData));});
-
-          var fsy = ySliceAxis.tickFormat();
-          ySliceAxisHoverUpdate
-              .attr("transform", function(d) {
-                return "translate(" + width + ", " +
-                  (mode === "offset" ? 0 : ySliceScale(closestSliceData[binsProp][index][yProp])) +
-                  ")";
-              })
-              .style("display", mode === "offset" ? "none" : "")
-            .select("text")
-              .text(function(d) { return fsy(closestSliceData[binsProp][index][yProp]); });
-
-          var svgMouse = d3.mouse(svgNode);
-          tooltip.style("transform", "translate(" + (svgMouse[0] + 15) + "px," +
-              (svgMouse[1] - 15) + "px)")
-            .select('span')
-            .text(mode === "offset" ?
-                fsy(closestSliceData[binsProp][index][yProp]) :
-                (timeProp === "step" ? "step " : "") +
-                fy(timeAccessor(closestSliceData)));
-        }
-      }
-    });
-    </script>
-
-  </dom-module>
-
-<dom-module id="tf-histogram-dashboard" assetpath="../tf-histogram-dashboard/">
-  <template>
-    <div id="plumbing">
-      <tf-color-scale id="colorScale" runs="[[runs]]" out-color-scale="{{_colorScale}}"></tf-color-scale>
-    </div>
-
-    <tf-dashboard-layout>
-      <div class="sidebar">
-        <tf-sidebar-helper backend="[[backend]]" categories="{{_categories}}" color-scale="[[_colorScale]]" run2tag="[[run2tag]]" runs="[[runs]]" selected-runs="{{_selectedRuns}}" show-download-links="{{_showDownloadLinks}}">
-          <div class="sidebar-section">
-            <tf-option-selector id="histogramModeSelector" name="Histogram Mode" selected-id="{{_histogramMode}}">
-              <paper-button id="overlay">overlay</paper-button>
-              <paper-button id="offset">offset</paper-button>
-            </tf-option-selector>
-          </div>
-          <div class="sidebar-section">
-            <tf-option-selector id="timePropertySelector" name="Offset Time Axis" selected-id="{{_timeProperty}}">
-              <paper-button id="step">step</paper-button>
-              <paper-button id="relative">relative</paper-button>
-              <paper-button id="wall_time">wall</paper-button>
-            </tf-option-selector>
-          
-       </div>
-      </tf-sidebar-helper></div>
-
-      <div class="center">
-        <tf-panes-helper categories="[[_categories]]" color-scale="[[_colorScale]]" data-type="[[dataType]]" data-provider="[[dataProvider]]" data-not-found="[[dataNotFound]]" run2tag="[[run2tag]]" selected-runs="[[_selectedRuns]]" repeat-for-runs="">
-          <template>
-            <vz-histogram-timeseries time-property="[[_timeProperty]]" mode="[[_histogramMode]]" color-scale="[[_colorScaleFunction]]"></vz-histogram-timeseries>
-          </template>
-        </tf-panes-helper>
-      </div>
-    </tf-dashboard-layout>
-
-    <style include="dashboard-style"></style>
-    <style>
-      tf-panes-helper {
-        --card-expanded-height: 500px;
-        --card-expanded-width: 700px;
-      }
-    </style>
-  </template>
-
-  <script>
-    TF.Dashboard.TfHistogramDashboard = Polymer({
-      is: "tf-histogram-dashboard",
-      factoryImpl: function(backend) {
-        this.backend = backend;
-      },
-      behaviors: [
-        TF.Dashboard.DashboardBehavior("histograms"),
-        TF.Dashboard.ReloadBehavior("tf-chart-scaffold"),
-        TF.Backend.Behavior,
-      ],
-      properties: {
-        backend: Object,
-        dataType: {
-          type: String,
-          value: "histogram"
-        },
-        _histogramMode: {
-          type: String,
-          value: "offset"
-        },
-        _timeProperty: {
-          type: String,
-          value: "step"
-        },
-        _colorScaleFunction: {
-          type: Function,
-          computed: "_getColorScaleFunction(_colorScale)"
-        },
-      },
-      attached: function() {
-        this.async(function() {
-          this.fire("rendered");
-        });
-      },
-      _getColorScaleFunction: function() {
-        return this._colorScale.scale.bind(this._colorScale);
-      },
-    });
-  </script>
-</dom-module>
-<link rel="import" href="../paper-spinner/paper-spinner-lite.html">
-
-<dom-module id="tf-image-loader" assetpath="../tf-image-dashboard/">
-  <template>
-    <div id="image-annotation">
-      <template is="dom-if" if="[[_hasAtLeastOneStep]]">
-        step
-        <span class="step-value">
-          [[_stepValue]]
-        </span>
-        <template is="dom-if" if="[[_currentWallTime]]">
-          ([[_currentWallTime]])
-        </template>
-        <paper-spinner-lite active="" hidden$="[[!_isImageLoading]]"></paper-spinner-lite>
-      </template>
-      <template is="dom-if" if="[[_hasMultipleSteps]]">
-        <paper-slider id="steps" immediate-value="{{_stepIndex}}" max="[[_maxStepIndex]]" max-markers="[[_maxStepIndex]]" snaps="" step="1" value="{{_stepIndex}}"></paper-slider>
-      </template>
-    </div>
-
-    <div id="main-image-container"></div>
-
-    <style>
-      :host {
-        display: block;
-        width: 100%;
-        height: auto;
-        position: relative;
-        --step-slider-knob-color: #424242;
-      }
-
-      #image-annotation {
-        border-left: 4px solid;
-        padding-left: 5px;
-        font-size: 12px;
-        margin: -10px 0 10px 0;
-      }
-
-      #image-annotation .step-value {
-        font-weight: bold;
-      }
-
-      #image-annotation paper-spinner-lite {
-        width: 14px;
-        height: 14px;
-        vertical-align: text-bottom;
-        --paper-spinner-color: var(--tb-orange-strong)
-      }
-
-      #steps {
-        height: 15px;
-        margin: 0 0 0 -15px;
-        /* 31 comes from adding a padding of 15px from both sides of the paper-slider, subtracting
-         * 1px so that the slider width aligns with the image (the last slider marker takes up 1px),
-         * and adding 2px to account for a border of 1px on both sides of the image. 30 - 1 + 2. */
-        width: calc(100% + 31px);
-        --paper-slider-active-color: var(--step-slider-knob-color);
-        --paper-slider-knob-color: var(--step-slider-knob-color);
-        --paper-slider-pin-color: var(--step-slider-knob-color);
-        --paper-slider-knob-start-color: var(--step-slider-knob-color);
-        --paper-slider-knob-start-border-color: var(--step-slider-knob-color);
-        --paper-slider-pin-start-color: var(--step-slider-knob-color);
-      }
-
-      #main-image-container img {
-        border: 1px solid #f5f5f5;
-        image-rendering: -moz-crisp-edges;
-        image-rendering: pixelated;
-        display: block;
-        width: 100%;
-        height: auto;
-      }
-    </style>
-  </template>
-  <script>
-    Polymer({
-      is: "tf-image-loader",
-      properties: {
-        colorScale: Object,
-        run: String,
-        // This is an array of Tensorboard Image&Datum objects (See backend.ts for details). The
-        // properties of objects in this array are
-        // {
-        //   width: number,
-        //   height: number,
-        //   wall_time: Date,
-        //   step: number,
-        //   url: string,
-        // }
-        _steps: {
-          type: Array,
-          value: [],
-          notify: true,
-        },
-        _stepIndex: {
-          type: Number,
-          notify: true,
-        },
-        _hasAtLeastOneStep: {
-          type: Boolean,
-          computed: "_computeHasAtLeastOneStep(_steps)",
-        },
-        _hasMultipleSteps: {
-          type: Boolean,
-          computed: "_computeHasMultipleSteps(_steps)",
-        },
-        _stepValue: {
-          type: Number,
-          computed: "_computeStepValue(_stepIndex)",
-        },
-        _currentWallTime: {
-          type: Number,
-          computed: "_computeCurrentWallTime(_stepIndex)",
-        },
-        _maxStepIndex: {
-          type: Number,
-          computed: "_computeMaxStepIndex(_steps)",
-        },
-        // We use a strictly increasing index to make sure that we don't settle on a stale image.
-        _currentImageLoadIndex: {
-          type: Number,
-          value: 1,
-        },
-        _isImageLoading: {
-          type: Boolean,
-          value: false,
-        },
-      },
-      observers: [
-        "_updateImageUrl(_steps, _stepIndex)",
-      ],
-      redraw: function() {
-        // Other dashboards logic requires a redraw method to be defined. redraw is called at
-        // various places such as when the image is expanded.
-        this.setSeriesData(this.run, this._steps);
-      },
-      setVisibleSeries: function(runs) {
-        // Do nothing.
-      },
-      setSeriesData: function(run, steps) {
-        this.set("run", run);
-        this.set("_steps", steps);
-        this.set("_stepIndex", steps.length - 1);
-
-        // Update the border color based on the run.
-        var color = this.colorScale.scale(run);
-        this.$$("#image-annotation").style.borderColor = color;
-      },
-      _updateImageUrl: function(steps, stepIndex) {
-        // We manually change the image URL (instead of binding to the image's src attribute)
-        // because we would like to manage what happens when the image starts to / finishes loading.
-        if (!steps.length) {
-          return;
-        }
-
-        let img = new Image();
-        img.id = "img"; // '#img' used to select the image in tf-image-dashboard.
-
-        const loadIndex = ++this._currentImageLoadIndex;
-        img.onload = img.onerror = (function() {
-          if (loadIndex != this._currentImageLoadIndex) {
-            // This load is no longer relevant.
-            return;
-          }
-
-          // The new image has finished loading. Remove the old image. Add the new one.
-          let mainImageContainer = this.$$("#main-image-container");
-          mainImageContainer.innerHTML = "";
-          Polymer.dom(mainImageContainer).appendChild(img);
-
-          // The image has finished loading (or has erred and failed to load).
-          this.set("_isImageLoading", false);
-        }).bind(this);
-
-        // Load the new image.
-        this.set("_isImageLoading", true);
-        img.src = steps[stepIndex].url;
-      },
-      _computeHasAtLeastOneStep: function(steps) {
-        return !!steps && steps.length > 0;
-      },
-      _computeHasMultipleSteps: function(steps) {
-        return !!steps && steps.length > 1;
-      },
-      _computeStepValue: function(stepIndex) {
-        return this._steps[stepIndex].step;
-      },
-      _computeCurrentWallTime: function(stepIndex) {
-        return this._steps[stepIndex].wall_time.toString();
-      },
-      _computeMaxStepIndex: function(steps) {
-        return steps.length - 1;
-      },
-    });
-  </script>
-</dom-module>
-
-<dom-module id="tf-image-dashboard" assetpath="../tf-image-dashboard/">
-  <template>
-    <paper-dialog with-backdrop="" id="actual-image-size-dialog"></paper-dialog>
-    <div id="plumbing">
-      <tf-color-scale id="colorScale" runs="[[runs]]" out-color-scale="{{_colorScale}}"></tf-color-scale>
-    </div>
-
-    <tf-dashboard-layout>
-      <div class="sidebar">
-        <tf-sidebar-helper backend="[[backend]]" categories="{{_categories}}" color-scale="[[_colorScale]]" run2tag="[[run2tag]]" runs="[[runs]]" selected-runs="{{_selectedRuns}}">
-        </tf-sidebar-helper>
-      </div>
-      <div class="center">
-        <tf-panes-helper categories="[[_categories]]" color-scale="[[_colorScale]]" data-type="[[dataType]]" data-provider="[[dataProvider]]" data-not-found="[[dataNotFound]]" run2tag="[[run2tag]]" selected-runs="[[_selectedRuns]]" repeat-for-runs="">
-          <template>
-            <tf-image-loader color-scale="[[_colorScale]]"></tf-image-loader>
-            <paper-icon-button class="actual-size-button" icon="aspect-ratio" on-tap="_showActualSize" title="Show the image at its true pixel size"></paper-icon-button>
-          </template>
-        </tf-panes-helper>
-      </div>
-    </tf-dashboard-layout>
-    <style include="dashboard-style"></style>
-    <style>
-      tf-panes-helper {
-        --card-width: 340px;
-        --card-height: auto;
-        --card-expanded-width: 700px;
-        --card-expanded-height: auto;
-      }
-
-      .actual-size-button {
-        background: #fff;
-        border-radius: 100%;
-        bottom: -35px;
-        color: #2196f3;
-        height: 32px;
-        left: 35px;
-        padding: 4px;
-        pointer-events: auto;
-        position: absolute;
-        width: 32px;
-      }
-
-      .actual-size-button-selected {
-        background: var(--tb-ui-light-accent);
-      }
-
-      #actual-image-size-dialog {
-        overflow: auto;
-      }
-    </style>
-  </template>
-  <script>
-    TF.Dashboard.TfImageDashboard = Polymer({
-      is: "tf-image-dashboard",
-      factoryImpl: function(backend) {
-        this.backend = backend;
-      },
-      properties: {
-        backend: Object,
-        dataType: {
-          type: String,
-          value: "image"
-        },
-      },
-      behaviors: [
-        TF.Dashboard.DashboardBehavior("images"),
-        TF.Dashboard.ReloadBehavior("tf-chart-scaffold"),
-        TF.Backend.Behavior,
-      ],
-      attached: function() {
-        this.async(function() {
-          this.fire("rendered");
-        });
-      },
-      _showActualSize: function(e) {
-        var currentTarget = Polymer.dom(e.currentTarget);
-        var card = currentTarget.node.closest('.card');
-
-        // Create a full-size copy of the image.
-        var newImage = card.querySelector('#img').cloneNode();
-        newImage.style.height = 'auto';
-        newImage.style.width = 'auto';
-        newImage.style.margin = 0;
-        newImage.style.padding = 0;
-        newImage.classList.add("actual-size-image");
-
-        // When the user clicks on the image, empty and close the dialog.
-        var dialog = this.$$('#actual-image-size-dialog');
-        newImage.addEventListener('click', function() {
-          dialog.close();
-        });
-
-        // Update dialog content. Show the dialog.
-        dialog.innerHTML = '';
-        dialog.appendChild(newImage);
-        dialog.open();
-      }
-    });
-  </script>
-</dom-module>
-
-<dom-module id="tf-audio-loader" assetpath="../tf-audio-dashboard/">
-  <style>
-    :host {
-      display: block;
-      --step-slider-knob-color: #424242;
-    }
-
-    img {
-      width: 100%;
-      height: 100%;
-      image-rendering: pixelated;
-    }
-
-    .step-description {
-      font-size: 12px;
-    }
-
-    .step-value {
-      font-weight: bold;
-    }
-
-    #audio-loading-spinner {
-      width: 14px;
-      height: 14px;
-      vertical-align: text-bottom;
-      --paper-spinner-color: var(--tb-orange-strong)
-    }
-
-    #steps {
-      height: 15px;
-      margin: 0 0 0 -15px;
-      /* 31 comes from adding a padding of 15px from both sides of the paper-slider, subtracting
-       * 1px so that the slider width aligns with the image (the last slider marker takes up 1px),
-       * and adding 2px to account for a border of 1px on both sides of the image. 30 - 1 + 2. */
-      width: calc(100% + 31px);
-      --paper-slider-active-color: var(--step-slider-knob-color);
-      --paper-slider-knob-color: var(--step-slider-knob-color);
-      --paper-slider-pin-color: var(--step-slider-knob-color);
-      --paper-slider-knob-start-color: var(--step-slider-knob-color);
-      --paper-slider-knob-start-border-color: var(--step-slider-knob-color);
-      --paper-slider-pin-start-color: var(--step-slider-knob-color);
-    }
-
-    #individual-audio-container audio {
-      margin: 5px 0 0 -10px;
-      width: calc(100% + 20px);
-    }
-  </style>
-  <template>
-    <template is="dom-if" if="[[_metadatas]]">
-      <template is="dom-if" if="[[_hasAtLeastOneStep(_metadatas)]]">
-        <div class="step-description">
-          step
-          <span class="step-value">
-            [[_stepValue]]
-          </span><br>
-          <template is="dom-if" if="[[_stepWallTime]]">
-            [[_stepWallTime]]
-          </template>
-          <paper-spinner-lite active="" id="audio-loading-spinner" hidden$="[[!_isAudioLoading]]"></paper-spinner-lite>
-        </div>
-      </template>
-      <template is="dom-if" if="[[_maxStepIndex]]">
-        <paper-slider id="steps" immediate-value="{{_stepIndex}}" max="[[_maxStepIndex]]" max-markers="[[_maxStepIndex]]" snaps="" step="1" value="{{_stepIndex}}"></paper-slider>
-      </template>
-      <div id="individual-audio-container"></div>
-    </template>
-  </template>
-  <script>
-    Polymer({
-      is: "tf-audio-loader",
-      properties: {
-        run: String,
-        tag: String,
-        audioGenerator: Function,
-        // todo: document.
-        _metadatas: Array,
-        _stepIndex: Number,
-        _stepValue: {
-          type: Number,
-          computed: "_computeStepValue(_metadatas, _stepIndex)",
-          value: 0,
-        },
-        _stepWallTime: {
-          type: Number,
-          computed: "_computeStepWallTime(_metadatas, _stepIndex)",
-          value: 0,
-        },
-        _maxStepIndex: {
-          type: Number,
-          computed: "_computeMaxStepIndex(_metadatas)",
-          value: 0,
-        },
-        _isAudioLoading: Boolean,
-        // Used to identify stale requests for audio.
-        _audioRequestId: {
-          type: Number,
-          value: 1
-        },
-      },
-      observers: [
-        "_updateAudio(_metadatas, _stepIndex)",
-      ],
-      reload: function() {
-        this.audioGenerator(this.tag, this.run).then(function(metadatas) {
-          // Set the list of available metadata.
-          this.set("_metadatas", metadatas);
-
-          // Set the index to be the last one.
-          this.set("_stepIndex", this._maxStepIndex);
-        }.bind(this));
-      },
-      ready: function() {
-        // Need to test so that it will not error if it is constructed w/o
-        // all properties (so that it's possible to use stub to mock it out)
-        if (this.run != null && this.tag != null && this.audioGenerator != null) {
-          this.reload();
-        }
-      },
-      _updateAudio: function(metadatas, stepIndex) {
-        if (!metadatas || stepIndex >= metadatas.length) {
-          // No audio to show. The audio section should be hidden.
-          return;
-        }
-
-        // Load new audio.
-        const requestId = ++this._audioRequestId;
-        this.set("_isAudioLoading", true);
-
-        // Create a new audio element. Only replace the previous one once the new audio loads.
-        let audioElement = document.createElement("audio");
-        audioElement.setAttribute("controls", true);
-        audioElement.setAttribute("loop", "loop");
-        let canPlayHandler = function() {
-          if (requestId !== this._audioRequestId) {
-            // This request is no longer relevant.
-            return;
-          }
-
-          // Remove this event listener: "canplay" apparently fires in Chrome every time playing
-          // begins again on loop. So, if we create a new audio element every time that happens, we
-          // don't actually loop.
-          audioElement.removeEventListener("canplay", canPlayHandler);
-
-          let individualAudioContainer = this.$$("#individual-audio-container");
-          individualAudioContainer.innerHTML = "";
-          Polymer.dom(individualAudioContainer).appendChild(audioElement);
-          this.set("_isAudioLoading", false);
-        }.bind(this);
-        audioElement.addEventListener("canplay", canPlayHandler);
-        audioElement.addEventListener("error", function() {
-          if (requestId !== this._audioRequestId) {
-            // This request is no longer relevant.
-            return;
-          }
-
-          // The audio could not be loaded.
-          this.$$("#individual-audio-container").innerHTML = "";
-          this.set("_isAudioLoading", false);
-        }.bind(this));
-
-        // Initiate the request for new audio.
-        var sourceElement = document.createElement("source");
-        let metadata = metadatas[stepIndex];
-        sourceElement.setAttribute("src", metadata.url);
-        sourceElement.setAttribute("type", metadata.content_type);
-        audioElement.appendChild(sourceElement);
-      },
-      _computeStepValue: function(metadatas, stepIndex) {
-        if (!metadatas || stepIndex >= metadatas.length) {
-          // No audio to show. The audio section should be hidden.
-          return 0;
-        }
-        return metadatas[stepIndex].step;
-      },
-      _computeStepWallTime: function(metadatas, stepIndex) {
-        if (!metadatas || stepIndex >= metadatas.length) {
-          // No audio to show. The audio section should be hidden.
-          return 0;
-        }
-        return metadatas[stepIndex].wall_time.toString();
-      },
-      _computeMaxStepIndex: function(metadatas) {
-        if (!metadatas || metadatas.length === 0) {
-          // No audio to show. The audio section should be hidden.
-          return 0;
-        }
-        return metadatas.length - 1;
-      },
-      _hasAtLeastOneStep: function(metadatas) {
-        return metadatas && metadatas.length > 0;
-      },
-    });
-  </script>
-</dom-module>
-
-<dom-module id="tf-audio-grid" assetpath="../tf-audio-dashboard/">
-  <template>
-    <style include="scrollbar-style"></style>
-    <div id="fullContainer" class="container scrollbar">
-      <div id="topRow" class="container">
-        <div class="noshrink" id="paddingCell"></div>
-        <template is="dom-if" if="[[_tagsExist(tags)]]">
-          <template is="dom-repeat" items="[[runs]]" as="run">
-            <div class="run-name-cell noshrink">
-              <span>[[run]]</span>
-            </div>
-          </template>
-        </template>
-      </div>
-      <div id="bottomContainer" class="container">
-        <template is="dom-repeat" items="[[tags]]" as="tag">
-          <div class="audio-row container noshrink">
-            <div class="tag-name-cell noshrink">
-              <span class="tag-name">[[tag]]</span>
-            </div>
-            <template is="dom-repeat" items="[[runs]]" as="run">
-              <div class="audio-cell noshrink">
-                <template is="dom-if" if="[[_exists(run, tag, runToAudio.*)]]">
-                  <tf-audio-loader id="loader" run="[[run]]" tag="[[tag]]" audio-generator="[[audioGenerator]]">
-                  </tf-audio-loader>
-                </template>
-              </div>
-            </template>
-          </div>
-        </template>
-      </div>
-    </div>
-    <style>
-      :host {
-        display: block;
-        height: 100%;
-        --audio-cell-min-height: 105px;
-      }
-      .container {
-        display: flex;
-        flex-wrap: nowrap;
-      }
-      #fullContainer {
-        width: 100%;
-        height: 100%;
-        flex-direction: column;
-        padding-top: 20px;
-        overflow: auto;
-        -webkit-box-sizing: border-box;
-        -moz-box-sizing: border-box;
-        box-sizing: border-box;
-      }
-      #topRow {
-        flex-direction: row;
-      }
-      #bottomContainer {
-        flex-direction: column;
-        height: 100%;
-        width: 100%;
-      }
-      .audio-row {
-        flex-direction: row;
-        padding-top: 5px;
-      }
-      .audio-cell {
-        background: #FAFAFA;
-        width: 300px;
-        min-height: var(--audio-cell-min-height);
-        border: 1px solid black;
-        margin-right: 3px;
-        padding: 10px;
-        box-sizing: border-box;
-      }
-      .tag-name-cell {
-        width: 300px;
-        height: var(--audio-cell-min-height);
-        display:flex;
-        flex-direction: column;
-        justify-content: center;
-      }
-      .tag-name {
-        word-wrap: break-word;
-        text-align: center;
-        white-space: nowrap;
-      }
-      .run-name-cell {
-        width: 300px;
-        text-align: center;
-        margin-right: 5px;
-      }
-      .noshrink {
-        flex-shrink: 0;
-      }
-      #paddingCell {
-        width: 300px;
-      }
-    </style>
-  </template>
-  <script>
-    Polymer({
-      is: "tf-audio-grid",
-      properties: {
-        runToAudio: Object,
-        tags: Array,
-        runs: Array,
-        audioGenerator: Function,
-      },
-      _tagsExist: function(tags) {
-        return tags && tags.length > 0;
-      },
-      _exists: function (run, tag) {
-        return this.runToAudio[run].indexOf(tag) !== -1;
-      },
-    });
-  </script>
-</dom-module>
-
-<dom-module id="tf-audio-dashboard" assetpath="../tf-audio-dashboard/">
-  <template>
-    <div class="center">
-      <tf-no-data-warning data-type="audio" show-warning="[[dataNotFound]]"></tf-no-data-warning>
-      <tf-audio-grid id="audioGrid" run-to-audio="[[run2tag]]" audio-generator="[[dataProvider]]" tags="[[tags]]" runs="[[runs]]"></tf-audio-grid>
-    </div>
-
-    <style>
-      .center {
-        height: 100%;
-        width: 100%;
-        -webkit-box-sizing: border-box;
-        -moz-box-sizing: border-box;
-        box-sizing: border-box;
-      }
-      :host {
-        height: 100%;
-        display: block;
-      }
-
-    </style>
-  </template>
-  <script>
-    TF.Dashboard.TfAudioDashboard = Polymer({
-      is: "tf-audio-dashboard",
-      factoryImpl: function(backend) {
-        this.backend = backend;
-      },
-      properties: {
-        dataType: {value: "audio"},
-      },
-      behaviors: [
-        TF.Dashboard.DashboardBehavior("audio"),
-        TF.Dashboard.ReloadBehavior("tf-audio-loader"),
-        TF.Backend.Behavior
-      ],
-      attached: function() {
-        this.async(function() {
-          this.fire("rendered");
-        });
-      },
-      _hasAudio: function(runToAudioChange) {
-        return _.values(runToAudioChange.base).some(function(arr) {
-          return arr.length > 0;
-        });
-      },
-    });
-  </script>
-</dom-module>
-
-<dom-module id="tf-graph-loader" assetpath="../tf-graph-loader/">
-</dom-module>
-
-<script>
-Polymer({
-
-  is: 'tf-graph-loader',
-
-  properties: {
-    /**
-     * @type {value: number, msg: string}
-     *
-     * A number between 0 and 100 denoting the % of progress
-     * for the progress bar and the displayed message.
-     */
-    progress: {
-      type: Object,
-      notify: true,
-    },
-    datasets: Array,
-    selectedDataset: Number,
-    selectedFile: {
-      type: Object,
-      observer: '_selectedFileChanged'
-    },
-    outGraphHierarchy: {
-      type: Object,
-      readOnly: true, //readonly so outsider can't change this via binding
-      notify: true
-    },
-    outGraph: {
-      type: Object,
-      readOnly: true, //readonly so outsider can't change this via binding
-      notify: true
-    },
-    outHierarchyParams: {
-      type: Object,
-      readOnly: true,
-      notify: true
-    },
-    outStats: {
-      type: Object,
-      readOnly: true, // This property produces data.
-      notify: true
-    }
-  },
-  observers: [
-    '_selectedDatasetChanged(selectedDataset, datasets)',
-    '_readAndParseMetadata(selectedMetadataTag)'
-  ],
-  _readAndParseMetadata: function(metadataIndex) {
-    if (metadataIndex == -1 || this.datasets[this.selectedDataset] == null ||
-        this.datasets[this.selectedDataset].runMetadata == null ||
-        this.datasets[this.selectedDataset].runMetadata[metadataIndex] == null) {
-      this._setOutStats(null);
-      return;
-    }
-    var path = this.datasets[this.selectedDataset].runMetadata[metadataIndex].path;
-    // Reset the progress bar to 0.
-    this.set('progress', {
-      value: 0,
-      msg: ''
-    });
-    var tracker = tf.graph.util.getTracker(this);
-    tf.graph.parser.fetchAndParseMetadata(path, tracker)
-    .then(function(stats) {
-      this._setOutStats(stats);
-    }.bind(this));
-  },
-  _parseAndConstructHierarchicalGraph: function(path, pbTxtFile) {
-    // Reset the progress bar to 0.
-    this.set('progress', {
-      value: 0,
-      msg: ''
-    });
-    var tracker = tf.graph.util.getTracker(this);
-    var hierarchyParams = {
-      verifyTemplate: true,
-      // If a set of numbered op nodes has at least this number of nodes
-      // then group them into a series node.
-      seriesNodeMinSize: 5,
-      // A map of series node names to series grouping settings, to indicate
-      // if a series is to be rendered as grouped or ungrouped.
-      // Starts out empty which allows the renderer to decide which series
-      // are initially rendered grouped and which aren't.
-      seriesMap: {},
-    };
-    this._setOutHierarchyParams(hierarchyParams);
-    var dataTracker = tf.graph.util.getSubtaskTracker(tracker, 30, 'Data');
-    tf.graph.parser.fetchAndParseGraphData(path, pbTxtFile, dataTracker)
-    .then(function(graph) {
-      if (!graph) {
-        throw 'The graph is empty. Make sure that the graph is passed to the ' +
-            'SummaryWriter after the graph is defined.';
-      }
-
-      // Build the flat graph (consists only of Op nodes).
-
-      // This is the whitelist of inputs on op types that are considered
-      // reference edges. "Assign 0" indicates that the first input to
-      // an OpNode with operation type "Assign" is a reference edge.
-      var refEdges = {};
-      refEdges["Assign 0"] = true;
-      refEdges["AssignAdd 0"] = true;
-      refEdges["AssignSub 0"] = true;
-      refEdges["assign 0"] = true;
-      refEdges["assign_add 0"] = true;
-      refEdges["assign_sub 0"] = true;
-      refEdges["count_up_to 0"] = true;
-      refEdges["ScatterAdd 0"] = true;
-      refEdges["ScatterSub 0"] = true;
-      refEdges["ScatterUpdate 0"] = true;
-      refEdges["scatter_add 0"] = true;
-      refEdges["scatter_sub 0"] = true;
-      refEdges["scatter_update 0"] = true;
-      var buildParams = {
-        enableEmbedding: true,
-        inEmbeddingTypes: ['Const'],
-        outEmbeddingTypes: ['^[a-zA-Z]+Summary$'],
-        refEdges: refEdges
-      };
-      var graphTracker = tf.graph.util.getSubtaskTracker(tracker, 20, 'Graph');
-      return tf.graph.build(graph, buildParams, graphTracker);
-    })
-    .then(function(graph) {
-      this._setOutGraph(graph);
-      var hierarchyTracker = tf.graph.util.getSubtaskTracker(tracker, 50,
-          'Namespace hierarchy');
-      return tf.graph.hierarchy.build(graph, hierarchyParams, hierarchyTracker);
-    }.bind(this))
-    .then(function(graphHierarchy) {
-      // Update the properties which notify the parent with the
-      // graph hierarchy and whether the data has live stats or not.
-      this._setOutGraphHierarchy(graphHierarchy);
-    }.bind(this))
-    .catch(function(e) {
-      // Generic error catch, for errors that happened outside
-      // asynchronous tasks.
-      tracker.reportError("Graph visualization failed: " + e, e);
-    });
-  },
-  _selectedDatasetChanged: function(datasetIndex, datasets) {
-    this._parseAndConstructHierarchicalGraph(datasets[datasetIndex].path);
-  },
-  _selectedFileChanged: function(e) {
-    if (!e) {
-      return;
-    }
-    var file = e.target.files[0];
-    if (!file) {
-      return;
-    }
-
-    // Clear out the value of the file chooser. This ensures that if the user
-    // selects the same file, we'll re-read it.
-    e.target.value = '';
-
-    this._parseAndConstructHierarchicalGraph(null, file);
-  }
-});
-</script>
-<script src="../graphlib/dist/graphlib.core.js"></script>
-<script src="../dagre/dist/dagre.core.js"></script>
-<script>/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-var tf;
-(function (tf) {
-    /**
-     * Mapping from color palette name to color palette, which contains
-     * exact colors for multiple states of a single color palette.
-     */
-    tf.COLORS = [
-        {
-            'name': 'Google Blue',
-            'color': '#4184f3',
-            'active': '#3a53c5',
-            'disabled': '#cad8fc'
-        },
-        {
-            'name': 'Google Red',
-            'color': '#db4437',
-            'active': '#8f2a0c',
-            'disabled': '#e8c6c1'
-        },
-        {
-            'name': 'Google Yellow',
-            'color': '#f4b400',
-            'active': '#db9200',
-            'disabled': '#f7e8b0'
-        },
-        {
-            'name': 'Google Green',
-            'color': '#0f9d58',
-            'active': '#488046',
-            'disabled': '#c2e1cc'
-        },
-        {
-            'name': 'Purple',
-            'color': '#aa46bb',
-            'active': '#5c1398',
-            'disabled': '#d7bce6'
-        },
-        {
-            'name': 'Teal',
-            'color': '#00abc0',
-            'active': '#47828e',
-            'disabled': '#c2eaf2'
-        },
-        {
-            'name': 'Deep Orange',
-            'color': '#ff6f42',
-            'active': '#ca4a06',
-            'disabled': '#f2cbba'
-        },
-        {
-            'name': 'Lime',
-            'color': '#9d9c23',
-            'active': '#7f771d',
-            'disabled': '#f1f4c2'
-        },
-        {
-            'name': 'Indigo',
-            'color': '#5b6abf',
-            'active': '#3e47a9',
-            'disabled': '#c5c8e8'
-        },
-        {
-            'name': 'Pink',
-            'color': '#ef6191',
-            'active': '#ca1c60',
-            'disabled': '#e9b9ce'
-        },
-        {
-            'name': 'Deep Teal',
-            'color': '#00786a',
-            'active': '#2b4f43',
-            'disabled': '#bededa'
-        },
-        {
-            'name': 'Deep Pink',
-            'color': '#c1175a',
-            'active': '#75084f',
-            'disabled': '#de8cae'
-        },
-        {
-            'name': 'Gray',
-            'color': '#9E9E9E',
-            'active': '#424242',
-            'disabled': 'F5F5F5' // 100
-        }
-    ].reduce(function (m, c) {
-        m[c.name] = c;
-        return m;
-    }, {});
-    /**
-     * Mapping from op category to color palette name
-     * e.g.,  OP_GROUP_COLORS['state_ops'] = 'Google Blue';
-     */
-    tf.OP_GROUP_COLORS = [
-        {
-            color: 'Google Red',
-            groups: [
-                'gen_legacy_ops', 'legacy_ops', 'legacy_flogs_input',
-                'legacy_image_input', 'legacy_input_example_input',
-                'legacy_sequence_input', 'legacy_seti_input_input'
-            ]
-        },
-        { color: 'Deep Orange', groups: ['constant_ops'] },
-        { color: 'Indigo', groups: ['state_ops'] },
-        { color: 'Purple', groups: ['nn_ops', 'nn'] },
-        { color: 'Google Green', groups: ['math_ops'] },
-        { color: 'Lime', groups: ['array_ops'] },
-        { color: 'Teal', groups: ['control_flow_ops', 'data_flow_ops'] },
-        { color: 'Pink', groups: ['summary_ops'] },
-        { color: 'Deep Pink', groups: ['io_ops'] }
-    ].reduce(function (m, c) {
-        c.groups.forEach(function (group) { m[group] = c.color; });
-        return m;
-    }, {});
-})(tf || (tf = {}));
-</script>
-<script>/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-</script>
-<script>/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-</script>
-<script>/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-var tf;
-(function (tf) {
-    var graph;
-    (function (graph_1) {
-        /** Delimiter used in node names to denote namespaces. */
-        graph_1.NAMESPACE_DELIM = '/';
-        graph_1.ROOT_NAME = '__root__';
-        /** Attribute key used for storing attributes that are too large. */
-        graph_1.LARGE_ATTRS_KEY = '_too_large_attrs';
-        /**
-         * Maximum allowed size in bytes, before the attribute is considered large
-         * and filtered out of the graph.
-         */
-        graph_1.LIMIT_ATTR_SIZE = 1024;
-        // Separator between the source and the destination name of the edge.
-        graph_1.EDGE_KEY_DELIM = '--';
-        var GraphType;
-        (function (GraphType) {
-            GraphType[GraphType["FULL"] = 0] = "FULL";
-            GraphType[GraphType["EMBEDDED"] = 1] = "EMBEDDED";
-            GraphType[GraphType["META"] = 2] = "META";
-            GraphType[GraphType["SERIES"] = 3] = "SERIES";
-            GraphType[GraphType["CORE"] = 4] = "CORE";
-            GraphType[GraphType["SHADOW"] = 5] = "SHADOW";
-            GraphType[GraphType["BRIDGE"] = 6] = "BRIDGE";
-            GraphType[GraphType["EDGE"] = 7] = "EDGE";
-        })(GraphType = graph_1.GraphType || (graph_1.GraphType = {}));
-        ;
-        var NodeType;
-        (function (NodeType) {
-            NodeType[NodeType["META"] = 0] = "META";
-            NodeType[NodeType["OP"] = 1] = "OP";
-            NodeType[NodeType["SERIES"] = 2] = "SERIES";
-            NodeType[NodeType["BRIDGE"] = 3] = "BRIDGE";
-            NodeType[NodeType["ELLIPSIS"] = 4] = "ELLIPSIS";
-        })(NodeType = graph_1.NodeType || (graph_1.NodeType = {}));
-        ;
-        /** Indicates if a node is to be included in the main graph when rendered. */
-        var InclusionType;
-        (function (InclusionType) {
-            InclusionType[InclusionType["INCLUDE"] = 0] = "INCLUDE";
-            InclusionType[InclusionType["EXCLUDE"] = 1] = "EXCLUDE";
-            InclusionType[InclusionType["UNSPECIFIED"] = 2] = "UNSPECIFIED";
-        })(InclusionType = graph_1.InclusionType || (graph_1.InclusionType = {}));
-        ;
-        /** Indicates if a series is to be grouped in the graph when rendered. */
-        var SeriesGroupingType;
-        (function (SeriesGroupingType) {
-            SeriesGroupingType[SeriesGroupingType["GROUP"] = 0] = "GROUP";
-            SeriesGroupingType[SeriesGroupingType["UNGROUP"] = 1] = "UNGROUP";
-        })(SeriesGroupingType = graph_1.SeriesGroupingType || (graph_1.SeriesGroupingType = {}));
-        ;
-        /** Attribute key reserved for the shapes of the output tensors. */
-        var OUTPUT_SHAPES_KEY = '_output_shapes';
-        /** Attribute key reserved for the XLA cluster that an op runs on. */
-        var _XLA_CLUSTER_KEY = '_XlaCluster';
-        /**
-         * A SlimGraph is inspired by graphlib.Graph, but having only the functionality
-         * that we need.
-         */
-        var SlimGraph = (function () {
-            function SlimGraph() {
-                this.nodes = {};
-                this.edges = [];
-            }
-            return SlimGraph;
-        }());
-        graph_1.SlimGraph = SlimGraph;
-        var EllipsisNodeImpl = (function () {
-            /**
-             * Constructs a new ellipsis annotation node.
-             *
-             * @param numNodes The number of additional annotations this node represents.
-             */
-            function EllipsisNodeImpl(numNodes) {
-                this.type = NodeType.ELLIPSIS;
-                this.isGroupNode = false;
-                this.cardinality = 1;
-                this.parentNode = null;
-                this.stats = null;
-                this.setNumMoreNodes(numNodes);
-                this.include = InclusionType.UNSPECIFIED;
-            }
-            EllipsisNodeImpl.prototype.setNumMoreNodes = function (numNodes) {
-                this.numMoreNodes = numNodes;
-                this.name = '... ' + numNodes + ' more';
-            };
-            return EllipsisNodeImpl;
-        }());
-        graph_1.EllipsisNodeImpl = EllipsisNodeImpl;
-        ;
-        /**
-         * A label object for nodes in the full graph and leaf nodes in the render
-         * graph.
-         */
-        var OpNodeImpl = (function () {
-            /**
-             * Constructs a new Op node.
-             *
-             * @param rawNode The raw node.
-             */
-            function OpNodeImpl(rawNode) {
-                this.op = rawNode.op;
-                this.name = rawNode.name;
-                this.device = rawNode.device;
-                this.attr = rawNode.attr;
-                // An array of normalized inputs that denote the incoming edges to
-                // the current node. Each input contains the normalized name of the
-                // source node, whether it has a number part and whether it is a
-                // control dependency.
-                this.inputs = normalizeInputs(rawNode.input);
-                this.outputShapes = extractOutputShapes(rawNode.attr);
-                this.xlaCluster = extractXlaCluster(rawNode.attr);
-                // additional properties
-                this.type = NodeType.OP;
-                this.isGroupNode = false;
-                this.cardinality = 1;
-                this.inEmbeddings = [];
-                this.outEmbeddings = [];
-                this.parentNode = null;
-                this.include = InclusionType.UNSPECIFIED;
-                this.owningSeries = null;
-            }
-            return OpNodeImpl;
-        }());
-        graph_1.OpNodeImpl = OpNodeImpl;
-        ;
-        function createMetanode(name, opt) {
-            if (opt === void 0) { opt = {}; }
-            return new MetanodeImpl(name, opt);
-        }
-        graph_1.createMetanode = createMetanode;
-        /**
-         * Joins the information from the stats file (memory, compute time) with the
-         * graph information.
-         */
-        function joinStatsInfoWithGraph(graph, stats, devicesForStats) {
-            // Reset stats for each node.
-            _.each(graph.nodes, function (node) { node.stats = null; });
-            _.each(stats.dev_stats, function (devStats) {
-                // Ignore devices that are not selected.
-                if (devicesForStats && !devicesForStats[devStats.device]) {
-                    return;
-                }
-                _.each(devStats.node_stats, function (nodeStats) {
-                    // Lookup the node in the graph by its original name, e.g. A. If not
-                    // found, lookup by the rewritten name A/(A) in case the name is both
-                    // a namespace and a node name.
-                    var nodeName = nodeStats.node_name in graph.nodes ? nodeStats.node_name :
-                        nodeStats.node_name +
-                            graph_1.NAMESPACE_DELIM + '(' + nodeStats.node_name + ')';
-                    // Couldn't find a matching node.
-                    if (!(nodeName in graph.nodes)) {
-                        return;
-                    }
-                    // Compute the total bytes used.
-                    var totalBytes = 0;
-                    if (nodeStats.memory) {
-                        _.each(nodeStats.memory, function (alloc) {
-                            if (alloc.total_bytes) {
-                                if (alloc.total_bytes > 0) {
-                                    totalBytes += Number(alloc.total_bytes);
-                                }
-                                else {
-                                    /* tslint:disable */
-                                    console.log('ignoring negative memory allocation for ' + nodeName);
-                                    /* tslint:enable */
-                                }
-                            }
-                        });
-                    }
-                    var outputSize = null;
-                    if (nodeStats.output) {
-                        outputSize = _.map(nodeStats.output, function (output) {
-                            return _.map(output.tensor_description.shape.dim, function (dim) { return Number(dim.size); });
-                        });
-                    }
-                    graph.nodes[nodeName].device = devStats.device;
-                    if (graph.nodes[nodeName].stats == null) {
-                        graph.nodes[nodeName].stats = new NodeStats(outputSize);
-                    }
-                    graph.nodes[nodeName].stats.addBytesAllocation(totalBytes);
-                    if (nodeStats.all_end_rel_micros) {
-                        if (nodeStats.all_end_rel_micros > 0) {
-                            graph.nodes[nodeName].stats.addExecutionTime(nodeStats.all_start_micros, nodeStats.all_start_micros + nodeStats.all_end_rel_micros);
-                        }
-                        else {
-                            /* tslint:disable */
-                            console.log('ignoring negative runtime for ' + nodeName);
-                            /* tslint:enable */
-                        }
-                    }
-                });
-            });
-        }
-        graph_1.joinStatsInfoWithGraph = joinStatsInfoWithGraph;
-        /**
-         * Execution stats for the node.
-         */
-        var NodeStats = (function () {
-            function NodeStats(outputSize) {
-                /**
-                 * Total number of bytes used for the node. Sum of all children
-                 * if it is a Group node.
-                 */
-                this.totalBytes = 0;
-                this.outputSize = outputSize;
-            }
-            /**
-             * Add the start and end time for a particular kernel execution of this op.
-             * Ops can have multiple kernel executions within the same session run.
-             */
-            NodeStats.prototype.addExecutionTime = function (startTime, endTime) {
-                if (this.startTime != null) {
-                    this.startTime = Math.min(this.startTime, startTime);
-                }
-                else {
-                    this.startTime = startTime;
-                }
-                if (this.endTime != null) {
-                    this.endTime = Math.max(this.endTime, endTime);
-                }
-                else {
-                    this.endTime = endTime;
-                }
-            };
-            /**
-             * Add the bytes allocated for a particular kernel execution of this op.
-             * Ops can have multiple kernel executions within the same session run.
-             */
-            NodeStats.prototype.addBytesAllocation = function (totalBytes) {
-                if (this.totalBytes != null) {
-                    this.totalBytes = Math.max(this.totalBytes, totalBytes);
-                }
-                else {
-                    this.totalBytes = totalBytes;
-                }
-            };
-            Object.defineProperty(NodeStats.prototype, "totalMicros", {
-                /**
-                 * Total number of compute time in microseconds used for the node.
-                 * Sum of all children if it is a Group node. Null if it is unknown.
-                 */
-                get: function () {
-                    if (this.startTime == null || this.endTime == null) {
-                        return null;
-                    }
-                    return this.endTime - this.startTime;
-                },
-                enumerable: true,
-                configurable: true
-            });
-            /**
-             * Combines the specified stats with the current stats.
-             * Modifies the current object. This method is used to
-             * compute aggregate stats for group nodes.
-             */
-            NodeStats.prototype.combine = function (stats) {
-                if (stats.totalBytes != null) {
-                    this.totalBytes += stats.totalBytes;
-                }
-                if (stats.totalMicros != null) {
-                    this.addExecutionTime(stats.startTime, stats.endTime);
-                }
-            };
-            return NodeStats;
-        }());
-        graph_1.NodeStats = NodeStats;
-        var MetanodeImpl = (function () {
-            /** A label object for meta-nodes in the graph hierarchy */
-            function MetanodeImpl(name, opt) {
-                if (opt === void 0) { opt = {}; }
-                this.name = name;
-                this.type = NodeType.META;
-                /** number of levels under this group */
-                this.depth = 1;
-                this.isGroupNode = true;
-                /** # of leaf nodes (including embedded ones) */
-                this.cardinality = 0;
-                /** graph contains metanodes, nodes, edges
-                 * and metaedges for main items within this metanode
-                 */
-                this.metagraph =
-                    createGraph(name, GraphType.META, opt);
-                /** bridgegraph must be constructed lazily-see hierarchy.getBridgegraph() */
-                this.bridgegraph = null;
-                /**
-                 * A dictionary that count ops type of nodes in this metanode
-                 * (op type => count).
-                 */
-                this.opHistogram = {};
-                this.deviceHistogram = {};
-                /** unique id for a metanode of similar subgraph */
-                this.templateId = null;
-                /** Metanode which contains this node, if any */
-                this.parentNode = null;
-                this.hasNonControlEdges = false;
-                this.include = InclusionType.UNSPECIFIED;
-            }
-            MetanodeImpl.prototype.getFirstChild = function () {
-                return this.metagraph.node(this.metagraph.nodes()[0]);
-            };
-            /**
-             * Returns the op node associated with the metanode.
-             * For example, if the metanode is 'sgd', the associated
-             * op node is sgd/(sgd).
-             */
-            MetanodeImpl.prototype.getRootOp = function () {
-                var nameSplit = this.name.split('/');
-                var rootOpName = this.name + '/(' + nameSplit[nameSplit.length - 1] + ')';
-                return this.metagraph.node(rootOpName);
-            };
-            /**
-             * Return an array of the names of all the leaves (non-GroupNodes) inside
-             * this metanode. This performs a breadth-first search of the tree, so
-             * immediate child leaves will appear earlier in the output array than
-             * descendant leaves.
-             */
-            MetanodeImpl.prototype.leaves = function () {
-                var leaves = [];
-                var queue = [this];
-                var metagraph; // Defined here due to a limitation of ES6->5 compilation.
-                while (queue.length) {
-                    var node = queue.shift();
-                    if (node.isGroupNode) {
-                        metagraph = node.metagraph;
-                        _.each(metagraph.nodes(), function (name) { return queue.push(metagraph.node(name)); });
-                    }
-                    else {
-                        leaves.push(node.name);
-                    }
-                }
-                return leaves;
-            };
-            return MetanodeImpl;
-        }());
-        graph_1.MetanodeImpl = MetanodeImpl;
-        ;
-        function createMetaedge(v, w) {
-            return new MetaedgeImpl(v, w);
-        }
-        graph_1.createMetaedge = createMetaedge;
-        /**
-         * A label object for edges between metanodes of subgraphs in the render graph.
-         */
-        var MetaedgeImpl = (function () {
-            function MetaedgeImpl(v, w) {
-                this.v = v;
-                this.w = w;
-                this.baseEdgeList = [];
-                this.inbound = null;
-                this.numRegularEdges = 0;
-                this.numControlEdges = 0;
-                this.numRefEdges = 0;
-                this.totalSize = 0;
-            }
-            MetaedgeImpl.prototype.addBaseEdge = function (edge, h) {
-                this.baseEdgeList.push(edge);
-                if (edge.isControlDependency) {
-                    this.numControlEdges += 1;
-                }
-                else {
-                    this.numRegularEdges += 1;
-                }
-                if (edge.isReferenceEdge) {
-                    this.numRefEdges += 1;
-                }
-                // Compute the size of the tensor flowing through this
-                // base edge.
-                this.totalSize += MetaedgeImpl.computeSizeOfEdge(edge, h);
-                h.maxMetaEdgeSize = Math.max(h.maxMetaEdgeSize, this.totalSize);
-            };
-            MetaedgeImpl.computeSizeOfEdge = function (edge, h) {
-                var opNode = h.node(edge.v);
-                if (opNode.outputShapes == null) {
-                    // No shape information. Asssume a single number. This gives
-                    // a lower bound for the total size.
-                    return 1;
-                }
-                h.hasShapeInfo = true;
-                // Sum the sizes of all output tensors.
-                return _(opNode.outputShapes).map(function (shape) {
-                    // If the shape is unknown, treat it as 1 when computing
-                    // total size. This gives a lower bound for the total size.
-                    if (shape == null) {
-                        return 1;
-                    }
-                    // Multiply all shapes to get the total size of the tensor.
-                    // E.g. The total size of [4, 2, 1] is 4 * 2 * 1.
-                    return _(shape).reduce(function (accumulated, currSize) {
-                        // If this particular dimension is unknown, treat
-                        // it as 1 when computing total size. This gives a lower bound
-                        // for the total size.
-                        if (currSize === -1) {
-                            currSize = 1;
-                        }
-                        return accumulated * currSize;
-                    }, 1);
-                }).sum();
-            };
-            return MetaedgeImpl;
-        }());
-        graph_1.MetaedgeImpl = MetaedgeImpl;
-        function createSeriesNode(prefix, suffix, parent, clusterId, name) {
-            return new SeriesNodeImpl(prefix, suffix, parent, clusterId, name);
-        }
-        graph_1.createSeriesNode = createSeriesNode;
-        function getSeriesNodeName(prefix, suffix, parent, startId, endId) {
-            var numRepresentation = (typeof startId !== 'undefined' && typeof endId !== 'undefined') ?
-                '[' + startId + '-' + endId + ']' :
-                '#';
-            var pattern = prefix + numRepresentation + suffix;
-            return (parent ? parent + '/' : '') + pattern;
-        }
-        graph_1.getSeriesNodeName = getSeriesNodeName;
-        var SeriesNodeImpl = (function () {
-            function SeriesNodeImpl(prefix, suffix, parent, clusterId, name) {
-                this.name = name || getSeriesNodeName(prefix, suffix, parent);
-                this.type = NodeType.SERIES;
-                this.hasLoop = false;
-                this.prefix = prefix;
-                this.suffix = suffix;
-                this.clusterId = clusterId;
-                this.ids = [];
-                this.parent = parent;
-                this.isGroupNode = true;
-                this.cardinality = 0;
-                this.metagraph = createGraph(name, GraphType.SERIES);
-                // bridgegraph must be constructed lazily-see hierarchy.getBridgegraph()
-                this.bridgegraph = null;
-                this.parentNode = null;
-                this.deviceHistogram = {};
-                this.hasNonControlEdges = false;
-                this.include = InclusionType.UNSPECIFIED;
-            }
-            return SeriesNodeImpl;
-        }());
-        /**
-         * Extracts the shapes of the output tensors from the attr property in the
-         * node proto.
-         */
-        // tslint:disable-next-line:no-any
-        function extractOutputShapes(attr) {
-            var result = null;
-            // We don't know anything about the output tensors.
-            if (!attr) {
-                return null;
-            }
-            for (var i = 0; i < attr.length; i++) {
-                var _a = attr[i], key = _a.key, value = _a.value;
-                if (key === OUTPUT_SHAPES_KEY) {
-                    if (!value.list.shape) {
-                        // The OUTPUT_SHAPES_KEY lacks a value. We know nothing about the shape.
-                        return null;
-                    }
-                    // Map all output tensors into array of numbers denoting their shape.
-                    var result_1 = value.list.shape.map(function (shape) {
-                        if (shape.unknown_rank) {
-                            // This output tensor is of unknown rank. We don't know if it is a
-                            // scalar, or a tensor, or of what shape it is.
-                            return null;
-                        }
-                        if (shape.dim == null ||
-                            (shape.dim.length === 1 && shape.dim[0].size == null)) {
-                            // This output tensor is a scalar.
-                            return [];
-                        }
-                        // This output tensor has a known rank. Map each dimension size
-                        // into a number.
-                        return shape.dim.map(function (dim) {
-                            // Size can be -1 if this particular dimension is unknown.
-                            return dim.size;
-                        });
-                    });
-                    // Since we already processed it, remove the entry from the attribute
-                    // list (saves memory).
-                    attr.splice(i, 1);
-                    return result_1;
-                }
-            }
-            // We didn't find OUTPUT_SHAPES_KEY in attributes, so we don't know anything
-            // about the output tensors.
-            return null;
-        }
-        /**
-         * Extracts the XLA Cluster that an op runs on from the attrs of the OpNode.
-         * @param attr The attr property.
-         * @return A string that is the name of the cluster. Or null if it could not be
-         *     determined.
-         */
-        // tslint:disable-next-line:no-any
-        function extractXlaCluster(attr) {
-            if (!attr) {
-                return null;
-            }
-            // Find the attribute for XLA cluster if there is one.
-            for (var i = 0; i < attr.length; i++) {
-                if (attr[i].key === _XLA_CLUSTER_KEY) {
-                    return attr[i].value['s'] || null;
-                }
-            }
-            return null;
-        }
-        /**
-         * Normalizes the inputs and extracts associated metadata:
-         * 1) Inputs can contain a colon followed by a number at the end
-         *    (e.g. inputName:1) and we remove this from the input name, and take note
-         *    that the input was numbered.
-         * 2) Control dependency inputs contain caret at the beginning and we
-         *    remove this and annotate the edge as a control dependency.
-         * @param inputs Array of unnormalized names of input nodes.
-         */
-        function normalizeInputs(inputs) {
-            var normalizedInputs = [];
-            _.each(inputs, function (inputName) {
-                var start = inputName[0] === '^';
-                var colon = inputName.lastIndexOf(':');
-                var end = colon !== -1 &&
-                    inputName.length - colon > 1 &&
-                    !(/\D/).test(inputName.substring(colon + 1)) ?
-                    colon : inputName.length;
-                var name = inputName.substring(start ? 1 : 0, end);
-                if (normalizedInputs.length === 0 ||
-                    name !== normalizedInputs[normalizedInputs.length - 1].name) {
-                    normalizedInputs.push({
-                        name: name,
-                        outputTensorIndex: end === inputName.length ? 0 : Number(inputName.slice(colon + 1)),
-                        isControlDependency: start
-                    });
-                }
-            });
-            return normalizedInputs;
-        }
-        function addEdgeToGraph(graph, inputName, outputNode, input, params, index) {
-            // Don't allow loops in the graph.
-            if (inputName === outputNode.name) {
-                return;
-            }
-            // Check if this op type and input number corresponds to a
-            // reference edge using the refEdges dictionary in the params.
-            var isRefEdge = params.refEdges[outputNode.op + ' ' + index] === true;
-            graph.edges.push({
-                v: inputName,
-                w: outputNode.name,
-                outputTensorIndex: input.outputTensorIndex,
-                isControlDependency: input.isControlDependency,
-                isReferenceEdge: isRefEdge
-            });
-        }
-        function build(rawNodes, params, tracker) {
-            /**
-             * A dictionary that maps each in-embedding node name to the node
-             * object.
-             */
-            var inEmbedding = {};
-            /**
-             * A dictionary that maps each out-embedding node name to the node
-             * object.
-             */
-            var outEmbedding = {};
-            /**
-             * A dictionary that maps each node name to an array of the node's
-             * out-embedding node label objects.
-             */
-            var outEmbeddings = {};
-            var isInEmbeddedPred = getEmbedPredicate(params.inEmbeddingTypes);
-            var isOutEmbeddedPred = getEmbedPredicate(params.outEmbeddingTypes);
-            var embeddingNodeNames = [];
-            /**
-             * A list of all the non-embedding node names which appear in the processed
-             * list of raw nodes. Here we pre-allocate enough room for all the rawNodes,
-             * even though there will some number of embeddings. The excess array length
-             * is spliced off later.
-             *
-             * Experimentation shows that around 30% of the array will go unused, and
-             * even for very large networks that amounts to less than 10k spaces.
-             */
-            var nodeNames = new Array(rawNodes.length);
-            return tf.graph.util
-                .runAsyncTask('Normalizing names', 30, function () {
-                var opNodes = new Array(rawNodes.length);
-                var index = 0;
-                _.each(rawNodes, function (rawNode) {
-                    var opNode = new OpNodeImpl(rawNode);
-                    if (isInEmbeddedPred(opNode)) {
-                        embeddingNodeNames.push(opNode.name);
-                        inEmbedding[opNode.name] = opNode;
-                        return;
-                    }
-                    if (isOutEmbeddedPred(opNode)) {
-                        embeddingNodeNames.push(opNode.name);
-                        outEmbedding[opNode.name] = opNode;
-                        _.each(opNode.inputs, function (input) {
-                            var inputName = input.name;
-                            outEmbeddings[inputName] = outEmbeddings[inputName] || [];
-                            outEmbeddings[inputName].push(opNode);
-                        });
-                        return;
-                    }
-                    // The node is not an embedding, so add it to the names and nodes
-                    // lists.
-                    opNodes[index] = opNode;
-                    nodeNames[index] = opNode.name;
-                    index++;
-                });
-                opNodes.splice(index);
-                nodeNames.splice(index);
-                return opNodes;
-            }, tracker)
-                .then(function (opNodes) {
-                // Create the graph data structure from the graphlib library.
-                return tf.graph.util.runAsyncTask('Building the data structure', 70, function () {
-                    var normalizedNameDict = mapStrictHierarchy(nodeNames, embeddingNodeNames);
-                    var graph = new SlimGraph;
-                    // Add the nodes to the graph.
-                    _.each(opNodes, function (opNode) {
-                        var normalizedName = normalizedNameDict[opNode.name] || opNode.name;
-                        graph.nodes[normalizedName] = opNode;
-                        // Check if the node has out-embeddings. If yes, add them to the
-                        // node.
-                        if (opNode.name in outEmbeddings) {
-                            opNode.outEmbeddings = outEmbeddings[opNode.name];
-                            // Normalize the names of the out-embeddings.
-                            _.each(opNode.outEmbeddings, function (node) {
-                                node.name = normalizedNameDict[node.name] || node.name;
-                            });
-                        }
-                        // Update the name of the node.
-                        opNode.name = normalizedName;
-                    });
-                    // Visit each node's inputs to add the edges to the graph. If the
-                    // input
-                    // is an in-embedding, then add it to the node's in-embeddings
-                    // instead.
-                    _.each(opNodes, function (opNode) {
-                        _.each(opNode.inputs, function (input, i) {
-                            var inputName = input.name;
-                            if (inputName in inEmbedding) {
-                                var inEmbedNode = inEmbedding[inputName];
-                                opNode.inEmbeddings.push(inEmbedNode);
-                                // Move the inputs of the in-embedding node into incoming
-                                // edges of
-                                // the main node. E.g. the control dependency of a constant
-                                // node
-                                // should be moved to the op node where the constant is
-                                // embedded.
-                                for (var _i = 0, _a = inEmbedNode.inputs; _i < _a.length; _i++) {
-                                    var embedInput = _a[_i];
-                                    addEdgeToGraph(graph, normalizedNameDict[embedInput.name] ||
-                                        embedInput.name, opNode, embedInput, params, i);
-                                }
-                            }
-                            else if (inputName in outEmbedding) {
-                                // Move the inputs of the out-embedding node into inputs of
-                                // the main node where the out-embedding points to.
-                                var outEmbedNode = outEmbedding[inputName];
-                                for (var _b = 0, _c = outEmbedNode.inputs; _b < _c.length; _b++) {
-                                    var embedInput = _c[_b];
-                                    addEdgeToGraph(graph, normalizedNameDict[embedInput.name] ||
-                                        embedInput.name, opNode, input, params, i);
-                                }
-                            }
-                            else {
-                                addEdgeToGraph(graph, normalizedNameDict[inputName] || inputName, opNode, input, params, i);
-                            }
-                        });
-                    });
-                    // Normalize the names of in-embeddings.
-                    _.each(inEmbedding, function (node, name) {
-                        node.name = normalizedNameDict[node.name] || node.name;
-                    });
-                    return graph;
-                }, tracker);
-            });
-        }
-        graph_1.build = build;
-        ;
-        /**
-         * Create a new graphlib.Graph() instance with default parameters
-         */
-        function createGraph(name, type, opt) {
-            if (opt === void 0) { opt = {}; }
-            var graph = new graphlib.Graph(opt);
-            graph.setGraph({
-                name: name,
-                rankdir: 'BT',
-                type: type
-            });
-            return graph;
-        }
-        graph_1.createGraph = createGraph;
-        ;
-        /**
-         * Create a predicate for checking whether a node should be embedded based on
-         * the specified types.
-         */
-        function getEmbedPredicate(types) {
-            return function (node) {
-                // check types
-                for (var i = 0; i < types.length; i++) {
-                    var regExp = new RegExp(types[i]);
-                    if (node.op.match(regExp)) {
-                        return true;
-                    }
-                }
-                return false;
-            };
-        }
-        ;
-        /**
-         * Returns a strict node name (name => name/(name)) to avoid conflicts
-         * where the node name is also a namespace.
-         */
-        function getStrictName(name) {
-            var parts = name.split(graph_1.NAMESPACE_DELIM);
-            return name + graph_1.NAMESPACE_DELIM + '(' + parts[parts.length - 1] + ')';
-        }
-        graph_1.getStrictName = getStrictName;
-        /**
-         * For each op node (embedding or non-embedding), rename it if there is a
-         * non-embedding node under its namespace. For example, assume node name 'A'.
-         * If there is a non-embedding node under its namespace (e.g. 'A/B'), 'A' will
-         * be renamed to 'A/(A)'. Then the namespace 'A' will contain 2 nodes: '(A)'
-         * and 'B'. If all the nodes under 'A' are embedding nodes (e.g. constant and
-         * summary), keep 'A' as an Op node and don't create a namespace.
-         *
-         * @param nodeNames An array of regular (non-embedding) node names.
-         * @param embeddingNodeNames An array of embedding node names.
-         * @return Dictionary object mapping names that need to be renamed to
-         *     new names.
-         */
-        function mapStrictHierarchy(nodeNames, embeddingNodeNames) {
-            /** Dictionary that maps the old new to the new name */
-            var newNameDictionary = {};
-            /** Set used to store all namespaces. */
-            var namespaceSet = {};
-            // sort the nodes to make prefix check faster
-            nodeNames.sort();
-            // look for nodes with a prefix a,a/b -> a/(a),a/b
-            for (var i = 0; i < nodeNames.length - 1; ++i) {
-                var a = nodeNames[i];
-                // Get all the parent namespaces of the current node
-                // and add them in the namespace set.
-                _.each(getHierarchicalPath(a).slice(0, -1), function (ns) {
-                    namespaceSet[ns] = true;
-                });
-                for (var j = i + 1; j < nodeNames.length; ++j) {
-                    var b = nodeNames[j];
-                    if (_.startsWith(b, a)) {
-                        if (b.length > a.length && b.charAt(a.length) === graph_1.NAMESPACE_DELIM) {
-                            newNameDictionary[a] = getStrictName(a);
-                            break;
-                        }
-                    }
-                    else {
-                        break;
-                    }
-                }
-            }
-            // Go through all the embedding node names and rename them in case they
-            // collide with namespaces.
-            _.each(embeddingNodeNames, function (embeddingName) {
-                if (embeddingName in namespaceSet) {
-                    // Rename to follow strict hierarchy.
-                    newNameDictionary[embeddingName] = getStrictName(embeddingName);
-                }
-            });
-            return newNameDictionary;
-        }
-        ;
-        /**
-         * Returns a list of the degrees of each node in the graph.
-         */
-        function degreeSequence(graph) {
-            var degrees = graph.nodes().map(function (name) {
-                return graph.neighbors(name).length;
-            });
-            degrees.sort();
-            return degrees;
-        }
-        ;
-        /**
-         * Returns if the degree sequence of the two graphs is the same.
-         */
-        function hasSimilarDegreeSequence(graph1, graph2) {
-            var dg1 = degreeSequence(graph1);
-            var dg2 = degreeSequence(graph2);
-            for (var i = 0; i < dg1.length; i++) {
-                if (dg1[i] !== dg2[i]) {
-                    return false;
-                }
-            }
-            return true;
-        }
-        graph_1.hasSimilarDegreeSequence = hasSimilarDegreeSequence;
-        ;
-        /**
-         * Returns the hierarchical path of the current node, based on the node's name.
-         * For example, if the name is 'a/b/c', the returned path is
-         * ['a', 'a/b', 'a/b/c'].
-         */
-        function getHierarchicalPath(name, seriesNames) {
-            var path = [];
-            var i = name.indexOf(graph_1.NAMESPACE_DELIM);
-            // Push all parent portions of the path.
-            while (i >= 0) {
-                path.push(name.substring(0, i));
-                i = name.indexOf(graph_1.NAMESPACE_DELIM, i + 1);
-            }
-            // If the node's path is under a series, then add the series node name to the
-            // hierarchical path as the parent of the leaf.
-            if (seriesNames) {
-                var seriesName = seriesNames[name];
-                if (seriesName) {
-                    path.push(seriesName);
-                }
-            }
-            // Push the leaf of the path.
-            path.push(name);
-            return path;
-        }
-        graph_1.getHierarchicalPath = getHierarchicalPath;
-        ;
-        /**
-         * Returns the string for the node inclusion toggle button, dependant
-         * on the provided current InclusionType.
-         */
-        function getIncludeNodeButtonString(include) {
-            if (include === tf.graph.InclusionType.EXCLUDE) {
-                return 'Add to main graph';
-            }
-            else {
-                return 'Remove from main graph';
-            }
-        }
-        graph_1.getIncludeNodeButtonString = getIncludeNodeButtonString;
-        ;
-        /**
-         * Returns the string for the series node grouping toggle button, dependant
-         * on the provided current SeriesGroupingType.
-         */
-        function getGroupSeriesNodeButtonString(group) {
-            if (group === tf.graph.SeriesGroupingType.GROUP) {
-                return 'Ungroup this series of nodes';
-            }
-            else {
-                return 'Group this series of nodes';
-            }
-        }
-        graph_1.getGroupSeriesNodeButtonString = getGroupSeriesNodeButtonString;
-        ;
-        /**
-         * Toggle the node series grouping option in the provided map, setting it
-         * to ungroup if the series is not already in the map.
-         */
-        function toggleNodeSeriesGroup(map, name) {
-            if (!(name in map) || map[name] === tf.graph.SeriesGroupingType.GROUP) {
-                map[name] = tf.graph.SeriesGroupingType.UNGROUP;
-            }
-            else {
-                map[name] = tf.graph.SeriesGroupingType.GROUP;
-            }
-        }
-        graph_1.toggleNodeSeriesGroup = toggleNodeSeriesGroup;
-        ;
-    })(graph = tf.graph || (tf.graph = {}));
-})(tf || (tf = {})); // close module tf.graph
-</script>
-<script>/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-/**
- * Package for the Graph Hierarchy for TensorFlow graph.
- */
-var tf;
-(function (tf) {
-    var graph;
-    (function (graph_1) {
-        var hierarchy;
-        (function (hierarchy_1) {
-            /**
-             * Class for the Graph Hierarchy for TensorFlow graph.
-             */
-            var HierarchyImpl = (function () {
-                function HierarchyImpl() {
-                    this.hasShapeInfo = false;
-                    this.maxMetaEdgeSize = 1;
-                    this.root = graph_1.createMetanode(graph_1.ROOT_NAME, { compound: true });
-                    this.templates = null;
-                    this.devices = null;
-                    /**
-                     * @type {Object} Dictionary object that maps node name to the node
-                     * (could be op-node, metanode, or series-node)
-                     */
-                    this.index = {};
-                    this.index[graph_1.ROOT_NAME] = this.root;
-                    this.orderings = {};
-                }
-                HierarchyImpl.prototype.getNodeMap = function () {
-                    return this.index;
-                };
-                HierarchyImpl.prototype.node = function (name) {
-                    return this.index[name];
-                };
-                HierarchyImpl.prototype.setNode = function (name, node) {
-                    this.index[name] = node;
-                };
-                /**
-                 * Given the name of a node in this hierarchy, get its bridgegraph, creating
-                 * it on the fly if necessary. If the node is not a GroupNode, then this
-                 * method returns null. If the provided name does not map to a node in the
-                 * hierarchy, an error will be thrown.
-                 */
-                HierarchyImpl.prototype.getBridgegraph = function (nodeName) {
-                    var _this = this;
-                    var node = this.index[nodeName];
-                    if (!node) {
-                        throw Error('Could not find node in hierarchy: ' + nodeName);
-                    }
-                    if (!('metagraph' in node)) {
-                        return null;
-                    }
-                    var groupNode = node;
-                    if (groupNode.bridgegraph) {
-                        return groupNode.bridgegraph;
-                    }
-                    var bridgegraph = groupNode.bridgegraph =
-                        graph_1.createGraph('BRIDGEGRAPH', graph_1.GraphType.BRIDGE);
-                    if (!node.parentNode || !('metagraph' in node.parentNode)) {
-                        return bridgegraph;
-                    }
-                    var parentNode = node.parentNode;
-                    var parentMetagraph = parentNode.metagraph;
-                    var parentBridgegraph = this.getBridgegraph(parentNode.name);
-                    // For each of the parent node's two Metaedge containing graphs, process
-                    // each Metaedge involving this node.
-                    _.each([parentMetagraph, parentBridgegraph], function (parentGraph) {
-                        _(parentGraph.edges())
-                            .filter(function (e) { return e.v === nodeName || e.w === nodeName; })
-                            .each(function (parentEdgeObj) {
-                            var inbound = parentEdgeObj.w === nodeName;
-                            var parentMetaedge = parentGraph.edge(parentEdgeObj);
-                            // The parent's Metaedge represents some number of underlying
-                            // BaseEdges from the original full graph. For each of those, we need
-                            // to determine which immediate child is involved and make sure
-                            // there's a Metaedge in the bridgegraph that covers it.
-                            _.each(parentMetaedge.baseEdgeList, function (baseEdge) {
-                                // Based on the direction, figure out which is the descendant node
-                                // and which is the 'other' node (sibling of parent or ancestor).
-                                var _a = inbound ?
-                                    [baseEdge.w, parentEdgeObj.v] :
-                                    [baseEdge.v, parentEdgeObj.w], descendantName = _a[0], otherName = _a[1];
-                                // Determine the immediate child containing this descendant node.
-                                var childName = _this.getChildName(nodeName, descendantName);
-                                // Look for an existing Metaedge in the bridgegraph (or create a
-                                // new one) that covers the relationship between child and other.
-                                var bridgeEdgeObj = {
-                                    v: inbound ? otherName : childName,
-                                    w: inbound ? childName : otherName,
-                                };
-                                var bridgeMetaedge = bridgegraph.edge(bridgeEdgeObj);
-                                if (!bridgeMetaedge) {
-                                    bridgeMetaedge = graph_1.createMetaedge(bridgeEdgeObj.v, bridgeEdgeObj.w);
-                                    bridgeMetaedge.inbound = inbound;
-                                    bridgegraph.setEdge(bridgeEdgeObj.v, bridgeEdgeObj.w, bridgeMetaedge);
-                                }
-                                // Copy the BaseEdge from the parent's Metaedge into this
-                                // bridgegraph Metaedge.
-                                bridgeMetaedge.addBaseEdge(baseEdge, _this);
-                            });
-                        })
-                            .value(); // force lodash chain execution.
-                    });
-                    return bridgegraph;
-                };
-                /**
-                 * Utility function for determining the name of the immediate child under a
-                 * node for a given descendant path. If the descendant corresponds to no
-                 * immediate child, an error is thrown.
-                 */
-                HierarchyImpl.prototype.getChildName = function (nodeName, descendantName) {
-                    // Walk up the hierarchy from the descendant to find the child.
-                    var currentNode = this.index[descendantName];
-                    while (currentNode) {
-                        if (currentNode.parentNode && currentNode.parentNode.name === nodeName) {
-                            return currentNode.name;
-                        }
-                        currentNode = currentNode.parentNode;
-                    }
-                    throw Error('Could not find immediate child for descendant: ' + descendantName);
-                };
-                ;
-                /** Given the name of a node, return its incoming metaedges. */
-                HierarchyImpl.prototype.getPredecessors = function (nodeName) {
-                    var _this = this;
-                    var node = this.index[nodeName];
-                    if (!node) {
-                        throw Error('Could not find node with name: ' + nodeName);
-                    }
-                    var predecessors = this.getOneWayEdges(node, true);
-                    // Add embedded predecessors, such as constants.
-                    if (!node.isGroupNode) {
-                        _.each(node.inEmbeddings, function (embeddedNode) {
-                            _.each(node.inputs, function (input) {
-                                if (input.name === embeddedNode.name) {
-                                    // Make a new metaedge holding the edge between the
-                                    // node and the in-embedding.
-                                    var metaedge = new graph_1.MetaedgeImpl(embeddedNode.name, nodeName);
-                                    metaedge.addBaseEdge({
-                                        isControlDependency: input.isControlDependency,
-                                        outputTensorIndex: input.outputTensorIndex,
-                                        isReferenceEdge: false,
-                                        v: embeddedNode.name,
-                                        w: nodeName
-                                    }, _this);
-                                    predecessors.regular.push(metaedge);
-                                }
-                            });
-                        });
-                    }
-                    return predecessors;
-                };
-                /**
-                 * Given the name of a node, return its outgoing metaedges.
-                 *
-                 * This is the inverse of getPredecessors(). See that method's documentation
-                 * for an in-depth example.
-                 */
-                HierarchyImpl.prototype.getSuccessors = function (nodeName) {
-                    var _this = this;
-                    var node = this.index[nodeName];
-                    if (!node) {
-                        throw Error('Could not find node with name: ' + nodeName);
-                    }
-                    var successors = this.getOneWayEdges(node, false);
-                    // Add embedded successors, such as summaries.
-                    if (!node.isGroupNode) {
-                        _.each(node.outEmbeddings, function (embeddedNode) {
-                            _.each(embeddedNode.inputs, function (input) {
-                                if (input.name === nodeName) {
-                                    // Make a new metaedge holding the edge between the
-                                    // node and the out-embedding.
-                                    var metaedge = new graph_1.MetaedgeImpl(nodeName, embeddedNode.name);
-                                    metaedge.addBaseEdge({
-                                        isControlDependency: input.isControlDependency,
-                                        outputTensorIndex: input.outputTensorIndex,
-                                        isReferenceEdge: false,
-                                        v: nodeName,
-                                        w: embeddedNode.name
-                                    }, _this);
-                                    successors.regular.push(metaedge);
-                                }
-                            });
-                        });
-                    }
-                    return successors;
-                };
-                /** Helper method for getPredecessors and getSuccessors */
-                HierarchyImpl.prototype.getOneWayEdges = function (node, inEdges) {
-                    var edges = { control: [], regular: [] };
-                    // A node with no parent cannot have any edges.
-                    if (!node.parentNode || !node.parentNode.isGroupNode) {
-                        return edges;
-                    }
-                    var parentNode = node.parentNode;
-                    var metagraph = parentNode.metagraph;
-                    var bridgegraph = this.getBridgegraph(parentNode.name);
-                    findEdgeTargetsInGraph(metagraph, node, inEdges, edges);
-                    findEdgeTargetsInGraph(bridgegraph, node, inEdges, edges);
-                    return edges;
-                };
-                /**
-                 * For a given GroupNode, get or calculate an object which describes a
-                 * topological ordering of child nodes within that GroupNode's metagraph.
-                 *
-                 * This ordering is used when rendering bridge control edges which are
-                 * sometimes backwards relative to the dataflow.
-                 *
-                 * For example, say we have a graph with two edges A->B and A->C, and we're
-                 * interested in the ordering under ROOT. In this case, any of the following
-                 * would be legitimate return values:
-                 *
-                 *  - { 'A': 0, 'B': 1, 'C': 2 } -- most likely
-                 *  - { 'A': 0, 'B': 2, 'C': 1 } -- less likely
-                 *  - { 'A': 12, 'B': 100, 'C': 99 } -- unlikely, but still OK
-                 *
-                 * The algorithm does not guarantee that all numbers from 0-N (where N is
-                 * the number of nodes) appear exactly once. Rather it guarantees that if
-                 * there is a path between two nodes, the earlier one will have a lower
-                 * number in the ordering hash.
-                 *
-                 * When generating the ordering, we ignore control Metaedges (those which
-                 * represent only BaseEdges that have isControlDependency set to true).
-                 *
-                 * If there is no node with the specified name, an error is thrown. If the
-                 * node with the specified name is not a group node, null is returned.
-                 */
-                HierarchyImpl.prototype.getTopologicalOrdering = function (nodeName) {
-                    var node = this.index[nodeName];
-                    if (!node) {
-                        throw Error('Could not find node with name: ' + nodeName);
-                    }
-                    if (!node.isGroupNode) {
-                        return null;
-                    }
-                    if (nodeName in this.orderings) {
-                        return this.orderings[nodeName];
-                    }
-                    // Mapping of a child node names to lists of their successors.
-                    var successors = {};
-                    // Set of node names which have appeared as a destination.
-                    var destinations = {};
-                    var metagraph = node.metagraph;
-                    _.each(metagraph.edges(), function (e) {
-                        if (!metagraph.edge(e).numRegularEdges) {
-                            return; // Skip control edges.
-                        }
-                        // Keep track of successors and destinations.
-                        if (!(e.v in successors)) {
-                            successors[e.v] = [];
-                        }
-                        successors[e.v].push(e.w);
-                        destinations[e.w] = true;
-                    });
-                    // Seed the queue with true sources (those that are not destinations).
-                    var queue = _.difference(_.keys(successors), _.keys(destinations));
-                    // Produce an ordering by traversing the graph breadth first.
-                    var ordering = this.orderings[nodeName] = {};
-                    var index = 0;
-                    while (queue.length) {
-                        var childName = queue.shift();
-                        ordering[childName] = index++;
-                        _.each(successors[childName], function (succName) { return queue.push(succName); });
-                        delete successors[childName]; // Prevent cycles from infinite looping.
-                    }
-                    return ordering;
-                };
-                /**
-                 * Returns a d3 Ordinal function that can be used to look up the index of
-                 * a node based on its template id.
-                 */
-                HierarchyImpl.prototype.getTemplateIndex = function () {
-                    var templateNames = d3.keys(this.templates);
-                    var templateIndex = d3.scale.ordinal()
-                        .domain(templateNames)
-                        .range(d3.range(0, templateNames.length));
-                    return function (templateId) { return templateIndex(templateId); };
-                };
-                return HierarchyImpl;
-            }());
-            /**
-             * Internal utility function - given a graph (should be either a metagraph or a
-             * bridgegraph) and a node which is known to be in that graph, determine
-             * the other ends of edges that involve that node in the direction specified
-             * by whether it's inbound.
-             *
-             * For example if you wanted to find the predecessors of a node, you'd call
-             * this method for the parent's metagraph and bridgegraph, specifying inbound
-             * as true (look at the source of inbound edges to the specified node).
-             *
-             * Discovered target names are appended to the targets array.
-             */
-            function findEdgeTargetsInGraph(graph, node, inbound, targets) {
-                var edges = inbound ? graph.inEdges(node.name) : graph.outEdges(node.name);
-                _.each(edges, function (e) {
-                    var metaedge = graph.edge(e);
-                    var targetList = metaedge.numRegularEdges ? targets.regular : targets.control;
-                    targetList.push(metaedge);
-                });
-            }
-            /**
-             * @param graph The raw graph.
-             * @param params Parameters used when building a hierarchy.
-             */
-            function build(graph, params, tracker) {
-                var h = new HierarchyImpl();
-                var seriesNames = {};
-                return tf.graph.util
-                    .runAsyncTask('Adding nodes', 20, function () {
-                    // Get all the possible device and XLA cluster names.
-                    var deviceNames = {};
-                    var xlaClusterNames = {};
-                    _.each(graph.nodes, function (node, nodeName) {
-                        if (node.device) {
-                            deviceNames[node.device] = true;
-                        }
-                        if (node.xlaCluster) {
-                            xlaClusterNames[node.xlaCluster] = true;
-                        }
-                    });
-                    h.devices = _.keys(deviceNames);
-                    h.xlaClusters = _.keys(xlaClusterNames);
-                    addNodes(h, graph);
-                }, tracker)
-                    .then(function () {
-                    return tf.graph.util.runAsyncTask('Detect series', 20, function () {
-                        if (params.seriesNodeMinSize > 0) {
-                            groupSeries(h.root, h, seriesNames, params.seriesNodeMinSize, params.seriesMap);
-                        }
-                    }, tracker);
-                })
-                    .then(function () {
-                    return tf.graph.util.runAsyncTask('Adding edges', 30, function () {
-                        addEdges(h, graph, seriesNames);
-                    }, tracker);
-                })
-                    .then(function () {
-                    return tf.graph.util.runAsyncTask('Finding similar subgraphs', 30, function () {
-                        h.templates = graph_1.template.detect(h, params.verifyTemplate);
-                    }, tracker);
-                })
-                    .then(function () {
-                    return h;
-                });
-            }
-            hierarchy_1.build = build;
-            ;
-            function joinAndAggregateStats(h, stats) {
-                // Get all the possible device names.
-                var deviceNames = {};
-                _.each(h.root.leaves(), function (nodeName) {
-                    var leaf = h.node(nodeName);
-                    if (leaf.device != null) {
-                        deviceNames[leaf.device] = true;
-                    }
-                });
-                h.devices = _.keys(deviceNames);
-                // Reset stats for each group node.
-                _.each(h.getNodeMap(), function (node, nodeName) {
-                    if (node.isGroupNode) {
-                        node.stats = new graph_1.NodeStats(null);
-                        node.deviceHistogram = {};
-                    }
-                });
-                // Bubble-up the stats and device distribution from leaves to parents.
-                _.each(h.root.leaves(), function (nodeName) {
-                    var leaf = h.node(nodeName);
-                    var node = leaf;
-                    while (node.parentNode != null) {
-                        if (leaf.device != null) {
-                            var deviceHistogram = node.parentNode.deviceHistogram;
-                            deviceHistogram[leaf.device] = (deviceHistogram[leaf.device] || 0) + 1;
-                        }
-                        if (leaf.stats != null) {
-                            node.parentNode.stats.combine(leaf.stats);
-                        }
-                        node = node.parentNode;
-                    }
-                });
-            }
-            hierarchy_1.joinAndAggregateStats = joinAndAggregateStats;
-            /**
-             * Creates the metanodes in the hierarchical graph and assigns parent-child
-             * relationship between them.
-             */
-            function addNodes(h, graph) {
-                _.each(graph.nodes, function (node, nodeName) {
-                    var path = graph_1.getHierarchicalPath(node.name);
-                    var parent = h.root;
-                    parent.depth = Math.max(path.length, parent.depth);
-                    // Create parent metanodes for each depth. For example if the node name
-                    // is 'a/b/c', then create metanodes 'a' and 'a/b', where 'a/b' is a child
-                    // of a.
-                    for (var i = 0; i < path.length; i++) {
-                        parent.depth = Math.max(parent.depth, path.length - i);
-                        parent.cardinality += node.cardinality;
-                        parent.opHistogram[node.op] = (parent.opHistogram[node.op] || 0) + 1;
-                        if (node.device != null) {
-                            parent.deviceHistogram[node.device] =
-                                (parent.deviceHistogram[node.device] || 0) + 1;
-                        }
-                        if (i === path.length - 1) {
-                            break;
-                        }
-                        var name_1 = path[i];
-                        var child = h.node(name_1);
-                        if (!child) {
-                            child = graph_1.createMetanode(name_1);
-                            child.parentNode = parent;
-                            h.setNode(name_1, child);
-                            parent.metagraph.setNode(name_1, child);
-                        }
-                        parent = child;
-                    }
-                    // Assuming node name is 'a/b/c', assign the OpNode as a child of the
-                    // metanode 'a/b'.
-                    h.setNode(node.name, node);
-                    node.parentNode = parent;
-                    parent.metagraph.setNode(node.name, node);
-                    // Add each of the in-embeddings and out-embeddings in the hierarchy.
-                    _.each(node.inEmbeddings, function (embedding) {
-                        h.setNode(embedding.name, embedding);
-                        embedding.parentNode = node;
-                    });
-                    _.each(node.outEmbeddings, function (embedding) {
-                        h.setNode(embedding.name, embedding);
-                        embedding.parentNode = node;
-                    });
-                });
-            }
-            ;
-            /**
-             * For each metanode in the hierarchical graph, this method adds:
-             * the edges in the metagraph. These are edges between nodes
-             * that share the same parent.
-             */
-            function addEdges(h, graph, seriesNames) {
-                var nodeIndex = h.getNodeMap();
-                // Ancestor paths for the source and destination nodes of an edge. These are
-                // reused for each edge rather than allocating new ones. It's about 10% faster
-                // than allocating new ones on each pass through the loop.
-                var sourcePath = [];
-                var destPath = [];
-                // Insert the ancestor path for a node into the provided array, including the
-                // node itself. Return the index of the last node inserted (always ROOT).
-                var getPath = function (node, path) {
-                    var i = 0;
-                    while (node) {
-                        path[i++] = node.name;
-                        node = node.parentNode;
-                    }
-                    return i - 1;
-                };
-                _.each(graph.edges, function (baseEdge) {
-                    // Get the hierarchical paths for the source and destination of the edge.
-                    var sourceAncestorIndex = getPath(graph.nodes[baseEdge.v], sourcePath);
-                    var destAncestorIndex = getPath(graph.nodes[baseEdge.w], destPath);
-                    // If the hierarchical path cannot be found for either endpoint, then we
-                    // cannot create the edge. This happens for example when a node has a
-                    // control dependency on a summary node, which are embedded.
-                    if (sourceAncestorIndex === -1 || destAncestorIndex === -1) {
-                        return;
-                    }
-                    // Find the lowest shared ancestor between source and dest by looking for
-                    // the highest nodes that differ between their ancestor paths.
-                    while (sourcePath[sourceAncestorIndex] === destPath[destAncestorIndex]) {
-                        sourceAncestorIndex--;
-                        destAncestorIndex--;
-                        if (sourceAncestorIndex < 0 || destAncestorIndex < 0) {
-                            // This would only occur if the two nodes were the same (a cycle in the
-                            // graph), or if one endpoint was a strict ancestor of the other. The
-                            // latter shouldn't happen because we rename nodes which are both
-                            // metanodes and op nodes. E.g. 'A/B' becomes 'A/B/(B)'.
-                            throw Error('No difference found between ancestor paths.');
-                        }
-                    }
-                    var sharedAncestorNode = nodeIndex[sourcePath[sourceAncestorIndex + 1]];
-                    var sourceAncestorName = sourcePath[sourceAncestorIndex];
-                    var destAncestorName = destPath[destAncestorIndex];
-                    // Find or create the Metaedge which should contain this BaseEdge inside
-                    // the shared ancestor.
-                    var metaedge = sharedAncestorNode.metagraph.edge(sourceAncestorName, destAncestorName);
-                    if (!metaedge) {
-                        metaedge = graph_1.createMetaedge(sourceAncestorName, destAncestorName);
-                        sharedAncestorNode.metagraph
-                            .setEdge(sourceAncestorName, destAncestorName, metaedge);
-                    }
-                    if (!sharedAncestorNode.hasNonControlEdges &&
-                        !baseEdge.isControlDependency) {
-                        sharedAncestorNode.hasNonControlEdges = true;
-                    }
-                    metaedge.addBaseEdge(baseEdge, h);
-                });
-            }
-            ;
-            /**
-             * Using the hierarchy template information, detect series in the provided
-             * metanode.  For each detected series, create a new SeriesNode
-             * and remove series members from the metanode's metagraph and move them to
-             * the new series node's metagraph.
-             *
-             * @param metanode
-             * @param hierarchy
-             * @param seriesNames Map of node names to their series they are contained in.
-             *     This should be provided empty and is populated by this method.
-             * @param threshold If the series has this many nodes or more, then group them
-             *     into a series.
-             * @param map Map of series names to their series grouping type, if one has
-             *     been set.
-             * @return A dictionary from node name to series node name that contains the
-             *     node.
-             */
-            function groupSeries(metanode, hierarchy, seriesNames, threshold, map) {
-                var metagraph = metanode.metagraph;
-                _.each(metagraph.nodes(), function (n) {
-                    var child = metagraph.node(n);
-                    if (child.type === tf.graph.NodeType.META) {
-                        groupSeries(child, hierarchy, seriesNames, threshold, map);
-                    }
-                });
-                var clusters = clusterNodes(metagraph);
-                var seriesDict = detectSeries(clusters, metagraph);
-                // Add each series node to the graph and add its grouped children to its own
-                // metagraph.
-                _.each(seriesDict, function (seriesNode, seriesName) {
-                    var nodeMemberNames = seriesNode.metagraph.nodes();
-                    _.each(nodeMemberNames, function (n) {
-                        var child = metagraph.node(n);
-                        if (!child.owningSeries) {
-                            child.owningSeries = seriesName;
-                        }
-                    });
-                    // If the series contains less than the threshold number of nodes and
-                    // this series has not been adding to the series map, then set this
-                    // series to be shown ungrouped in the map.
-                    if (nodeMemberNames.length < threshold && !(seriesNode.name in map)) {
-                        map[seriesNode.name] = tf.graph.SeriesGroupingType.UNGROUP;
-                    }
-                    // If the series is in the map as ungrouped then do not group the series.
-                    if (seriesNode.name in map
-                        && map[seriesNode.name] === tf.graph.SeriesGroupingType.UNGROUP) {
-                        return;
-                    }
-                    hierarchy.setNode(seriesName, seriesNode); // add to the index
-                    metagraph.setNode(seriesName, seriesNode);
-                    _.each(nodeMemberNames, function (n) {
-                        var child = metagraph.node(n);
-                        seriesNode.metagraph.setNode(n, child);
-                        seriesNode.parentNode = child.parentNode;
-                        seriesNode.cardinality++;
-                        if (child.device != null) {
-                            seriesNode.deviceHistogram[child.device] =
-                                (seriesNode.deviceHistogram[child.device] || 0) + 1;
-                        }
-                        child.parentNode = seriesNode;
-                        seriesNames[n] = seriesName;
-                        // Remove now-grouped node from its original parent's metagraph.
-                        metagraph.removeNode(n);
-                    });
-                });
-            }
-            ;
-            /** cluster op-nodes with similar op */
-            function clusterNodes(metagraph) {
-                var result = {};
-                return _.reduce(metagraph.nodes(), function (clusters, n) {
-                    var child = metagraph.node(n);
-                    if (child.type === graph_1.NodeType.META) {
-                        // skip metanodes
-                        return clusters;
-                    }
-                    var template = child.op;
-                    if (template) {
-                        clusters[template] = clusters[template] || [];
-                        clusters[template].push(child.name);
-                    }
-                    return clusters;
-                }, result);
-            }
-            /**
-             * For each cluster of op-nodes based op type, try to detect groupings.
-             * Infer series name using by trying to find pattern '<number>' in the node
-             * name.
-             *
-             * @param clusters Dictionary output from clusterNodes().
-             * @param metagraph
-             * @return A dictionary from series name => seriesNode
-             */
-            function detectSeries(clusters, metagraph) {
-                var seriesDict = {};
-                _.each(clusters, function (members, clusterId) {
-                    if (members.length <= 1) {
-                        return;
-                    } // isolated clusters can't make series
-                    /** @type {Object}  A dictionary mapping seriesName to seriesInfoArray,
-                     * which is an array that contains objects with name, id, prefix, suffix,
-                     * and parent properties.
-                     */
-                    var candidatesDict = {};
-                    // Group all nodes that have the same name, with the exception of a
-                    // number at the end of the name after an underscore, which is allowed to
-                    // vary.
-                    _.each(members, function (name) {
-                        var isGroup = name.charAt(name.length - 1) === '*';
-                        var namepath = name.split('/');
-                        var leaf = namepath[namepath.length - 1];
-                        var parent = namepath.slice(0, namepath.length - 1).join('/');
-                        var matches = leaf.match(/^(\D*)_(\d+)$/);
-                        var prefix;
-                        var id;
-                        var suffix = '';
-                        if (matches) {
-                            prefix = matches[1]; // the front non-numeric characters
-                            id = matches[2]; // the digits
-                        }
-                        else {
-                            prefix = isGroup ? leaf.substr(0, leaf.length - 1) : leaf;
-                            id = 0;
-                            suffix = isGroup ? '*' : '';
-                        }
-                        var seriesName = graph_1.getSeriesNodeName(prefix, suffix, parent);
-                        candidatesDict[seriesName] = candidatesDict[seriesName] || [];
-                        var seriesNode = graph_1.createSeriesNode(prefix, suffix, parent, +id, name);
-                        candidatesDict[seriesName].push(seriesNode);
-                    });
-                    // In each group of nodes, group nodes in bunches that have monotonically
-                    // increasing numbers in their names.  Each of these bunches is a series.
-                    _.each(candidatesDict, function (seriesInfoArray, seriesName) {
-                        if (seriesInfoArray.length < 2) {
-                            return;
-                        }
-                        seriesInfoArray.sort(function (a, b) {
-                            return (+a.clusterId) - (+b.clusterId);
-                        });
-                        // Loop through the nodes sorted by its detected series number, grouping
-                        // all nodes with monotonically-increasing series numbers.
-                        var seriesNodes = [seriesInfoArray[0]];
-                        for (var index = 1; index < seriesInfoArray.length; index++) {
-                            var nextNode = seriesInfoArray[index];
-                            if (nextNode.clusterId === seriesNodes[seriesNodes.length - 1].clusterId
-                                + 1) {
-                                seriesNodes.push(nextNode);
-                                continue;
-                            }
-                            addSeriesToDict(seriesNodes, seriesDict, +clusterId, metagraph);
-                            seriesNodes = [nextNode];
-                        }
-                        addSeriesToDict(seriesNodes, seriesDict, +clusterId, metagraph);
-                    });
-                });
-                return seriesDict;
-            }
-            /**
-             * Add a series to the provided dictionary mapping series names to series.
-             *
-             * @param seriesNodes the nodes in the series. Contains
-             *     name, id, prefix, suffix and parent properties of the node.
-             * @param seriesDict the dictionary of series
-             * @param clusterId ID of the template of the nodes of the series
-             * @param metagraph
-             */
-            function addSeriesToDict(seriesNodes, seriesDict, clusterId, metagraph) {
-                if (seriesNodes.length > 1) {
-                    var curSeriesName = graph_1.getSeriesNodeName(seriesNodes[0].prefix, seriesNodes[0].suffix, seriesNodes[0].parent, seriesNodes[0].clusterId, seriesNodes[seriesNodes.length - 1].clusterId);
-                    var curSeriesNode_1 = graph_1.createSeriesNode(seriesNodes[0].prefix, seriesNodes[0].suffix, seriesNodes[0].parent, clusterId, curSeriesName);
-                    _.each(seriesNodes, function (node) {
-                        curSeriesNode_1.ids.push(node.clusterId);
-                        curSeriesNode_1.metagraph.setNode(node.name, metagraph.node(node.name));
-                    });
-                    seriesDict[curSeriesName] = curSeriesNode_1;
-                }
-            }
-        })(hierarchy = graph_1.hierarchy || (graph_1.hierarchy = {}));
-    })(graph = tf.graph || (tf.graph = {}));
-})(tf || (tf = {})); // close module tf.graph.hierarchy
-</script>
-<script>/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-var tf;
-(function (tf) {
-    var graph;
-    (function (graph_1) {
-        var layout;
-        (function (layout) {
-            /** Set of parameters that define the look and feel of the graph. */
-            layout.PARAMS = {
-                animation: {
-                    /** Default duration for graph animations in ms. */
-                    duration: 250
-                },
-                graph: {
-                    /** Graph parameter for metanode. */
-                    meta: {
-                        /**
-                         * Dagre's nodesep param - number of pixels that
-                         * separate nodes horizontally in the layout.
-                         *
-                         * See https://github.com/cpettitt/dagre/wiki#configuring-the-layout
-                         */
-                        nodeSep: 5,
-                        /**
-                         * Dagre's ranksep param - number of pixels
-                         * between each rank in the layout.
-                         *
-                         * See https://github.com/cpettitt/dagre/wiki#configuring-the-layout
-                         */
-                        rankSep: 25,
-                        /**
-                         * Dagre's edgesep param - number of pixels that separate
-                         * edges horizontally in the layout.
-                         */
-                        edgeSep: 5,
-                    },
-                    /** Graph parameter for metanode. */
-                    series: {
-                        /**
-                         * Dagre's nodesep param - number of pixels that
-                         * separate nodes horizontally in the layout.
-                         *
-                         * See https://github.com/cpettitt/dagre/wiki#configuring-the-layout
-                         */
-                        nodeSep: 5,
-                        /**
-                         * Dagre's ranksep param - number of pixels
-                         * between each rank in the layout.
-                         *
-                         * See https://github.com/cpettitt/dagre/wiki#configuring-the-layout
-                         */
-                        rankSep: 25,
-                        /**
-                         * Dagre's edgesep param - number of pixels that separate
-                         * edges horizontally in the layout.
-                         */
-                        edgeSep: 5
-                    },
-                    /**
-                     * Padding is used to correctly position the graph SVG inside of its parent
-                     * element. The padding amounts are applied using an SVG transform of X and
-                     * Y coordinates.
-                     */
-                    padding: { paddingTop: 40, paddingLeft: 20 }
-                },
-                subscene: {
-                    meta: {
-                        paddingTop: 10,
-                        paddingBottom: 10,
-                        paddingLeft: 10,
-                        paddingRight: 10,
-                        /**
-                         * Used to leave room for the label on top of the highest node in
-                         * the core graph.
-                         */
-                        labelHeight: 20,
-                        /** X-space between each extracted node and the core graph. */
-                        extractXOffset: 15,
-                        /** Y-space between each extracted node. */
-                        extractYOffset: 20
-                    },
-                    series: {
-                        paddingTop: 10,
-                        paddingBottom: 10,
-                        paddingLeft: 10,
-                        paddingRight: 10,
-                        labelHeight: 10
-                    }
-                },
-                nodeSize: {
-                    /** Size of meta nodes. */
-                    meta: {
-                        radius: 5,
-                        width: 60,
-                        maxLabelWidth: 52,
-                        /** A scale for the node's height based on number of nodes inside */
-                        height: d3.scale.linear().domain([1, 200]).range([15, 60]).clamp(true),
-                        /** The radius of the circle denoting the expand button. */
-                        expandButtonRadius: 3
-                    },
-                    /** Size of op nodes. */
-                    op: {
-                        width: 15,
-                        height: 6,
-                        radius: 3,
-                        labelOffset: -8,
-                        maxLabelWidth: 30
-                    },
-                    /** Size of series nodes. */
-                    series: {
-                        expanded: {
-                            // For expanded series nodes, width and height will be
-                            // computed to account for the subscene.
-                            radius: 10,
-                            labelOffset: 0,
-                        },
-                        vertical: {
-                            // When unexpanded, series whose underlying metagraphs contain
-                            // one or more non-control edges will show as a vertical stack
-                            // of ellipses.
-                            width: 16,
-                            height: 13,
-                            labelOffset: -13,
-                        },
-                        horizontal: {
-                            // When unexpanded, series whose underlying metagraphs contain
-                            // no non-control edges will show as a horizontal stack of
-                            // ellipses.
-                            width: 24,
-                            height: 8,
-                            radius: 10,
-                            labelOffset: -10,
-                        },
-                    },
-                    /** Size of bridge nodes. */
-                    bridge: {
-                        // NOTE: bridge nodes will normally be invisible, but they must
-                        // take up some space so that the layout step leaves room for
-                        // their edges.
-                        width: 20,
-                        height: 20,
-                        radius: 2,
-                        labelOffset: 0
-                    }
-                },
-                shortcutSize: {
-                    /** Size of shortcuts for op nodes */
-                    op: { width: 10, height: 4 },
-                    /** Size of shortcuts for meta nodes */
-                    meta: { width: 12, height: 4, radius: 1 },
-                    /** Size of shortcuts for series nodes */
-                    series: {
-                        width: 14,
-                        height: 4,
-                    }
-                },
-                annotations: {
-                    /** Maximum possible width of the bounding box for in annotations */
-                    inboxWidth: 50,
-                    /** Maximum possible width of the bounding box for out annotations */
-                    outboxWidth: 50,
-                    /** X-space between the shape and each annotation-node. */
-                    xOffset: 10,
-                    /** Y-space between each annotation-node. */
-                    yOffset: 3,
-                    /** X-space between each annotation-node and its label. */
-                    labelOffset: 2,
-                    /** Defines the max width for annotation label */
-                    maxLabelWidth: 120
-                },
-                constant: { size: { width: 4, height: 4 } },
-                series: {
-                    /** Maximum number of repeated item for unexpanded series node. */
-                    maxStackCount: 3,
-                    /**
-                     * Positioning offset ratio for collapsed stack
-                     * of parallel series (series without edges between its members).
-                     */
-                    parallelStackOffsetRatio: 0.2,
-                    /**
-                     * Positioning offset ratio for collapsed stack
-                     * of tower series (series with edges between its members).
-                     */
-                    towerStackOffsetRatio: 0.5
-                },
-                minimap: {
-                    /** The maximum width/height the minimap can have. */
-                    size: 150
-                }
-            };
-            /** Calculate layout for a scene of a group node. */
-            function layoutScene(renderNodeInfo) {
-                // Update layout, size, and annotations of its children nodes and edges.
-                if (renderNodeInfo.node.isGroupNode) {
-                    layoutChildren(renderNodeInfo);
-                }
-                // Update position of its children nodes and edges
-                if (renderNodeInfo.node.type === graph_1.NodeType.META) {
-                    layoutMetanode(renderNodeInfo);
-                }
-                else if (renderNodeInfo.node.type === graph_1.NodeType.SERIES) {
-                    layoutSeriesNode(renderNodeInfo);
-                }
-            }
-            layout.layoutScene = layoutScene;
-            ;
-            /**
-             * Updates the total width of an unexpanded node which includes the size of its
-             * in and out annotations.
-             */
-            function updateTotalWidthOfNode(renderInfo) {
-                renderInfo.inboxWidth = renderInfo.inAnnotations.list.length > 0 ?
-                    layout.PARAMS.annotations.inboxWidth : 0;
-                renderInfo.outboxWidth = renderInfo.outAnnotations.list.length > 0 ?
-                    layout.PARAMS.annotations.outboxWidth : 0;
-                // Assign the width of the core box (the main shape of the node).
-                renderInfo.coreBox.width = renderInfo.width;
-                renderInfo.coreBox.height = renderInfo.height;
-                // TODO(jimbo): Account for font width rather than using a magic number.
-                var labelLength = renderInfo.node.name.length -
-                    renderInfo.node.name.lastIndexOf(graph_1.NAMESPACE_DELIM) - 1;
-                var charWidth = 3; // 3 pixels per character.
-                // Compute the total width of the node.
-                renderInfo.width = Math.max(renderInfo.coreBox.width +
-                    renderInfo.inboxWidth + renderInfo.outboxWidth, labelLength * charWidth);
-            }
-            /**
-             * Update layout, size, and annotations of its children nodes and edges.
-             */
-            function layoutChildren(renderNodeInfo) {
-                var children = renderNodeInfo.coreGraph.nodes().map(function (n) {
-                    return renderNodeInfo.coreGraph.node(n);
-                }).concat(renderNodeInfo.isolatedInExtract, renderNodeInfo.isolatedOutExtract);
-                _.each(children, function (childNodeInfo) {
-                    // Set size of each child
-                    switch (childNodeInfo.node.type) {
-                        case graph_1.NodeType.OP:
-                            _.extend(childNodeInfo, layout.PARAMS.nodeSize.op);
-                            break;
-                        case graph_1.NodeType.BRIDGE:
-                            _.extend(childNodeInfo, layout.PARAMS.nodeSize.bridge);
-                            break;
-                        case graph_1.NodeType.META:
-                            if (!childNodeInfo.expanded) {
-                                // Set fixed width and scalable height based on cardinality
-                                _.extend(childNodeInfo, layout.PARAMS.nodeSize.meta);
-                                childNodeInfo.height =
-                                    layout.PARAMS.nodeSize.meta.height(childNodeInfo.node.cardinality);
-                            }
-                            else {
-                                var childGroupNodeInfo = childNodeInfo;
-                                layoutScene(childGroupNodeInfo); // Recursively layout its subscene.
-                            }
-                            break;
-                        case graph_1.NodeType.SERIES:
-                            if (childNodeInfo.expanded) {
-                                _.extend(childNodeInfo, layout.PARAMS.nodeSize.series.expanded);
-                                var childGroupNodeInfo = childNodeInfo;
-                                layoutScene(childGroupNodeInfo); // Recursively layout its subscene.
-                            }
-                            else {
-                                var childGroupNodeInfo = childNodeInfo;
-                                var seriesParams = childGroupNodeInfo.node.hasNonControlEdges ?
-                                    layout.PARAMS.nodeSize.series.vertical :
-                                    layout.PARAMS.nodeSize.series.horizontal;
-                                _.extend(childNodeInfo, seriesParams);
-                            }
-                            break;
-                        default:
-                            throw Error('Unrecognized node type: ' + childNodeInfo.node.type);
-                    }
-                    // Compute total width of un-expanded nodes. Width of expanded nodes
-                    // has already been computed.
-                    if (!childNodeInfo.expanded) {
-                        updateTotalWidthOfNode(childNodeInfo);
-                    }
-                    // Layout each child's annotations
-                    layoutAnnotation(childNodeInfo);
-                });
-            }
-            /**
-             * Calculate layout for a graph using dagre
-             * @param graph the graph to be laid out
-             * @param params layout parameters
-             * @return width and height of the core graph
-             */
-            function dagreLayout(graph, params) {
-                _.extend(graph.graph(), {
-                    nodesep: params.nodeSep,
-                    ranksep: params.rankSep,
-                    edgesep: params.edgeSep
-                });
-                var bridgeNodeNames = [];
-                var nonBridgeNodeNames = [];
-                // Split out nodes into bridge and non-bridge nodes, and calculate the total
-                // width we should use for bridge nodes.
-                _.each(graph.nodes(), function (nodeName) {
-                    var nodeInfo = graph.node(nodeName);
-                    if (nodeInfo.node.type === graph_1.NodeType.BRIDGE) {
-                        bridgeNodeNames.push(nodeName);
-                    }
-                    else {
-                        nonBridgeNodeNames.push(nodeName);
-                    }
-                });
-                // If there are no non-bridge nodes, then the graph has zero size.
-                if (!nonBridgeNodeNames.length) {
-                    return {
-                        width: 0,
-                        height: 0,
-                    };
-                }
-                dagre.layout(graph);
-                // Calculate the true bounding box of the graph by iterating over nodes and
-                // edges rather than accepting dagre's word for it. In particular, we should
-                // ignore the extra-wide bridge nodes and bridge edges, and allow for
-                // annotation boxes and labels.
-                var minX = Infinity;
-                var minY = Infinity;
-                var maxX = -Infinity;
-                var maxY = -Infinity;
-                _.each(nonBridgeNodeNames, function (nodeName) {
-                    var nodeInfo = graph.node(nodeName);
-                    var w = 0.5 * nodeInfo.width;
-                    var x1 = nodeInfo.x - w;
-                    var x2 = nodeInfo.x + w;
-                    minX = x1 < minX ? x1 : minX;
-                    maxX = x2 > maxX ? x2 : maxX;
-                    // TODO(jimbo): Account for the height of labels above op nodes here.
-                    var h = 0.5 * nodeInfo.height;
-                    var y1 = nodeInfo.y - h;
-                    var y2 = nodeInfo.y + h;
-                    minY = y1 < minY ? y1 : minY;
-                    maxY = y2 > maxY ? y2 : maxY;
-                });
-                _.each(graph.edges(), function (edgeObj) {
-                    var edgeInfo = graph.edge(edgeObj);
-                    if (edgeInfo.structural) {
-                        return; // Skip structural edges from min/max calculations.
-                    }
-                    // Since the node size passed to dagre includes the in and out
-                    // annotations, the endpoints of the edge produced by dagre may not
-                    // point to the actual node shape (rectangle, ellipse). We correct the
-                    // end-points by finding the intersection of a line between the
-                    // next-to-last (next-to-first) point and the destination (source)
-                    // rectangle.
-                    var sourceNode = graph.node(edgeInfo.metaedge.v);
-                    var destNode = graph.node(edgeInfo.metaedge.w);
-                    // Straight 3-points edges are special case, since they are curved after
-                    // our default correction. To keep them straight, we remove the mid point
-                    // and correct the first and the last point to be the center of the
-                    // source and destination node respectively.
-                    if (edgeInfo.points.length === 3 && isStraightLine(edgeInfo.points)) {
-                        if (sourceNode != null) {
-                            var cxSource = sourceNode.expanded ?
-                                sourceNode.x : computeCXPositionOfNodeShape(sourceNode);
-                            edgeInfo.points[0].x = cxSource;
-                        }
-                        if (destNode != null) {
-                            var cxDest = destNode.expanded ?
-                                destNode.x : computeCXPositionOfNodeShape(destNode);
-                            edgeInfo.points[2].x = cxDest;
-                        }
-                        // Remove the middle point so the edge doesn't curve.
-                        edgeInfo.points = [edgeInfo.points[0], edgeInfo.points[1]];
-                    }
-                    // Correct the destination endpoint of the edge.
-                    var nextToLastPoint = edgeInfo.points[edgeInfo.points.length - 2];
-                    // The destination node might be null if this is a bridge edge.
-                    if (destNode != null) {
-                        edgeInfo.points[edgeInfo.points.length - 1] =
-                            intersectPointAndNode(nextToLastPoint, destNode);
-                    }
-                    // Correct the source endpoint of the edge.
-                    var secondPoint = edgeInfo.points[1];
-                    // The source might be null if this is a bridge edge.
-                    if (sourceNode != null) {
-                        edgeInfo.points[0] = intersectPointAndNode(secondPoint, sourceNode);
-                    }
-                    _.each(edgeInfo.points, function (point) {
-                        minX = point.x < minX ? point.x : minX;
-                        maxX = point.x > maxX ? point.x : maxX;
-                        minY = point.y < minY ? point.y : minY;
-                        maxY = point.y > maxY ? point.y : maxY;
-                    });
-                });
-                // Shift all nodes and edge points to account for the left-padding amount,
-                // and the invisible bridge nodes.
-                _.each(graph.nodes(), function (nodeName) {
-                    var nodeInfo = graph.node(nodeName);
-                    nodeInfo.x -= minX;
-                    nodeInfo.y -= minY;
-                });
-                _.each(graph.edges(), function (edgeObj) {
-                    _.each(graph.edge(edgeObj).points, function (point) {
-                        point.x -= minX;
-                        point.y -= minY;
-                    });
-                });
-                return {
-                    width: maxX - minX,
-                    height: maxY - minY
-                };
-            }
-            /** Layout a metanode. Only called for an expanded node. */
-            function layoutMetanode(renderNodeInfo) {
-                // First, copy params specific to meta nodes onto this render info object.
-                var params = layout.PARAMS.subscene.meta;
-                _.extend(renderNodeInfo, params);
-                // Invoke dagre.layout() on the core graph and record the bounding box
-                // dimensions.
-                _.extend(renderNodeInfo.coreBox, dagreLayout(renderNodeInfo.coreGraph, layout.PARAMS.graph.meta));
-                // Calculate the position of nodes in isolatedInExtract relative to the
-                // top-left corner of inExtractBox (the bounding box for all inExtract nodes)
-                // and calculate the size of the inExtractBox.
-                var maxInExtractWidth = _.max(renderNodeInfo.isolatedInExtract, function (renderNode) { return renderNode.width; }).width;
-                renderNodeInfo.inExtractBox.width = maxInExtractWidth != null ?
-                    maxInExtractWidth : 0;
-                renderNodeInfo.inExtractBox.height =
-                    _.reduce(renderNodeInfo.isolatedInExtract, function (height, child, i) {
-                        var yOffset = i > 0 ? params.extractYOffset : 0;
-                        // use width/height here to avoid overlaps between extracts
-                        child.x = 0;
-                        child.y = height + yOffset + child.height / 2;
-                        return height + yOffset + child.height;
-                    }, 0);
-                // Calculate the position of nodes in isolatedOutExtract relative to the
-                // top-left corner of outExtractBox (the bounding box for all outExtract
-                // nodes) and calculate the size of the outExtractBox.
-                var maxOutExtractWidth = _.max(renderNodeInfo.isolatedOutExtract, function (renderNode) { return renderNode.width; }).width;
-                renderNodeInfo.outExtractBox.width = maxOutExtractWidth != null ?
-                    maxOutExtractWidth : 0;
-                renderNodeInfo.outExtractBox.height =
-                    _.reduce(renderNodeInfo.isolatedOutExtract, function (height, child, i) {
-                        var yOffset = i > 0 ? params.extractYOffset : 0;
-                        // use width/height here to avoid overlaps between extracts
-                        child.x = 0;
-                        child.y = height + yOffset + child.height / 2;
-                        return height + yOffset + child.height;
-                    }, 0);
-                // Compute the total padding between the core graph, in-extract and
-                // out-extract boxes.
-                var numParts = 0;
-                if (renderNodeInfo.isolatedInExtract.length > 0) {
-                    numParts++;
-                }
-                if (renderNodeInfo.isolatedOutExtract.length > 0) {
-                    numParts++;
-                }
-                if (renderNodeInfo.coreGraph.nodeCount() > 0) {
-                    numParts++;
-                }
-                var offset = layout.PARAMS.subscene.meta.extractXOffset;
-                var padding = numParts <= 1 ? 0 : (numParts <= 2 ? offset : 2 * offset);
-                // Add the in-extract and out-extract width to the core box width.
-                renderNodeInfo.coreBox.width += renderNodeInfo.inExtractBox.width +
-                    renderNodeInfo.outExtractBox.width + padding;
-                renderNodeInfo.coreBox.height =
-                    params.labelHeight +
-                        Math.max(renderNodeInfo.inExtractBox.height, renderNodeInfo.coreBox.height, renderNodeInfo.outExtractBox.height);
-                // Determine the whole metanode's width (from left to right).
-                renderNodeInfo.width = renderNodeInfo.coreBox.width +
-                    params.paddingLeft + params.paddingRight;
-                // Determine the whole metanode's height (from top to bottom).
-                renderNodeInfo.height =
-                    renderNodeInfo.paddingTop +
-                        renderNodeInfo.coreBox.height +
-                        renderNodeInfo.paddingBottom;
-            }
-            /**
-             * Calculate layout for series node's core graph. Only called for an expanded
-             * series.
-             */
-            function layoutSeriesNode(node) {
-                var graph = node.coreGraph;
-                var params = layout.PARAMS.subscene.series;
-                _.extend(node, params);
-                // Layout the core.
-                _.extend(node.coreBox, dagreLayout(node.coreGraph, layout.PARAMS.graph.series));
-                _.each(graph.nodes(), function (nodeName) {
-                    graph.node(nodeName).excluded = false;
-                });
-                // Series do not have in/outExtractBox so no need to include them here.
-                node.width = node.coreBox.width + params.paddingLeft + params.paddingRight;
-                node.height = node.coreBox.height + params.paddingTop + params.paddingBottom;
-            }
-            /**
-             * Calculate layout for annotations of a given node.
-             * This will modify positions of the given node and its annotations.
-             *
-             * @see tf.graph.render.Node and tf.graph.render.Annotation
-             * for description of each property of each render node.
-             *
-             */
-            function layoutAnnotation(renderNodeInfo) {
-                // If the render node is an expanded metanode, then its annotations will not
-                // be visible and we should skip the annotation calculations.
-                if (renderNodeInfo.expanded) {
-                    return;
-                }
-                var inAnnotations = renderNodeInfo.inAnnotations.list;
-                var outAnnotations = renderNodeInfo.outAnnotations.list;
-                // Calculate size for in-annotations
-                _.each(inAnnotations, function (a) { return sizeAnnotation(a); });
-                // Calculate size for out-annotations
-                _.each(outAnnotations, function (a) { return sizeAnnotation(a); });
-                var params = layout.PARAMS.annotations;
-                // Calculate annotation node position (a.dx, a.dy)
-                // and total height for in-annotations
-                // After this chunk of code:
-                // inboxHeight = sum of annotation heights+ (annotation.length - 1 * yOffset)
-                var inboxHeight = _.reduce(inAnnotations, function (height, a, i) {
-                    var yOffset = i > 0 ? params.yOffset : 0;
-                    a.dx = -(renderNodeInfo.coreBox.width + a.width) / 2 - params.xOffset;
-                    a.dy = height + yOffset + a.height / 2;
-                    return height + yOffset + a.height;
-                }, 0);
-                _.each(inAnnotations, function (a) {
-                    a.dy -= inboxHeight / 2;
-                    a.labelOffset = params.labelOffset;
-                });
-                // Calculate annotation node position (a.dx, a.dy)
-                // and total height for out-annotations
-                // After this chunk of code:
-                // outboxHeight = sum of annotation heights +
-                //                (annotation.length - 1 * yOffset)
-                var outboxHeight = _.reduce(outAnnotations, function (height, a, i) {
-                    var yOffset = i > 0 ? params.yOffset : 0;
-                    a.dx = (renderNodeInfo.coreBox.width + a.width) / 2 + params.xOffset;
-                    a.dy = height + yOffset + a.height / 2;
-                    return height + yOffset + a.height;
-                }, 0);
-                _.each(outAnnotations, function (a) {
-                    // adjust by (half of ) the total height
-                    // so dy is relative to the host node's center.
-                    a.dy -= outboxHeight / 2;
-                    a.labelOffset = params.labelOffset;
-                });
-                // Creating scales for touch point between the in-annotation edges
-                // and their hosts.
-                var inTouchHeight = Math.min(renderNodeInfo.height / 2 - renderNodeInfo.radius, inboxHeight / 2);
-                inTouchHeight = inTouchHeight < 0 ? 0 : inTouchHeight;
-                var inY = d3.scale.linear()
-                    .domain([0, inAnnotations.length - 1])
-                    .range([-inTouchHeight, inTouchHeight]);
-                // Calculate annotation edge position
-                _.each(inAnnotations, function (a, i) {
-                    a.points = [
-                        // The annotation node end
-                        {
-                            dx: a.dx + a.width / 2,
-                            dy: a.dy
-                        },
-                        // The host node end
-                        {
-                            dx: -renderNodeInfo.coreBox.width / 2,
-                            // only use scale if there are more than one,
-                            // otherwise center it vertically
-                            dy: inAnnotations.length > 1 ? inY(i) : 0
-                        }
-                    ];
-                });
-                // Creating scales for touch point between the out-annotation edges
-                // and their hosts.
-                var outTouchHeight = Math.min(renderNodeInfo.height / 2 - renderNodeInfo.radius, outboxHeight / 2);
-                outTouchHeight = outTouchHeight < 0 ? 0 : outTouchHeight;
-                var outY = d3.scale.linear()
-                    .domain([0, outAnnotations.length - 1])
-                    .range([-outTouchHeight, outTouchHeight]);
-                _.each(outAnnotations, function (a, i) {
-                    // Add point from the border of the annotation node
-                    a.points = [
-                        // The host node end
-                        {
-                            dx: renderNodeInfo.coreBox.width / 2,
-                            // only use scale if there are more than one,
-                            // otherwise center it vertically
-                            dy: outAnnotations.length > 1 ? outY(i) : 0
-                        },
-                        // The annotation node end
-                        {
-                            dx: a.dx - a.width / 2,
-                            dy: a.dy
-                        }
-                    ];
-                });
-                renderNodeInfo.height =
-                    Math.max(renderNodeInfo.height, inboxHeight, outboxHeight);
-            }
-            /**
-             * Set size of an annotation node.
-             */
-            function sizeAnnotation(a) {
-                switch (a.annotationType) {
-                    case graph_1.render.AnnotationType.CONSTANT:
-                        _.extend(a, layout.PARAMS.constant.size);
-                        break;
-                    case graph_1.render.AnnotationType.SHORTCUT:
-                        if (a.node.type === graph_1.NodeType.OP) {
-                            _.extend(a, layout.PARAMS.shortcutSize.op);
-                        }
-                        else if (a.node.type === graph_1.NodeType.META) {
-                            _.extend(a, layout.PARAMS.shortcutSize.meta);
-                        }
-                        else if (a.node.type === graph_1.NodeType.SERIES) {
-                            _.extend(a, layout.PARAMS.shortcutSize.series);
-                        }
-                        else {
-                            throw Error('Invalid node type: ' + a.node.type);
-                        }
-                        break;
-                    case graph_1.render.AnnotationType.SUMMARY:
-                        _.extend(a, layout.PARAMS.constant.size);
-                        break;
-                }
-            }
-            /**
-             * Determines the center position of the node's shape. The position depends
-             * on if the node has in and out-annotations.
-             */
-            function computeCXPositionOfNodeShape(renderInfo) {
-                if (renderInfo.expanded) {
-                    return renderInfo.x;
-                }
-                var dx = renderInfo.inAnnotations.list.length ? renderInfo.inboxWidth : 0;
-                return renderInfo.x - renderInfo.width / 2 + dx +
-                    renderInfo.coreBox.width / 2;
-            }
-            layout.computeCXPositionOfNodeShape = computeCXPositionOfNodeShape;
-            /** Returns the angle (in degrees) between two points. */
-            function angleBetweenTwoPoints(a, b) {
-                var dx = b.x - a.x;
-                var dy = b.y - a.y;
-                return 180 * Math.atan(dy / dx) / Math.PI;
-            }
-            /**
-             * Returns if a line going through the specified points is a straight line.
-             */
-            function isStraightLine(points) {
-                var angle = angleBetweenTwoPoints(points[0], points[1]);
-                for (var i = 1; i < points.length - 1; i++) {
-                    var newAngle = angleBetweenTwoPoints(points[i], points[i + 1]);
-                    // Have a tolerance of 1 degree.
-                    if (Math.abs(newAngle - angle) > 1) {
-                        return false;
-                    }
-                    angle = newAngle;
-                }
-                return true;
-            }
-            /**
-             * Returns the intersection of a line between the provided point
-             * and the provided rectangle.
-             */
-            function intersectPointAndNode(point, node) {
-                // cx and cy are the center of the rectangle.
-                var cx = node.expanded ?
-                    node.x : computeCXPositionOfNodeShape(node);
-                var cy = node.y;
-                // Calculate the slope
-                var dx = point.x - cx;
-                var dy = point.y - cy;
-                var w = node.expanded ? node.width : node.coreBox.width;
-                var h = node.expanded ? node.height : node.coreBox.height;
-                var deltaX, deltaY;
-                if (Math.abs(dy) * w / 2 > Math.abs(dx) * h / 2) {
-                    // The intersection is above or below the rectangle.
-                    if (dy < 0) {
-                        h = -h;
-                    }
-                    deltaX = dy === 0 ? 0 : h / 2 * dx / dy;
-                    deltaY = h / 2;
-                }
-                else {
-                    // The intersection is left or right of the rectangle.
-                    if (dx < 0) {
-                        w = -w;
-                    }
-                    deltaX = w / 2;
-                    deltaY = dx === 0 ? 0 : w / 2 * dy / dx;
-                }
-                return { x: cx + deltaX, y: cy + deltaY };
-            }
-        })(layout = graph_1.layout || (graph_1.layout = {}));
-    })(graph = tf.graph || (tf.graph = {}));
-})(tf || (tf = {})); // close module
-</script>
-<script>/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-var tf;
-(function (tf) {
-    var graph;
-    (function (graph) {
-        var parser;
-        (function (parser) {
-            /**
-             * Parses a native js value, which can be either a string, boolean or number.
-             *
-             * @param value The value to be parsed.
-             */
-            function parseValue(value) {
-                if (value === 'true') {
-                    return true;
-                }
-                if (value === 'false') {
-                    return false;
-                }
-                var firstChar = value[0];
-                if (firstChar === '"') {
-                    return value.substring(1, value.length - 1);
-                }
-                var num = parseFloat(value);
-                return isNaN(num) ? value : num;
-            }
-            /**
-             * Fetches a text file and returns a promise of the result.
-             */
-            function fetchPbTxt(filepath) {
-                return new Promise(function (resolve, reject) {
-                    var request = new XMLHttpRequest();
-                    request.open('GET', filepath);
-                    request.responseType = 'arraybuffer';
-                    request.onerror = function () { return reject(request.status); };
-                    request.onload = function () { return resolve(request.response); };
-                    request.send(null);
-                });
-            }
-            parser.fetchPbTxt = fetchPbTxt;
-            /**
-             * Fetches the metadata file, parses it and returns a promise of the result.
-             */
-            function fetchAndParseMetadata(path, tracker) {
-                return tf.graph.util
-                    .runTask('Reading metadata pbtxt', 40, function () {
-                    if (path == null) {
-                        return Promise.resolve(null);
-                    }
-                    return fetchPbTxt(path);
-                }, tracker)
-                    .then(function (arrayBuffer) {
-                    return tf.graph.util.runAsyncPromiseTask('Parsing metadata.pbtxt', 60, function () {
-                        return arrayBuffer != null ? parseStatsPbTxt(arrayBuffer) :
-                            Promise.resolve(null);
-                    }, tracker);
-                });
-            }
-            parser.fetchAndParseMetadata = fetchAndParseMetadata;
-            /**
-             * Fetches the graph file, parses it and returns a promise of the result. The
-             * result will be undefined if the graph is empty.
-             */
-            function fetchAndParseGraphData(path, pbTxtFile, tracker) {
-                return tf.graph.util
-                    .runTask('Reading graph pbtxt', 40, function () {
-                    if (pbTxtFile) {
-                        return new Promise(function (resolve, reject) {
-                            var fileReader = new FileReader();
-                            fileReader.onload = function () { return resolve(fileReader.result); };
-                            fileReader.onerror = function () { return reject(fileReader.error); };
-                            fileReader.readAsArrayBuffer(pbTxtFile);
-                        });
-                    }
-                    else {
-                        return fetchPbTxt(path);
-                    }
-                }, tracker)
-                    .then(function (arrayBuffer) {
-                    return tf.graph.util.runTask('Parsing graph.pbtxt', 60, function () {
-                        return parseGraphPbTxt(arrayBuffer);
-                    }, tracker);
-                });
-            }
-            parser.fetchAndParseGraphData = fetchAndParseGraphData;
-            /**
-             * Parse a file object in a streaming fashion line by line (or custom delim).
-             * Can handle very large files.
-             * @param input The file object as an array buffer.
-             * @param callback The callback called on each line
-             * @param chunkSize The size of each read chunk. (optional)
-             * @param delim The delimiter used to split a line. (optional)
-             * @returns A promise for when it is finished.
-             */
-            function streamParse(arrayBuffer, callback, chunkSize, delim) {
-                if (chunkSize === void 0) { chunkSize = 1000000; }
-                if (delim === void 0) { delim = '\n'; }
-                return new Promise(function (resolve, reject) {
-                    var offset = 0;
-                    var bufferSize = arrayBuffer.byteLength - 1;
-                    var data = '';
-                    function readHandler(str) {
-                        offset += chunkSize;
-                        var parts = str.split(delim);
-                        var first = data + parts[0];
-                        if (parts.length === 1) {
-                            data = first;
-                            readChunk(offset, chunkSize);
-                            return;
-                        }
-                        data = parts[parts.length - 1];
-                        callback(first);
-                        for (var i = 1; i < parts.length - 1; i++) {
-                            callback(parts[i]);
-                        }
-                        if (offset >= bufferSize) {
-                            if (data) {
-                                callback(data);
-                            }
-                            resolve(true);
-                            return;
-                        }
-                        readChunk(offset, chunkSize);
-                    }
-                    function readChunk(offset, size) {
-                        var arrayBufferChunk = arrayBuffer.slice(offset, offset + size);
-                        var blob = new Blob([arrayBufferChunk]);
-                        var file = new FileReader();
-                        file.onload = function (e) { return readHandler(e.target.result); };
-                        file.readAsText(blob);
-                    }
-                    readChunk(offset, chunkSize);
-                });
-            }
-            parser.streamParse = streamParse;
-            /**
-             * Since proto-txt doesn't explicitly say whether an attribute is repeated
-             * (an array) or not, we keep a hard-coded list of attributes that are known
-             * to be repeated. This list is used in parsing time to convert repeated
-             * attributes into arrays even when the attribute only shows up once in the
-             * object.
-             */
-            var GRAPH_REPEATED_FIELDS = {
-                'node': true,
-                'node.input': true,
-                'node.attr': true,
-                'node.attr.value.list.type': true,
-                'node.attr.value.shape.dim': true,
-                'node.attr.value.tensor.string_val': true,
-                'node.attr.value.tensor.tensor_shape.dim': true,
-                'node.attr.value.list.shape': true,
-                'node.attr.value.list.shape.dim': true,
-                'node.attr.value.list.s': true
-            };
-            var METADATA_REPEATED_FIELDS = {
-                'step_stats.dev_stats': true,
-                'step_stats.dev_stats.node_stats': true,
-                'step_stats.dev_stats.node_stats.output': true,
-                'step_stats.dev_stats.node_stats.memory': true,
-                'step_stats.dev_stats.node_stats.output.tensor_description.shape.dim': true
-            };
-            /**
-             * Parses an ArrayBuffer of a proto txt file into a raw Graph object.
-             */
-            function parseGraphPbTxt(input) {
-                return parsePbtxtFile(input, GRAPH_REPEATED_FIELDS).then(function (obj) { return obj['node']; });
-            }
-            parser.parseGraphPbTxt = parseGraphPbTxt;
-            /**
-             * Parses an ArrayBuffer of a proto txt file into a StepStats object.
-             */
-            function parseStatsPbTxt(input) {
-                return parsePbtxtFile(input, METADATA_REPEATED_FIELDS)
-                    .then(function (obj) { return obj['step_stats']; });
-            }
-            parser.parseStatsPbTxt = parseStatsPbTxt;
-            /**
-             * Parses a ArrayBuffer of a proto txt file into javascript object.
-             *
-             * @param input The ArrayBuffer or file object implementing slice.
-             * @param repeatedFields Map (Set) of all the repeated fields, since you can't
-             *   tell directly from the pbtxt if a field is repeated or not.
-             * @returns The parsed object.
-             */
-            function parsePbtxtFile(input, repeatedFields) {
-                var output = {};
-                var stack = [];
-                var path = [];
-                var current = output;
-                function splitNameAndValueInAttribute(line) {
-                    var colonIndex = line.indexOf(':');
-                    var name = line.substring(0, colonIndex).trim();
-                    var value = parseValue(line.substring(colonIndex + 2).trim());
-                    return {
-                        name: name,
-                        value: value
-                    };
-                }
-                /**
-                 * Adds a value, given the attribute name and the host object. If the
-                 * attribute already exists, but is not an array, it will convert it to an
-                 * array of values.
-                 *
-                 * @param obj The host object that holds the attribute.
-                 * @param name The attribute name (key).
-                 * @param value The attribute value.
-                 * @param path A path that identifies the attribute. Used to check if
-                 *     an attribute is an array or not.
-                 */
-                function addAttribute(obj, name, value, path) {
-                    // We treat 'node' specially since it is done so often.
-                    var existingValue = obj[name];
-                    if (existingValue == null) {
-                        obj[name] = path.join('.') in repeatedFields ? [value] : value;
-                    }
-                    else if (Array.isArray(existingValue)) {
-                        existingValue.push(value);
-                    }
-                    else {
-                        obj[name] = [existingValue, value];
-                    }
-                }
-                // Run through the file a line at a time.
-                return streamParse(input, function (line) {
-                    if (!line) {
-                        return;
-                    }
-                    line = line.trim();
-                    switch (line[line.length - 1]) {
-                        case '{':
-                            var name_1 = line.substring(0, line.length - 2).trim();
-                            var newValue = {};
-                            stack.push(current);
-                            path.push(name_1);
-                            addAttribute(current, name_1, newValue, path);
-                            current = newValue;
-                            break;
-                        case '}':
-                            current = stack.pop();
-                            path.pop();
-                            break;
-                        default:
-                            var x = splitNameAndValueInAttribute(line);
-                            addAttribute(current, x.name, x.value, path.concat(x.name));
-                            break;
-                    }
-                }).then(function () {
-                    return output;
-                });
-            }
-        })(parser = graph.parser || (graph.parser = {}));
-    })(graph = tf.graph || (tf.graph = {}));
-})(tf || (tf = {})); // Close module tf.graph.parser.
-</script>
-<script>/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-</script>
-<script>var __extends = (this && this.__extends) || (function () {
-    var extendStatics = Object.setPrototypeOf ||
-        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
-        function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
-    return function (d, b) {
-        extendStatics(d, b);
-        function __() { this.constructor = d; }
-        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
-    };
-})();
-/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-/**
- * Package for the Render Hierarchy for TensorFlow graph.
- */
-var tf;
-(function (tf) {
-    var graph;
-    (function (graph_1) {
-        var render;
-        (function (render) {
-            /**
-             * Color parameters for op nodes.
-             */
-            render.OpNodeColors = { DEFAULT_FILL: 'white', DEFAULT_STROKE: '#b2b2b2' };
-            /**
-             * Color parameters for node encoding.
-             * @type {Object}
-             */
-            render.MetanodeColors = {
-                /**
-                 * Default fill and stroke to use when no other information is available.
-                 */
-                DEFAULT_FILL: '#d9d9d9',
-                DEFAULT_STROKE: '#a6a6a6',
-                SATURATION: 0.6,
-                LIGHTNESS: 0.85,
-                /**
-                 * Neutral color to use when the node is expanded (used when coloring by
-                 * compute time, memory and device).
-                 */
-                EXPANDED_COLOR: '#f0f0f0',
-                /**
-                 * Standard hue values for node color palette.
-                 */
-                HUES: [220, 100, 180, 40, 20, 340, 260, 300, 140, 60],
-                STRUCTURE_PALETTE: function (id, lightened) {
-                    // The code below is a flexible way to computationally create a set
-                    // of colors that go well together.
-                    var hues = render.MetanodeColors.HUES;
-                    var n = hues.length;
-                    var hue = hues[id % n];
-                    var m = Math.sin(hue * Math.PI / 360);
-                    var sat = lightened ? 30 : 90 - 60 * m;
-                    var light = lightened ? 95 : 80;
-                    return d3.hsl(hue, .01 * sat, .01 * light).toString();
-                },
-                DEVICE_PALETTE: function (index) {
-                    return render.MetanodeColors.STRUCTURE_PALETTE(index);
-                },
-                XLA_CLUSTER_PALETTE: function (index) {
-                    return render.MetanodeColors.STRUCTURE_PALETTE(index);
-                },
-                UNKNOWN: '#eee',
-                GRADIENT_OUTLINE: '#888'
-            };
-            /**
-             * Color parameters for op nodes.
-             */
-            render.SeriesNodeColors = {
-                DEFAULT_FILL: 'white',
-                DEFAULT_STROKE: '#b2b2b2'
-            };
-            /**
-             * Parameters that affect how the graph is rendered on the screen.
-             */
-            var PARAMS = {
-                /**
-                 * Whether to extract high degree nodes from the core part of the graph.
-                 */
-                enableExtraction: true,
-                /**
-                 * The minimum number of nodes for a graph to have in order for high in and
-                 * out degree nodes to be extracted in auxiliary. The aim here is to prevent
-                 * nodes from being extracted from small graphs.
-                 */
-                minNodeCountForExtraction: 15,
-                /**
-                 * The minimum in or out degree a node must have in order to be possibly
-                 * extracted.
-                 */
-                minDegreeForExtraction: 5,
-                /**
-                 * Maximum number of control edges a node can have before they aren't
-                 * displayed.
-                 */
-                maxControlDegree: 4,
-                /**
-                 * Maximum in (for outbound bridge paths) or out (for inbound bridge paths)
-                 * degree of a node allowed for a bridge path to be rendered to it from a
-                 * subhierarchy of nodes. Having a max prevents having too many nodes emanate
-                 * from a subhierarchy and crowding up.
-                 */
-                maxBridgePathDegree: 4,
-                /**
-                 * Types patterns for predefined out-extract nodes, which are
-                 * sink-like nodes that will be extracted from the main graph.
-                 */
-                outExtractTypes: [
-                    'NoOp' // NoOps are sink-like used for managing control dependencies.
-                ],
-                /**
-                 * Types patterns for predefined in-extract nodes, which are
-                 * source-like nodes that will be extracted from the main graph.
-                 */
-                inExtractTypes: [],
-                /**
-                 * When removing edges from a high degree node, remove all of its edges if
-                 * detachAllEdgesForHighDegree is true.  Otherwise remove all in-edges if
-                 * the node has high in-degree, or all out-edges if the node has high
-                 * out-degree.
-                 */
-                detachAllEdgesForHighDegree: true,
-                /**
-                 * After extracting high in/out degree nodes and predefined
-                 * source-like/sink-like, extract isolated nodes to the side
-                 * if this extractIsolatedNodesWithAnnotationsOnOneSide is true.
-                 */
-                extractIsolatedNodesWithAnnotationsOnOneSide: true,
-                /**
-                 * Whether to add bridge nodes and edges to the core when building the
-                 * subhierarchy of an expanded metanode. See buildSubhierarchy().
-                 */
-                enableBridgegraph: true,
-                /**
-                 * 2 colors, for the minimum and maximum value respectively, whenever we
-                 * have a gradient scale.
-                 */
-                minMaxColors: ['#fff5f0', '#fb6a4a'],
-                /**
-                 * Maximum number of annotations to be displayed on a node before an
-                 * ellipsis is used.
-                 */
-                maxAnnotations: 5
-            };
-            /**
-             * Stores the rendering information, such as x and y coordinates,
-             * for each node in the graph.
-             */
-            var RenderGraphInfo = (function () {
-                function RenderGraphInfo(hierarchy, displayingStats) {
-                    this.hierarchy = hierarchy;
-                    this.displayingStats = displayingStats;
-                    this.index = {};
-                    this.renderedOpNames = [];
-                    this.computeScales();
-                    // Maps node name to whether the rendering hierarchy was already
-                    // constructed.
-                    this.hasSubhierarchy = {};
-                    this.root = new RenderGroupNodeInfo(hierarchy.root);
-                    this.index[hierarchy.root.name] = this.root;
-                    this.renderedOpNames.push(hierarchy.root.name);
-                    this.buildSubhierarchy(hierarchy.root.name);
-                    this.root.expanded = true;
-                    this.traceInputs = false;
-                }
-                RenderGraphInfo.prototype.computeScales = function () {
-                    this.deviceColorMap = d3.scale.ordinal()
-                        .domain(this.hierarchy.devices)
-                        .range(_.map(d3.range(this.hierarchy.devices.length), render.MetanodeColors.DEVICE_PALETTE));
-                    this.xlaClusterColorMap =
-                        d3.scale.ordinal()
-                            .domain(this.hierarchy.xlaClusters)
-                            .range(_.map(d3.range(this.hierarchy.xlaClusters.length), render.MetanodeColors.XLA_CLUSTER_PALETTE));
-                    var topLevelGraph = this.hierarchy.root.metagraph;
-                    // Find the maximum and minimum memory usage.
-                    var memoryExtent = d3.extent(topLevelGraph.nodes(), function (nodeName, index) {
-                        var node = topLevelGraph.node(nodeName);
-                        // Some ops don't have stats at all.
-                        if (node.stats != null) {
-                            return node.stats.totalBytes;
-                        }
-                    });
-                    this.memoryUsageScale = d3.scale.linear()
-                        .domain(memoryExtent)
-                        .range(PARAMS.minMaxColors);
-                    // Find also the minimum and maximum compute time.
-                    var computeTimeExtent = d3.extent(topLevelGraph.nodes(), function (nodeName, index) {
-                        var node = topLevelGraph.node(nodeName);
-                        // Some ops don't have stats at all.
-                        if (node.stats != null) {
-                            return node.stats.totalMicros;
-                        }
-                    });
-                    this.computeTimeScale = d3.scale.linear()
-                        .domain(computeTimeExtent)
-                        .range(PARAMS.minMaxColors);
-                    this.edgeWidthScale = this.hierarchy.hasShapeInfo ?
-                        graph_1.scene.edge.EDGE_WIDTH_SCALE :
-                        d3.scale.linear()
-                            .domain([1, this.hierarchy.maxMetaEdgeSize])
-                            .range([graph_1.scene.edge.MIN_EDGE_WIDTH, graph_1.scene.edge.MAX_EDGE_WIDTH]);
-                };
-                /**
-                 * Get a previously created RenderNodeInfo by its node name.
-                 */
-                RenderGraphInfo.prototype.getRenderNodeByName = function (nodeName) {
-                    return this.index[nodeName];
-                };
-                /**
-                 * Get the underlying node in the hierarchical graph by its name.
-                 */
-                RenderGraphInfo.prototype.getNodeByName = function (nodeName) {
-                    return this.hierarchy.node(nodeName);
-                };
-                /**
-                 * Get a previously created RenderNodeInfo for the specified node name,
-                 * or create one if it hasn't been created yet.
-                 */
-                RenderGraphInfo.prototype.getOrCreateRenderNodeByName = function (nodeName) {
-                    var _this = this;
-                    // Polymer may invoke this with null.
-                    if (!nodeName) {
-                        return null;
-                    }
-                    if (nodeName in this.index) {
-                        return this.index[nodeName];
-                    }
-                    var node = this.hierarchy.node(nodeName);
-                    // Exit early if the node does not exist in the hierarchy. This can happen
-                    // when a graph is reloaded while the infocard points to a node not visible
-                    // at the top-level.
-                    if (!node) {
-                        return null;
-                    }
-                    var renderInfo = node.isGroupNode ?
-                        new RenderGroupNodeInfo(node) :
-                        new RenderNodeInfo(node);
-                    this.index[nodeName] = renderInfo;
-                    this.renderedOpNames.push(nodeName);
-                    if (node.stats) {
-                        renderInfo.memoryColor = this.memoryUsageScale(node.stats.totalBytes);
-                        renderInfo.computeTimeColor =
-                            this.computeTimeScale(node.stats.totalMicros);
-                    }
-                    if (!node.isGroupNode) {
-                        var clusterName = node.xlaCluster;
-                        if (clusterName) {
-                            renderInfo.xlaClusterColor = this.xlaClusterColorMap(clusterName);
-                        }
-                    }
-                    // We only fade nodes when we're displaying stats.
-                    renderInfo.isFadedOut = this.displayingStats &&
-                        !tf.graph.util.hasDisplayableNodeStats(node.stats);
-                    if (node.isGroupNode) {
-                        // Make a list of tuples (device, proportion), where proportion
-                        // is the fraction of op nodes that have that device.
-                        var pairs = _.pairs(node.deviceHistogram);
-                        if (pairs.length > 0) {
-                            // Compute the total # of devices.
-                            var numDevices_1 = _.sum(pairs, _.last);
-                            renderInfo.deviceColors = _.map(pairs, function (pair) { return ({
-                                color: _this.deviceColorMap(pair[0]),
-                                // Normalize to a proportion of total # of devices.
-                                proportion: pair[1] / numDevices_1
-                            }); });
-                        }
-                    }
-                    else {
-                        var device = renderInfo.node.device;
-                        if (device) {
-                            renderInfo.deviceColors = [{
-                                    color: this.deviceColorMap(device),
-                                    proportion: 1.0
-                                }];
-                        }
-                    }
-                    return this.index[nodeName];
-                };
-                /**
-                 * Return the nearest ancestor node, including itself, that is visible
-                 * in the visualization. This method is used so that we can select
-                 * (highlight) a node that isn't drawn yet, by selecting (highlighting)
-                 * its nearest ancestor that has been drawn.
-                 */
-                RenderGraphInfo.prototype.getNearestVisibleAncestor = function (name) {
-                    var path = graph_1.getHierarchicalPath(name);
-                    for (var i = 0; i < path.length; i++) {
-                        var nodeName = path[i];
-                        // Op nodes have expanded set to false by default.
-                        if (!this.getRenderNodeByName(nodeName).expanded) {
-                            return nodeName;
-                        }
-                    }
-                    // Fallthrough. If everything was expanded return the node.
-                    return name;
-                };
-                // TODO(jimbo): Delete this an any code it touches (all deprecated).
-                RenderGraphInfo.prototype.setDepth = function (depth) {
-                    setGroupNodeDepth(this.root, +depth);
-                };
-                /**
-                 * Returns true if the renderNode is an isolated node within its parent node.
-                 */
-                RenderGraphInfo.prototype.isNodeAuxiliary = function (renderNode) {
-                    var parentNode = this.getRenderNodeByName(renderNode.node.parentNode.name);
-                    var found = _.find(parentNode.isolatedInExtract, function (node) {
-                        return node.node.name === renderNode.node.name;
-                    });
-                    if (found) {
-                        return true;
-                    }
-                    found = _.find(parentNode.isolatedOutExtract, function (node) {
-                        return node.node.name === renderNode.node.name;
-                    });
-                    return !!found;
-                };
-                /**
-                 * Returns a list of ops that have been rendered so far for this graph. More
-                 * ops may later be rendered if the user expands nodes for instance. The list
-                 * returned here can only stay the same size or grow on successive calls.
-                 */
-                RenderGraphInfo.prototype.getNamesOfRenderedOps = function () {
-                    return this.renderedOpNames;
-                };
-                RenderGraphInfo.prototype.buildSubhierarchy = function (nodeName) {
-                    var _this = this;
-                    // Terminate if the rendering hierarchy was already constructed
-                    // for this node.
-                    if (nodeName in this.hasSubhierarchy) {
-                        return;
-                    }
-                    var renderNodeInfo = this.index[nodeName];
-                    // If it is not a meta node or a series node, don't do anything.
-                    if (renderNodeInfo.node.type !== graph_1.NodeType.META &&
-                        renderNodeInfo.node.type !== graph_1.NodeType.SERIES) {
-                        return;
-                    }
-                    // At this point we know the rendering information is about a group node.
-                    var renderGroupNodeInfo = renderNodeInfo;
-                    var metagraph = renderGroupNodeInfo.node.metagraph;
-                    var coreGraph = renderGroupNodeInfo.coreGraph;
-                    // Create render nodes to represent each child from the metagraph. Although
-                    // these will initially be added to the coreGraph, they may later be
-                    // extracted. Also, due to extraction, the coreGraph may contain disjoint
-                    // groups between which there is no visible path (other than annotations).
-                    _.each(metagraph.nodes(), function (childName) {
-                        var childRenderInfo = _this.getOrCreateRenderNodeByName(childName);
-                        var childNode = childRenderInfo.node;
-                        coreGraph.setNode(childName, childRenderInfo);
-                        if (!childNode.isGroupNode) {
-                            _.each(childNode.inEmbeddings, function (embedding) {
-                                var renderMetaedgeInfo = new RenderMetaedgeInfo(null);
-                                addInAnnotation(childRenderInfo, embedding, null, renderMetaedgeInfo, AnnotationType.CONSTANT);
-                                _this.index[embedding.name] = new RenderNodeInfo(embedding);
-                            });
-                            _.each(childNode.outEmbeddings, function (embedding) {
-                                var renderMetaedgeInfo = new RenderMetaedgeInfo(null);
-                                addOutAnnotation(childRenderInfo, embedding, null, renderMetaedgeInfo, AnnotationType.SUMMARY);
-                                _this.index[embedding.name] = new RenderNodeInfo(embedding);
-                            });
-                        }
-                    });
-                    // Add render metaedge info for edges in the metagraph.
-                    _.each(metagraph.edges(), function (edgeObj) {
-                        var metaedge = metagraph.edge(edgeObj);
-                        var renderMetaedgeInfo = new RenderMetaedgeInfo(metaedge);
-                        renderMetaedgeInfo.isFadedOut =
-                            _this.index[edgeObj.v].isFadedOut || _this.index[edgeObj.w].isFadedOut;
-                        coreGraph.setEdge(edgeObj.v, edgeObj.w, renderMetaedgeInfo);
-                    });
-                    if (PARAMS.enableExtraction &&
-                        renderGroupNodeInfo.node.type === graph_1.NodeType.META) {
-                        extractHighDegrees(renderGroupNodeInfo);
-                    }
-                    // Record that we constructed the rendering hierarchy for this node, so we
-                    // don't construct it another time.
-                    this.hasSubhierarchy[nodeName] = true;
-                    // Look up the parent node's render information and short circuit if none.
-                    var parentNode = renderGroupNodeInfo.node.parentNode;
-                    if (!parentNode) {
-                        return;
-                    }
-                    var parentNodeInfo = this.index[parentNode.name];
-                    // Utility function for computing the name of a bridge node.
-                    var getBridgeNodeName = function (inbound) {
-                        var rest = [];
-                        for (var _i = 1; _i < arguments.length; _i++) {
-                            rest[_i - 1] = arguments[_i];
-                        }
-                        return rest.concat([inbound ? 'IN' : 'OUT']).join('~~');
-                    };
-                    // Build out the bridgegraph.
-                    var bridgegraph = this.hierarchy.getBridgegraph(nodeName);
-                    // Look for popular nodes so we can make annotations instead of paths.
-                    var otherCounts = {
-                        // Counts of edges coming INTO other nodes by name (outgoing from self).
-                        in: {},
-                        // Counts of edges going OUT from other nodes by name (coming into self).
-                        out: {},
-                        // Counts of all control edges involving other nodes by name.
-                        control: {},
-                    };
-                    _.each(bridgegraph.edges(), function (e) {
-                        // An edge is inbound if its destination node is in the metagraph.
-                        var inbound = !!metagraph.node(e.w);
-                        var otherName = inbound ? e.v : e.w;
-                        var metaedge = bridgegraph.edge(e);
-                        if (!metaedge.numRegularEdges) {
-                            otherCounts.control[otherName] =
-                                (otherCounts.control[otherName] || 0) + 1;
-                        }
-                        else if (inbound) {
-                            otherCounts.out[otherName] = (otherCounts.out[otherName] || 0) + 1;
-                        }
-                        else {
-                            otherCounts.in[otherName] = (otherCounts.in[otherName] || 0) + 1;
-                        }
-                    });
-                    // Add annotations and edges for bridgegraph relationships.
-                    var hierarchyNodeMap = this.hierarchy.getNodeMap();
-                    _.each(bridgegraph.edges(), function (bridgeEdgeObj) {
-                        var bridgeMetaedge = bridgegraph.edge(bridgeEdgeObj);
-                        // Determine whether this bridge edge is incoming by checking the
-                        // metagraph for a node that matches the destination end.
-                        var inbound = !!metagraph.node(bridgeEdgeObj.w);
-                        // Based on the direction of the edge, one endpoint will be an immediate
-                        // child of this renderNodeInfo, and the other endpoint will be a sibling
-                        // of the parent (or an ancestor further up).
-                        var _a = inbound ?
-                            [bridgeEdgeObj.w, bridgeEdgeObj.v] :
-                            [bridgeEdgeObj.v, bridgeEdgeObj.w], childName = _a[0], otherName = _a[1];
-                        var childRenderInfo = _this.index[childName];
-                        var otherRenderInfo = _this.index[otherName];
-                        var otherNode = otherRenderInfo ?
-                            otherRenderInfo.node :
-                            hierarchyNodeMap[otherName];
-                        // Determine whether this edge is a control edge between nodes where
-                        // either node is high-degree with respect to control edges. This will
-                        // be a signal to show it as an annotation instead of a bridge edge.
-                        var isHighDegreeControlEdge = !bridgeMetaedge.numRegularEdges &&
-                            otherCounts.control[otherName] > PARAMS.maxControlDegree;
-                        var _b = inbound ?
-                            [renderNodeInfo.inAnnotations, childRenderInfo.inAnnotations] :
-                            [renderNodeInfo.outAnnotations, childRenderInfo.outAnnotations], childAnnotations = _b[1];
-                        // Don't render a bridge path if the other node has in or out degree above
-                        // a threshold, lest bridge paths emanating out of a metagraph crowd up,
-                        // as was the case for the Fatcat LSTM lstm_1 > lstm_1 metagraph.
-                        var otherDegreeCount = (inbound ? otherCounts.out : otherCounts.in)[otherName];
-                        var isOtherHighDegree = otherDegreeCount > PARAMS.maxBridgePathDegree;
-                        // The adjoining render metaedge info from the parent's coreGraph, if any.
-                        // It will either be a Metaedge involving this node directly, if it
-                        // previously came from a metagraph, or it'll be a Metaedge involving
-                        // a previously created bridge node standing in for the other node.
-                        var adjoiningMetaedge = null;
-                        // We can only hope to render a bridge path if:
-                        //  - bridgegraph paths are enabled,
-                        //  - the other node is not too high-degree,
-                        //  - the child is in the core (not extracted for being high-degree), and
-                        //  - there's a path (in the traversal sense) between child and other.
-                        var canDrawBridgePath = false;
-                        if (PARAMS.enableBridgegraph &&
-                            !isOtherHighDegree &&
-                            !isHighDegreeControlEdge &&
-                            childRenderInfo.isInCore()) {
-                            // Utility function for finding an adjoining metaedge.
-                            var findAdjoiningMetaedge = function (targetName) {
-                                var adjoiningEdgeObj = inbound ?
-                                    { v: targetName, w: nodeName } :
-                                    { v: nodeName, w: targetName };
-                                return parentNodeInfo.coreGraph.edge(adjoiningEdgeObj);
-                            };
-                            adjoiningMetaedge = findAdjoiningMetaedge(otherName);
-                            if (!adjoiningMetaedge) {
-                                adjoiningMetaedge = findAdjoiningMetaedge(getBridgeNodeName(inbound, otherName, parentNode.name));
-                            }
-                            canDrawBridgePath = !!adjoiningMetaedge;
-                        }
-                        // Although dataflow edges are acyclic, control dependency edges may
-                        // actually point 'backwards' in the graph. If this bridgeMetaedge is
-                        // a control dependency, we need to determine whether it's backwards
-                        // pointing so that we render it appropriately.
-                        //
-                        // For instance, say we're rendering a graph with nodes named A/B and Z/Y,
-                        // and we're currently rendering the bridgegraph for A. Further, let's say
-                        // that there was an original BaseEdge from A/B->Z/Y and a CONTROL EDGE
-                        // from Z/Y=>A/B.
-                        //
-                        //     +----------------+
-                        //     | A              |
-                        //     |  +-----+       |         +------+
-                        //     |  | B   |>-----\x3e|>-------\x3e| Z    |
-                        //     |  |     |       |         |      |
-                        //     |  |     |   *   |         |      |
-                        //     |  |     |<=====<|<=======<|      |
-                        //     |  +-----+       |         +------+
-                        //     +----------------+
-                        //
-                        // When we render the subhierarchy for Metanode A, we'll come across a
-                        // control-only Metaedge in the bridgegraph from Z=>A/B (*). The question
-                        // is whether this edge is backwards.
-                        //
-                        // To answer that question, we follow the chain of adjoining metaedges
-                        // until we reach the topmost one. In this case, that's the control-only
-                        // Metaedge Z=>A in the ROOT's metagraph. We determine that this edge
-                        // is backwards by looking at the topological ordering of ROOT's metagraph
-                        // (which ignores control edges) and seeing that Z comes AFTER A.
-                        //
-                        // The property of being backwards is independent of whether the edge
-                        // is inbound or outbound. In the preceding example, if we were building
-                        // the subhierarchy for Z, we'd find bridge edge Z/Y=>A, walk to its
-                        // topmost adjoining metaedge Z=>A and discover that it's backwards.
-                        var backwards = false;
-                        if (adjoiningMetaedge && !bridgeMetaedge.numRegularEdges) {
-                            // Find the top-most adjoining render metaedge information, and the
-                            // GroupNode whose metagraph must contain the associated metaedge.
-                            var topAdjoiningMetaedge = adjoiningMetaedge;
-                            var topGroupNode = parentNodeInfo.node;
-                            while (topAdjoiningMetaedge.adjoiningMetaedge) {
-                                topAdjoiningMetaedge = topAdjoiningMetaedge.adjoiningMetaedge;
-                                topGroupNode = topGroupNode.parentNode;
-                            }
-                            // Check against the topological ordering for the top node. The current
-                            // bridge metaedge we're evaluating is backwards if its source comes
-                            // after its destination.
-                            var ordering = _this.hierarchy.getTopologicalOrdering(topGroupNode.name);
-                            var e = topAdjoiningMetaedge.metaedge;
-                            backwards = ordering[e.v] > ordering[e.w];
-                        }
-                        // Render backwards control edges as annotations.
-                        canDrawBridgePath = canDrawBridgePath && !backwards;
-                        // If we can't make a bridge path for any reason, then we add an
-                        // annotation instead.
-                        if (!canDrawBridgePath) {
-                            childAnnotations.push(new Annotation(otherNode, otherRenderInfo, new RenderMetaedgeInfo(bridgeMetaedge), AnnotationType.SHORTCUT, inbound));
-                            return;
-                        }
-                        // At this point, all conditions have been met for drawing a bridge path.
-                        // Find or create the IN/OUT node representing otherNode.
-                        var bridgeContainerName = getBridgeNodeName(inbound, nodeName);
-                        var bridgeNodeName = getBridgeNodeName(inbound, otherName, nodeName);
-                        var bridgeNodeRenderInfo = coreGraph.node(bridgeNodeName);
-                        if (!bridgeNodeRenderInfo) {
-                            // Find or create the directional container for the bridge node.
-                            var bridgeContainerInfo = coreGraph.node(bridgeContainerName);
-                            if (!bridgeContainerInfo) {
-                                var bridgeContainerNode = {
-                                    // Important node properties.
-                                    name: bridgeContainerName,
-                                    type: graph_1.NodeType.BRIDGE,
-                                    // Unused node properties.
-                                    isGroupNode: false,
-                                    cardinality: 0,
-                                    parentNode: null,
-                                    stats: null,
-                                    include: graph_1.InclusionType.UNSPECIFIED,
-                                    // BridgeNode properties.
-                                    inbound: inbound,
-                                    nodeAttributes: {},
-                                };
-                                bridgeContainerInfo =
-                                    new RenderNodeInfo(bridgeContainerNode);
-                                _this.index[bridgeContainerName] = bridgeContainerInfo;
-                                coreGraph.setNode(bridgeContainerName, bridgeContainerInfo);
-                            }
-                            var bridgeNode = {
-                                // Important node properties.
-                                name: bridgeNodeName,
-                                type: graph_1.NodeType.BRIDGE,
-                                // Unimportant node properties.
-                                isGroupNode: false,
-                                cardinality: 1,
-                                parentNode: null,
-                                stats: null,
-                                include: graph_1.InclusionType.UNSPECIFIED,
-                                // BridgeNode properties.
-                                inbound: inbound,
-                                nodeAttributes: {},
-                            };
-                            bridgeNodeRenderInfo = new RenderNodeInfo(bridgeNode);
-                            _this.index[bridgeNodeName] = bridgeNodeRenderInfo;
-                            coreGraph.setNode(bridgeNodeName, bridgeNodeRenderInfo);
-                            // Set bridgeNode to be a graphlib child of the container node.
-                            coreGraph.setParent(bridgeNodeName, bridgeContainerName);
-                            bridgeContainerInfo.node.cardinality++;
-                        }
-                        // Create and add a bridge render metaedge.
-                        var bridgeRenderMetaedge = new RenderMetaedgeInfo(bridgeMetaedge);
-                        bridgeRenderMetaedge.adjoiningMetaedge = adjoiningMetaedge;
-                        inbound ?
-                            coreGraph.setEdge(bridgeNodeName, childName, bridgeRenderMetaedge) :
-                            coreGraph.setEdge(childName, bridgeNodeName, bridgeRenderMetaedge);
-                    }); // End _.each(bridgegraph.edges).
-                    // For each bridge container (IN and/or OUT), add structural edges between
-                    // terminal nodes and that container. A terminal node is one which has no
-                    // non-bridge edges in the direction of the container.
-                    //
-                    // For example, consider a Metanode A which contains two child nodes A/B
-                    // and A/C. Let's say it has one edge in the metagraph from A/B->A/C, and
-                    // one edge in the bridgegraph from Z->A/C.
-                    //
-                    // At this point, we've added a container bridge node IN to house all
-                    // incoming bridge nodes. We've also added a bridge node Z' (with parent IN)
-                    // to A, and a bridge edge from Z'->C.
-                    //
-                    //     +----------------------+
-                    //     | A          +---+     |
-                    //     |    +------\x3e| C |     |
-                    //     |    |       +---+     |
-                    //     |    |         ^       |
-                    //     |    |         |       |
-                    //     |    |    +----|----+  |
-                    //     |    |    | IN |    |  |
-                    //     |  +---+  |  +---+  |  |
-                    //     |  | B |  |  | Z'|  |  |
-                    //     |  +---+  |  +---+  |  |
-                    //     |         +---------+  |
-                    //     +----------------------+
-                    //
-                    // With no other help, dagre would lay out B and Z' on the same level,
-                    // because both of them have no incoming edges. In other words, B is a
-                    // terminal node in the INCOMING direction.
-                    //
-                    // But we want to force dagre to lay out Z' (and everything in IN) lower
-                    // than all non-bridge nodes, so that there's enough room for the bridge
-                    // edges after they've been adjusted to meet up with paths coming in from
-                    // outside.
-                    //
-                    // To force Z' (and all other bridge nodes) to be lowest in the graph, we
-                    // identify terminal nodes like B and give them structural edges to
-                    // a new structural bridge node S which we add to IN.
-                    //
-                    //     +----------------------+
-                    //     | A          +---+     |
-                    //     |       +---\x3e| C |     |
-                    //     |       |    +---+     |
-                    //     |     +---+    ^       |
-                    //     |     | B |    |       |
-                    //     |     +---+    |       |
-                    //     |       ^      |       |
-                    //     |       |      |       |
-                    //     |  +----|------|----+  |
-                    //     |  |IN  |      |    |  |
-                    //     |  |  +---+  +---+  |  |
-                    //     |  |  | S |  | Z'|  |  |
-                    //     |  |  +---+  +---+  |  |
-                    //     |  +----------------+  |
-                    //     +----------------------+
-                    //
-                    // This ensures that dagre will lay out the bridge containers strictly at
-                    // the ends of the graph. The structural edges will never be seen in the
-                    // visualization except as a debugging aid.
-                    _.each([true, false], function (inbound) {
-                        var bridgeContainerName = getBridgeNodeName(inbound, nodeName);
-                        var bridgeContainerInfo = coreGraph.node(bridgeContainerName);
-                        if (!bridgeContainerInfo) {
-                            return;
-                        }
-                        _.each(coreGraph.nodes(), function (childName) {
-                            // Short-circuit if this child is a bridge node or it's not a terminal
-                            // node in the direction we're interested in.
-                            var childNodeInfo = coreGraph.node(childName);
-                            if (childNodeInfo.node.type === graph_1.NodeType.BRIDGE) {
-                                return;
-                            }
-                            var isTerminal = inbound ?
-                                !coreGraph.predecessors(childName).length :
-                                !coreGraph.successors(childName).length;
-                            if (!isTerminal) {
-                                return;
-                            }
-                            // Find or create a bridge node in the container for all structural
-                            // metaedges. It would have been nice to skip this step and simply
-                            // set a metaedge between the terminal node and the container node, but
-                            // in that case, something about the graph upsets dagre.layout()'s
-                            // longestPath algorithm (was getting errors due to an undefined).
-                            var structuralNodeName = getBridgeNodeName(inbound, nodeName, 'STRUCTURAL_TARGET');
-                            var structuralRenderInfo = coreGraph.node(structuralNodeName);
-                            if (!structuralRenderInfo) {
-                                var bridgeNode = {
-                                    // Important Node properties.
-                                    name: structuralNodeName,
-                                    type: graph_1.NodeType.BRIDGE,
-                                    // Unimportant Node properties.
-                                    isGroupNode: false,
-                                    cardinality: 1,
-                                    parentNode: null,
-                                    stats: null,
-                                    include: graph_1.InclusionType.UNSPECIFIED,
-                                    // BridgeNode properties.
-                                    inbound: inbound,
-                                    nodeAttributes: {},
-                                };
-                                structuralRenderInfo = new RenderNodeInfo(bridgeNode);
-                                structuralRenderInfo.structural = true;
-                                _this.index[structuralNodeName] = structuralRenderInfo;
-                                coreGraph.setNode(structuralNodeName, structuralRenderInfo);
-                                bridgeContainerInfo.node.cardinality++;
-                                coreGraph.setParent(structuralNodeName, bridgeContainerName);
-                            }
-                            // Create the structural Metaedge and insert it.
-                            var structuralMetaedgeInfo = new RenderMetaedgeInfo(null);
-                            structuralMetaedgeInfo.structural = true;
-                            structuralMetaedgeInfo.weight--; // Reduce weight for dagre layout.
-                            inbound ?
-                                coreGraph.setEdge(structuralNodeName, childName, structuralMetaedgeInfo) :
-                                coreGraph.setEdge(childName, structuralNodeName, structuralMetaedgeInfo);
-                        });
-                    });
-                };
-                return RenderGraphInfo;
-            }());
-            render.RenderGraphInfo = RenderGraphInfo;
-            /**
-             * A class for rendering annotation object which contains label
-             * about the node embedded as annotation, type of annotation and the location
-             * of both the annotation's node and edge.
-             *
-             * Annotation objects include embedded constants, embedded summary, and
-             * edge shortcuts.
-             */
-            var Annotation = (function () {
-                /**
-                 * Creates a new Annotation.
-                 *
-                 * @param node The underlying node this annotation points to.
-                 * @param renderNodeInfo The render information for the underlying node
-                 *     this annotation points to. This can be null if the annotation
-                 *     denotes an embedding (constant, summary), in which case we
-                 *     use the node property.
-                 * @param renderMetaedgeInfo The render information for the edge associated
-                 *     with the annotation.
-                 * @param type The type of the annotation.
-                 * @param isIn True if it is an in-annotation. False if it is an
-                 *     out-annotation.
-                 */
-                function Annotation(node, renderNodeInfo, renderMetaedgeInfo, type, isIn) {
-                    this.node = node;
-                    this.renderNodeInfo = renderNodeInfo;
-                    this.renderMetaedgeInfo = renderMetaedgeInfo;
-                    this.annotationType = type;
-                    // Properties specified by layout
-                    this.dx = 0;
-                    this.dy = 0;
-                    this.width = 0;
-                    this.height = 0;
-                    // Properties needed for generating an ID for the edge's path element if
-                    // this annotation is associated with a metaedge.
-                    if (renderMetaedgeInfo && renderMetaedgeInfo.metaedge) {
-                        this.v = renderMetaedgeInfo.metaedge.v;
-                        this.w = renderMetaedgeInfo.metaedge.w;
-                    }
-                    this.isIn = isIn;
-                    this.points = [];
-                }
-                return Annotation;
-            }());
-            render.Annotation = Annotation;
-            ;
-            var AnnotationType;
-            (function (AnnotationType) {
-                AnnotationType[AnnotationType["SHORTCUT"] = 0] = "SHORTCUT";
-                AnnotationType[AnnotationType["CONSTANT"] = 1] = "CONSTANT";
-                AnnotationType[AnnotationType["SUMMARY"] = 2] = "SUMMARY";
-                AnnotationType[AnnotationType["ELLIPSIS"] = 3] = "ELLIPSIS";
-            })(AnnotationType = render.AnnotationType || (render.AnnotationType = {}));
-            ;
-            /**
-             * Manages a list of annotations. Two will be used for each
-             * RenderNodeInfo, one for in annotations and one for out annotations.
-             */
-            var AnnotationList = (function () {
-                function AnnotationList() {
-                    this.list = [];
-                    this.nodeNames = {};
-                }
-                /**
-                 * Append an annotation to the list, or a stand-in ellipsis annotation instead
-                 * if this would make it too many.
-                 */
-                AnnotationList.prototype.push = function (annotation) {
-                    if (annotation.node.name in this.nodeNames) {
-                        return; // Skip duplicate annotation.
-                    }
-                    this.nodeNames[annotation.node.name] = true;
-                    if (this.list.length < PARAMS.maxAnnotations) {
-                        this.list.push(annotation);
-                        return;
-                    }
-                    var lastAnnotation = this.list[this.list.length - 1];
-                    if (lastAnnotation.annotationType === AnnotationType.ELLIPSIS) {
-                        var ellipsisNode_1 = lastAnnotation.node;
-                        ellipsisNode_1.setNumMoreNodes(++ellipsisNode_1.numMoreNodes);
-                        return;
-                    }
-                    var ellipsisNode = new tf.graph.EllipsisNodeImpl(1);
-                    this.list.push(new Annotation(ellipsisNode, new RenderNodeInfo(ellipsisNode), null, AnnotationType.ELLIPSIS, annotation.isIn));
-                };
-                return AnnotationList;
-            }());
-            render.AnnotationList = AnnotationList;
-            /**
-             * Contains rendering information about a node in the hierarchical graph.
-             */
-            var RenderNodeInfo = (function () {
-                function RenderNodeInfo(node) {
-                    this.node = node;
-                    this.expanded = false;
-                    this.inAnnotations = new AnnotationList();
-                    this.outAnnotations = new AnnotationList();
-                    // Params specified by layout
-                    this.x = 0;
-                    this.y = 0;
-                    this.width = 0;
-                    this.height = 0;
-                    this.inboxWidth = 0;
-                    this.outboxWidth = 0;
-                    this.excluded = false;
-                    // Params for bridge paths.
-                    this.structural = false;
-                    // Params for node box.
-                    this.labelOffset = 0;
-                    this.radius = 0;
-                    // Params for expanded node
-                    this.labelHeight = 0;
-                    this.paddingTop = 0;
-                    this.paddingLeft = 0;
-                    this.paddingRight = 0;
-                    this.paddingBottom = 0;
-                    this.isInExtract = false;
-                    this.isOutExtract = false;
-                    this.coreBox = { width: 0, height: 0 };
-                    // By default, we don't fade nodes out. Default to false for safety.
-                    this.isFadedOut = false;
-                }
-                RenderNodeInfo.prototype.isInCore = function () {
-                    return !this.isInExtract && !this.isOutExtract;
-                };
-                return RenderNodeInfo;
-            }());
-            render.RenderNodeInfo = RenderNodeInfo;
-            /**
-             * Contains rendering information about a Metaedge from the underlying
-             * hierarchical graph. It may be from either a metagraph or a bridgegraph.
-             */
-            var RenderMetaedgeInfo = (function () {
-                function RenderMetaedgeInfo(metaedge) {
-                    this.metaedge = metaedge;
-                    this.adjoiningMetaedge = null;
-                    this.structural = false;
-                    this.weight = 1;
-                    this.isFadedOut = false;
-                }
-                return RenderMetaedgeInfo;
-            }());
-            render.RenderMetaedgeInfo = RenderMetaedgeInfo;
-            function addInAnnotation(node, predecessor, predecessorRenderInfo, edge, type) {
-                var annotation = new Annotation(predecessor, predecessorRenderInfo, edge, type, true);
-                node.inAnnotations.push(annotation);
-            }
-            function addOutAnnotation(node, successor, successorRenderInfo, edge, type) {
-                var annotation = new Annotation(successor, successorRenderInfo, edge, type, false);
-                node.outAnnotations.push(annotation);
-            }
-            function setGraphDepth(graph, depth) {
-                _.each(graph.nodes(), function (nodeName) {
-                    var child = graph.node(nodeName);
-                    child.expanded = depth > 1; // set all child of depth 1 to collapsed
-                    if (depth > 0) {
-                        switch (child.node.type) {
-                            case graph_1.NodeType.META:
-                            case graph_1.NodeType.SERIES:
-                                setGroupNodeDepth(child, depth - 1);
-                                break;
-                        }
-                    }
-                });
-            }
-            ;
-            var RenderGroupNodeInfo = (function (_super) {
-                __extends(RenderGroupNodeInfo, _super);
-                function RenderGroupNodeInfo(groupNode) {
-                    var _this = _super.call(this, groupNode) || this;
-                    var metagraph = groupNode.metagraph;
-                    var gl = metagraph.graph();
-                    _this.coreGraph =
-                        graph_1.createGraph(gl.name, graph_1.GraphType.CORE, { compound: true });
-                    _this.inExtractBox = { width: 0, height: 0 };
-                    _this.outExtractBox = { width: 0, height: 0 };
-                    _this.isolatedInExtract = [];
-                    _this.isolatedOutExtract = [];
-                    return _this;
-                }
-                return RenderGroupNodeInfo;
-            }(RenderNodeInfo));
-            render.RenderGroupNodeInfo = RenderGroupNodeInfo;
-            function setGroupNodeDepth(renderInfo, depth) {
-                if (renderInfo.coreGraph) {
-                    setGraphDepth(renderInfo.coreGraph, depth);
-                }
-            }
-            /**
-             * Remove an edge from the graph and add annotations to both ends of the edge.
-             *
-             * @param The core graph.
-             * @param v Source name.
-             * @param w Sink name.
-             */
-            function createShortcut(graph, v, w) {
-                var src = graph.node(v);
-                var sink = graph.node(w);
-                var edge = graph.edge(v, w);
-                // If either of the nodes is explicitly included in the main graph and
-                // both nodes are in the main graph then do not create the shortcut
-                // and instead keep the real edge.
-                if ((src.node.include === graph_1.InclusionType.INCLUDE ||
-                    sink.node.include === graph_1.InclusionType.INCLUDE) &&
-                    src.node.include !== graph_1.InclusionType.EXCLUDE &&
-                    sink.node.include !== graph_1.InclusionType.EXCLUDE) {
-                    return;
-                }
-                // Add each annotation.
-                addOutAnnotation(src, sink.node, sink, edge, AnnotationType.SHORTCUT);
-                addInAnnotation(sink, src.node, src, edge, AnnotationType.SHORTCUT);
-                // Remove the edge from the core graph.
-                graph.removeEdge(v, w);
-            }
-            /**
-             * Remove edges from a node, and set its isOutExtract property to true,
-             * and remove the node and move it to isolatedOutExtract.
-             *
-             * If detachAllEdgesForHighDegree or forceDetach is true, extract all of its
-             * edges. Otherwise, only extract all in-edges.
-             */
-            function makeOutExtract(renderNode, n, forceDetach) {
-                var graph = renderNode.coreGraph;
-                var child = graph.node(n);
-                child.isOutExtract = true;
-                _.each(graph.predecessors(n), function (p, index) {
-                    createShortcut(graph, p, n);
-                });
-                if (PARAMS.detachAllEdgesForHighDegree || forceDetach) {
-                    _.each(graph.successors(n), function (s, index) {
-                        createShortcut(graph, n, s);
-                    });
-                }
-                // Remove the node from the core graph if it no longer has neighbors.
-                if (graph.neighbors(n).length === 0) {
-                    child.node.include = graph_1.InclusionType.EXCLUDE;
-                    renderNode.isolatedOutExtract.push(child);
-                    graph.removeNode(n);
-                }
-            }
-            /**
-             * Remove edges from a node, set its isInExtract property to true,
-             * and remove the node and move it to isolatedInExtract.
-             *
-             * If detachAllEdgesForHighDegree or forceDetach is true, extract all of its
-             * edges. Otherwise, only remove all out-edges.
-             */
-            function makeInExtract(renderNode, n, forceDetach) {
-                var graph = renderNode.coreGraph;
-                var child = graph.node(n);
-                child.isInExtract = true;
-                _.each(graph.successors(n), function (s, index) {
-                    createShortcut(graph, n, s);
-                });
-                if (PARAMS.detachAllEdgesForHighDegree || forceDetach) {
-                    _.each(graph.predecessors(n), function (p, index) {
-                        createShortcut(graph, p, n);
-                    });
-                }
-                // Remove the node from the core graph if it no longer has neighbors.
-                if (graph.neighbors(n).length === 0) {
-                    child.node.include = graph_1.InclusionType.EXCLUDE;
-                    renderNode.isolatedInExtract.push(child);
-                    graph.removeNode(n);
-                }
-            }
-            render.makeInExtract = makeInExtract;
-            /**
-             * Check whether the node's type is a member of the given list of types.
-             *
-             * @param node Node.
-             * @param types List of type to match.
-             */
-            function hasTypeIn(node, types) {
-                if (node.type === graph_1.NodeType.OP) {
-                    for (var i = 0; i < types.length; i++) {
-                        if (node.op === types[i]) {
-                            return true;
-                        }
-                    }
-                }
-                else if (node.type === graph_1.NodeType.META) {
-                    var rootOpNode = node.getRootOp();
-                    if (rootOpNode) {
-                        for (var i = 0; i < types.length; i++) {
-                            if (rootOpNode.op === types[i]) {
-                                return true;
-                            }
-                        }
-                    }
-                }
-                return false;
-            }
-            /** Move nodes that are specified to be excluded out of the core graph. */
-            function extractSpecifiedNodes(renderNode) {
-                var graph = renderNode.coreGraph;
-                _.each(graph.nodes(), function (n) {
-                    var renderInfo = graph.node(n);
-                    if (renderInfo.node.include === graph_1.InclusionType.EXCLUDE) {
-                        if (renderNode.coreGraph.outEdges(n).length >
-                            renderNode.coreGraph.inEdges(n).length) {
-                            makeOutExtract(renderNode, n, true);
-                        }
-                        else {
-                            makeInExtract(renderNode, n, true);
-                        }
-                    }
-                });
-            }
-            /** Remove edges from pre-defined out-extract patterns */
-            function extractPredefinedSink(renderNode) {
-                var graph = renderNode.coreGraph;
-                _.each(graph.nodes(), function (n) {
-                    var renderInfo = graph.node(n);
-                    if (renderInfo.node.include !== graph_1.InclusionType.UNSPECIFIED) {
-                        return;
-                    }
-                    if (hasTypeIn(renderInfo.node, PARAMS.outExtractTypes)) {
-                        makeOutExtract(renderNode, n);
-                    }
-                });
-            }
-            /** Remove edges from pre-defined in-extract patterns */
-            function extractPredefinedSource(renderNode) {
-                var graph = renderNode.coreGraph;
-                _.each(graph.nodes(), function (n) {
-                    var renderInfo = graph.node(n);
-                    if (renderInfo.node.include !== graph_1.InclusionType.UNSPECIFIED) {
-                        return;
-                    }
-                    if (hasTypeIn(renderInfo.node, PARAMS.inExtractTypes)) {
-                        makeInExtract(renderNode, n);
-                    }
-                });
-            }
-            /** Extract nodes deemed to have either high in-degree or high out-degree. */
-            function extractHighInOrOutDegree(renderNode) {
-                var graph = renderNode.coreGraph;
-                // Create mappings from node to in and out degrees. Count the number of valid
-                // nodes along the way.
-                var nodeToInDegree = {};
-                var nodeToOutDegree = {};
-                var validNodeCount = 0;
-                _.each(graph.nodes(), function (currentNode) {
-                    if (graph.node(currentNode).node.include !== graph_1.InclusionType.UNSPECIFIED) {
-                        // This node is not included in the first place.
-                        return;
-                    }
-                    // Count the in and out degrees based on only regular edges, unless there
-                    // are no regular edges, in which case use the number of control edges.
-                    // This is done so that control edges don't affect if nodes are extracted
-                    // from the core graph, unless the node is only used for control.
-                    var inDegree = _.reduce(graph.predecessors(currentNode), function (inDegree, pred) {
-                        var metaedge = graph.edge(pred, currentNode).metaedge;
-                        return inDegree + (metaedge.numRegularEdges ? 1 : 0);
-                    }, 0);
-                    if (inDegree === 0 && graph.predecessors(currentNode).length > 0) {
-                        inDegree = graph.predecessors(currentNode).length;
-                    }
-                    var outDegree = _.reduce(graph.successors(currentNode), function (outDegree, succ) {
-                        var metaedge = graph.edge(currentNode, succ).metaedge;
-                        return outDegree + (metaedge.numRegularEdges ? 1 : 0);
-                    }, 0);
-                    if (outDegree === 0 && graph.successors(currentNode).length > 0) {
-                        outDegree = graph.successors(currentNode).length;
-                    }
-                    // Store the in and out degrees of this node to avoid recomputing.
-                    nodeToInDegree[currentNode] = inDegree;
-                    nodeToOutDegree[currentNode] = outDegree;
-                    validNodeCount++;
-                });
-                if (validNodeCount < PARAMS.minNodeCountForExtraction) {
-                    // This graph has few nodes. Do not extract any nodes.
-                    return;
-                }
-                // We only extract if the node has a min in or out degree greater than this.
-                var minUpperBound = PARAMS.minDegreeForExtraction - 1;
-                // Mark for extraction nodes with in-degree > Q3 + (Q3 - Q1).
-                var q3Index = Math.round(validNodeCount * 0.75);
-                var q1Index = Math.round(validNodeCount * 0.25);
-                var sortedByInDegree = Object.keys(nodeToInDegree).sort(function (node0, node1) {
-                    return nodeToInDegree[node0] - nodeToInDegree[node1];
-                });
-                var inDegreeQ3 = nodeToInDegree[sortedByInDegree[q3Index]];
-                var inDegreeQ1 = nodeToInDegree[sortedByInDegree[q1Index]];
-                var inDegreeUpperBound = inDegreeQ3 + inDegreeQ3 - inDegreeQ1;
-                // Only extract if the upper bound is high enough.
-                inDegreeUpperBound = Math.max(inDegreeUpperBound, minUpperBound);
-                for (var i = validNodeCount - 1; nodeToInDegree[sortedByInDegree[i]] > inDegreeUpperBound; i--) {
-                    // Extract a high in-degree node.
-                    makeInExtract(renderNode, sortedByInDegree[i]);
-                }
-                // Mark for extraction nodes with out-degree > Q3 + (Q3 - Q1) * 4.
-                var sortedByOutDegree = Object.keys(nodeToOutDegree).sort(function (node0, node1) {
-                    return nodeToOutDegree[node0] - nodeToOutDegree[node1];
-                });
-                var outDegreeQ3 = nodeToOutDegree[sortedByOutDegree[q3Index]];
-                var outDegreeQ1 = nodeToOutDegree[sortedByOutDegree[q1Index]];
-                // The upper bound for extracting out-degree nodes is higher than that for
-                // extracting in-degree ones (Note the "* 4") because, in practice, some
-                // graphs look worse with a smaller out-degree bound. For instance, a smaller
-                // out-degree bound removes the convolution nodes from cifar 10 train's graph.
-                var outDegreeUpperBound = outDegreeQ3 + (outDegreeQ3 - outDegreeQ1) * 4;
-                // Only extract if the upper bound is high enough.
-                outDegreeUpperBound = Math.max(outDegreeUpperBound, minUpperBound);
-                for (var i = validNodeCount - 1; nodeToOutDegree[sortedByOutDegree[i]] > outDegreeUpperBound; i--) {
-                    var node = graph.node(sortedByOutDegree[i]);
-                    if (!node || node.isInExtract) {
-                        // This node has already been extracted due to high in-degree. It might
-                        // have been removed from the graph in general (during in-degree
-                        // extraction) due to a lack of neighbors. Do not extract this node twice.
-                        continue;
-                    }
-                    // Extract a high out-degree node that has not already been extracted.
-                    makeOutExtract(renderNode, sortedByOutDegree[i]);
-                }
-            }
-            /** Remove control edges from nodes that have too many control edges */
-            function removeControlEdges(renderNode) {
-                var graph = renderNode.coreGraph;
-                // Collect control edges into a map by node name.
-                var map = {};
-                _.each(graph.edges(), function (e) {
-                    if (!graph.edge(e).metaedge.numRegularEdges) {
-                        (map[e.v] = map[e.v] || []).push(e);
-                        (map[e.w] = map[e.w] || []).push(e);
-                    }
-                });
-                // For each node with too many control edges, turn them into annotations.
-                _.each(map, function (edges, nodeName) {
-                    if (edges.length > PARAMS.maxControlDegree) {
-                        _.each(edges, function (e) { return createShortcut(graph, e.v, e.w); });
-                    }
-                });
-            }
-            /**
-             * Given an integer, picks a hue that is far apart from other colors.
-             * The formula for picking color that avoid collision is:
-             *     hue = (color range * golden ratio * index) % color range
-             */
-            function mapIndexToHue(id) {
-                var GOLDEN_RATIO = 1.61803398875;
-                // Hue of 0 is reserved for the gray nodes.
-                var MIN_HUE = 1;
-                var MAX_HUE = 359;
-                var COLOR_RANGE = MAX_HUE - MIN_HUE;
-                return MIN_HUE + ((COLOR_RANGE * GOLDEN_RATIO * id) % COLOR_RANGE);
-            }
-            render.mapIndexToHue = mapIndexToHue;
-            ;
-            /**
-             * Remove edges and add to annotation instead.
-             *
-             * For root node, consider predefined types for source and sink.
-             * We do not extract predefined type from non-root so that Variables and the
-             * sgd node (op type = 'NoOp') do not get extract from inside own group.
-             *
-             * The order of extraction is important here as swapping the order can totally
-             * screw up the graph layout.
-             *
-             * @param {Render.Node} renderNode Node to manipulate.
-             */
-            function extractHighDegrees(renderNode) {
-                extractSpecifiedNodes(renderNode);
-                if (PARAMS.outExtractTypes) {
-                    extractPredefinedSink(renderNode);
-                }
-                // This has to come before extract high in-degree to protect the core part
-                // that takes many variables.
-                if (PARAMS.inExtractTypes) {
-                    extractPredefinedSource(renderNode);
-                }
-                extractHighInOrOutDegree(renderNode);
-                if (PARAMS.maxControlDegree) {
-                    removeControlEdges(renderNode);
-                }
-                // Extract isolated nodes, which can be
-                // (1) source-like and sink-like nodes that are not originally isolated but
-                //     become isolated after further removal.
-                // (2) isolated nodes with annotations on one-side.  These might be either
-                //     - nodes that originally have high out-degree but because we remove
-                //       high in-degree nodes first, they no longer have high in-degree when
-                //       we check.  (Detecting all high-degree before removing also leads to
-                //       another problem.)
-                //     - nodes that do not have high degree, but their neighbors are all
-                //       extracted, so it might make sense to extract them too.
-                var graph = renderNode.coreGraph;
-                _.each(graph.nodes(), function (n) {
-                    var child = graph.node(n);
-                    var degree = graph.neighbors(n).length;
-                    if (child.node.include !== graph_1.InclusionType.UNSPECIFIED) {
-                        return;
-                    }
-                    if (degree === 0) {
-                        var hasOutAnnotations = child.outAnnotations.list.length > 0;
-                        var hasInAnnotations = child.inAnnotations.list.length > 0;
-                        if (child.isInExtract) {
-                            // This case only happens if detachAllEdgesForHighDegree is false.
-                            // (Otherwise all source-like nodes are all isolated already.)
-                            renderNode.isolatedInExtract.push(child);
-                            child.node.include = graph_1.InclusionType.EXCLUDE;
-                            graph.removeNode(n);
-                        }
-                        else if (child.isOutExtract) {
-                            // This case only happens if detachAllEdgesForHighDegree is false.
-                            // // (Otherwise all sink-like nodes are all isolated already.)
-                            renderNode.isolatedOutExtract.push(child);
-                            child.node.include = graph_1.InclusionType.EXCLUDE;
-                            graph.removeNode(n);
-                        }
-                        else if (PARAMS.extractIsolatedNodesWithAnnotationsOnOneSide) {
-                            if (hasOutAnnotations && !hasInAnnotations) {
-                                child.isInExtract = true; // for ones with high out-annotations
-                                renderNode.isolatedInExtract.push(child);
-                                child.node.include = graph_1.InclusionType.EXCLUDE;
-                                graph.removeNode(n);
-                            }
-                            else if (hasInAnnotations && !hasOutAnnotations) {
-                                child.isOutExtract = true; // for ones with high in-annotations
-                                renderNode.isolatedOutExtract.push(child);
-                                child.node.include = graph_1.InclusionType.EXCLUDE;
-                                graph.removeNode(n);
-                            }
-                            else {
-                                // if a low degree node has both in- & out- annotations, do nothing
-                                // because it is unclear which side it should go to.
-                            }
-                        }
-                    }
-                });
-            }
-        })(render = graph_1.render || (graph_1.render = {}));
-    })(graph = tf.graph || (tf.graph = {}));
-})(tf || (tf = {})); // close module tf.graph.render
-</script>
-<script>/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-var tf;
-(function (tf) {
-    var graph;
-    (function (graph) {
-        var scene;
-        (function (scene) {
-            var annotation;
-            (function (annotation_1) {
-                /**
-                 * Populate a given annotation container group
-                 *
-                 *     <g class='{in|out}-annotations'></g>
-                 *
-                 * with annotation group of the following structure:
-                 *
-                 * <g class='annotation'>
-                 *   <g class='annotation-node'>
-                 *   \x3c!--
-                 *   Content here determined by Scene.node.buildGroup.
-                 *   --\x3e
-                 *   </g>
-                 * </g>
-                 *
-                 * @param container selection of the container.
-                 * @param annotationData node.{in|out}Annotations
-                 * @param d node to build group for.
-                 * @param sceneElement <tf-graph-scene> polymer element.
-                 * @return selection of appended objects
-                 */
-                function buildGroup(container, annotationData, d, sceneElement) {
-                    // Select all children and join with data.
-                    var annotationGroups = container
-                        .selectAll(function () {
-                        // using d3's selector function
-                        // See https://github.com/mbostock/d3/releases/tag/v2.0.0
-                        // (It's not listed in the d3 wiki.)
-                        return this.childNodes;
-                    })
-                        .data(annotationData.list, function (d) { return d.node.name; });
-                    annotationGroups.enter()
-                        .append('g')
-                        .attr('data-name', function (a) { return a.node.name; })
-                        .each(function (a) {
-                        var aGroup = d3.select(this);
-                        // Add annotation to the index in the scene
-                        sceneElement.addAnnotationGroup(a, d, aGroup);
-                        // Append annotation edge
-                        var edgeType = scene.Class.Annotation.EDGE;
-                        var metaedge = a.renderMetaedgeInfo && a.renderMetaedgeInfo.metaedge;
-                        if (metaedge && !metaedge.numRegularEdges) {
-                            edgeType += ' ' + scene.Class.Annotation.CONTROL_EDGE;
-                        }
-                        // If any edges are reference edges, add the reference edge class.
-                        if (metaedge && metaedge.numRefEdges) {
-                            edgeType += ' ' + scene.Class.Edge.REF_LINE;
-                        }
-                        scene.edge.appendEdge(aGroup, a, sceneElement, edgeType);
-                        if (a.annotationType !== graph.render.AnnotationType.ELLIPSIS) {
-                            addAnnotationLabelFromNode(aGroup, a);
-                            buildShape(aGroup, a);
-                        }
-                        else {
-                            addAnnotationLabel(aGroup, a.node.name, a, scene.Class.Annotation.ELLIPSIS);
-                        }
-                    });
-                    annotationGroups
-                        .attr('class', function (a) {
-                        return scene.Class.Annotation.GROUP + ' ' +
-                            annotationToClassName(a.annotationType) + ' ' +
-                            scene.node.nodeClass(a);
-                    })
-                        .each(function (a) {
-                        var aGroup = d3.select(this);
-                        update(aGroup, d, a, sceneElement);
-                        if (a.annotationType !== graph.render.AnnotationType.ELLIPSIS) {
-                            addInteraction(aGroup, d, a, sceneElement);
-                        }
-                    });
-                    annotationGroups.exit()
-                        .each(function (a) {
-                        var aGroup = d3.select(this);
-                        // Remove annotation from the index in the scene
-                        sceneElement.removeAnnotationGroup(a, d, aGroup);
-                    })
-                        .remove();
-                    return annotationGroups;
-                }
-                annotation_1.buildGroup = buildGroup;
-                ;
-                /**
-                 * Maps an annotation enum to a class name used in css rules.
-                 */
-                function annotationToClassName(annotationType) {
-                    return (graph.render.AnnotationType[annotationType] || '').toLowerCase() || null;
-                }
-                function buildShape(aGroup, a) {
-                    if (a.annotationType === graph.render.AnnotationType.SUMMARY) {
-                        var summary = scene.selectOrCreateChild(aGroup, 'use');
-                        summary.attr({
-                            'class': 'summary',
-                            'xlink:href': '#summary-icon',
-                            'cursor': 'pointer'
-                        });
-                    }
-                    else {
-                        var shape = scene.node.buildShape(aGroup, a, scene.Class.Annotation.NODE);
-                        // add title tag to get native tooltips
-                        scene.selectOrCreateChild(shape, 'title').text(a.node.name);
-                    }
-                }
-                function addAnnotationLabelFromNode(aGroup, a) {
-                    var namePath = a.node.name.split('/');
-                    var text = namePath[namePath.length - 1];
-                    return addAnnotationLabel(aGroup, text, a, null);
-                }
-                function addAnnotationLabel(aGroup, label, a, additionalClassNames) {
-                    var classNames = scene.Class.Annotation.LABEL;
-                    if (additionalClassNames) {
-                        classNames += ' ' + additionalClassNames;
-                    }
-                    var txtElement = aGroup.append('text')
-                        .attr('class', classNames)
-                        .attr('dy', '.35em')
-                        .attr('text-anchor', a.isIn ? 'end' : 'start')
-                        .text(label);
-                    return tf.graph.scene.node.enforceLabelWidth(txtElement, -1);
-                }
-                function addInteraction(selection, d, annotation, sceneElement) {
-                    selection
-                        .on('mouseover', function (a) {
-                        sceneElement.fire('annotation-highlight', { name: a.node.name, hostName: d.node.name });
-                    })
-                        .on('mouseout', function (a) {
-                        sceneElement.fire('annotation-unhighlight', { name: a.node.name, hostName: d.node.name });
-                    })
-                        .on('click', function (a) {
-                        // Stop this event's propagation so that it isn't also considered a
-                        // graph-select.
-                        d3.event.stopPropagation();
-                        sceneElement.fire('annotation-select', { name: a.node.name, hostName: d.node.name });
-                    });
-                    if (annotation.annotationType !== graph.render.AnnotationType.SUMMARY &&
-                        annotation.annotationType !== graph.render.AnnotationType.CONSTANT) {
-                        selection.on('contextmenu', scene.contextmenu.getMenu(scene.node.getContextMenu(annotation.node, sceneElement)));
-                    }
-                }
-                ;
-                /**
-                 * Adjust annotation's position.
-                 *
-                 * @param aGroup selection of a 'g.annotation' element.
-                 * @param d Host node data.
-                 * @param a annotation node data.
-                 * @param sceneElement <tf-graph-scene> polymer element.
-                 */
-                function update(aGroup, d, a, sceneElement) {
-                    var cx = graph.layout.computeCXPositionOfNodeShape(d);
-                    // Annotations that point to embedded nodes (constants,summary)
-                    // don't have a render information attached so we don't stylize these.
-                    // Also we don't stylize ellipsis annotations (the string '... and X more').
-                    if (a.renderNodeInfo &&
-                        a.annotationType !== graph.render.AnnotationType.ELLIPSIS) {
-                        scene.node.stylize(aGroup, a.renderNodeInfo, sceneElement, scene.Class.Annotation.NODE);
-                    }
-                    if (a.annotationType === graph.render.AnnotationType.SUMMARY) {
-                        // Update the width of the annotation to give space for the image.
-                        a.width += 10;
-                    }
-                    // label position
-                    aGroup.select('text.' + scene.Class.Annotation.LABEL).transition().attr({
-                        x: cx + a.dx + (a.isIn ? -1 : 1) * (a.width / 2 + a.labelOffset),
-                        y: d.y + a.dy
-                    });
-                    // Some annotations (such as summary) are represented using a 12x12 image tag.
-                    // Purposely omitted units (e.g. pixels) since the images are vector graphics.
-                    // If there is an image, we adjust the location of the image to be vertically
-                    // centered with the node and horizontally centered between the arrow and the
-                    // text label.
-                    aGroup.select('use.summary').transition().attr({
-                        x: cx + a.dx - 3,
-                        y: d.y + a.dy - 6
-                    });
-                    // Node position (only one of the shape selection will be non-empty.)
-                    scene.positionEllipse(aGroup.select('.' + scene.Class.Annotation.NODE + ' ellipse'), cx + a.dx, d.y + a.dy, a.width, a.height);
-                    scene.positionRect(aGroup.select('.' + scene.Class.Annotation.NODE + ' rect'), cx + a.dx, d.y + a.dy, a.width, a.height);
-                    scene.positionRect(aGroup.select('.' + scene.Class.Annotation.NODE + ' use'), cx + a.dx, d.y + a.dy, a.width, a.height);
-                    // Edge position
-                    aGroup.select('path.' + scene.Class.Annotation.EDGE).transition().attr('d', function (a) {
-                        // map relative position to absolute position
-                        var points = a.points.map(function (p) { return { x: p.dx + cx, y: p.dy + d.y }; });
-                        return scene.edge.interpolate(points);
-                    });
-                }
-                ;
-            })(annotation = scene.annotation || (scene.annotation = {}));
-        })(scene = graph.scene || (graph.scene = {}));
-    })(graph = tf.graph || (tf.graph = {}));
-})(tf || (tf = {})); // close module
-</script>
-<script>/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-var tf;
-(function (tf) {
-    var graph;
-    (function (graph) {
-        var scene;
-        (function (scene) {
-            var contextmenu;
-            (function (contextmenu) {
-                /**
-                 * Returns the event listener, which can be used as an argument for the d3
-                 * selection.on function. Renders the context menu that is to be displayed
-                 * in response to the event.
-                 */
-                function getMenu(menu) {
-                    var menuSelection = d3.select('.context-menu');
-                    // Close the menu when anything else is clicked.
-                    d3.select('body').on('click.context', function () { menuSelection.style('display', 'none'); });
-                    // Function called to populate the context menu.
-                    return function (data, index) {
-                        var _this = this;
-                        // Position and display the menu.
-                        var event = d3.event;
-                        menuSelection.style({
-                            'display': 'block',
-                            'left': (event.layerX + 1) + 'px',
-                            'top': (event.layerY + 1) + 'px'
-                        });
-                        // Stop the event from propagating further.
-                        event.preventDefault();
-                        event.stopPropagation();
-                        // Add provided items to the context menu.
-                        menuSelection.html('');
-                        var list = menuSelection.append('ul');
-                        list.selectAll('li')
-                            .data(menu)
-                            .enter()
-                            .append('li')
-                            .html(function (d) { return d.title(data); })
-                            .on('click', function (d, i) {
-                            d.action(_this, data, index);
-                            menuSelection.style('display', 'none');
-                        });
-                    };
-                }
-                contextmenu.getMenu = getMenu;
-                ;
-            })(contextmenu = scene.contextmenu || (scene.contextmenu = {}));
-        })(scene = graph.scene || (graph.scene = {}));
-    })(graph = tf.graph || (tf.graph = {}));
-})(tf || (tf = {})); // close module
-</script>
-<script>/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-var tf;
-(function (tf) {
-    var graph;
-    (function (graph_1) {
-        var scene;
-        (function (scene) {
-            var edge;
-            (function (edge) {
-                /** Delimiter between dimensions when showing sizes of tensors. */
-                var TENSOR_SHAPE_DELIM = '×';
-                /** The minimum stroke width of an edge. */
-                edge.MIN_EDGE_WIDTH = 0.75;
-                /** The maximum stroke width of an edge. */
-                edge.MAX_EDGE_WIDTH = 12;
-                /** The exponent used in the power scale for edge thickness. */
-                var EDGE_WIDTH_SCALE_EXPONENT = 0.3;
-                /** The domain (min and max value) for the edge width. */
-                var DOMAIN_EDGE_WIDTH_SCALE = [1, 5E6];
-                edge.EDGE_WIDTH_SCALE = d3.scale.pow()
-                    .exponent(EDGE_WIDTH_SCALE_EXPONENT)
-                    .domain(DOMAIN_EDGE_WIDTH_SCALE)
-                    .range([edge.MIN_EDGE_WIDTH, edge.MAX_EDGE_WIDTH])
-                    .clamp(true);
-                var arrowheadMap = d3.scale.quantize().domain([edge.MIN_EDGE_WIDTH, edge.MAX_EDGE_WIDTH]).range([
-                    'small', 'medium', 'large', 'xlarge'
-                ]);
-                /** Minimum stroke width to put edge labels in the middle of edges */
-                var CENTER_EDGE_LABEL_MIN_STROKE_WIDTH = 2.5;
-                function getEdgeKey(edgeObj) {
-                    return edgeObj.v + graph_1.EDGE_KEY_DELIM + edgeObj.w;
-                }
-                edge.getEdgeKey = getEdgeKey;
-                /**
-                 * Select or Create a 'g.edges' group to a given sceneGroup
-                 * and builds a number of 'g.edge' groups inside the group.
-                 *
-                 * Structure Pattern:
-                 *
-                 * <g class='edges'>
-                 *   <g class='edge'>
-                 *     <path class='edgeline'/>
-                 *   </g>
-                 *   ...
-                 * </g>
-                 *
-                 *
-                 * @param sceneGroup container
-                 * @param graph
-                 * @param sceneElement <tf-graph-scene> polymer element.
-                 * @return selection of the created nodeGroups
-                 */
-                function buildGroup(sceneGroup, graph, sceneElement) {
-                    var edges = [];
-                    edges = _.reduce(graph.edges(), function (edges, edgeObj) {
-                        var edgeLabel = graph.edge(edgeObj);
-                        edges.push({
-                            v: edgeObj.v,
-                            w: edgeObj.w,
-                            label: edgeLabel
-                        });
-                        return edges;
-                    }, edges);
-                    var container = scene.selectOrCreateChild(sceneGroup, 'g', scene.Class.Edge.CONTAINER);
-                    // Select all children and join with data.
-                    // (Note that all children of g.edges are g.edge)
-                    var edgeGroups = container.selectAll(function () {
-                        // using d3's selector function
-                        // See https://github.com/mbostock/d3/releases/tag/v2.0.0
-                        // (It's not listed in the d3 wiki.)
-                        return this.childNodes;
-                    }).data(edges, getEdgeKey);
-                    // Make edges a group to support rendering multiple lines for metaedge
-                    edgeGroups.enter()
-                        .append('g')
-                        .attr('class', scene.Class.Edge.GROUP)
-                        .attr('data-edge', getEdgeKey)
-                        .each(function (d) {
-                        var edgeGroup = d3.select(this);
-                        d.label.edgeGroup = edgeGroup;
-                        // index node group for quick highlighting
-                        sceneElement._edgeGroupIndex[getEdgeKey(d)] = edgeGroup;
-                        // Add line during enter because we're assuming that type of line
-                        // normally does not change.
-                        appendEdge(edgeGroup, d, sceneElement);
-                    });
-                    edgeGroups.each(position);
-                    edgeGroups.each(function (d) {
-                        stylize(d3.select(this), d, sceneElement);
-                    });
-                    edgeGroups.exit()
-                        .each(function (d) {
-                        delete sceneElement._edgeGroupIndex[getEdgeKey(d)];
-                    })
-                        .remove();
-                    return edgeGroups;
-                }
-                edge.buildGroup = buildGroup;
-                ;
-                /**
-                 * Returns the label for the given base edge.
-                 * The label is the shape of the underlying tensor.
-                 */
-                function getLabelForBaseEdge(baseEdge, renderInfo) {
-                    var node = renderInfo.getNodeByName(baseEdge.v);
-                    if (node.outputShapes == null || node.outputShapes.length === 0) {
-                        return null;
-                    }
-                    var shape = node.outputShapes[baseEdge.outputTensorIndex];
-                    if (shape == null) {
-                        return null;
-                    }
-                    if (shape.length === 0) {
-                        return 'scalar';
-                    }
-                    return shape.map(function (size) { return size === -1 ? '?' : size; })
-                        .join(TENSOR_SHAPE_DELIM);
-                }
-                edge.getLabelForBaseEdge = getLabelForBaseEdge;
-                /**
-                 * Creates the label for the given metaedge. If the metaedge consists
-                 * of only 1 tensor, and it's shape is known, the label will contain that
-                 * shape. Otherwise, the label will say the number of tensors in the metaedge.
-                 */
-                function getLabelForEdge(metaedge, renderInfo) {
-                    var isMultiEdge = metaedge.baseEdgeList.length > 1;
-                    return isMultiEdge ?
-                        metaedge.baseEdgeList.length + ' tensors' :
-                        getLabelForBaseEdge(metaedge.baseEdgeList[0], renderInfo);
-                }
-                edge.getLabelForEdge = getLabelForEdge;
-                /**
-                 * Shortens the path enought such that the tip of the start/end marker will
-                 * point to the start/end of the path. The marker can be of arbitrary size.
-                 *
-                 * @param points Array of path control points.
-                 * @param marker D3 selection of the <marker> svg element.
-                 * @param isStart Is the marker a `start-marker`. If false, the marker is
-                 *     an `end-marker`.
-                 * @return The new array of control points.
-                 */
-                function adjustPathPointsForMarker(points, marker, isStart) {
-                    var lineFunc = d3.svg.line()
-                        .x(function (d) { return d.x; })
-                        .y(function (d) { return d.y; });
-                    var path = d3.select(document.createElementNS('http://www.w3.org/2000/svg', 'path'))
-                        .attr('d', lineFunc(points));
-                    var markerWidth = +marker.attr('markerWidth');
-                    var viewBox = marker.attr('viewBox').split(' ').map(Number);
-                    var viewBoxWidth = viewBox[2] - viewBox[0];
-                    var refX = +marker.attr('refX');
-                    var pathNode = path.node();
-                    if (isStart) {
-                        var fractionStickingOut = refX / viewBoxWidth;
-                        var length_1 = markerWidth * fractionStickingOut;
-                        var point = pathNode.getPointAtLength(length_1);
-                        // Figure out how many segments of the path we need to remove in order
-                        // to shorten the path.
-                        var segIndex = pathNode.getPathSegAtLength(length_1);
-                        // Update the very first segment.
-                        points[segIndex - 1] = { x: point.x, y: point.y };
-                        // Ignore every point before segIndex - 1.
-                        return points.slice(segIndex - 1);
-                    }
-                    else {
-                        var fractionStickingOut = 1 - refX / viewBoxWidth;
-                        var length_2 = pathNode.getTotalLength() - markerWidth * fractionStickingOut;
-                        var point = pathNode.getPointAtLength(length_2);
-                        // Figure out how many segments of the path we need to remove in order
-                        // to shorten the path.
-                        var segIndex = pathNode.getPathSegAtLength(length_2);
-                        // Update the very last segment.
-                        points[segIndex] = { x: point.x, y: point.y };
-                        // Ignore every point after segIndex.
-                        return points.slice(0, segIndex + 1);
-                    }
-                }
-                /**
-                 * For a given d3 selection and data object, create a path to represent the
-                 * edge described in d.label.
-                 *
-                 * If d.label is defined, it will be a RenderMetaedgeInfo instance. It
-                 * will sometimes be undefined, for example for some Annotation edges for which
-                 * there is no underlying Metaedge in the hierarchical graph.
-                 */
-                function appendEdge(edgeGroup, d, sceneElement, edgeClass) {
-                    var size = 1;
-                    if (d.label != null && d.label.metaedge != null) {
-                        // There is an underlying Metaedge.
-                        size = d.label.metaedge.totalSize;
-                    }
-                    edgeClass = edgeClass || scene.Class.Edge.LINE; // set default type
-                    if (d.label && d.label.structural) {
-                        edgeClass += ' ' + scene.Class.Edge.STRUCTURAL;
-                    }
-                    // Give the path a unique id, which will be used to link
-                    // the textPath (edge label) to this path.
-                    var pathId = 'path_' + getEdgeKey(d);
-                    var strokeWidth = sceneElement.renderHierarchy.edgeWidthScale(size);
-                    var path = edgeGroup.append('path')
-                        .attr({
-                        'id': pathId,
-                        'class': edgeClass,
-                    })
-                        .style({ 'stroke-width': strokeWidth + 'px' });
-                    // Check if there is a reference edge and add an arrowhead of the right size.
-                    if (d.label && d.label.metaedge && d.label.metaedge.numRefEdges) {
-                        var markerId = "ref-arrowhead-" + arrowheadMap(strokeWidth);
-                        path.style('marker-start', "url(#" + markerId + ")");
-                        d.label.startMarkerId = markerId;
-                    }
-                    if (d.label == null || d.label.metaedge == null) {
-                        // There is no associated metaedge, thus no text.
-                        // This happens for annotation edges.
-                        return;
-                    }
-                    var labelForEdge = getLabelForEdge(d.label.metaedge, sceneElement.renderHierarchy);
-                    if (labelForEdge == null) {
-                        // We have no information to show on this edge.
-                        return;
-                    }
-                    // Put edge label in the middle of edge only if the edge is thick enough.
-                    var baseline = strokeWidth > CENTER_EDGE_LABEL_MIN_STROKE_WIDTH ?
-                        'central' :
-                        'text-after-edge';
-                    edgeGroup.append('text')
-                        .append('textPath')
-                        .attr({
-                        'xlink:href': '#' + pathId,
-                        'startOffset': '50%',
-                        'text-anchor': 'middle',
-                        'dominant-baseline': 'central'
-                    })
-                        .text(labelForEdge);
-                }
-                edge.appendEdge = appendEdge;
-                ;
-                edge.interpolate = d3.svg.line()
-                    .interpolate('basis')
-                    .x(function (d) { return d.x; })
-                    .y(function (d) { return d.y; });
-                /**
-                 * Returns a tween interpolator for the endpoint of an edge path.
-                 */
-                function getEdgePathInterpolator(d, i, a) {
-                    var renderMetaedgeInfo = d.label;
-                    var adjoiningMetaedge = renderMetaedgeInfo.adjoiningMetaedge;
-                    var points = renderMetaedgeInfo.points;
-                    // Adjust the path so that start/end markers point to the end
-                    // of the path.
-                    if (d.label.startMarkerId) {
-                        points = adjustPathPointsForMarker(points, d3.select('#' + d.label.startMarkerId), true);
-                    }
-                    if (d.label.endMarkerId) {
-                        points = adjustPathPointsForMarker(points, d3.select('#' + d.label.endMarkerId), false);
-                    }
-                    if (!adjoiningMetaedge) {
-                        return d3.interpolate(a, edge.interpolate(points));
-                    }
-                    var renderPath = this;
-                    // Get the adjoining path that matches the adjoining metaedge.
-                    var adjoiningPath = (adjoiningMetaedge.edgeGroup.node()
-                        .firstChild);
-                    // Find the desired SVGPoint along the adjoining path, then convert those
-                    // coordinates into the space of the renderPath using its Current
-                    // Transformation Matrix (CTM).
-                    var inbound = renderMetaedgeInfo.metaedge.inbound;
-                    return function (t) {
-                        var adjoiningPoint = adjoiningPath
-                            .getPointAtLength(inbound ? adjoiningPath.getTotalLength() : 0)
-                            .matrixTransform(adjoiningPath.getCTM())
-                            .matrixTransform(renderPath.getCTM().inverse());
-                        // Update the relevant point in the renderMetaedgeInfo's points list, then
-                        // re-interpolate the path.
-                        var index = inbound ? 0 : points.length - 1;
-                        points[index].x = adjoiningPoint.x;
-                        points[index].y = adjoiningPoint.y;
-                        var dPath = edge.interpolate(points);
-                        return dPath;
-                    };
-                }
-                function position(d) {
-                    d3.select(this)
-                        .select('path.' + scene.Class.Edge.LINE)
-                        .transition()
-                        .attrTween('d', getEdgePathInterpolator);
-                }
-                ;
-                /**
-                 * For a given d3 selection and data object, mark the edge as a control
-                 * dependency if it contains only control edges.
-                 *
-                 * d's label property will be a RenderMetaedgeInfo object.
-                 */
-                function stylize(edgeGroup, d, stylize) {
-                    edgeGroup.classed('faded', d.label.isFadedOut);
-                    var metaedge = d.label.metaedge;
-                    edgeGroup.select('path.' + scene.Class.Edge.LINE)
-                        .classed('control-dep', metaedge && !metaedge.numRegularEdges);
-                }
-                ;
-            })(edge = scene.edge || (scene.edge = {}));
-        })(scene = graph_1.scene || (graph_1.scene = {}));
-    })(graph = tf.graph || (tf.graph = {}));
-})(tf || (tf = {})); // close module
-</script>
-<script>/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-var tf;
-(function (tf) {
-    var graph;
-    (function (graph) {
-        var scene;
-        (function (scene) {
-            var node;
-            (function (node_1) {
-                /**
-                 * Select or Create a 'g.nodes' group to a given sceneGroup
-                 * and builds a number of 'g.node' groups inside the group.
-                 *
-                 * Structure Pattern:
-                 *
-                 * <g class='nodes'>
-                 *   <g class='node'>
-                 *     <g class='in-annotations'>
-                 *       ...
-                 *     </g>
-                 *     <g class='out-annotations'>
-                 *       ...
-                 *     </g>
-                 *     <g class='nodeshape'>
-                 *      \x3c!--
-                 *      Content of the node shape should be for the node itself. For example a
-                 *      Metanode would have a <rect> with rounded edges, an op would have an
-                 *      <ellipse>. More complex nodes like series may contain multiple
-                 *      elements which are conditionally visible based on whether the node is
-                 *      expanded.
-                 *      --\x3e
-                 *     </g>
-                 *     <text class='label'>node name</text>
-                 *     <g class='subscene'>
-                 *       \x3c!--
-                 *       Content of  the subscene (only for metanode and series node).
-                 *
-                 *       Subscene is a svg group that contains content of the
-                 *       metanode's metagraph that is recursively generated by Scene.build().
-                 *
-                 *       When the graph is expanded multiple times, a subscene can contain
-                 *       nested subscenes inside.
-                 *       --\x3e
-                 *     </g>
-                 *   </g>
-                 *   ...
-                 * </g>
-                 *
-                 *
-                 * @param sceneGroup selection of the container
-                 * @param nodeData array of render node information to map
-                 * @param sceneElement <tf-graph-scene> polymer element
-                 * @return selection of the created nodeGroups
-                 */
-                function buildGroup(sceneGroup, nodeData, sceneElement) {
-                    var container = scene.selectOrCreateChild(sceneGroup, 'g', scene.Class.Node.CONTAINER);
-                    // Select all children and join with data.
-                    // (Note that all children of g.nodes are g.node)
-                    var nodeGroups = container
-                        .selectAll(function () {
-                        // using d3's selector function
-                        // See https://github.com/mbostock/d3/releases/tag/v2.0.0
-                        // (It's not listed in the d3 wiki.)
-                        return this.childNodes; // this here refers to container.node()
-                    })
-                        .data(nodeData, function (d) {
-                        // make sure that we don't have to swap shape type
-                        return d.node.name + ':' + d.node.type;
-                    });
-                    // ENTER
-                    nodeGroups.enter()
-                        .append('g')
-                        .attr('data-name', function (d) { return d.node.name; })
-                        .each(function (d) {
-                        var nodeGroup = d3.select(this);
-                        // index node group for quick stylizing
-                        sceneElement.addNodeGroup(d.node.name, nodeGroup);
-                    });
-                    // UPDATE
-                    nodeGroups
-                        .attr('class', function (d) { return scene.Class.Node.GROUP + ' ' + nodeClass(d); })
-                        .each(function (d) {
-                        var nodeGroup = d3.select(this);
-                        // Add g.in-annotations (always add -- to keep layer order
-                        // consistent.)
-                        var inAnnotationBox = scene.selectOrCreateChild(nodeGroup, 'g', scene.Class.Annotation.INBOX);
-                        scene.annotation.buildGroup(inAnnotationBox, d.inAnnotations, d, sceneElement);
-                        // Add g.out-annotations  (always add -- to keep layer order
-                        // consistent.)
-                        var outAnnotationBox = scene.selectOrCreateChild(nodeGroup, 'g', scene.Class.Annotation.OUTBOX);
-                        scene.annotation.buildGroup(outAnnotationBox, d.outAnnotations, d, sceneElement);
-                        // Build .shape first (background of the node).
-                        var shape = buildShape(nodeGroup, d, scene.Class.Node.SHAPE);
-                        if (d.node.isGroupNode) {
-                            addButton(shape, d, sceneElement);
-                        }
-                        addInteraction(shape, d, sceneElement);
-                        // Build subscene on the top.
-                        subsceneBuild(nodeGroup, d, sceneElement);
-                        // Build label last. Should be on top of everything else.
-                        var label = labelBuild(nodeGroup, d, sceneElement);
-                        // Do not add interaction to metanode labels as they live inside the
-                        // metanode shape which already has the same interactions.
-                        addInteraction(label, d, sceneElement, d.node.type === graph.NodeType.META);
-                        stylize(nodeGroup, d, sceneElement);
-                        position(nodeGroup, d);
-                    });
-                    // EXIT
-                    nodeGroups.exit()
-                        .each(function (d) {
-                        // remove all indices on remove
-                        sceneElement.removeNodeGroup(d.node.name);
-                        var nodeGroup = d3.select(this);
-                        if (d.inAnnotations.list.length > 0) {
-                            nodeGroup.select('.' + scene.Class.Annotation.INBOX)
-                                .selectAll('.' + scene.Class.Annotation.GROUP)
-                                .each(function (a) { sceneElement.removeAnnotationGroup(a, d); });
-                        }
-                        if (d.outAnnotations.list.length > 0) {
-                            nodeGroup.select('.' + scene.Class.Annotation.OUTBOX)
-                                .selectAll('.' + scene.Class.Annotation.GROUP)
-                                .each(function (a) { sceneElement.removeAnnotationGroup(a, d); });
-                        }
-                    })
-                        .remove();
-                    return nodeGroups;
-                }
-                node_1.buildGroup = buildGroup;
-                ;
-                /**
-                 * Update or remove the subscene of a render group node depending on whether it
-                 * is a expanded. If the node is not a group node, this method has no effect.
-                 *
-                 * @param nodeGroup selection of the container
-                 * @param renderNodeInfo the render information for the node.
-                 * @param sceneElement <tf-graph-scene> polymer element.
-                 * @return Selection of the subscene group, or null if node group does not have
-                 *        a subscene. Op nodes, bridge nodes and unexpanded group nodes will
-                 *        not have a subscene.
-                 */
-                function subsceneBuild(nodeGroup, renderNodeInfo, sceneElement) {
-                    if (renderNodeInfo.node.isGroupNode) {
-                        if (renderNodeInfo.expanded) {
-                            // Recursively build the subscene.
-                            return scene.buildGroup(nodeGroup, renderNodeInfo, sceneElement, scene.Class.Subscene.GROUP);
-                        }
-                        // Clean out existing subscene if the node is not expanded.
-                        scene.selectChild(nodeGroup, 'g', scene.Class.Subscene.GROUP).remove();
-                    }
-                    return null;
-                }
-                ;
-                /**
-                 * Translate the subscene of the given node group
-                 */
-                function subscenePosition(nodeGroup, d) {
-                    var x0 = d.x - d.width / 2.0 + d.paddingLeft;
-                    var y0 = d.y - d.height / 2.0 + d.paddingTop;
-                    var subscene = scene.selectChild(nodeGroup, 'g', scene.Class.Subscene.GROUP);
-                    scene.translate(subscene, x0, y0);
-                }
-                ;
-                /**
-                 * Add an expand/collapse button to a group node
-                 *
-                 * @param selection The group node selection.
-                 * @param d Info about the node being rendered.
-                 * @param sceneElement <tf-graph-scene> polymer element.
-                 */
-                function addButton(selection, d, sceneElement) {
-                    var group = scene.selectOrCreateChild(selection, 'g', scene.Class.Node.BUTTON_CONTAINER);
-                    scene.selectOrCreateChild(group, 'circle', scene.Class.Node.BUTTON_CIRCLE);
-                    scene.selectOrCreateChild(group, 'path', scene.Class.Node.EXPAND_BUTTON)
-                        .attr('d', 'M0,-2.2 V2.2 M-2.2,0 H2.2');
-                    scene.selectOrCreateChild(group, 'path', scene.Class.Node.COLLAPSE_BUTTON)
-                        .attr('d', 'M-2.2,0 H2.2');
-                    group.on('click', function (d) {
-                        // Stop this event's propagation so that it isn't also considered a
-                        // node-select.
-                        d3.event.stopPropagation();
-                        sceneElement.fire('node-toggle-expand', { name: d.node.name });
-                    });
-                    scene.positionButton(group, d);
-                }
-                ;
-                /**
-                 * Fire node-* events when the selection is interacted.
-                 *
-                 * @param disableInteraction When true, have the provided selection
-                 * ignore all pointer events. Used for text labels inside of metanodes, which
-                 * don't need interaction as their surrounding shape has interaction, and if
-                 * given interaction would cause conflicts with the expand/collapse button.
-                 */
-                function addInteraction(selection, d, sceneElement, disableInteraction) {
-                    if (disableInteraction) {
-                        selection.attr('pointer-events', 'none');
-                        return;
-                    }
-                    var contextMenuFunction = scene.contextmenu.getMenu(getContextMenu(d.node, sceneElement));
-                    selection
-                        .on('dblclick', function (d) {
-                        sceneElement.fire('node-toggle-expand', { name: d.node.name });
-                    })
-                        .on('mouseover', function (d) {
-                        // don't send mouseover over expanded group,
-                        // otherwise it is causing too much glitches
-                        if (sceneElement.isNodeExpanded(d)) {
-                            return;
-                        }
-                        sceneElement.fire('node-highlight', { name: d.node.name });
-                    })
-                        .on('mouseout', function (d) {
-                        // don't send mouseover over expanded group,
-                        // otherwise it is causing too much glitches
-                        if (sceneElement.isNodeExpanded(d)) {
-                            return;
-                        }
-                        sceneElement.fire('node-unhighlight', { name: d.node.name });
-                    })
-                        .on('click', function (d) {
-                        // Stop this event's propagation so that it isn't also considered
-                        // a graph-select.
-                        d3.event.stopPropagation();
-                        sceneElement.fire('node-select', { name: d.node.name });
-                    })
-                        .on('contextmenu', function (d, i) {
-                        sceneElement.fire('node-select', { name: d.node.name });
-                        contextMenuFunction.call(d, i);
-                    });
-                }
-                ;
-                /**
-                 * Returns the d3 context menu specification for the provided node.
-                 */
-                function getContextMenu(node, sceneElement) {
-                    var menu = [{
-                            title: function (d) {
-                                return graph.getIncludeNodeButtonString(node.include);
-                            },
-                            action: function (elm, d, i) {
-                                sceneElement.fire('node-toggle-extract', { name: node.name });
-                            }
-                        }];
-                    if (canBeInSeries(node)) {
-                        menu.push({
-                            title: function (d) { return getGroupSettingLabel(node); },
-                            action: function (elm, d, i) {
-                                sceneElement.fire('node-toggle-seriesgroup', { name: getSeriesName(node) });
-                            }
-                        });
-                    }
-                    return menu;
-                }
-                node_1.getContextMenu = getContextMenu;
-                /** Returns if a node can be part of a grouped series */
-                function canBeInSeries(node) {
-                    return getSeriesName(node) !== null;
-                }
-                node_1.canBeInSeries = canBeInSeries;
-                /**
-                 * Returns the name of the possible grouped series containing this node.
-                 * Returns null if the node cannot be part of a grouped series of nodes.
-                 */
-                function getSeriesName(node) {
-                    if (!node) {
-                        return null;
-                    }
-                    if (node.type === graph.NodeType.SERIES) {
-                        return node.name;
-                    }
-                    if (node.type === graph.NodeType.OP) {
-                        var op = node;
-                        return op.owningSeries;
-                    }
-                    return null;
-                }
-                node_1.getSeriesName = getSeriesName;
-                /**
-                 * Returns the SeriesNode that represents the series that the provided node
-                 * is contained in (or itself if the provided node is itself a SeriesNode).
-                 * Returns null if the node is not rendered as part of a series.
-                 */
-                function getContainingSeries(node) {
-                    var s = null;
-                    if (!node) {
-                        return null;
-                    }
-                    else if (node.type === graph.NodeType.SERIES) {
-                        s = node;
-                    }
-                    else if (node.parentNode && node.parentNode.type === graph.NodeType.SERIES) {
-                        s = node.parentNode;
-                    }
-                    return s;
-                }
-                /**
-                 * Returns the label for a button to toggle the group setting of the provided
-                 * node.
-                 */
-                function getGroupSettingLabel(node) {
-                    return tf.graph.getGroupSeriesNodeButtonString(getContainingSeries(node) !== null ? tf.graph.SeriesGroupingType.GROUP :
-                        tf.graph.SeriesGroupingType.UNGROUP);
-                }
-                node_1.getGroupSettingLabel = getGroupSettingLabel;
-                /**
-                 * Append svg text for label and assign data.
-                 * @param nodeGroup
-                 * @param renderNodeInfo The render node information for the label.
-                 * @param sceneElement <tf-graph-scene> polymer element.
-                 */
-                function labelBuild(nodeGroup, renderNodeInfo, sceneElement) {
-                    var namePath = renderNodeInfo.node.name.split('/');
-                    var text = namePath[namePath.length - 1];
-                    // Truncate long labels for unexpanded Metanodes.
-                    var useFontScale = renderNodeInfo.node.type === graph.NodeType.META &&
-                        !renderNodeInfo.expanded;
-                    var label = scene.selectOrCreateChild(nodeGroup, 'text', scene.Class.Node.LABEL);
-                    // Make sure the label is visually on top among its siblings.
-                    var labelNode = label.node();
-                    labelNode.parentNode.appendChild(labelNode);
-                    label.attr('dy', '.35em').attr('text-anchor', 'middle');
-                    if (useFontScale) {
-                        if (text.length > sceneElement.maxMetanodeLabelLength) {
-                            text = text.substr(0, sceneElement.maxMetanodeLabelLength - 2) + '...';
-                        }
-                        var scale = getLabelFontScale(sceneElement);
-                        label.attr('font-size', scale(text.length) + 'px');
-                    }
-                    var txtElement = label.text(text);
-                    enforceLabelWidth(txtElement, renderNodeInfo.node.type, renderNodeInfo);
-                    return label;
-                }
-                /**
-                 * This function shortens text which would exceed the maximum pixel width of
-                 * a label.
-                 *
-                 * @param txtElementSelection The text element containing the label's text as d3
-                 * selection.
-                 * @param nodeType The type of the node the label belongs to. If the node is
-                 * an annotation, the value is -1. Label widths are defined in
-                 * layout.PARAMS.nodeSize.{meta|op|...}.maxLabelWidth for nodes and
-                 * layout.PARAMS.annotations.labelWidth for annotations.
-                 * @param renderNodeInfo The render information about the node, required to
-                 * determine whether META nodes are collapsed or expanded.
-                 */
-                function enforceLabelWidth(txtElementSelection, nodeType, renderNodeInfo) {
-                    // Get text element itself and its on-screen width.
-                    var txtNode = txtElementSelection.node();
-                    var computedTxtLength = txtNode.getComputedTextLength();
-                    var labelContent = txtNode.textContent;
-                    // Get maximum length from settings.
-                    var maxLength = null;
-                    switch (nodeType) {
-                        case graph.NodeType.META:
-                            if (renderNodeInfo && !renderNodeInfo.expanded) {
-                                // node expanded.
-                                maxLength = graph.layout.PARAMS.nodeSize.meta.maxLabelWidth;
-                            }
-                            break;
-                        case graph.NodeType.OP:
-                            maxLength = graph.layout.PARAMS.nodeSize.op.maxLabelWidth;
-                            break;
-                        case -1:
-                            maxLength = graph.layout.PARAMS.annotations.maxLabelWidth;
-                            break;
-                        default:
-                            break;
-                    }
-                    // Return if no max length provided for node type, or current label length is
-                    // less than or equal to the provided length limit.
-                    if (maxLength === null || computedTxtLength <= maxLength) {
-                        return;
-                    }
-                    // Find the index of the character which exceeds the width.
-                    // getSubStringLength performs far better than getComputedTextLength, and
-                    // results in a 3x speed-up on average.
-                    var index = 1;
-                    while (txtNode.getSubStringLength(0, index) < maxLength) {
-                        index++;
-                    }
-                    // Shorten the label starting at the string length known to be one
-                    // character above max pixel length.
-                    // When shortened the original label's substring is concatenated with
-                    // '...', baseText contains the substring not including the '...'.
-                    var baseText = txtNode.textContent.substr(0, index);
-                    do {
-                        baseText = baseText.substr(0, baseText.length - 1);
-                        // Recompute text length.
-                        txtNode.textContent = baseText + '...';
-                        computedTxtLength = txtNode.getComputedTextLength();
-                    } while (computedTxtLength > maxLength && baseText.length > 0);
-                    // Add tooltip with full name and return.
-                    return txtElementSelection.append('title').text(labelContent);
-                }
-                node_1.enforceLabelWidth = enforceLabelWidth;
-                /**
-                 * d3 scale used for sizing font of labels, used by labelBuild,
-                 * initialized once by getLabelFontScale.
-                 */
-                var fontScale = null;
-                function getLabelFontScale(sceneElement) {
-                    if (!fontScale) {
-                        fontScale = d3.scale.linear()
-                            .domain([sceneElement.maxMetanodeLabelLengthLargeFont,
-                            sceneElement.maxMetanodeLabelLength])
-                            .range([sceneElement.maxMetanodeLabelLengthFontSize,
-                            sceneElement.minMetanodeLabelLengthFontSize]).clamp(true);
-                    }
-                    return fontScale;
-                }
-                /**
-                 * Set label position of a given node group
-                 */
-                function labelPosition(nodeGroup, cx, cy, yOffset) {
-                    scene.selectChild(nodeGroup, 'text', scene.Class.Node.LABEL)
-                        .transition()
-                        .attr('x', cx)
-                        .attr('y', cy + yOffset);
-                }
-                ;
-                /**
-                 * Select or append/insert shape for a node and assign renderNode
-                 * as the shape's data.
-                 *
-                 * @param nodeGroup
-                 * @param d Render node information.
-                 * @param nodeClass class for the element.
-                 * @return Selection of the shape.
-                 */
-                function buildShape(nodeGroup, d, nodeClass) {
-                    // Create a group to house the underlying visual elements.
-                    var shapeGroup = scene.selectOrCreateChild(nodeGroup, 'g', nodeClass);
-                    // TODO(jimbo): DOM structure should be templated in HTML somewhere, not JS.
-                    switch (d.node.type) {
-                        case graph.NodeType.OP:
-                            scene.selectOrCreateChild(shapeGroup, 'ellipse', scene.Class.Node.COLOR_TARGET);
-                            break;
-                        case graph.NodeType.SERIES:
-                            // Choose the correct stamp to use to represent this series.
-                            var stampType = 'annotation';
-                            var groupNodeInfo = d;
-                            if (groupNodeInfo.coreGraph) {
-                                stampType =
-                                    groupNodeInfo.node.hasNonControlEdges ? 'vertical' : 'horizontal';
-                            }
-                            var classList = [scene.Class.Node.COLOR_TARGET];
-                            if (groupNodeInfo.isFadedOut) {
-                                classList.push('faded-ellipse');
-                            }
-                            scene.selectOrCreateChild(shapeGroup, 'use', classList)
-                                .attr('xlink:href', '#op-series-' + stampType + '-stamp');
-                            scene.selectOrCreateChild(shapeGroup, 'rect', scene.Class.Node.COLOR_TARGET)
-                                .attr({ rx: d.radius, ry: d.radius });
-                            break;
-                        case graph.NodeType.BRIDGE:
-                            scene.selectOrCreateChild(shapeGroup, 'rect', scene.Class.Node.COLOR_TARGET)
-                                .attr({ rx: d.radius, ry: d.radius });
-                            break;
-                        case graph.NodeType.META:
-                            scene.selectOrCreateChild(shapeGroup, 'rect', scene.Class.Node.COLOR_TARGET)
-                                .attr({ rx: d.radius, ry: d.radius });
-                            break;
-                        default:
-                            throw Error('Unrecognized node type: ' + d.node.type);
-                    }
-                    return shapeGroup;
-                }
-                node_1.buildShape = buildShape;
-                ;
-                function nodeClass(d) {
-                    switch (d.node.type) {
-                        case graph.NodeType.OP:
-                            return scene.Class.OPNODE;
-                        case graph.NodeType.META:
-                            return scene.Class.METANODE;
-                        case graph.NodeType.SERIES:
-                            return scene.Class.SERIESNODE;
-                        case graph.NodeType.BRIDGE:
-                            return scene.Class.BRIDGENODE;
-                        case graph.NodeType.ELLIPSIS:
-                            return scene.Class.ELLIPSISNODE;
-                    }
-                    ;
-                    throw Error('Unrecognized node type: ' + d.node.type);
-                }
-                node_1.nodeClass = nodeClass;
-                ;
-                /** Modify node and its subscene and its label's positional attributes */
-                function position(nodeGroup, d) {
-                    var shapeGroup = scene.selectChild(nodeGroup, 'g', scene.Class.Node.SHAPE);
-                    var cx = graph.layout.computeCXPositionOfNodeShape(d);
-                    switch (d.node.type) {
-                        case graph.NodeType.OP: {
-                            // position shape
-                            var shape = scene.selectChild(shapeGroup, 'ellipse');
-                            scene.positionEllipse(shape, cx, d.y, d.coreBox.width, d.coreBox.height);
-                            labelPosition(nodeGroup, cx, d.y, d.labelOffset);
-                            break;
-                        }
-                        case graph.NodeType.META: {
-                            // position shape
-                            var shape = scene.selectChild(shapeGroup, 'rect');
-                            if (d.expanded) {
-                                scene.positionRect(shape, d.x, d.y, d.width, d.height);
-                                subscenePosition(nodeGroup, d);
-                                // put label on top
-                                labelPosition(nodeGroup, cx, d.y, -d.height / 2 + d.labelHeight / 2);
-                            }
-                            else {
-                                scene.positionRect(shape, cx, d.y, d.coreBox.width, d.coreBox.height);
-                                labelPosition(nodeGroup, cx, d.y, 0);
-                            }
-                            break;
-                        }
-                        case graph.NodeType.SERIES: {
-                            var shape = scene.selectChild(shapeGroup, 'use');
-                            if (d.expanded) {
-                                scene.positionRect(shape, d.x, d.y, d.width, d.height);
-                                subscenePosition(nodeGroup, d);
-                                // put label on top
-                                labelPosition(nodeGroup, cx, d.y, -d.height / 2 + d.labelHeight / 2);
-                            }
-                            else {
-                                scene.positionRect(shape, cx, d.y, d.coreBox.width, d.coreBox.height);
-                                labelPosition(nodeGroup, cx, d.y, d.labelOffset);
-                            }
-                            break;
-                        }
-                        case graph.NodeType.BRIDGE: {
-                            // position shape
-                            // NOTE: In reality, these will not be visible, but it helps to put them
-                            // in the correct position for debugging purposes.
-                            var shape = scene.selectChild(shapeGroup, 'rect');
-                            scene.positionRect(shape, d.x, d.y, d.width, d.height);
-                            break;
-                        }
-                        default: {
-                            throw Error('Unrecognized node type: ' + d.node.type);
-                        }
-                    }
-                }
-                ;
-                /** Enum specifying the options to color nodes by */
-                var ColorBy;
-                (function (ColorBy) {
-                    ColorBy[ColorBy["STRUCTURE"] = 0] = "STRUCTURE";
-                    ColorBy[ColorBy["DEVICE"] = 1] = "DEVICE";
-                    ColorBy[ColorBy["XLA_CLUSTER"] = 2] = "XLA_CLUSTER";
-                    ColorBy[ColorBy["COMPUTE_TIME"] = 3] = "COMPUTE_TIME";
-                    ColorBy[ColorBy["MEMORY"] = 4] = "MEMORY";
-                })(ColorBy = node_1.ColorBy || (node_1.ColorBy = {}));
-                ;
-                /**
-                 * Returns the fill color for the node given its state and the 'color by'
-                 * option.
-                 */
-                function getFillForNode(templateIndex, colorBy, renderInfo, isExpanded) {
-                    var colorParams = graph.render.MetanodeColors;
-                    switch (colorBy) {
-                        case ColorBy.STRUCTURE:
-                            if (renderInfo.node.type === graph.NodeType.META) {
-                                var tid = renderInfo.node.templateId;
-                                return tid === null ?
-                                    colorParams.UNKNOWN :
-                                    colorParams.STRUCTURE_PALETTE(templateIndex(tid), isExpanded);
-                            }
-                            else if (renderInfo.node.type === graph.NodeType.SERIES) {
-                                // If expanded, we're showing the background rect, which we want to
-                                // appear gray. Otherwise we're showing a stack of ellipses which we
-                                // want to show white.
-                                return isExpanded ? colorParams.EXPANDED_COLOR : 'white';
-                            }
-                            else if (renderInfo.node.type === graph.NodeType.BRIDGE) {
-                                return renderInfo.structural ?
-                                    '#f0e' :
-                                    renderInfo.node.inbound ? '#0ef' : '#fe0';
-                            }
-                            else {
-                                // Op nodes are white.
-                                return 'white';
-                            }
-                        case ColorBy.DEVICE:
-                            if (renderInfo.deviceColors == null) {
-                                // Return the hue for unknown device.
-                                return colorParams.UNKNOWN;
-                            }
-                            var id = renderInfo.node.name;
-                            var escapedId = tf.graph.util.escapeQuerySelector(id);
-                            var gradientDefs = d3.select('svg#svg defs #linearGradients');
-                            var linearGradient_1 = gradientDefs.select('linearGradient#' + escapedId);
-                            // If the linear gradient is not there yet, create it.
-                            if (linearGradient_1.size() === 0) {
-                                linearGradient_1 = gradientDefs.append('linearGradient').attr('id', id);
-                                // Re-create the stops of the linear gradient.
-                                linearGradient_1.selectAll('*').remove();
-                                var cumulativeProportion_1 = 0;
-                                // For each device, create a stop using the proportion of that device.
-                                _.each(renderInfo.deviceColors, function (d) {
-                                    var color = d.color;
-                                    linearGradient_1.append('stop')
-                                        .attr('offset', cumulativeProportion_1)
-                                        .attr('stop-color', color);
-                                    linearGradient_1.append('stop')
-                                        .attr('offset', cumulativeProportion_1 + d.proportion)
-                                        .attr('stop-color', color);
-                                    cumulativeProportion_1 += d.proportion;
-                                });
-                            }
-                            return isExpanded ? colorParams.EXPANDED_COLOR : "url(#" + escapedId + ")";
-                        case ColorBy.XLA_CLUSTER:
-                            return isExpanded ? colorParams.EXPANDED_COLOR :
-                                renderInfo.xlaClusterColor || colorParams.UNKNOWN;
-                        case ColorBy.COMPUTE_TIME:
-                            return isExpanded ?
-                                colorParams.EXPANDED_COLOR : renderInfo.computeTimeColor ||
-                                colorParams.UNKNOWN;
-                        case ColorBy.MEMORY:
-                            return isExpanded ?
-                                colorParams.EXPANDED_COLOR : renderInfo.memoryColor ||
-                                colorParams.UNKNOWN;
-                        default:
-                            throw new Error('Unknown case to color nodes by');
-                    }
-                }
-                node_1.getFillForNode = getFillForNode;
-                /**
-                 * Modify node style by toggling class and assign attributes (only for things
-                 * that can't be done in css).
-                 */
-                function stylize(nodeGroup, renderInfo, sceneElement, nodeClass) {
-                    nodeClass = nodeClass || scene.Class.Node.SHAPE;
-                    var isHighlighted = sceneElement.isNodeHighlighted(renderInfo.node.name);
-                    var isSelected = sceneElement.isNodeSelected(renderInfo.node.name);
-                    var isExtract = renderInfo.isInExtract || renderInfo.isOutExtract;
-                    var isExpanded = renderInfo.expanded;
-                    var isFadedOut = renderInfo.isFadedOut;
-                    nodeGroup.classed('highlighted', isHighlighted);
-                    nodeGroup.classed('selected', isSelected);
-                    nodeGroup.classed('extract', isExtract);
-                    nodeGroup.classed('expanded', isExpanded);
-                    nodeGroup.classed('faded', isFadedOut);
-                    // Main node always exists here and it will be reached before subscene,
-                    // so d3 selection is fine here.
-                    var node = nodeGroup.select('.' + nodeClass + ' .' + scene.Class.Node.COLOR_TARGET);
-                    var fillColor = getFillForNode(sceneElement.templateIndex, ColorBy[sceneElement.colorBy.toUpperCase()], renderInfo, isExpanded);
-                    node.style('fill', fillColor);
-                    // Choose outline to be darker version of node color if the node is a single
-                    // color and is not selected.
-                    node.style('stroke', isSelected ? null : getStrokeForFill(fillColor));
-                }
-                node_1.stylize = stylize;
-                ;
-                /**
-                 * Given a node's fill color/gradient, determine the stroke for the node.
-                 */
-                function getStrokeForFill(fill) {
-                    // If node is colored by a gradient, then use a dark gray outline.
-                    return fill.substring(0, 3) === 'url' ?
-                        graph.render.MetanodeColors.GRADIENT_OUTLINE :
-                        d3.rgb(fill).darker().toString();
-                }
-                node_1.getStrokeForFill = getStrokeForFill;
-                /**
-                 * Finds selected node and highlights all nodes which are providing direct
-                 * or indirect input to the node and all edges connecting these nodes
-                 * together and to the selected node.
-                 *
-                 * @param renderGraphInfo Information on the rendered state of the graph.
-                 */
-                function traceInputs(renderGraphInfo) {
-                    // Reset all styling.
-                    d3.selectAll('.input-highlight').classed('input-highlight', false);
-                    d3.selectAll('.non-input').classed('non-input', false);
-                    d3.selectAll('.input-parent').classed('input-parent', false);
-                    d3.selectAll('.input-child').classed('input-child', false);
-                    d3.selectAll('.input-edge-highlight').classed('input-edge-highlight', false);
-                    d3.selectAll('.non-input-edge-highlight')
-                        .classed('non-input-edge-highlight', false);
-                    d3.selectAll('.input-highlight-selected')
-                        .classed('input-highlight-selected', false);
-                    // Extract currently selected node. Return if input tracing disabled or no
-                    // node is selected.
-                    var selectedNodeSelectorString = 'g.node.selected,g.op.selected';
-                    var node = d3.select(selectedNodeSelectorString);
-                    var currentNode = undefined;
-                    if (renderGraphInfo && renderGraphInfo.traceInputs && node && node[0] &&
-                        node[0][0]) {
-                        currentNode = node[0][0];
-                    }
-                    else {
-                        return;
-                    }
-                    var nodeName = currentNode.getAttribute('data-name');
-                    var opNodes = _getAllContainedOpNodes(nodeName, renderGraphInfo);
-                    var allTracedNodes = {};
-                    _.each(opNodes, function (nodeInstance) {
-                        allTracedNodes =
-                            traceAllInputsOfOpNode(renderGraphInfo, nodeInstance, allTracedNodes);
-                    });
-                    d3.selectAll(selectedNodeSelectorString).classed({
-                        // Remove the input-highlight from the selected node.
-                        'input-highlight': false,
-                        // Add input-highlight-selected class to selected node, which allows
-                        // treating the selected not as a special case of an input node.
-                        'input-highlight-selected': true
-                    });
-                    // Highlight all parent nodes of each OpNode as input parent to allow
-                    // specific highlighting.
-                    var highlightedNodes = Object.keys(allTracedNodes);
-                    var visibleNodes = _findVisibleParentsFromOpNodes(renderGraphInfo, highlightedNodes);
-                    _markParentsOfNodes(visibleNodes);
-                    // Attach class to all non-input nodes and edges for styling.
-                    d3.selectAll('g.node:not(.selected):not(.input-highlight)' +
-                        ':not(.input-parent):not(.input-children)')
-                        .classed('non-input', true)
-                        .each(function (d) {
-                        // Mark all nodes with the specified name as non-inputs. This
-                        // results in Annotation nodes which are attached to inputs to be
-                        // tagged as well.
-                        var nodeName = d.node.name;
-                        d3.selectAll("[data-name=\"" + nodeName + "\"]").classed('non-input', true);
-                    });
-                    d3.selectAll('g.edge:not(.input-edge-highlight)')
-                        .classed('non-input-edge-highlight', true);
-                }
-                node_1.traceInputs = traceInputs;
-                /**
-                 * Recursively find all op nodes contained by the node identified by the
-                 * provided name.
-                 * @param nodeName The meta or op node of which the OpNode instances are
-                 * required.
-                 * @param renderGraphInfo The rendered graph information object.
-                 * @returns {Array} An array of OpNodeImpl instances.
-                 */
-                function _getAllContainedOpNodes(nodeName, renderGraphInfo) {
-                    var opNodes = [];
-                    // Get current node.
-                    var node = renderGraphInfo.getNodeByName(nodeName);
-                    // If node is already OpNode then return the node plus its input embeddings.
-                    if (node instanceof tf.graph.OpNodeImpl) {
-                        return [node].concat(node.inEmbeddings);
-                    }
-                    // Otherwise, make recursive call for each node contained by the GroupNode.
-                    var childNodeNames = node.metagraph.nodes();
-                    _.each(childNodeNames, function (childNodeName) {
-                        opNodes =
-                            opNodes.concat(_getAllContainedOpNodes(childNodeName, renderGraphInfo));
-                    });
-                    return opNodes;
-                }
-                node_1._getAllContainedOpNodes = _getAllContainedOpNodes;
-                function traceAllInputsOfOpNode(renderGraphInfo, startNode, allTracedNodes) {
-                    // To prevent infinite loops due to cyclical relationships and improving
-                    // performance by tracing OpNode which is input to 2+ nodes only once.
-                    if (allTracedNodes[startNode.name]) {
-                        return allTracedNodes;
-                    }
-                    else {
-                        allTracedNodes[startNode.name] = true;
-                    }
-                    // Extract the inputs.
-                    var inputs = startNode.inputs;
-                    // Get visible parent.
-                    var currentVisibleParent = getVisibleParent(renderGraphInfo, startNode);
-                    // Mark as input node.
-                    d3.select(".node[data-name=\"" + currentVisibleParent.name + "\"]")
-                        .classed('input-highlight', true);
-                    // Find the visible parent of each input.
-                    var visibleInputs = {};
-                    _.each(inputs, function (nodeInstance) {
-                        var resolvedNode = renderGraphInfo.getNodeByName(nodeInstance.name);
-                        if (resolvedNode === undefined) {
-                            // Node could not be found in rendered Hierarchy, which happens when
-                            // tracing inputs of a SummaryNode.
-                            return;
-                        }
-                        // Ensure node is resolved to OpNode if name collision with Metanode exists.
-                        if (resolvedNode instanceof graph.MetanodeImpl) {
-                            var resolvedNodeName = tf.graph.getStrictName(resolvedNode.name);
-                            resolvedNode = renderGraphInfo.getNodeByName(resolvedNodeName);
-                        }
-                        var visibleParent = getVisibleParent(renderGraphInfo, resolvedNode);
-                        // Append OpNode to visible parent entry.
-                        var visibleInputsEntry = visibleInputs[visibleParent.name];
-                        if (visibleInputsEntry) {
-                            visibleInputsEntry.opNodes.push(resolvedNode);
-                        }
-                        else {
-                            visibleInputs[visibleParent.name] = {
-                                visibleParent: visibleParent,
-                                opNodes: [resolvedNode]
-                            };
-                        }
-                    });
-                    // Find all parents of the start node.
-                    var startNodeParents = {};
-                    var indexedStartNodeParents = [currentVisibleParent];
-                    startNodeParents[currentVisibleParent.name] = {
-                        traced: false,
-                        index: 0,
-                        connectionEndpoints: []
-                    };
-                    var currentNode = currentVisibleParent;
-                    for (var index = 1; currentNode.name !== tf.graph.ROOT_NAME; index++) {
-                        currentNode = currentNode.parentNode;
-                        startNodeParents[currentNode.name] = {
-                            traced: false,
-                            index: index,
-                            connectionEndpoints: []
-                        };
-                        indexedStartNodeParents[index] = currentNode;
-                    }
-                    // Find first mutual parent of each input node and highlight connection.
-                    _.forOwn(visibleInputs, function (visibleParentInfo, key) {
-                        var nodeInstance = visibleParentInfo.visibleParent;
-                        // Make recursive call for each input-OpNode contained by the visible
-                        // parent.
-                        _.each(visibleParentInfo.opNodes, function (opNode) {
-                            allTracedNodes =
-                                traceAllInputsOfOpNode(renderGraphInfo, opNode, allTracedNodes);
-                        });
-                        if (nodeInstance.name !== currentVisibleParent.name) {
-                            _createVisibleTrace(nodeInstance, startNodeParents, indexedStartNodeParents);
-                        }
-                    });
-                    return allTracedNodes;
-                }
-                node_1.traceAllInputsOfOpNode = traceAllInputsOfOpNode;
-                /**
-                 * Colors the edges to connect the passed node to the start node. This is
-                 * done by:
-                 *
-                 * a) Finding the first (visible) common parent in the rendered
-                 * hierarchy.
-                 * NB: There are 2 types of connections:
-                 * 1) Direct connections between node A
-                 * and B, marked below as II,
-                 * 2) Connections from any node A to its parent, A'. Marked below as I and III.
-                 * For type 2 connection you need to know the inner-nested node, the
-                 * direct parent, and the ultimate destination of the connection.
-                 *
-                 *  A_parent      B_parent
-                 * +--------+    +---------+
-                 * |        |    |         |
-                 * |  +--+ I| II |III+--+  |
-                 * |  |A +----------\x3e+B |  |
-                 * |  +--+  |    |   +--+  |
-                 * |        |    |         |
-                 * +--------+    +---------+
-                 *
-                 *
-                 * b) Highlighting the direct connection between the parents of A and B,
-                 * called A_parent and B_parent, s.t. A_parent and B_parent are children of the
-                 * mutual parent of A and B found in a), marked above as II.
-                 *
-                 * c) Highlighting the connection from A to A_parent and B to B_parent
-                 * (through all layers of parents between A and A_parent and B and B_parent,
-                 * respectively). Marked above as I and III.
-                 *
-                 * @param nodeInstance The instance of the node to use as destination node, B.
-                 * @param startNodeParents Map of startNodeParent names to information objects
-                 * about the parent.
-                 * @param indexedStartNodeParents An array of all parents of the start node.
-                 * This is required to find the child of the mutual parent which is a parent
-                 * of the start node.
-                 * @private
-                 */
-                function _createVisibleTrace(nodeInstance, startNodeParents, indexedStartNodeParents) {
-                    var currentNode = nodeInstance;
-                    var previousNode = nodeInstance;
-                    // Ascend through parents until a mutual parent is found with the start
-                    // node.
-                    var destinationParentPairs = [];
-                    while (!startNodeParents[currentNode.name]) {
-                        if (previousNode.name !== currentNode.name) {
-                            destinationParentPairs.push([previousNode, currentNode]);
-                        }
-                        previousNode = currentNode;
-                        currentNode = currentNode.parentNode;
-                    }
-                    // Connection between nodes is drawn between the parents of each
-                    // respective node, both of which share the mutual parent.
-                    var startNodeIndex = startNodeParents[currentNode.name].index;
-                    var startNodeName = indexedStartNodeParents[Math.max(startNodeIndex - 1, 0)].name;
-                    var startNodeTopParentName = startNodeName;
-                    var targetNodeTopParentName = previousNode.name;
-                    var endNodeName = previousNode.name;
-                    d3.selectAll("[data-edge=\"" + endNodeName + "--" + startNodeName + "\"]")
-                        .classed('input-edge-highlight', true);
-                    // Trace up the parents of the input.
-                    _.each(destinationParentPairs, function (value) {
-                        var inner = value[0];
-                        var outer = value[1];
-                        var edgeSelector = "[data-edge=\"" + inner.name + "--" + startNodeTopParentName +
-                            ("~~" + outer.name + "~~OUT\"]");
-                        d3.selectAll(edgeSelector).classed('input-edge-highlight', true);
-                    });
-                    // Trace up the parents of the start node.
-                    for (var index = 1; index < startNodeIndex; index++) {
-                        var inner = indexedStartNodeParents[index - 1];
-                        var outer = indexedStartNodeParents[index];
-                        var edgeSelector = "[data-edge=\"" + targetNodeTopParentName + "~~" + outer.name +
-                            ("~~IN--" + inner.name + "\"]");
-                        d3.selectAll(edgeSelector).classed('input-edge-highlight', true);
-                    }
-                }
-                /**
-                 * Creates map { [name: string] -> Node } of all visible / rendered parents
-                 * of the nodes identified by the node names passed in.
-                 *
-                 * @param renderGraphInfo The information on the rendered graph.
-                 * @param nodeNames String array of node names.
-                 * @returns {[nodeName: string]: Node}
-                 * @private
-                 */
-                function _findVisibleParentsFromOpNodes(renderGraphInfo, nodeNames) {
-                    var visibleParents = {};
-                    _.each(nodeNames, function (nodeName) {
-                        var currentNode = renderGraphInfo.getNodeByName(nodeName);
-                        var visibleParent = getVisibleParent(renderGraphInfo, currentNode);
-                        visibleParents[visibleParent.name] = visibleParent;
-                    });
-                    return visibleParents;
-                }
-                /**
-                 * Traverse through the parents of all nodes in the list and mark each
-                 * encountered node as input-parent.
-                 * @param visibleNodes Map of input nodes, have to be visible/rendered when
-                 * called.
-                 * @private
-                 */
-                function _markParentsOfNodes(visibleNodes) {
-                    _.forOwn(visibleNodes, function (nodeInstance) {
-                        // Mark all parents of the node as input-parents.
-                        var currentNode = nodeInstance;
-                        while (currentNode.name !== tf.graph.ROOT_NAME) {
-                            var renderedElement = d3.select(".node[data-name=\"" + currentNode.name + "\"]");
-                            // Only mark the element as a parent node to an input if it is not
-                            // marked as input node itself.
-                            if (renderedElement[0][0] &&
-                                !renderedElement.classed('input-highlight') &&
-                                !renderedElement.classed('selected') &&
-                                // OpNode only parent if start node is embedded node, in which case
-                                // the OpNode should be faded as well.
-                                !renderedElement.classed('op')) {
-                                renderedElement.classed('input-parent', true);
-                            }
-                            currentNode = currentNode.parentNode;
-                        }
-                    });
-                }
-                /**
-                 * Find the parent of the passed in op node which is expanded. This is done
-                 * by going through all parents until the parent's parent is expanded, thus
-                 * finding the first unexpanded parent which is rendered on the screen.
-                 * @param renderGraphInfo The graph info object used to gain access to the
-                 * render info of the parents.
-                 * @param currentNode The node whose parent is to be found.
-                 * @returns Node
-                 */
-                function getVisibleParent(renderGraphInfo, currentNode) {
-                    var found = false;
-                    var currentParent = currentNode;
-                    while (!found) {
-                        // Get parent element, to extract name.
-                        currentNode = currentParent;
-                        currentParent = currentNode.parentNode;
-                        if (currentParent === undefined) {
-                            found = true;
-                        }
-                        else {
-                            var renderNode = renderGraphInfo.getRenderNodeByName(currentParent.name);
-                            // Found if node is rendered on the screen (renderNode truthy), and
-                            // the parent is either expanded (i.e. it is a metanode or seriesnode)
-                            // or the parent is an OpNode in which case currentNode is an embedded
-                            // node which has another OpNode as parent.
-                            if (renderNode &&
-                                (renderNode.expanded || currentParent instanceof graph.OpNodeImpl)) {
-                                found = true;
-                            }
-                        }
-                    } // Close while loop.
-                    return currentNode;
-                }
-                node_1.getVisibleParent = getVisibleParent;
-            })(node = scene.node || (scene.node = {}));
-        })(scene = graph.scene || (graph.scene = {}));
-    })(graph = tf.graph || (tf.graph = {}));
-})(tf || (tf = {})); // Close module.
-</script>
-<script>/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-var tf;
-(function (tf) {
-    var graph;
-    (function (graph) {
-        var scene;
-        (function (scene) {
-            var svgNamespace = 'http://www.w3.org/2000/svg';
-            /** Enums element class of objects in the scene */
-            scene.Class = {
-                Node: {
-                    // <g> element that contains nodes.
-                    CONTAINER: 'nodes',
-                    // <g> element that contains detail about a node.
-                    GROUP: 'node',
-                    // <g> element that contains visual elements (like rect, ellipse).
-                    SHAPE: 'nodeshape',
-                    // <*> element(s) under SHAPE that should receive color updates.
-                    COLOR_TARGET: 'nodecolortarget',
-                    // <text> element showing the node's label.
-                    LABEL: 'nodelabel',
-                    // <g> element that contains all visuals for the expand/collapse
-                    // button for expandable group nodes.
-                    BUTTON_CONTAINER: 'buttoncontainer',
-                    // <circle> element that surrounds expand/collapse buttons.
-                    BUTTON_CIRCLE: 'buttoncircle',
-                    // <path> element of the expand button.
-                    EXPAND_BUTTON: 'expandbutton',
-                    // <path> element of the collapse button.
-                    COLLAPSE_BUTTON: 'collapsebutton'
-                },
-                Edge: {
-                    CONTAINER: 'edges',
-                    GROUP: 'edge',
-                    LINE: 'edgeline',
-                    REF_LINE: 'refline',
-                    STRUCTURAL: 'structural'
-                },
-                Annotation: {
-                    OUTBOX: 'out-annotations',
-                    INBOX: 'in-annotations',
-                    GROUP: 'annotation',
-                    NODE: 'annotation-node',
-                    EDGE: 'annotation-edge',
-                    CONTROL_EDGE: 'annotation-control-edge',
-                    LABEL: 'annotation-label',
-                    ELLIPSIS: 'annotation-ellipsis'
-                },
-                Scene: {
-                    GROUP: 'scene',
-                    CORE: 'core',
-                    INEXTRACT: 'in-extract',
-                    OUTEXTRACT: 'out-extract'
-                },
-                Subscene: { GROUP: 'subscene' },
-                OPNODE: 'op',
-                METANODE: 'meta',
-                SERIESNODE: 'series',
-                BRIDGENODE: 'bridge',
-                ELLIPSISNODE: 'ellipsis'
-            };
-            ;
-            ;
-            scene.healthPillEntries = [
-                {
-                    background_color: '#CC2F2C',
-                    label: 'NaN',
-                },
-                {
-                    background_color: '#FF8D00',
-                    label: '- ∞',
-                },
-                {
-                    background_color: '#EAEAEA',
-                    label: '-',
-                },
-                {
-                    background_color: '#A5A5A5',
-                    label: '0',
-                },
-                {
-                    background_color: '#262626',
-                    label: '+',
-                },
-                {
-                    background_color: '#003ED4',
-                    label: '+ ∞',
-                },
-            ];
-            /**
-             * Helper method for fitting the graph in the svg view.
-             *
-             * @param svg The main svg.
-             * @param zoomG The svg group used for panning and zooming.
-             * @param d3zoom The zoom behavior.
-             * @param callback Called when the fitting is done.
-             */
-            function fit(svg, zoomG, d3zoom, callback) {
-                var svgRect = svg.getBoundingClientRect();
-                var sceneSize = null;
-                try {
-                    sceneSize = zoomG.getBBox();
-                    if (sceneSize.width === 0) {
-                        // There is no scene anymore. We have been detached from the dom.
-                        return;
-                    }
-                }
-                catch (e) {
-                    // Firefox produced NS_ERROR_FAILURE if we have been
-                    // detached from the dom.
-                    return;
-                }
-                var scale = 0.9 *
-                    Math.min(svgRect.width / sceneSize.width, svgRect.height / sceneSize.height, 2);
-                var params = graph.layout.PARAMS.graph;
-                var zoomEvent = d3zoom.scale(scale)
-                    .on('zoomend.fitted', function () {
-                    // Remove the listener for the zoomend event,
-                    // so we don't get called at the end of regular zoom events,
-                    // just those that fit the graph to screen.
-                    d3zoom.on('zoomend.fitted', null);
-                    callback();
-                })
-                    .translate([params.padding.paddingLeft, params.padding.paddingTop])
-                    .event;
-                d3.select(zoomG).transition().duration(500).call(zoomEvent);
-            }
-            scene.fit = fit;
-            ;
-            /**
-             * Helper method for panning the graph to center on the provided node,
-             * if the node is currently off-screen.
-             *
-             * @param nodeName The node to center the graph on
-             * @param svg The root SVG element for the graph
-             * @param zoomG The svg group used for panning and zooming.
-             * @param d3zoom The zoom behavior.
-             * @return True if the graph had to be panned to display the
-             *            provided node.
-             */
-            function panToNode(nodeName, svg, zoomG, d3zoom) {
-                var node = d3
-                    .select('[data-name="' + nodeName + '"].' + scene.Class.Node.GROUP)
-                    .node();
-                if (!node) {
-                    return false;
-                }
-                var translate = d3zoom.translate();
-                // Check if the selected node is off-screen in either
-                // X or Y dimension in either direction.
-                var nodeBox = node.getBBox();
-                var nodeCtm = node.getScreenCTM();
-                var pointTL = svg.createSVGPoint();
-                var pointBR = svg.createSVGPoint();
-                pointTL.x = nodeBox.x;
-                pointTL.y = nodeBox.y;
-                pointBR.x = nodeBox.x + nodeBox.width;
-                pointBR.y = nodeBox.y + nodeBox.height;
-                pointTL = pointTL.matrixTransform(nodeCtm);
-                pointBR = pointBR.matrixTransform(nodeCtm);
-                var isOutsideOfBounds = function (start, end, bound) {
-                    return end < 0 || start > bound;
-                };
-                var svgRect = svg.getBoundingClientRect();
-                if (isOutsideOfBounds(pointTL.x, pointBR.x, svgRect.width) ||
-                    isOutsideOfBounds(pointTL.y, pointBR.y, svgRect.height)) {
-                    // Determine the amount to transform the graph in both X and Y
-                    // dimensions in order to center the selected node. This takes into
-                    // acount the position of the node, the size of the svg scene, the
-                    // amount the scene has been scaled by through zooming, and any previous
-                    // transform already performed by this logic.
-                    var centerX = (pointTL.x + pointBR.x) / 2;
-                    var centerY = (pointTL.y + pointBR.y) / 2;
-                    var dx = ((svgRect.width / 2) - centerX);
-                    var dy = ((svgRect.height / 2) - centerY);
-                    var zoomEvent = d3zoom.translate([translate[0] + dx, translate[1] + dy])
-                        .event;
-                    d3.select(zoomG).transition().duration(500).call(zoomEvent);
-                    return true;
-                }
-                return false;
-            }
-            scene.panToNode = panToNode;
-            ;
-            /**
-             * Given a container d3 selection, select a child svg element of a given tag
-             * and class if exists or append / insert one otherwise.  If multiple children
-             * matches the tag and class name, returns only the first one.
-             *
-             * @param container
-             * @param tagName tag name.
-             * @param className (optional) Class name or a list of class names.
-             * @param before (optional) reference DOM node for insertion.
-             * @return selection of the element
-             */
-            function selectOrCreateChild(container, tagName, className, before) {
-                var child = selectChild(container, tagName, className);
-                if (!child.empty()) {
-                    return child;
-                }
-                var newElement = document.createElementNS('http://www.w3.org/2000/svg', tagName);
-                if (className instanceof Array) {
-                    for (var i = 0; i < className.length; i++) {
-                        newElement.classList.add(className[i]);
-                    }
-                }
-                else {
-                    newElement.classList.add(className);
-                }
-                if (before) {
-                    container.node().insertBefore(newElement, before);
-                }
-                else {
-                    container.node().appendChild(newElement);
-                }
-                return d3.select(newElement)
-                    .datum(container.datum());
-            }
-            scene.selectOrCreateChild = selectOrCreateChild;
-            ;
-            /**
-             * Given a container d3 selection, select a child element of a given tag and
-             * class. If multiple children matches the tag and class name, returns only
-             * the first one.
-             *
-             * @param container
-             * @param tagName tag name.
-             * @param className (optional) Class name or list of class names.
-             * @return selection of the element, or an empty selection
-             */
-            function selectChild(container, tagName, className) {
-                var children = container.node().childNodes;
-                for (var i = 0; i < children.length; i++) {
-                    var child = children[i];
-                    if (child.tagName === tagName) {
-                        if (className instanceof Array) {
-                            var hasAllClasses = true;
-                            for (var j = 0; j < className.length; j++) {
-                                hasAllClasses =
-                                    hasAllClasses && child.classList.contains(className[j]);
-                            }
-                            if (hasAllClasses) {
-                                return d3.select(child);
-                            }
-                        }
-                        else if ((!className || child.classList.contains(className))) {
-                            return d3.select(child);
-                        }
-                    }
-                }
-                return d3.select(null);
-            }
-            scene.selectChild = selectChild;
-            ;
-            /**
-             * Select or create a sceneGroup and build/update its nodes and edges.
-             *
-             * Structure Pattern:
-             *
-             * <g class='scene'>
-             *   <g class='core'>
-             *     <g class='edges'>
-             *       ... stuff from tf.graph.scene.edges.build ...
-             *     </g>
-             *     <g class='nodes'>
-             *       ... stuff from tf.graph.scene.nodes.build ...
-             *     </g>
-             *   </g>
-             *   <g class='in-extract'>
-             *     <g class='nodes'>
-             *       ... stuff from tf.graph.scene.nodes.build ...
-             *     </g>
-             *   </g>
-             *   <g class='out-extract'>
-             *     <g class='nodes'>
-             *       ... stuff from tf.graph.scene.nodes.build ...
-             *     </g>
-             *   </g>
-             * </g>
-             *
-             * @param container D3 selection of the parent.
-             * @param renderNode render node of a metanode or series node.
-             * @param sceneElement <tf-graph-scene> polymer element.
-             * @param sceneClass class attribute of the scene (default='scene').
-             */
-            function buildGroup(container, renderNode, sceneElement, sceneClass) {
-                sceneClass = sceneClass || scene.Class.Scene.GROUP;
-                var isNewSceneGroup = selectChild(container, 'g', sceneClass).empty();
-                var sceneGroup = selectOrCreateChild(container, 'g', sceneClass);
-                // core
-                var coreGroup = selectOrCreateChild(sceneGroup, 'g', scene.Class.Scene.CORE);
-                var coreNodes = _.reduce(renderNode.coreGraph.nodes(), function (nodes, name) {
-                    var node = renderNode.coreGraph.node(name);
-                    if (!node.excluded) {
-                        nodes.push(node);
-                    }
-                    return nodes;
-                }, []);
-                if (renderNode.node.type === graph.NodeType.SERIES) {
-                    // For series, we want the first item on top, so reverse the array so
-                    // the first item in the series becomes last item in the top, and thus
-                    // is rendered on the top.
-                    coreNodes.reverse();
-                }
-                // Create the layer of edges for this scene (paths).
-                scene.edge.buildGroup(coreGroup, renderNode.coreGraph, sceneElement);
-                // Create the layer of nodes for this scene (ellipses, rects etc).
-                scene.node.buildGroup(coreGroup, coreNodes, sceneElement);
-                // In-extract
-                if (renderNode.isolatedInExtract.length > 0) {
-                    var inExtractGroup = selectOrCreateChild(sceneGroup, 'g', scene.Class.Scene.INEXTRACT);
-                    scene.node.buildGroup(inExtractGroup, renderNode.isolatedInExtract, sceneElement);
-                }
-                else {
-                    selectChild(sceneGroup, 'g', scene.Class.Scene.INEXTRACT).remove();
-                }
-                // Out-extract
-                if (renderNode.isolatedOutExtract.length > 0) {
-                    var outExtractGroup = selectOrCreateChild(sceneGroup, 'g', scene.Class.Scene.OUTEXTRACT);
-                    scene.node.buildGroup(outExtractGroup, renderNode.isolatedOutExtract, sceneElement);
-                }
-                else {
-                    selectChild(sceneGroup, 'g', scene.Class.Scene.OUTEXTRACT).remove();
-                }
-                position(sceneGroup, renderNode);
-                // Fade in the scene group if it didn't already exist.
-                if (isNewSceneGroup) {
-                    sceneGroup.attr('opacity', 0).transition().attr('opacity', 1);
-                }
-                return sceneGroup;
-            }
-            scene.buildGroup = buildGroup;
-            ;
-            /**
-             * Given a scene's svg group, set  g.in-extract, g.coreGraph, g.out-extract svg
-             * groups' position relative to the scene.
-             *
-             * @param sceneGroup
-             * @param renderNode render node of a metanode or series node.
-             */
-            function position(sceneGroup, renderNode) {
-                // Translate scenes down by the label height so that when showing graphs in
-                // expanded metanodes, the graphs are below the labels.  Do not shift them
-                // down for series nodes as series nodes don't have labels inside of their
-                // bounding boxes.
-                var yTranslate = renderNode.node.type === graph.NodeType.SERIES ?
-                    0 : graph.layout.PARAMS.subscene.meta.labelHeight;
-                // core
-                translate(selectChild(sceneGroup, 'g', scene.Class.Scene.CORE), 0, yTranslate);
-                // in-extract
-                var hasInExtract = renderNode.isolatedInExtract.length > 0;
-                var hasOutExtract = renderNode.isolatedOutExtract.length > 0;
-                if (hasInExtract) {
-                    var offset = graph.layout.PARAMS.subscene.meta.extractXOffset;
-                    var inExtractX = renderNode.coreBox.width -
-                        renderNode.inExtractBox.width / 2 - renderNode.outExtractBox.width -
-                        (hasOutExtract ? offset : 0);
-                    translate(selectChild(sceneGroup, 'g', scene.Class.Scene.INEXTRACT), inExtractX, yTranslate);
-                }
-                // out-extract
-                if (hasOutExtract) {
-                    var outExtractX = renderNode.coreBox.width -
-                        renderNode.outExtractBox.width / 2;
-                    translate(selectChild(sceneGroup, 'g', scene.Class.Scene.OUTEXTRACT), outExtractX, yTranslate);
-                }
-            }
-            ;
-            /** Adds a click listener to a group that fires a graph-select event */
-            function addGraphClickListener(graphGroup, sceneElement) {
-                d3.select(graphGroup).on('click', function () {
-                    sceneElement.fire('graph-select');
-                });
-            }
-            scene.addGraphClickListener = addGraphClickListener;
-            ;
-            /** Helper for adding transform: translate(x0, y0) */
-            function translate(selection, x0, y0) {
-                // If it is already placed on the screen, make it a transition.
-                if (selection.attr('transform') != null) {
-                    selection = selection.transition('position');
-                }
-                selection.attr('transform', 'translate(' + x0 + ',' + y0 + ')');
-            }
-            scene.translate = translate;
-            ;
-            /**
-             * Helper for setting position of a svg rect
-             * @param rect rect to set position of.
-             * @param cx Center x.
-             * @param cy Center x.
-             * @param width Width to set.
-             * @param height Height to set.
-             */
-            function positionRect(rect, cx, cy, width, height) {
-                rect.transition().attr({
-                    x: cx - width / 2,
-                    y: cy - height / 2,
-                    width: width,
-                    height: height
-                });
-            }
-            scene.positionRect = positionRect;
-            ;
-            /**
-             * Helper for setting position of a svg expand/collapse button
-             * @param button container group
-             * @param renderNode the render node of the group node to position
-             *        the button on.
-             */
-            function positionButton(button, renderNode) {
-                var cx = graph.layout.computeCXPositionOfNodeShape(renderNode);
-                // Position the button in the top-right corner of the group node,
-                // with space given the draw the button inside of the corner.
-                var width = renderNode.expanded ?
-                    renderNode.width : renderNode.coreBox.width;
-                var height = renderNode.expanded ?
-                    renderNode.height : renderNode.coreBox.height;
-                var x = cx + width / 2 - 6;
-                var y = renderNode.y - height / 2 + 6;
-                // For unexpanded series nodes, the button has special placement due
-                // to the unique visuals of this group node.
-                if (renderNode.node.type === graph.NodeType.SERIES && !renderNode.expanded) {
-                    x += 10;
-                    y -= 2;
-                }
-                var translateStr = 'translate(' + x + ',' + y + ')';
-                button.selectAll('path').transition().attr('transform', translateStr);
-                button.select('circle').transition().attr({ cx: x, cy: y, r: graph.layout.PARAMS.nodeSize.meta.expandButtonRadius });
-            }
-            scene.positionButton = positionButton;
-            ;
-            /**
-             * Helper for setting position of a svg ellipse
-             * @param ellipse ellipse to set position of.
-             * @param cx Center x.
-             * @param cy Center x.
-             * @param width Width to set.
-             * @param height Height to set.
-             */
-            function positionEllipse(ellipse, cx, cy, width, height) {
-                ellipse.transition().attr({
-                    cx: cx,
-                    cy: cy,
-                    rx: width / 2,
-                    ry: height / 2
-                });
-            }
-            scene.positionEllipse = positionEllipse;
-            ;
-            /**
-             * @param {number} stat A stat for a health pill (such as mean or variance).
-             * @param {boolean} shouldRoundOnesDigit Whether to round this number to the
-             *     ones digit. Useful for say int, uint, and bool output types.
-             * @return {string} A human-friendly string representation of that stat.
-             */
-            function humanizeHealthPillStat(stat, shouldRoundOnesDigit) {
-                if (shouldRoundOnesDigit) {
-                    return stat.toFixed(0);
-                }
-                if (Math.abs(stat) >= 1) {
-                    return stat.toFixed(1);
-                }
-                return stat.toExponential(1);
-            }
-            scene.humanizeHealthPillStat = humanizeHealthPillStat;
-            /**
-             * Renders a health pill for an op atop a node.
-             */
-            function _addHealthPill(nodeGroupElement, healthPill, nodeInfo) {
-                // Check if text already exists at location.
-                d3.select(nodeGroupElement.parentNode).selectAll('.health-pill').remove();
-                if (!nodeInfo || !healthPill) {
-                    return;
-                }
-                var lastHealthPillData = healthPill.value;
-                // For now, we only visualize the 6 values that summarize counts of tensor
-                // elements of various categories: -Inf, negative, 0, positive, Inf, and NaN.
-                var lastHealthPillOverview = lastHealthPillData.slice(2, 8);
-                var totalCount = lastHealthPillData[1];
-                var healthPillWidth = 60;
-                var healthPillHeight = 10;
-                if (nodeInfo.node.type === tf.graph.NodeType.OP) {
-                    // Use a smaller health pill for op nodes (rendered as smaller ellipses).
-                    healthPillWidth /= 2;
-                    healthPillHeight /= 2;
-                }
-                var healthPillGroup = document.createElementNS(svgNamespace, 'g');
-                healthPillGroup.classList.add('health-pill');
-                // Define the gradient for the health pill.
-                var healthPillDefs = document.createElementNS(svgNamespace, 'defs');
-                healthPillGroup.appendChild(healthPillDefs);
-                var healthPillGradient = document.createElementNS(svgNamespace, 'linearGradient');
-                var healthPillGradientId = 'health-pill-gradient';
-                healthPillGradient.setAttribute('id', healthPillGradientId);
-                var titleOnHoverTextEntries = [];
-                var cumulativeCount = 0;
-                var previousOffset = '0%';
-                for (var i = 0; i < lastHealthPillOverview.length; i++) {
-                    if (!lastHealthPillOverview[i]) {
-                        // Exclude empty categories.
-                        continue;
-                    }
-                    cumulativeCount += lastHealthPillOverview[i];
-                    // Create a color interval using 2 stop elements.
-                    var stopElement0 = document.createElementNS(svgNamespace, 'stop');
-                    stopElement0.setAttribute('offset', previousOffset);
-                    stopElement0.setAttribute('stop-color', scene.healthPillEntries[i].background_color);
-                    healthPillGradient.appendChild(stopElement0);
-                    var stopElement1 = document.createElementNS(svgNamespace, 'stop');
-                    var percent = (cumulativeCount * 100 / totalCount) + '%';
-                    stopElement1.setAttribute('offset', percent);
-                    stopElement1.setAttribute('stop-color', scene.healthPillEntries[i].background_color);
-                    healthPillGradient.appendChild(stopElement1);
-                    previousOffset = percent;
-                    // Include this number in the title that appears on hover.
-                    titleOnHoverTextEntries.push(scene.healthPillEntries[i].label + ': ' + lastHealthPillOverview[i]);
-                }
-                healthPillDefs.appendChild(healthPillGradient);
-                // Create the rectangle for the health pill.
-                var rect = document.createElementNS(svgNamespace, 'rect');
-                rect.setAttribute('fill', 'url(#' + healthPillGradientId + ')');
-                rect.setAttribute('width', String(healthPillWidth));
-                rect.setAttribute('height', String(healthPillHeight));
-                healthPillGroup.appendChild(rect);
-                // Show a title with specific counts on hover.
-                var titleSvg = document.createElementNS(svgNamespace, 'title');
-                titleSvg.textContent = titleOnHoverTextEntries.join(', ');
-                healthPillGroup.appendChild(titleSvg);
-                // Center this health pill just right above the node for the op.
-                var healthPillX = nodeInfo.x - healthPillWidth / 2;
-                var healthPillY = nodeInfo.y - healthPillHeight - nodeInfo.height / 2 - 2;
-                if (nodeInfo.labelOffset < 0) {
-                    // The label is positioned above the node. Do not occlude the label.
-                    healthPillY += nodeInfo.labelOffset;
-                }
-                if (lastHealthPillOverview[2] || lastHealthPillOverview[3] ||
-                    lastHealthPillOverview[4]) {
-                    // At least 1 "non-Inf and non-NaN" value exists (a -, 0, or + value). Show
-                    // stats on tensor values.
-                    // Determine if we should display the output range as integers.
-                    var shouldRoundOnesDigit = false;
-                    var node_1 = nodeInfo.node;
-                    var attributes = node_1.attr;
-                    if (attributes && attributes.length) {
-                        // Find the attribute for output type if there is one.
-                        for (var i = 0; i < attributes.length; i++) {
-                            if (attributes[i].key === 'T') {
-                                // Note whether the output type is an integer.
-                                var outputType = attributes[i].value['type'];
-                                shouldRoundOnesDigit =
-                                    outputType && /^DT_(BOOL|INT|UINT)/.test(outputType);
-                                break;
-                            }
-                        }
-                    }
-                    var statsSvg = document.createElementNS(svgNamespace, 'text');
-                    var minString = humanizeHealthPillStat(lastHealthPillData[8], shouldRoundOnesDigit);
-                    var maxString = humanizeHealthPillStat(lastHealthPillData[9], shouldRoundOnesDigit);
-                    statsSvg.textContent = minString + ' ~ ' + maxString;
-                    statsSvg.classList.add('health-pill-stats');
-                    statsSvg.setAttribute('x', String(healthPillWidth / 2));
-                    statsSvg.setAttribute('y', '-2');
-                    healthPillGroup.appendChild(statsSvg);
-                }
-                healthPillGroup.setAttribute('transform', 'translate(' + healthPillX + ', ' + healthPillY + ')');
-                Polymer.dom(nodeGroupElement.parentNode).appendChild(healthPillGroup);
-            }
-            /**
-             * Adds health pills (which visualize tensor summaries) to a graph group.
-             * @param svgRoot The root SVG element of the graph to add heath pills to.
-             * @param nodeNamesToHealthPills An object mapping node name to health pill.
-             * @param colors A list of colors to use.
-             */
-            function addHealthPills(svgRoot, nodeNamesToHealthPills, healthPillStepIndex) {
-                if (!nodeNamesToHealthPills) {
-                    // No health pill information available.
-                    return;
-                }
-                var svgRootSelection = d3.select(svgRoot);
-                svgRootSelection.selectAll('g.nodeshape')
-                    .each(function (nodeInfo) {
-                    // Only show health pill data for this node if it is available.
-                    var healthPills = nodeNamesToHealthPills[nodeInfo.node.name];
-                    var healthPill = healthPills ? healthPills[healthPillStepIndex] : null;
-                    _addHealthPill(this, healthPill, nodeInfo);
-                });
-            }
-            scene.addHealthPills = addHealthPills;
-            ;
-        })(scene = graph.scene || (graph.scene = {}));
-    })(graph = tf.graph || (tf.graph = {}));
-})(tf || (tf = {})); // close module
-</script>
-<script>/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-var tf;
-(function (tf) {
-    var graph;
-    (function (graph_1) {
-        var template;
-        (function (template) {
-            /**
-             * Detect repeating patterns of subgraphs.
-             * Assign templateId to each subgraph if it belongs to a template.
-             * Returns clusters of similar subgraphs .
-             *
-             * @param graph
-             * @param verifyTemplate whether to run the template verification algorithm
-             * @return a dict (template id => Array of node names)
-             */
-            function detect(h, verifyTemplate) {
-                // In any particular subgraph, there are either
-                // - leaf nodes (which do not have subgraph)
-                // - metanode nodes - some of them have only one member (singular metanode)
-                //                    and some have multiple members (non-singular metanode)
-                // First, generate a nearest neighbor hash of metanode nodes.
-                var nnGroups = clusterSimilarSubgraphs(h);
-                // For each metanode, compare its subgraph (starting from shallower groups)
-                // and assign template id.
-                var templates = groupTemplateAndAssignId(nnGroups, verifyTemplate);
-                // Sort the templates by minimum level in the graph at which they appear,
-                // as this leads to optimal setting of the colors of each template for
-                // maximum differentiation.
-                return _(templates)
-                    .pairs()
-                    .sortBy(function (pair) {
-                    return pair[1].level;
-                })
-                    .map(function (pair) {
-                    return [pair[0], pair[1].nodes];
-                })
-                    .object()
-                    .value();
-            }
-            template.detect = detect;
-            ;
-            /**
-             * @return Unique string for a metanode based on depth, |V|, |E| and
-             * op type histogram.
-             */
-            function getSignature(metanode) {
-                // depth=<number> |V|=<number> |E|=<number>
-                var props = _.map({
-                    'depth': metanode.depth,
-                    '|V|': metanode.metagraph.nodes().length,
-                    '|E|': metanode.metagraph.edges().length
-                }, function (v, k) { return k + '=' + v; })
-                    .join(' ');
-                // optype1=count1,optype2=count2
-                var ops = _.map(metanode.opHistogram, function (count, op) {
-                    return op + '=' + count;
-                }).join(',');
-                return props + ' [ops] ' + ops;
-            }
-            /**
-             * Generate a nearest neighbor hash of metanodes
-             * based on depth, |V|, |E|, and opHistogram of their subgraph
-             * (excluding leaf nodes and singular metanodes).
-             * @param graph The graph
-             * @return Array of pairs of [signature,
-             *   Object with min level of the template and an Array of tf.graph.Group]
-             *   sort by ascending order of minimum depth at which metanode appears.
-             */
-            function clusterSimilarSubgraphs(h) {
-                /** a dict from metanode.signature() => Array of tf.graph.Groups */
-                var hashDict = _(h.getNodeMap()).reduce(function (hash, node, name) {
-                    if (node.type !== graph_1.NodeType.META) {
-                        return hash;
-                    }
-                    var levelOfMetaNode = name.split('/').length - 1;
-                    var signature = getSignature(node);
-                    var templateInfo = hash[signature] ||
-                        { nodes: [], level: levelOfMetaNode };
-                    hash[signature] = templateInfo;
-                    templateInfo.nodes.push(node);
-                    if (templateInfo.level > levelOfMetaNode) {
-                        templateInfo.level = levelOfMetaNode;
-                    }
-                    return hash;
-                }, {});
-                return _(hashDict)
-                    .pairs()
-                    .filter(function (pair) {
-                    return pair[1].nodes.length > 1;
-                })
-                    .sortBy(function (pair) {
-                    // sort by depth
-                    // (all members in the same nnGroup has equal depth)
-                    return pair[1].nodes[0].depth;
-                })
-                    .value();
-            }
-            function groupTemplateAndAssignId(nnGroups, verifyTemplate) {
-                // For each metanode, compare its subgraph (starting from shallower groups)
-                // and assign template id.
-                var result = {};
-                return _.reduce(nnGroups, function (templates, nnGroupPair) {
-                    var signature = nnGroupPair[0], nnGroup = nnGroupPair[1].nodes, clusters = [];
-                    nnGroup.forEach(function (metanode) {
-                        // check with each existing cluster
-                        for (var i = 0; i < clusters.length; i++) {
-                            var similar = !verifyTemplate ||
-                                isSimilarSubgraph(clusters[i].metanode.metagraph, metanode.metagraph);
-                            // if similar, just add this metanode to the cluster
-                            if (similar) {
-                                // get template from the first one
-                                metanode.templateId = clusters[i].metanode.templateId;
-                                clusters[i].members.push(metanode.name);
-                                return;
-                            }
-                        }
-                        // otherwise create a new cluster with id 'signature [count] '
-                        metanode.templateId = signature + '[' + clusters.length + ']';
-                        clusters.push({
-                            metanode: metanode,
-                            members: [metanode.name]
-                        });
-                    });
-                    clusters.forEach(function (c) {
-                        templates[c.metanode.templateId] = {
-                            level: nnGroupPair[1].level,
-                            nodes: c.members
-                        };
-                    });
-                    return templates;
-                }, result);
-            }
-            function sortNodes(names, graph, prefix) {
-                return _.sortByAll(names, function (name) {
-                    var node = graph.node(name);
-                    return node.op;
-                }, function (name) {
-                    var node = graph.node(name);
-                    return node.templateId;
-                }, function (name) {
-                    return graph.neighbors(name).length;
-                }, function (name) {
-                    return graph.predecessors(name).length;
-                }, function (name) {
-                    return graph.successors(name).length;
-                }, function (name) {
-                    return name.substr(prefix.length);
-                });
-            }
-            function isSimilarSubgraph(g1, g2) {
-                if (!tf.graph.hasSimilarDegreeSequence(g1, g2)) {
-                    return false;
-                }
-                // if we want to skip, just return true here.
-                // return true;
-                // Verify sequence by running DFS
-                var g1prefix = g1.graph().name;
-                var g2prefix = g2.graph().name;
-                var visited1 = {};
-                var visited2 = {};
-                var stack = [];
-                /**
-                 * push sources or successors into the stack
-                 * if the visiting pattern has been similar.
-                 */
-                function stackPushIfNotDifferent(n1, n2) {
-                    var sub1 = n1.substr(g1prefix.length), sub2 = n2.substr(g2prefix.length);
-                    /* tslint:disable */
-                    if (visited1[sub1] ^ visited2[sub1]) {
-                        console.warn('different visit pattern', '[' + g1prefix + ']', sub1, '[' + g2prefix + ']', sub2);
-                        return true;
-                    }
-                    /* tslint:enable */
-                    if (!visited1[sub1]) {
-                        visited1[sub1] = visited2[sub2] = true;
-                        stack.push({ n1: n1, n2: n2 });
-                    }
-                    return false;
-                }
-                // check if have same # of sources then sort and push
-                var sources1 = g1.sources();
-                var sources2 = g2.sources();
-                if (sources1.length !== sources2.length) {
-                    /* tslint:disable */
-                    console.log('different source length');
-                    /* tslint:enable */
-                    return false;
-                }
-                sources1 = sortNodes(sources1, g1, g1prefix);
-                sources2 = sortNodes(sources2, g2, g2prefix);
-                for (var i = 0; i < sources1.length; i++) {
-                    var different = stackPushIfNotDifferent(sources1[i], sources2[i]);
-                    if (different) {
-                        return false;
-                    }
-                }
-                while (stack.length > 0) {
-                    var cur = stack.pop();
-                    // check node
-                    var similar = isSimilarNode(g1.node(cur.n1), g2.node(cur.n2));
-                    if (!similar) {
-                        return false;
-                    }
-                    // check if have same # of successors then sort and push
-                    var succ1 = g1.successors(cur.n1), succ2 = g2.successors(cur.n2);
-                    if (succ1.length !== succ2.length) {
-                        /* tslint:disable */
-                        console.log('# of successors mismatch', succ1, succ2);
-                        /* tslint:enable */
-                        return false;
-                    }
-                    succ1 = sortNodes(succ1, g1, g1prefix);
-                    succ2 = sortNodes(succ2, g2, g2prefix);
-                    for (var j = 0; j < succ1.length; j++) {
-                        var different = stackPushIfNotDifferent(succ1[j], succ2[j]);
-                        if (different) {
-                            return false;
-                        }
-                    }
-                }
-                return true;
-            }
-            /**
-             * Returns if two nodes have identical structure.
-             */
-            function isSimilarNode(n1, n2) {
-                if (n1.type === graph_1.NodeType.META) {
-                    // compare metanode
-                    var metanode1 = n1;
-                    var metanode2 = n2;
-                    return metanode1.templateId && metanode2.templateId &&
-                        metanode1.templateId === metanode2.templateId;
-                }
-                else if (n1.type === graph_1.NodeType.OP && n2.type === graph_1.NodeType.OP) {
-                    // compare leaf node
-                    return n1.op === n2.op;
-                }
-                else if (n1.type === graph_1.NodeType.SERIES && n2.type === graph_1.NodeType.SERIES) {
-                    // compare series node sizes and operations
-                    // (only need to check one op as all op nodes are identical in series)
-                    var sn1 = n1;
-                    var sn2 = n2;
-                    var seriesnode1Count = sn1.metagraph.nodeCount();
-                    return (seriesnode1Count === sn2.metagraph.nodeCount() &&
-                        (seriesnode1Count === 0 ||
-                            (sn1.metagraph.node(sn1.metagraph.nodes()[0]).op ===
-                                sn2.metagraph.node(sn2.metagraph.nodes()[0]).op)));
-                }
-                return false;
-            }
-        })(template = graph_1.template || (graph_1.template = {}));
-    })(graph = tf.graph || (tf.graph = {}));
-})(tf || (tf = {}));
-</script>
-<script>/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-/**
- * @fileoverview Utility functions for the tensorflow graph visualizer.
- */
-var tf;
-(function (tf) {
-    var graph;
-    (function (graph) {
-        var util;
-        (function (util) {
-            /**
-             * Recommended delay (ms) when running an expensive task asynchronously
-             * that gives enough time for the progress bar to update its UI.
-             */
-            var ASYNC_TASK_DELAY = 20;
-            function time(msg, task) {
-                var start = Date.now();
-                var result = task();
-                /* tslint:disable */
-                console.log(msg, ':', Date.now() - start, 'ms');
-                /* tslint:enable */
-                return result;
-            }
-            util.time = time;
-            /**
-             * Creates a tracker that sets the progress property of the
-             * provided polymer component. The provided component must have
-             * a property called 'progress' that is not read-only. The progress
-             * property is an object with a numerical 'value' property and a
-             * string 'msg' property.
-             */
-            function getTracker(polymerComponent) {
-                return {
-                    setMessage: function (msg) {
-                        polymerComponent.set('progress', { value: polymerComponent.progress.value, msg: msg });
-                    },
-                    updateProgress: function (value) {
-                        polymerComponent.set('progress', {
-                            value: polymerComponent.progress.value + value,
-                            msg: polymerComponent.progress.msg
-                        });
-                    },
-                    reportError: function (msg, err) {
-                        // Log the stack trace in the console.
-                        console.error(err.stack);
-                        // And send a user-friendly message to the UI.
-                        polymerComponent.set('progress', { value: polymerComponent.progress.value, msg: msg, error: true });
-                    },
-                };
-            }
-            util.getTracker = getTracker;
-            /**
-             * Creates a tracker for a subtask given the parent tracker, the total
-             * progress
-             * of the subtask and the subtask message. The parent task should pass a
-             * subtracker to its subtasks. The subtask reports its own progress which
-             * becames relative to the main task.
-             */
-            function getSubtaskTracker(parentTracker, impactOnTotalProgress, subtaskMsg) {
-                return {
-                    setMessage: function (progressMsg) {
-                        // The parent should show a concatenation of its message along with
-                        // its subtask tracker message.
-                        parentTracker.setMessage(subtaskMsg + ': ' + progressMsg);
-                    },
-                    updateProgress: function (incrementValue) {
-                        // Update the parent progress relative to the child progress.
-                        // For example, if the sub-task progresses by 30%, and the impact on the
-                        // total progress is 50%, then the task progresses by 30% * 50% = 15%.
-                        parentTracker.updateProgress(incrementValue * impactOnTotalProgress / 100);
-                    },
-                    reportError: function (msg, err) {
-                        // The parent should show a concatenation of its message along with
-                        // its subtask error message.
-                        parentTracker.reportError(subtaskMsg + ': ' + msg, err);
-                    }
-                };
-            }
-            util.getSubtaskTracker = getSubtaskTracker;
-            /**
-             * Runs an expensive task and return the result.
-             */
-            function runTask(msg, incProgressValue, task, tracker) {
-                // Update the progress message to say the current running task.
-                tracker.setMessage(msg);
-                // Run the expensive task with a delay that gives enough time for the
-                // UI to update.
-                try {
-                    var result = tf.graph.util.time(msg, task);
-                    // Update the progress value.
-                    tracker.updateProgress(incProgressValue);
-                    // Return the result to be used by other tasks.
-                    return result;
-                }
-                catch (e) {
-                    // Errors that happen inside asynchronous tasks are
-                    // reported to the tracker using a user-friendly message.
-                    tracker.reportError('Failed ' + msg, e);
-                }
-            }
-            util.runTask = runTask;
-            /**
-             * Runs an expensive task asynchronously and returns a promise of the result.
-             */
-            function runAsyncTask(msg, incProgressValue, task, tracker) {
-                return new Promise(function (resolve, reject) {
-                    // Update the progress message to say the current running task.
-                    tracker.setMessage(msg);
-                    // Run the expensive task with a delay that gives enough time for the
-                    // UI to update.
-                    setTimeout(function () {
-                        try {
-                            var result = tf.graph.util.time(msg, task);
-                            // Update the progress value.
-                            tracker.updateProgress(incProgressValue);
-                            // Return the result to be used by other tasks.
-                            resolve(result);
-                        }
-                        catch (e) {
-                            // Errors that happen inside asynchronous tasks are
-                            // reported to the tracker using a user-friendly message.
-                            tracker.reportError('Failed ' + msg, e);
-                        }
-                    }, ASYNC_TASK_DELAY);
-                });
-            }
-            util.runAsyncTask = runAsyncTask;
-            /**
-             * Asynchronously runs an expensive task that returns a promise. Updates the
-             * tracker's progress after the promise resolves. Returns a new promise that
-             * resolves after the progress is updated.
-             */
-            function runAsyncPromiseTask(msg, incProgressValue, task, tracker) {
-                return new Promise(function (resolve, reject) {
-                    var handleError = function (e) {
-                        // Errors that happen inside asynchronous tasks are
-                        // reported to the tracker using a user-friendly message.
-                        tracker.reportError('Failed ' + msg, e);
-                        reject(e);
-                    };
-                    // Update the progress message to say the current running task.
-                    tracker.setMessage(msg);
-                    // Run the expensive task with a delay that gives enough time for the
-                    // UI to update.
-                    setTimeout(function () {
-                        try {
-                            var start_1 = Date.now();
-                            task()
-                                .then(function (value) {
-                                /* tslint:disable */
-                                console.log(msg, ':', Date.now() - start_1, 'ms');
-                                // Update the progress value.
-                                tracker.updateProgress(incProgressValue);
-                                // Return the result to be used by other tasks.
-                                resolve(value);
-                            })
-                                .catch(handleError);
-                        }
-                        catch (e) {
-                            handleError(e);
-                        }
-                    }, ASYNC_TASK_DELAY);
-                });
-            }
-            util.runAsyncPromiseTask = runAsyncPromiseTask;
-            /**
-             * Returns a query selector with escaped special characters that are not
-             * allowed in a query selector.
-             */
-            function escapeQuerySelector(querySelector) {
-                return querySelector.replace(/([:.\[\],/\\\(\)])/g, '\\$1');
-            }
-            util.escapeQuerySelector = escapeQuerySelector;
-            // For unit conversion.
-            util.MEMORY_UNITS = [
-                // Atomic unit.
-                { symbol: 'B' },
-                // numUnits specifies how many previous units this unit contains.
-                { symbol: 'KB', numUnits: 1024 }, { symbol: 'MB', numUnits: 1024 },
-                { symbol: 'GB', numUnits: 1024 }, { symbol: 'TB', numUnits: 1024 },
-                { symbol: 'PB', numUnits: 1024 }
-            ];
-            util.TIME_UNITS = [
-                // Atomic unit. Finest granularity in TensorFlow stat collection.
-                { symbol: 'µs' },
-                // numUnits specifies how many previous units this unit contains.
-                { symbol: 'ms', numUnits: 1000 }, { symbol: 's', numUnits: 1000 },
-                { symbol: 'min', numUnits: 60 }, { symbol: 'hr', numUnits: 60 },
-                { symbol: 'days', numUnits: 24 }
-            ];
-            /**
-             * Returns the human readable version of the unit.
-             * (e.g. 1.35 GB, 23 MB, 34 ms, 6.53 min etc).
-             */
-            function convertUnitsToHumanReadable(value, units, unitIndex) {
-                unitIndex = unitIndex == null ? 0 : unitIndex;
-                if (unitIndex + 1 < units.length &&
-                    value >= units[unitIndex + 1].numUnits) {
-                    return tf.graph.util.convertUnitsToHumanReadable(value / units[unitIndex + 1].numUnits, units, unitIndex + 1);
-                }
-                // toPrecision() has the tendency to return a number in scientific
-                // notation and (number - 0) brings it back to normal notation.
-                return (value.toPrecision(3) - 0) + ' ' + units[unitIndex].symbol;
-            }
-            util.convertUnitsToHumanReadable = convertUnitsToHumanReadable;
-            function hasDisplayableNodeStats(stats) {
-                if (stats &&
-                    (stats.totalBytes > 0 || stats.totalMicros > 0 || stats.outputSize)) {
-                    return true;
-                }
-                return false;
-            }
-            util.hasDisplayableNodeStats = hasDisplayableNodeStats;
-            /**
-             * Given a list of strings, it returns a new list of strings with the longest
-             * common prefix removed. If the common prefix is one of the strings in the
-             * list, it returns the original strings.
-             */
-            function removeCommonPrefix(strings) {
-                if (strings.length < 2) {
-                    return strings;
-                }
-                var index = 0;
-                var largestIndex = 0;
-                // Find the shortest name across all strings.
-                var minLength = _.min(_.map(strings, function (str) { return str.length; }));
-                var _loop_1 = function () {
-                    index++;
-                    var prefixes = _.map(strings, function (str) { return str.substring(0, index); });
-                    var allTheSame = prefixes.every(function (prefix, i) {
-                        return (i === 0 ? true : prefix === prefixes[i - 1]);
-                    });
-                    if (allTheSame) {
-                        if (index >= minLength) {
-                            return { value: strings };
-                        }
-                        largestIndex = index;
-                    }
-                    else {
-                        return "break";
-                    }
-                };
-                while (true) {
-                    var state_1 = _loop_1();
-                    if (typeof state_1 === "object")
-                        return state_1.value;
-                    if (state_1 === "break")
-                        break;
-                }
-                return _.map(strings, function (str) { return str.substring(largestIndex); });
-            }
-            util.removeCommonPrefix = removeCommonPrefix;
-            /**
-             * Given a queryString, aka ?foo=1&bar=2, return the object representation.
-             */
-            function getQueryParams(queryString) {
-                if (queryString.charAt(0) === '?') {
-                    queryString = queryString.slice(1);
-                }
-                var queryParams = _.chain(queryString.split('&'))
-                    .map(function (item) {
-                    if (item) {
-                        return item.split('=');
-                    }
-                })
-                    .compact()
-                    .value();
-                return _.object(queryParams);
-            }
-            util.getQueryParams = getQueryParams;
-        })(util = graph.util || (graph.util = {}));
-    })(graph = tf.graph || (tf.graph = {}));
-})(tf || (tf = {}));
-</script>
-<script>/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-var tf;
-(function (tf) {
-    var scene;
-    (function (scene) {
-        /** Show minimap when the viewpoint area is less than X% of the whole area. */
-        var FRAC_VIEWPOINT_AREA = 0.8;
-        var Minimap = (function () {
-            /**
-             * Constructs a new minimap.
-             *
-             * @param svg The main svg element.
-             * @param zoomG The svg group used for panning and zooming the main svg.
-             * @param mainZoom The main zoom behavior.
-             * @param minimap The minimap container.
-             * @param maxWandH The maximum width/height for the minimap.
-             * @param labelPadding Padding in pixels due to the main graph labels.
-             */
-            function Minimap(svg, zoomG, mainZoom, minimap, maxWandH, labelPadding) {
-                var _this = this;
-                this.svg = svg;
-                this.labelPadding = labelPadding;
-                this.zoomG = zoomG;
-                this.mainZoom = mainZoom;
-                this.maxWandH = maxWandH;
-                var $minimap = d3.select(minimap);
-                // The minimap will have 2 main components: the canvas showing the content
-                // and an svg showing a rectangle of the currently zoomed/panned viewpoint.
-                var $minimapSvg = $minimap.select('svg');
-                // Make the viewpoint rectangle draggable.
-                var $viewpoint = $minimapSvg.select('rect');
-                var dragmove = function (d) {
-                    _this.viewpointCoord.x = d3.event.x;
-                    _this.viewpointCoord.y = d3.event.y;
-                    _this.updateViewpoint();
-                };
-                this.viewpointCoord = { x: 0, y: 0 };
-                var drag = d3.behavior.drag().origin(Object).on('drag', dragmove);
-                $viewpoint.datum(this.viewpointCoord).call(drag);
-                // Make the minimap clickable.
-                $minimapSvg.on('click', function () {
-                    if (d3.event.defaultPrevented) {
-                        // This click was part of a drag event, so suppress it.
-                        return;
-                    }
-                    // Update the coordinates of the viewpoint.
-                    var width = Number($viewpoint.attr('width'));
-                    var height = Number($viewpoint.attr('height'));
-                    var clickCoords = d3.mouse($minimapSvg.node());
-                    _this.viewpointCoord.x = clickCoords[0] - width / 2;
-                    _this.viewpointCoord.y = clickCoords[1] - height / 2;
-                    _this.updateViewpoint();
-                });
-                this.viewpoint = $viewpoint.node();
-                this.minimapSvg = $minimapSvg.node();
-                this.minimap = minimap;
-                this.canvas = $minimap.select('canvas.first').node();
-                this.canvasBuffer =
-                    $minimap.select('canvas.second').node();
-                this.downloadCanvas =
-                    $minimap.select('canvas.download').node();
-                d3.select(this.downloadCanvas).style('display', 'none');
-                this.update();
-            }
-            /**
-             * Updates the position and the size of the viewpoint rectangle.
-             * It also notifies the main svg about the new panned position.
-             */
-            Minimap.prototype.updateViewpoint = function () {
-                // Update the coordinates of the viewpoint rectangle.
-                d3.select(this.viewpoint)
-                    .attr('x', this.viewpointCoord.x)
-                    .attr('y', this.viewpointCoord.y);
-                // Update the translation vector of the main svg to reflect the
-                // new viewpoint.
-                var mainX = -this.viewpointCoord.x * this.scaleMain / this.scaleMinimap;
-                var mainY = -this.viewpointCoord.y * this.scaleMain / this.scaleMinimap;
-                var zoomEvent = this.mainZoom.translate([mainX, mainY]).event;
-                d3.select(this.zoomG).call(zoomEvent);
-            };
-            /**
-             * Redraws the minimap. Should be called whenever the main svg
-             * was updated (e.g. when a node was expanded).
-             */
-            Minimap.prototype.update = function () {
-                var _this = this;
-                var sceneSize = null;
-                try {
-                    // Get the size of the entire scene.
-                    sceneSize = this.zoomG.getBBox();
-                    if (sceneSize.width === 0) {
-                        // There is no scene anymore. We have been detached from the dom.
-                        return;
-                    }
-                }
-                catch (e) {
-                    // Firefox produced NS_ERROR_FAILURE if we have been
-                    // detached from the dom.
-                    return;
-                }
-                var $download = d3.select('#graphdownload');
-                this.download = $download.node();
-                $download.on('click', function (d) {
-                    _this.download.href = _this.downloadCanvas.toDataURL('image/png');
-                });
-                var $svg = d3.select(this.svg);
-                // Read all the style rules in the document and embed them into the svg.
-                // The svg needs to be self contained, i.e. all the style rules need to be
-                // embedded so the canvas output matches the origin.
-                var stylesText = '';
-                for (var k = 0; k < document.styleSheets.length; k++) {
-                    try {
-                        var cssRules = document.styleSheets[k].cssRules ||
-                            document.styleSheets[k].rules;
-                        if (cssRules == null) {
-                            continue;
-                        }
-                        for (var i = 0; i < cssRules.length; i++) {
-                            // Remove tf-* selectors from the styles.
-                            stylesText +=
-                                cssRules[i].cssText.replace(/ ?tf-[\w-]+ ?/g, '') + '\n';
-                        }
-                    }
-                    catch (e) {
-                        if (e.name !== 'SecurityError') {
-                            throw e;
-                        }
-                    }
-                }
-                // Temporarily add the css rules to the main svg.
-                var svgStyle = $svg.append('style');
-                svgStyle.text(stylesText);
-                // Temporarily remove the zoom/pan transform from the main svg since we
-                // want the minimap to show a zoomed-out and centered view.
-                var $zoomG = d3.select(this.zoomG);
-                var zoomTransform = $zoomG.attr('transform');
-                $zoomG.attr('transform', null);
-                // Since we add padding, account for that here.
-                sceneSize.height += this.labelPadding * 2;
-                sceneSize.width += this.labelPadding * 2;
-                // Temporarily assign an explicit width/height to the main svg, since
-                // it doesn't have one (uses flex-box), but we need it for the canvas
-                // to work.
-                $svg.attr({
-                    width: sceneSize.width,
-                    height: sceneSize.height,
-                });
-                // Since the content inside the svg changed (e.g. a node was expanded),
-                // the aspect ratio have also changed. Thus, we need to update the scale
-                // factor of the minimap. The scale factor is determined such that both
-                // the width and height of the minimap are <= maximum specified w/h.
-                this.scaleMinimap =
-                    this.maxWandH / Math.max(sceneSize.width, sceneSize.height);
-                this.minimapSize = {
-                    width: sceneSize.width * this.scaleMinimap,
-                    height: sceneSize.height * this.scaleMinimap
-                };
-                // Update the size of the minimap's svg, the buffer canvas and the
-                // viewpoint rect.
-                d3.select(this.minimapSvg).attr(this.minimapSize);
-                d3.select(this.canvasBuffer).attr(this.minimapSize);
-                // Download canvas width and height are multiples of the style width and
-                // height in order to increase pixel density of the PNG for clarity.
-                d3.select(this.downloadCanvas).style({ width: sceneSize.width, height: sceneSize.height });
-                d3.select(this.downloadCanvas).attr({ width: sceneSize.width * 3, height: sceneSize.height * 3 });
-                if (this.translate != null && this.zoom != null) {
-                    // Update the viewpoint rectangle shape since the aspect ratio of the
-                    // map has changed.
-                    requestAnimationFrame(function () { return _this.zoom(); });
-                }
-                // Serialize the main svg to a string which will be used as the rendering
-                // content for the canvas.
-                var svgXml = (new XMLSerializer()).serializeToString(this.svg);
-                // Now that the svg is serialized for rendering, remove the temporarily
-                // assigned styles, explicit width and height and bring back the pan/zoom
-                // transform.
-                svgStyle.remove();
-                $svg.attr({
-                    width: null,
-                    height: null
-                });
-                $zoomG.attr('transform', zoomTransform);
-                var image = new Image();
-                image.onload = function () {
-                    // Draw the svg content onto the buffer canvas.
-                    var context = _this.canvasBuffer.getContext('2d');
-                    context.clearRect(0, 0, _this.canvasBuffer.width, _this.canvasBuffer.height);
-                    context.drawImage(image, 0, 0, _this.minimapSize.width, _this.minimapSize.height);
-                    requestAnimationFrame(function () {
-                        // Hide the old canvas and show the new buffer canvas.
-                        d3.select(_this.canvasBuffer).style('display', null);
-                        d3.select(_this.canvas).style('display', 'none');
-                        // Swap the two canvases.
-                        _a = [_this.canvasBuffer, _this.canvas], _this.canvas = _a[0], _this.canvasBuffer = _a[1];
-                        var _a;
-                    });
-                    var downloadContext = _this.downloadCanvas.getContext('2d');
-                    downloadContext.clearRect(0, 0, _this.downloadCanvas.width, _this.downloadCanvas.height);
-                    downloadContext.drawImage(image, 0, 0, _this.downloadCanvas.width, _this.downloadCanvas.height);
-                };
-                image.onerror = function () {
-                    var blob = new Blob([svgXml], { type: 'image/svg+xml;charset=utf-8' });
-                    image.src = URL.createObjectURL(blob);
-                };
-                image.src =
-                    'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svgXml);
-            };
-            /**
-             * Handles changes in zooming/panning. Should be called from the main svg
-             * to notify that a zoom/pan was performed and this minimap will update it's
-             * viewpoint rectangle.
-             *
-             * @param translate The translate vector, or none to use the last used one.
-             * @param scale The scaling factor, or none to use the last used one.
-             */
-            Minimap.prototype.zoom = function (translate, scale) {
-                if (this.scaleMinimap == null) {
-                    // Scene is not ready yet.
-                    return;
-                }
-                // Update the new translate and scale params, only if specified.
-                this.translate = translate || this.translate;
-                this.scaleMain = scale || this.scaleMain;
-                // Update the location of the viewpoint rectangle.
-                var svgRect = this.svg.getBoundingClientRect();
-                var $viewpoint = d3.select(this.viewpoint);
-                this.viewpointCoord.x = -this.translate[0] * this.scaleMinimap /
-                    this.scaleMain;
-                this.viewpointCoord.y = -this.translate[1] * this.scaleMinimap /
-                    this.scaleMain;
-                var viewpointWidth = svgRect.width * this.scaleMinimap / this.scaleMain;
-                var viewpointHeight = svgRect.height * this.scaleMinimap / this.scaleMain;
-                $viewpoint.attr({
-                    x: this.viewpointCoord.x,
-                    y: this.viewpointCoord.y,
-                    width: viewpointWidth,
-                    height: viewpointHeight
-                });
-                // Show/hide the minimap depending on the viewpoint area as fraction of the
-                // whole minimap.
-                var mapWidth = this.minimapSize.width;
-                var mapHeight = this.minimapSize.height;
-                var x = this.viewpointCoord.x;
-                var y = this.viewpointCoord.y;
-                var w = Math.min(Math.max(0, x + viewpointWidth), mapWidth) -
-                    Math.min(Math.max(0, x), mapWidth);
-                var h = Math.min(Math.max(0, y + viewpointHeight), mapHeight) -
-                    Math.min(Math.max(0, y), mapHeight);
-                var fracIntersect = (w * h) / (mapWidth * mapHeight);
-                if (fracIntersect < FRAC_VIEWPOINT_AREA) {
-                    this.minimap.classList.remove('hidden');
-                }
-                else {
-                    this.minimap.classList.add('hidden');
-                }
-            };
-            return Minimap;
-        }());
-        scene.Minimap = Minimap;
-    })(scene = tf.scene || (tf.scene = {}));
-})(tf || (tf = {})); // close module tf.scene
-</script>
-
-<dom-module id="tf-graph-minimap" assetpath="../tf-graph/">
-<template>
-<style>
-:host {
-  background-color:white;
-  transition: opacity .3s linear;
-  pointer-events: auto;
-}
-
-:host.hidden {
-  opacity: 0;
-  pointer-events: none;
-}
-
-canvas {
-  border: 1px solid #999;
-}
-
-rect {
-  fill: white;
-  stroke: #111111;
-  stroke-width: 1px;
-  fill-opacity: 0;
-  filter: url("#minimapDropShadow");
-  cursor: move;
-}
-
-svg {
-  position: absolute;
-}
-</style>
-<svg>
-  <defs>
-    <filter id="minimapDropShadow" x="-20%" y="-20%" width="150%" height="150%">
-      <feOffset result="offOut" in="SourceGraphic" dx="1" dy="1"></feOffset>
-      <feColorMatrix result="matrixOut" in="offOut" type="matrix" values="0.1 0 0 0 0 0 0.1 0 0 0 0 0 0.1 0 0 0 0 0 0.5 0"></feColorMatrix>
-      <feGaussianBlur result="blurOut" in="matrixOut" stdDeviation="2"></feGaussianBlur>
-      <feBlend in="SourceGraphic" in2="blurOut" mode="normal"></feBlend>
-    </filter>
-  </defs>
-  <rect></rect>
-</svg>
-<canvas class="first"></canvas>
-
-<canvas class="second"></canvas>
-<canvas class="download"></canvas>
-</template>
-<script>
-Polymer({
-  is: 'tf-graph-minimap',
-
-  /**
-   * Initializes the minimap and returns a minimap object to notify when
-   * things update.
-   *
-   * @param svg The main svg element.
-   * @param zoomG The svg group used for panning and zooming the main svg.
-   * @param mainZoom The main zoom behavior.
-   * @param maxWandH The maximum width/height for the minimap.
-   * @param labelPadding Padding in pixels due to the main graph labels.
-   */
-  init: function(svg, zoomG, mainZoom, maxWAndH, labelPadding) {
-    return new tf.scene.Minimap(svg, zoomG, mainZoom, this, maxWAndH,
-        labelPadding);
-  }
-});
-</script>
-</dom-module>
-
-<dom-module id="tf-graph-scene" assetpath="../tf-graph/">
-<template>
-<style>
-:host {
-  display: flex;
-  width: 100%;
-  font-size: 20px;
-}
-
-::content #svg {
-  overflow: hidden;
-  flex: 1;
-  height: 100%;
-  width: 100%;
-}
-
-::content #hidden {
-  position: fixed;
-  top: 0px;
-  visibility: hidden;
-}
-
-/* --- Node and annotation-node for Metanode --- */
-
-::content .meta > .nodeshape > rect,
-::content .meta > .annotation-node > rect {
-  cursor: pointer;
-  fill: hsl(0, 0%, 70%);
-}
-
-::content .node.meta.highlighted > .nodeshape > rect,
-::content .node.meta.highlighted > .annotation-node > rect {
-  stroke-width: 2;
-}
-
-::content .annotation.meta.highlighted > .nodeshape > rect,
-::content .annotation.meta.highlighted > .annotation-node > rect {
-  stroke-width: 1;
-}
-
-::content .meta.selected > .nodeshape > rect,
-::content .meta.selected > .annotation-node > rect {
-  stroke: red;
-  stroke-width: 2;
-}
-
-::content .node.meta.selected.expanded > .nodeshape > rect,
-::content .node.meta.selected.expanded > .annotation-node > rect {
-  stroke: red;
-  stroke-width: 3;
-}
-
-::content .annotation.meta.selected > .nodeshape > rect,
-::content .annotation.meta.selected > .annotation-node > rect {
-  stroke: red;
-  stroke-width: 2;
-}
-
-::content .node.meta.selected.expanded.highlighted > .nodeshape > rect,
-::content .node.meta.selected.expanded.highlighted > .annotation-node > rect {
-  stroke: red;
-  stroke-width: 4;
-}
-
-::content .faded,
-::content .faded rect,
-::content .faded ellipse,
-::content .faded path,
-::content .faded use,
-::content #rectHatch line,
-::content #ellipseHatch line {
-  color: #e0d4b3 !important;
-  fill: white;
-  stroke: #e0d4b3 !important;
-}
-
-
-::content .faded path {
-  stroke-width: 1px !important;
-}
-
-::content .faded rect {
-  fill: url("#rectHatch") !important;
-}
-
-::content .faded ellipse,
-::content .faded use {
-  fill: url("#ellipseHatch") !important;
-}
-
-::content .faded text {
-  opacity: 0;
-}
-
-/* Rules used for input-tracing. */
-::content .input-highlight > * > rect,
-::content .input-highlight > * > ellipse,
-::content .input-highlight > * > use
-{
-  fill: white;
-  stroke: #ff9800 !important;
-}
-
-/*  - Faded non-input styling */
-::content .non-input > * > rect,
-::content .non-input > * > ellipse,
-::content .non-input > * > use,
-/* For Const nodes. */
-::content .non-input > * > .constant:not([class*="input-highlight"]) >
-  .annotation-node > ellipse,
-/* For styling of annotation nodes of non-input nodes. */
-::content .non-input > g > .annotation > .annotation-node > rect {
-  stroke: #e0d4b3 !important;
-  stroke-width: inherit;
-  stroke-dasharray: inherit;
-}
-
-
-::content .non-input path {
-  visibility: hidden;
-}
-
-::content .non-input > .nodeshape > rect,
-::content .non-input > .annotation-node > rect,
-/* For styling of annotation nodes of non-input nodes. */
-::content .non-input > g > .annotation > .annotation-node > rect
-{
-  fill: url("#rectHatch") !important;
-}
-
-::content .non-input ellipse,
-::content .non-input use {
-  fill: url("#ellipseHatch") !important;
-}
-
-::content .non-input > text {
-  opacity: 0;
-}
-
-::content .non-input .annotation > .annotation-edge {
-  marker-end: url("#annotation-arrowhead-faded");
-}
-
-::content .non-input .annotation > .annotation-edge.refline {
-  marker-start: url("#ref-annotation-arrowhead-faded");
-}
-
-/* Input edges. */
-::content .input-edge-highlight > text {
-  fill: black !important;
-}
-::content .input-edge-highlight > path,
-::content .input-highlight > .in-annotations > .annotation > .annotation-edge,
-::content .input-highlight-selected > .in-annotations > .annotation >
-.annotation-edge {
-  stroke: #999 !important;
-}
-
-/* Non-input edges. */
-::content .non-input-edge-highlight,
-::content .non-input > g > .annotation > path,
-/* Annotation styles (label and edges respectively). */
-::content .non-input > g >
-.annotation:not(.input-highlight):not(.input-highlight-selected) >
-.annotation-label
-/*.annotation-edge*/
-{
-  visibility: hidden;
-}
-
-/* --- Op Node --- */
-
-::content .op > .nodeshape > ellipse,
-::content .op > .annotation-node > ellipse {
-  cursor: pointer;
-  fill: #fff;
-  stroke: #ccc;
-}
-
-::content .op.selected > .nodeshape > ellipse,
-::content .op.selected > .annotation-node > ellipse {
-  stroke: red;
-  stroke-width: 2;
-}
-
-::content .op.highlighted > .nodeshape > ellipse,
-::content .op.highlighted > .annotation-node > ellipse {
-  stroke-width: 2;
-}
-
-/* --- Series Node --- */
-
-/* By default, don't show the series background <rect>. */
-::content .series > .nodeshape > rect {
-  fill: hsl(0, 0%, 70%);
-  fill-opacity: 0;
-  stroke-dasharray: 5, 5;
-  stroke-opacity: 0;
-  cursor: pointer;
-}
-
-/* Once expanded, show the series background <rect> and hide the <use>. */
-::content .series.expanded > .nodeshape > rect {
-  fill-opacity: 0.15;
-  stroke: hsl(0, 0%, 70%);
-  stroke-opacity: 1;
-}
-::content .series.expanded > .nodeshape > use {
-  visibility: hidden;
-}
-
-/**
- * TODO(jimbo): Simplify this by applying a stable class name to all <g>
- * elements that currently have either the nodeshape or annotation-node classes.
- */
-::content .series > .nodeshape > use ,
-::content .series > .annotation-node > use {
-  stroke: #ccc;
-}
-::content .series.highlighted > .nodeshape > use ,
-::content .series.highlighted > .annotation-node > use {
-  stroke-width: 2;
-}
-::content .series.selected > .nodeshape > use ,
-::content .series.selected > .annotation-node > use {
-  stroke: red;
-  stroke-width: 2;
-}
-
-::content .series.selected > .nodeshape > rect {
-  stroke: red;
-  stroke-width: 2;
-}
-
-::content .annotation.series.selected > .annotation-node > use {
-  stroke: red;
-  stroke-width: 2;
-}
-
-/* --- Bridge Node --- */
-::content .bridge > .nodeshape > rect {
-  stroke: #f0f;
-  opacity: 0.2;
-  display: none;
-}
-
-/* --- Structural Elements --- */
-::content .edge > path.edgeline.structural {
-  stroke: #f0f;
-  opacity: 0.2;
-  display: none;
-}
-
-/* --- Series Nodes --- */
-
-/* Hide the rect for a series' annotation. */
-::content .series > .annotation-node > rect {
-  display: none;
-}
-
-/* --- Node label --- */
-
-
-::content .node > text.nodelabel {
-  cursor: pointer;
-  fill: #444;
-}
-
-::content .meta.expanded > text.nodelabel {
-  font-size: 9px;
-}
-
-::content .series > text.nodelabel {
-  font-size: 8px;
-}
-
-::content .op > text.nodelabel {
-  font-size: 6px;
-}
-
-::content .bridge > text.nodelabel {
-  display: none;
-}
-
-::content .node.meta.expanded > text.nodelabel{
-  cursor: normal;
-}
-
-::content .annotation.meta.highlighted > text.annotation-label {
-  fill: #50A3F7;
-}
-
-::content .annotation.meta.selected > text.annotation-label {
-  fill: #4285F4;
-}
-
-/* --- Annotation --- */
-
-/* only applied for annotations that are not summary or constant.
-(.summary, .constant gets overriden below) */
-::content .annotation > .annotation-node > * {
-  stroke-width: 0.5;
-  stroke-dasharray: 1, 1;
-}
-
-::content .annotation.summary > .annotation-node > *,
-::content .annotation.constant > .annotation-node > * {
-  stroke-width: 1;
-  stroke-dasharray: none;
-}
-
-::content .annotation > .annotation-edge {
-  fill: none;
-  stroke: #aaa;
-  stroke-width: 0.5;
-  marker-end: url("#annotation-arrowhead");
-}
-
-::content .faded .annotation > .annotation-edge {
-  marker-end: url("#annotation-arrowhead-faded");
-}
-
-::content .annotation > .annotation-edge.refline {
-  marker-start: url("#ref-annotation-arrowhead");
-}
-
-::content .faded .annotation > .annotation-edge.refline {
-  marker-start: url("#ref-annotation-arrowhead-faded");
-}
-
-::content .annotation > .annotation-control-edge {
-  stroke-dasharray: 1, 1;
-}
-
-::content #annotation-arrowhead {
-  fill: #aaa;
-}
-
-::content #annotation-arrowhead-faded {
-  fill: #e0d4b3;
-}
-
-::content #ref-annotation-arrowhead {
-  fill: #aaa;
-}
-
-::content #ref-annotation-arrowhead-faded {
-  fill: #e0d4b3;
-}
-
-::content .annotation > .annotation-label {
-  font-size: 5px;
-  cursor: pointer;
-}
-::content .annotation > .annotation-label.annotation-ellipsis {
-  cursor: default;
-}
-
-/* Hide annotations on expanded meta nodes since they're redundant. */
-::content .expanded > .in-annotations,
-::content .expanded > .out-annotations {
-  display: none;
-}
-
-/* --- Annotation: Constant --- */
-
-::content .constant > .annotation-node > ellipse {
-  cursor: pointer;
-  fill: white;
-  stroke: #848484;
-}
-
-::content .constant.selected > .annotation-node > ellipse {
-  fill: white;
-  stroke: red;
-}
-
-::content .constant.highlighted > .annotation-node > ellipse {
-  stroke-width: 1.5;
-}
-
-/* --- Annotation: Summary --- */
-
-::content .summary > .annotation-node > ellipse {
-  cursor: pointer;
-  fill: #DB4437;
-  stroke: #DB4437;
-}
-
-::content .summary.selected > .annotation-node > ellipse {
-  fill: #A52714;
-  stroke: #A52714;
-}
-
-::content .summary.highlighted > .annotation-node > ellipse {
-  stroke-width: 1.5;
-}
-
-/* --- Edge --- */
-
-::content .edge > path.edgeline {
-  fill: none;
-  stroke: #bbb;
-  stroke-linecap: round;
-  stroke-width: 0.75;
-}
-
-/* Labels showing tensor shapes on edges */
-::content .edge > text {
-  font-size: 3.5px;
-  fill: #666;
-}
-
-::content .ref-arrowhead {
-  fill: #bbb;
-}
-
-::content .edge .control-dep {
-  stroke-dasharray: 2, 2;
-}
-
-/* --- Group node expand/collapse button --- */
-
-/* Hides expand/collapse buttons when a node isn't expanded or highlighted. Using
-   incredibly small opacity so that the bounding box of the <g> parent still takes
-   this container into account even when it isn't visible */
-::content .node:not(.highlighted):not(.expanded) > .nodeshape > .buttoncontainer {
-  opacity: 0.01;
-}
-::content .node.highlighted > .nodeshape > .buttoncontainer {
-  cursor: pointer;
-}
-::content .buttoncircle {
-  fill: #E7811D;
-}
-::content .buttoncircle:hover {
-  fill: #B96717;
-}
-::content .expandbutton,
-::content .collapsebutton {
-  stroke: white;
-}
-/* Do not let the path elements in the button take pointer focus */
-::content .node > .nodeshape > .buttoncontainer > .expandbutton,
-::content .node > .nodeshape > .buttoncontainer > .collapsebutton {
-  pointer-events: none;
-}
-/* Only show the expand button when a node is collapsed and only show the
-   collapse button when a node is expanded. */
-::content .node.expanded > .nodeshape > .buttoncontainer > .expandbutton {
-  display: none;
-}
-::content .node:not(.expanded) > .nodeshape > .buttoncontainer > .collapsebutton {
-  display: none;
-}
-
-::content .health-pill-stats {
-  font-size: 4px;
-  text-anchor: middle;
-}
-
-::content .health-pill rect {
-  filter: url("#health-pill-shadow");
-  rx: 3;
-  ry: 3;
-}
-
-.titleContainer {
-  position: relative;
-  top: 20px;
-}
-
-.title {
-  position: absolute;
-}
-
-.auxTitle {
-  position: absolute;
-}
-
-#minimap {
-  position: absolute;
-  right: 20px;
-  bottom: 20px;
-}
-</style>
-<div class="titleContainer">
-  <div id="title" class="title">Main Graph</div>
-  <div id="auxTitle" class="auxTitle">Auxiliary Nodes</div>
-</div>
-<svg id="svg">
-  <defs>
-
-    
-    <path id="ref-arrowhead-path" d="M 10,0 L 0,5 L 10,10 C 7,7 7,3 10,0"></path>
-    <marker class="ref-arrowhead" id="ref-arrowhead-small" viewBox="0 0 10 10" markerWidth="10" markerHeight="10" refX="8" refY="5" orient="auto" markerUnits="userSpaceOnUse">
-      <use xlink:href="#ref-arrowhead-path"></use>
-    </marker>
-    <marker class="ref-arrowhead" id="ref-arrowhead-medium" viewBox="0 0 10 10" markerWidth="13" markerHeight="13" refX="8" refY="5" orient="auto" markerUnits="userSpaceOnUse">
-      <use xlink:href="#ref-arrowhead-path"></use>
-    </marker>
-    <marker class="ref-arrowhead" id="ref-arrowhead-large" viewBox="0 0 10 10" markerWidth="16" markerHeight="16" refX="8" refY="5" orient="auto" markerUnits="userSpaceOnUse">
-      <use xlink:href="#ref-arrowhead-path"></use>
-    </marker>
-    <marker class="ref-arrowhead" id="ref-arrowhead-xlarge" viewBox="0 0 10 10" markerWidth="20" markerHeight="20" refX="8" refY="5" orient="auto" markerUnits="userSpaceOnUse">
-      <use xlink:href="#ref-arrowhead-path"></use>
-    </marker>
-
-    
-    <marker id="annotation-arrowhead" markerWidth="5" markerHeight="5" refX="5" refY="2.5" orient="auto">
-      <path d="M 0,0 L 5,2.5 L 0,5 L 0,0"></path>
-    </marker>
-    <marker id="annotation-arrowhead-faded" markerWidth="5" markerHeight="5" refX="5" refY="2.5" orient="auto">
-      <path d="M 0,0 L 5,2.5 L 0,5 L 0,0"></path>
-    </marker>
-    <marker id="ref-annotation-arrowhead" markerWidth="5" markerHeight="5" refX="0" refY="2.5" orient="auto">
-      <path d="M 5,0 L 0,2.5 L 5,5 L 5,0"></path>
-    </marker>
-    <marker id="ref-annotation-arrowhead-faded" markerWidth="5" markerHeight="5" refX="0" refY="2.5" orient="auto">
-      <path d="M 5,0 L 0,2.5 L 5,5 L 5,0"></path>
-    </marker>
-    
-    <ellipse id="op-node-stamp" rx="7.5" ry="3" stroke="inherit" fill="inherit"></ellipse>
-    
-    <ellipse id="op-node-annotation-stamp" rx="5" ry="2" stroke="inherit" fill="inherit"></ellipse>
-    
-    <g id="op-series-vertical-stamp">
-      <use xlink:href="#op-node-stamp" x="8" y="9"></use>
-      <use xlink:href="#op-node-stamp" x="8" y="6"></use>
-      <use xlink:href="#op-node-stamp" x="8" y="3"></use>
-    </g>
-    
-    <g id="op-series-horizontal-stamp">
-      <use xlink:href="#op-node-stamp" x="16" y="4"></use>
-      <use xlink:href="#op-node-stamp" x="12" y="4"></use>
-      <use xlink:href="#op-node-stamp" x="8" y="4"></use>
-    </g>
-    
-    <g id="op-series-annotation-stamp">
-      <use xlink:href="#op-node-annotation-stamp" x="9" y="2"></use>
-      <use xlink:href="#op-node-annotation-stamp" x="7" y="2"></use>
-      <use xlink:href="#op-node-annotation-stamp" x="5" y="2"></use>
-    </g>
-    <svg id="summary-icon" fill="#848484" height="12" viewBox="0 0 24 24" width="12">
-      <path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9 17H7v-7h2v7zm4 0h-2V7h2v10zm4 0h-2v-4h2v4z"></path>
-    </svg>
-    
-    <g id="linearGradients"></g>
-
-    
-    <pattern id="rectHatch" patternTransform="rotate(45 0 0)" width="5" height="5" patternUnits="userSpaceOnUse">
-      <line x1="0" y1="0" x2="0" y2="5" style="stroke-width: 1"></line>
-    </pattern>
-    <pattern id="ellipseHatch" patternTransform="rotate(45 0 0)" width="2" height="2" patternUnits="userSpaceOnUse">
-      <line x1="0" y1="0" x2="0" y2="2" style="stroke-width: 1"></line>
-    </pattern>
-
-    
-    <filter id="health-pill-shadow" x="-40%" y="-40%" width="180%" height="180%">
-      <feGaussianBlur in="SourceAlpha" stdDeviation="0.8"></feGaussianBlur>
-      <feOffset dx="0" dy="0" result="offsetblur"></feOffset>
-      <feFlood flood-color="#000000"></feFlood>
-      <feComposite in2="offsetblur" operator="in"></feComposite>
-      <feMerge>
-        <feMergeNode></feMergeNode>
-        <feMergeNode in="SourceGraphic"></feMergeNode>
-      </feMerge>
-    </filter>
-  </defs>
-  
-  <rect fill="white" width="10000" height="10000"></rect>
-  <g id="root"></g>
-</svg>
-<tf-graph-minimap id="minimap"></tf-graph-minimap>
-</template>
-<script>
-Polymer({
-  is: 'tf-graph-scene',
-  properties: {
-    renderHierarchy: Object,
-    name: String,
-    colorBy: String,
-
-    // For each render hierarchy, we only fit it to the viewport once (when the scene is attached to
-    // the DOM). We do not fit the hierarchy again (unless the user clicks the reset button). For
-    // instance, if the user enters a certain view in the graph, switches to another dashboard, and
-    // returns to the graph dashboard, the user expects the previous view. These properties enable
-    // that behavior.
-
-    /** Whether the scene has fit the current render hierarchy (to the viewport) at least once. */
-    _hasRenderHierarchyBeenFitOnce: Boolean,
-    /** Whether this scene element is currently attached to a parent element. */
-    _isAttached: Boolean,
-
-    /** @type {d3_zoom} d3 zoom object */
-    _zoom: Object,
-    highlightedNode: {
-      type: String,
-      observer: '_highlightedNodeChanged'
-    },
-    selectedNode: {
-      type: String,
-      observer: '_selectedNodeChanged'
-    },
-    /** Keeps track of if the graph has been zoomed/panned since loading */
-    _zoomed: {
-      type: Boolean,
-      observer: '_onZoomChanged',
-      value: false
-    },
-    /** Keeps track of the starting coordinates of a graph zoom/pan */
-    _zoomStartCoords: {
-      type: Array,
-      value: null
-    },
-    /** Keeps track of the current coordinates of a graph zoom/pan */
-    _zoomCoords: {
-      type: Array,
-      value: null
-    },
-    /** Maximum distance of a zoom event for it to be interpreted as a click */
-    _maxZoomDistanceForClick: {
-      type: Number,
-      value: 20
-    },
-    /**
-     * @type {d3.scale.ordinal}
-     * Scale mapping from template name to a number between 0 and N-1
-     * where N is the number of different template names. Used by
-     * tf.graph.scene.node when computing node color by structure.
-     */
-    templateIndex: Function,
-    /**
-     * @type {tf.scene.Minimap}
-     * A minimap object to notify for zoom events.
-     */
-    minimap: Object,
-    /*
-     * Dictionary for easily stylizing nodes when state changes.
-     * _nodeGroupIndex[nodeName] = d3_selection of the nodeGroup
-     */
-    _nodeGroupIndex: {
-      type: Object,
-      value: function() { return {}; }
-    },
-    /*
-     * Dictionary for easily stylizing annotation nodes when state changes.
-     * _annotationGroupIndex[nodeName][hostNodeName] =
-     *   d3_selection of the annotationGroup
-     */
-    _annotationGroupIndex: {
-      type: Object,
-      value: function() { return {}; }
-    },
-    /*
-     * Dictionary for easily stylizing edges when state changes.
-     * _edgeGroupIndex[edgeName] = d3_selection of the edgeGroup
-     */
-    _edgeGroupIndex: {
-      type: Object,
-      value: function() { return {}; }
-    },
-    /**
-     * Max font size for metanode label strings.
-     */
-    maxMetanodeLabelLengthFontSize: {
-      type: Number,
-      value: 9
-    },
-    /**
-     * Min font size for metanode label strings.
-     */
-    minMetanodeLabelLengthFontSize: {
-      type: Number,
-      value: 6
-    },
-    /**
-     * Metanode label strings longer than this are given smaller fonts.
-     */
-    maxMetanodeLabelLengthLargeFont: {
-      type: Number,
-      value: 11
-    },
-    /**
-     * Metanode label strings longer than this are truncated with ellipses.
-     */
-    maxMetanodeLabelLength: {
-      type: Number,
-      value: 18
-    },
-    progress: Object,
-    // A mapping between node name to the tf.graph.scene.HealthPill to render.
-    nodeNamesToHealthPills: Object,
-    // The step of health pills to show throughout the graph.
-    healthPillStepIndex: Number,
-  },
-  observers: [
-    '_colorByChanged(colorBy)',
-    '_renderHierarchyChanged(renderHierarchy)',
-    // Animation and fitting must come after the observer for the hierarchy changing because we must
-    // first build the render hierarchy.
-    '_animateAndFit(_isAttached, renderHierarchy)',
-    '_updateHealthPills(nodeNamesToHealthPills, healthPillStepIndex)',
-  ],
-  getNode: function(nodeName) {
-    return this.renderHierarchy.getRenderNodeByName(nodeName);
-  },
-  isNodeExpanded: function(node) {
-    return node.expanded;
-  },
-  setNodeExpanded: function(renderNode) {
-    this._build(this.renderHierarchy);
-    this._updateLabels(!this._zoomed);
-  },
-  /**
-   * Resets the state of the component. Called whenever the whole graph
-   * (dataset) changes.
-   */
-  _resetState: function() {
-    // Reset the state of the component.
-    this._nodeGroupIndex = {};
-    this._annotationGroupIndex = {};
-    this._edgeGroupIndex = {};
-    this._updateLabels(false);
-    // Remove all svg elements under the 'root' svg group.
-    d3.select(this.$.svg).select('#root').selectAll('*').remove();
-    // And the defs.
-    d3.select(this.$.svg).select('defs #linearGradients')
-        .selectAll('*').remove();
-  },
-  /** Main method for building the scene */
-  _build: function(renderHierarchy) {
-    this.templateIndex = renderHierarchy.hierarchy.getTemplateIndex();
-    tf.graph.util.time('tf-graph-scene (layout):', function() {
-      // layout the scene for this meta / series node
-      tf.graph.layout.layoutScene(renderHierarchy.root, this);
-    }.bind(this));
-
-    tf.graph.util.time('tf-graph-scene (build scene):', function() {
-      tf.graph.scene.buildGroup(d3.select(this.$.root), renderHierarchy.root, this);
-      tf.graph.scene.addGraphClickListener(this.$.svg, this);
-      tf.graph.scene.node.traceInputs(renderHierarchy);
-    }.bind(this));
-    // Update the minimap again when the graph is done animating.
-    setTimeout(function() {
-      this._updateHealthPills(this.nodeNamesToHealthPills, this.healthPillStepIndex);
-      this.minimap.update();
-    }.bind(this), tf.graph.layout.PARAMS.animation.duration);
-  },
-  ready: function() {
-    this._zoom = d3.behavior.zoom()
-      .on('zoomend', function() {
-        if (this._zoomStartCoords) {
-          // Calculate the total distance dragged during the zoom event.
-          // If it is sufficiently small, then fire an event indicating
-          // that zooming has ended. Otherwise wait to fire the zoom end
-          // event, so that a mouse click registered as part of this zooming
-          // is ignored (as this mouse click was part of a zooming, and should
-          // not be used to indicate an actual click on the graph).
-          var dragDistance = Math.sqrt(
-            Math.pow(this._zoomStartCoords[0] - this._zoomCoords[0], 2) +
-            Math.pow(this._zoomStartCoords[1] - this._zoomCoords[1], 2));
-          if (dragDistance < this._maxZoomDistanceForClick) {
-            this._fireEnableClick();
-          } else {
-            setTimeout(this._fireEnableClick.bind(this), 50);
-          }
-        }
-        this._zoomStartCoords = null;
-      }.bind(this))
-      .on('zoom', function() {
-        // Store the coordinates of the zoom event
-        this._zoomCoords = d3.event.translate;
-
-        // If this is the first zoom event after a zoom-end, then
-        // store the coordinates as the start coordinates as well,
-        // and fire an event to indicate that zooming has started.
-        // This doesn't use the zoomstart event, as d3 sends this
-        // event on mouse-down, even if there has been no dragging
-        // done to translate the graph around.
-        if (!this._zoomStartCoords) {
-          this._zoomStartCoords = this._zoomCoords.slice();
-          this.fire('disable-click');
-        }
-        this._zoomed = true;
-        d3.select(this.$.root).attr('transform',
-                    'translate(' + d3.event.translate + ')' +
-                    'scale(' + d3.event.scale + ')');
-        // Notify the minimap.
-        this.minimap.zoom(d3.event.translate, d3.event.scale);
-      }.bind(this));
-    d3.select(this.$.svg).call(this._zoom)
-      .on('dblclick.zoom', null);
-    d3.select(window).on('resize', function() {
-      // Notify the minimap that the user's window was resized.
-      // The minimap will figure out the new dimensions of the main svg
-      // and will use the existing translate and scale params.
-      this.minimap.zoom();
-    }.bind(this));
-    // Initialize the minimap.
-    this.minimap = this.$.minimap.init(this.$.svg, this.$.root, this._zoom,
-        tf.graph.layout.PARAMS.minimap.size,
-        tf.graph.layout.PARAMS.subscene.meta.labelHeight);
-  },
-  attached: function() {
-    this.set('_isAttached', true);
-  },
-  detached: function() {
-    this.set('_isAttached', false);
-  },
-  _renderHierarchyChanged: function(renderHierarchy) {
-    this._hasRenderHierarchyBeenFitOnce = false;
-    this._resetState();
-    this._build(renderHierarchy);
-  },
-  _animateAndFit: function(isAttached, renderHierarchy) {
-    if (this._hasRenderHierarchyBeenFitOnce || !isAttached) {
-      // Do not animate and fit if the scene has already fitted this render hierarchy once. Or if
-      // the graph dashboard is not attached (in which case the scene lacks DOM info for fitting).
-      return;
-    }
-
-    // Fit to screen after the graph is done animating.
-    setTimeout(this.fit.bind(this), tf.graph.layout.PARAMS.animation.duration);
-  },
-  _updateLabels: function(showLabels) {
-    var mainGraphTitleElement = this.getElementsByClassName('title')[0];
-    var titleStyle = mainGraphTitleElement.style;
-    var auxTitleStyle = this.getElementsByClassName('auxTitle')[0].style;
-    var core = d3.select("." + tf.graph.scene.Class.Scene.GROUP + ">." +
-      tf.graph.scene.Class.Scene.CORE)[0][0];
-    // Only show labels if the graph is fully loaded.
-    if (showLabels && core && this.progress && this.progress.value === 100) {
-      var aux =
-        d3.select("." + tf.graph.scene.Class.Scene.GROUP + ">." +
-          tf.graph.scene.Class.Scene.INEXTRACT)[0][0] ||
-        d3.select("." + tf.graph.scene.Class.Scene.GROUP + ">." +
-          tf.graph.scene.Class.Scene.OUTEXTRACT)[0][0];
-      var coreX = core.getCTM().e;
-      var auxX = aux ? aux.getCTM().e : null;
-      titleStyle.display = 'inline';
-      titleStyle.left = coreX + 'px';
-      if (auxX !== null && auxX !== coreX) {
-        auxTitleStyle.display = 'inline';
-
-        // Make sure that the aux title is positioned rightwards enough so as to
-        // prevent overlap with the main graph title.
-        auxX = Math.max(
-            coreX + mainGraphTitleElement.getBoundingClientRect().width, auxX);
-
-        auxTitleStyle.left = auxX + 'px';
-      } else {
-        auxTitleStyle.display = 'none';
-      }
-    } else {
-      titleStyle.display='none';
-      auxTitleStyle.display = 'none';
-    }
-  },
-  /**
-    * Called whenever the user changed the 'color by' option in the
-    * UI controls.
-    */
-  _colorByChanged: function() {
-    if (this.renderHierarchy != null) {
-      // We iterate through each svg node and update its state.
-      _.each(this._nodeGroupIndex, function(nodeGroup, nodeName) {
-        this._updateNodeState(nodeName);
-      }, this);
-      // Notify also the minimap.
-      this.minimap.update();
-    }
-  },
-  fit: function() {
-    this._hasRenderHierarchyBeenFitOnce = true;
-    tf.graph.scene.fit(this.$.svg, this.$.root, this._zoom, function() {
-      this._zoomed = false;
-    }.bind(this));
-  },
-  isNodeSelected: function(n) {
-    return n === this.selectedNode;
-  },
-  isNodeHighlighted: function(n) {
-    return n === this.highlightedNode;
-  },
-  addAnnotationGroup: function(a, d, selection) {
-    var an = a.node.name;
-    this._annotationGroupIndex[an] = this._annotationGroupIndex[an] || {};
-    this._annotationGroupIndex[an][d.node.name] = selection;
-  },
-  getAnnotationGroupsIndex: function(a) {
-    return this._annotationGroupIndex[a];
-  },
-  removeAnnotationGroup: function(a, d) {
-    delete this._annotationGroupIndex[a.node.name][d.node.name];
-  },
-  addNodeGroup: function(n, selection) {
-    this._nodeGroupIndex[n] = selection;
-  },
-  getNodeGroup: function(n) {
-    return this._nodeGroupIndex[n];
-  },
-  removeNodeGroup: function(n) {
-    delete this._nodeGroupIndex[n];
-  },
-  addEdgeGroup: function(n, selection) {
-    this._edgeGroupIndex[e] = selection;
-  },
-  getEdgeGroup: function(e) {
-    return this._edgeGroupIndex[e];
-  },
-  _updateHealthPills: function(nodeNamesToHealthPills, healthPillStepIndex) {
-    tf.graph.scene.addHealthPills(
-        this.$.svg, nodeNamesToHealthPills, healthPillStepIndex);
-  },
-  /**
-   * Update node and annotation node of the given name.
-   * @param  {String} n node name
-   */
-  _updateNodeState: function(n) {
-    var node = this.getNode(n);
-    var nodeGroup = this.getNodeGroup(n);
-
-    if (nodeGroup) {
-      tf.graph.scene.node.stylize(nodeGroup, node, this);
-    }
-
-    var annotationGroupIndex = this.getAnnotationGroupsIndex(n);
-    _.each(annotationGroupIndex, function(aGroup, hostName) {
-      tf.graph.scene.node.stylize(aGroup, node, this,
-          tf.graph.scene.Class.Annotation.NODE);
-    }, this);
-  },
-
-  /**
-   * Handles new node selection. 1) Updates the selected-state of each node,
-   * 2) triggers input tracing.
-   * @param selectedNode {string} The name of the newly selected node.
-   * @param oldSelectedNode {string} The name of the previously selected node.
-   * @private
-   */
-  _selectedNodeChanged: function(selectedNode, oldSelectedNode) {
-    if (selectedNode === oldSelectedNode) {
-      return;
-    }
-
-    if (selectedNode) {
-      this._updateNodeState(selectedNode);
-    }
-    if (oldSelectedNode) {
-      this._updateNodeState(oldSelectedNode);
-    }
-
-    tf.graph.scene.node.traceInputs(this.renderHierarchy);
-
-    if (!selectedNode) {
-      return;
-    }
-
-
-    // Update the minimap to reflect the highlighted (selected) node.
-    this.minimap.update();
-    var node = this.renderHierarchy.hierarchy.node(selectedNode);
-    var nodeParents = [];
-    // Create list of all metanode parents of the selected node.
-    while (node.parentNode != null
-        && node.parentNode.name != tf.graph.ROOT_NAME) {
-      node = node.parentNode;
-      nodeParents.push(node.name);
-    }
-    // Ensure each parent metanode is built and expanded.
-    var topParentNodeToBeExpanded;
-    _.forEachRight(nodeParents, function(parentName) {
-      this.renderHierarchy.buildSubhierarchy(parentName);
-      var renderNode = this.renderHierarchy.getRenderNodeByName(parentName);
-      if (renderNode.node.isGroupNode && !renderNode.expanded) {
-        renderNode.expanded = true;
-        if (!topParentNodeToBeExpanded) {
-          topParentNodeToBeExpanded = renderNode;
-        }
-      }
-    }, this);
-    // If any expansion was needed to display this selected node, then
-    // inform the scene of the top-most expansion.
-    if (topParentNodeToBeExpanded) {
-      this.setNodeExpanded(topParentNodeToBeExpanded);
-      this._zoomed = true;
-    }
-
-    if (tf.graph.scene.panToNode(selectedNode, this.$.svg, this.$.root,
-        this._zoom)) {
-      this._zoomed = true;
-    }
-  },
-  _highlightedNodeChanged: function(highlightedNode, oldHighlightedNode) {
-    if (highlightedNode === oldHighlightedNode) {
-      return;
-    }
-
-    if (highlightedNode) {
-      this._updateNodeState(highlightedNode);
-    }
-    if (oldHighlightedNode) {
-      this._updateNodeState(oldHighlightedNode);
-    }
-  },
-  _onZoomChanged: function() {
-    this._updateLabels(!this._zoomed);
-  },
-  _fireEnableClick: function() {
-    this.fire('enable-click');
-  },
-});
-</script>
-</dom-module>
-<link rel="import" href="../iron-flex-layout/iron-flex-layout.html">
-<dom-module id="tf-graph" assetpath="../tf-graph/">
-<template>
-<style>
-.container {
-  width: 100%;
-  height: 100%;
-  background: white;
-  box-shadow: 0 1px 5px rgba(0,0,0,0.2);
-}
-
-.vertical {
-  width:100%;
-  height:100%;
-  @apply(--layout-vertical);
-}
-
-.auto {
-  @apply(--layout-flex-auto);
-  @apply(--layout-vertical);
-}
-
-h2 {
-  text-align: center;
-}
-
-paper-button {
-  text-transform: none;
-}
-</style>
-<div class="container">
-  <div class="vertical">
-    <template is="dom-if" if="[[title]]">
-      <h2>[[title]]</h2>
-    </template>
-    <tf-graph-scene id="scene" class="auto" render-hierarchy="[[renderHierarchy]]" highlighted-node="[[_getVisible(highlightedNode)]]" selected-node="{{selectedNode}}" color-by="[[colorBy]]" progress="[[progress]]" node-names-to-health-pills="[[nodeNamesToHealthPills]]" health-pill-step-index="{{healthPillStepIndex}}"></tf-graph-scene>
-  </div>
-</div>
-</template>
-</dom-module>
-
-<script>
-Polymer({
-
-  is: 'tf-graph',
-
-  properties: {
-    graphHierarchy: {
-      type: Object,
-      notify: true,
-      observer: '_graphChanged'
-    },
-    basicGraph: Object,
-    stats: Object,
-    devicesForStats: Object,
-    hierarchyParams: Object,
-    progress: {
-      type: Object,
-      notify: true,
-    },
-    title: String,
-    selectedNode: {
-      type: String,
-      notify: true,
-    },
-    highlightedNode: {
-      type: String,
-      notify: true
-    },
-    /** What to color the nodes by (compute time, memory, device etc.) */
-    colorBy: String,
-    colorByParams: {
-      type: Object,
-      notify: true,
-      readOnly: true, // Produces and doesn't consume.
-    },
-    renderHierarchy: {
-      type: Object,
-      readOnly: true,
-      notify: true,
-    },
-    _renderDepth: {
-      type: Number,
-      value: 1
-    },
-    _allowGraphSelect: {
-      type: Boolean,
-      value: true
-    },
-    // A mapping between node name to the tf.graph.scene.HealthPill to render.
-    nodeNamesToHealthPills: Object,
-    // The step of health pills to show throughout the graph.
-    healthPillStepIndex: Number,
-  },
-  observers: [
-    '_statsChanged(stats, devicesForStats)',
-    '_buildRenderHierarchy(graphHierarchy)'
-  ],
-  _statsChanged: function(stats, devicesForStats) {
-    if (this.graphHierarchy) {
-      if (stats && devicesForStats) {
-        tf.graph.joinStatsInfoWithGraph(this.basicGraph, stats, devicesForStats);
-        tf.graph.hierarchy.joinAndAggregateStats(this.graphHierarchy, stats);
-      }
-      // Recompute the rendering information.
-      this._buildRenderHierarchy(this.graphHierarchy);
-    }
-  },
-  _buildRenderHierarchy: function(graphHierarchy) {
-    tf.graph.util.time('new tf.graph.render.Hierarchy', function() {
-      if (graphHierarchy.root.type !== tf.graph.NodeType.META) {
-        // root must be metanode but sometimes Polymer's dom-if has not
-        // remove tf-graph element yet in <tf-node-info>
-        // and thus mistakenly pass non-metanode to this module.
-        return;
-      }
-      var renderGraph = new tf.graph.render.RenderGraphInfo(
-          graphHierarchy, !!this.stats /** displayingStats */);
-      // Producing the 'color by' parameters to be consumed
-      // by the tf-graph-controls panel. It contains information about the
-      // min and max values and their respective colors, as well as list
-      // of devices with their respective colors.
-
-      function getColorParamsFromScale(scale) {
-        return {
-          minValue: scale.domain()[0],
-          maxValue: scale.domain()[1],
-          startColor: scale.range()[0],
-          endColor: scale.range()[1]
-        };
-      }
-
-      this._setColorByParams({
-        compute_time: getColorParamsFromScale(renderGraph.computeTimeScale),
-        memory: getColorParamsFromScale(renderGraph.memoryUsageScale),
-        device: _.map(renderGraph.deviceColorMap.domain(),
-            function(deviceName) {
-          return {
-            device: deviceName,
-            color: renderGraph.deviceColorMap(deviceName)
-          };
-        }),
-        xla_cluster: _.map(renderGraph.xlaClusterColorMap.domain(),
-            function(xlaClusterName) {
-          return {
-            xla_cluster: xlaClusterName,
-            color: renderGraph.xlaClusterColorMap(xlaClusterName)
-          };
-        }),
-      });
-      this._setRenderHierarchy(renderGraph);
-      this.async(function() {
-        this.fire("rendered");
-      });
-    }.bind(this));
-  },
-  _getVisible: function(name) {
-    if (!name) {
-      return name;
-    }
-    return this.renderHierarchy.getNearestVisibleAncestor(name);
-  },
-  listeners: {
-    'graph-select': '_graphSelected',
-    'disable-click': '_disableClick',
-    'enable-click': '_enableClick',
-    // Nodes
-    'node-toggle-expand': '_nodeToggleExpand',
-    'node-select': '_nodeSelected',
-    'node-highlight': '_nodeHighlighted',
-    'node-unhighlight': '_nodeUnhighlighted',
-    'node-toggle-extract': '_nodeToggleExtract',
-    'node-toggle-seriesgroup': '_nodeToggleSeriesGroup',
-
-    // Annotations
-
-    /* Note: currently highlighting/selecting annotation node has the same
-      * behavior as highlighting/selecting actual node so we point to the same
-      * set of event listeners.  However, we might redesign this to be a bit
-      * different.
-      */
-    'annotation-select': '_nodeSelected',
-    'annotation-highlight': '_nodeHighlighted',
-    'annotation-unhighlight': '_nodeUnhighlighted',
-  },
-  _graphChanged: function() {
-    // When a new graph is loaded, fire this event so that there is no
-    // info-card being displayed for the previously-loaded graph.
-    this.fire('graph-select');
-  },
-  _graphSelected: function(event) {
-    // Graph selection is not allowed during an active zoom event, as the
-    // click seen during a zoom/pan is part of the zooming and does not
-    // indicate a user desire to click on a specific section of the graph.
-    if (this._allowGraphSelect) {
-      this.set('selectedNode', null);
-    }
-    // Reset this variable as a bug in d3 zoom behavior can cause zoomend
-    // callback not to be called if a right-click happens during a zoom event.
-    this._allowGraphSelect = true;
-  },
-  _disableClick: function(event) {
-    this._allowGraphSelect = false;
-  },
-  _enableClick: function(event) {
-    this._allowGraphSelect = true;
-  },
-  _nodeSelected: function(event) {
-    if (this._allowGraphSelect) {
-      this.set('selectedNode', event.detail.name);
-    }
-    // Reset this variable as a bug in d3 zoom behavior can cause zoomend
-    // callback not to be called if a right-click happens during a zoom event.
-    this._allowGraphSelect = true;
-  },
-  _nodeHighlighted: function(event) {
-    this.set('highlightedNode', event.detail.name);
-  },
-  _nodeUnhighlighted: function(event) {
-    this.set('highlightedNode', null);
-  },
-  _nodeToggleExpand: function(event) {
-    // Immediately select the node that is about to be expanded.
-    this._nodeSelected(event);
-
-    // Compute the sub-hierarchy scene.
-    var nodeName = event.detail.name;
-    var renderNode = this.renderHierarchy.getRenderNodeByName(nodeName);
-    // Op nodes are not expandable.
-    if (renderNode.node.type === tf.graph.NodeType.OP) {
-      return;
-    }
-    this.renderHierarchy.buildSubhierarchy(nodeName);
-    renderNode.expanded = !renderNode.expanded;
-
-    // Expand the node with some delay so that the user can immediately see
-    // the visual effect of selecting that node, before the expansion is
-    // done.
-    this.async(function() {
-      this.querySelector('#scene').setNodeExpanded(renderNode);
-    }, 75);
-  },
-  _nodeToggleExtract: function(event) {
-    // Toggle the include setting of the specified node appropriately.
-    var nodeName = event.detail.name;
-    var renderNode = this.renderHierarchy.getRenderNodeByName(nodeName);
-    if (renderNode.node.include == tf.graph.InclusionType.INCLUDE) {
-      renderNode.node.include = tf.graph.InclusionType.EXCLUDE;
-    } else if (renderNode.node.include == tf.graph.InclusionType.EXCLUDE) {
-      renderNode.node.include = tf.graph.InclusionType.INCLUDE;
-    } else {
-      renderNode.node.include =
-       this.renderHierarchy.isNodeAuxiliary(renderNode)
-          ? tf.graph.InclusionType.INCLUDE : tf.graph.InclusionType.EXCLUDE;
-    }
-
-    // Rebuild the render hierarchy.
-    this._buildRenderHierarchy(this.graphHierarchy);
-  },
-  _nodeToggleSeriesGroup: function(event) {
-    // Toggle the group setting of the specified node appropriately.
-    var nodeName = event.detail.name;
-    tf.graph.toggleNodeSeriesGroup(this.hierarchyParams.seriesMap, nodeName);
-
-    // Rebuild the render hierarchy with the updated series grouping map.
-    this.set('progress', {
-      value: 0,
-      msg: ''
-    });
-    var tracker = tf.graph.util.getTracker(this);
-    var hierarchyTracker = tf.graph.util.getSubtaskTracker(tracker, 100,
-          'Namespace hierarchy');
-    tf.graph.hierarchy.build(this.basicGraph, this.hierarchyParams, hierarchyTracker)
-    .then(function(graphHierarchy) {
-      this.set('graphHierarchy', graphHierarchy);
-      this._buildRenderHierarchy(this.graphHierarchy);
-    }.bind(this));
-  },
-  not: function(x) {
-    return !x;
-  }
-});
-</script>
-<dom-module id="tf-graph-icon" assetpath="../tf-graph/">
-  <style>
-    .faded-rect {
-      fill: url("#rectHatch");
-    }
-
-    .faded-ellipse {
-      fill: url("#ellipseHatch");
-    }
-
-    .faded-rect, .faded-ellipse, .faded-series {
-      stroke:   var(--tb-graph-faded) !important;
-    }
-  </style>
-  <template>
-    <template is="dom-if" if="[[_isType(node, type, 'OP')]]">
-      <template is="dom-if" if="[[_isConst(node, const)]]">
-        <svg height$="[[height]]" preserveAspectRatio="xMinYMid meet" viewBox="0 0 10 10">
-          <circle cx="5" cy="5" r="3" fill$="[[_getFill(_computedFill, 'OP')]]" stroke$="[[_getStroke(_computedFill, 'OP')]]"></circle>
-        </svg>
-      </template>
-      <template is="dom-if" if="[[_isSummary(node, summary)]]">
-        <svg width$="[[height]]" height$="[[height]]" viewBox="0 0 12 12">
-          <use x="0" y="0" xlink:href="#summary-icon"></use>
-        </svg>
-      </template>
-      <template is="dom-if" if="[[_isRegularOp(node, const, summary)]]">
-        <svg height$="[[height]]" preserveAspectRatio="xMinYMid meet" viewBox="0 0 16 8">
-          <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#op-node-stamp" fill$="[[_getFill(_computedFill, 'OP')]]" stroke$="[[_getStroke(_computedFill, 'OP')]]" class$="{{_fadedClass(renderInfo, 'ellipse')}}" x="8" y="4"></use>
-        </svg>
-      </template>
-    </template>
-    <template is="dom-if" if="[[_isType(node, type, 'META')]]">
-      <svg height$="[[height]]" preserveAspectRatio="xMinYMid meet" viewBox="0 0 37 16">
-        <rect x="1" y="1" fill$="[[_getFill(_computedFill, 'META')]]" stroke$="[[_getStroke(_computedFill, 'META')]]" class$="{{_fadedClass(renderInfo, 'rect')}}" stroke-width="2px" height="14" width="35" rx="5" ry="5"></rect>
-      </svg>
-    </template>
-    <template is="dom-if" if="[[_isType(node, type, 'SERIES')]]">
-      <template is="dom-if" if="[[_isVertical(node, vertical)]]">
-        <svg height$="[[height]]" preserveAspectRatio="xMinYMid meet" viewBox="0 0 16 15">
-          <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#op-series-vertical-stamp" fill$="[[_getFill(_computedFill, 'SERIES')]]" stroke$="[[_getStroke(_computedFill, 'SERIES')]]" class$="{{_fadedClass(renderInfo, 'series')}}" x="0" y="2"></use>
-        </svg>
-      </template>
-      <template is="dom-if" if="[[!_isVertical(node, vertical)]]">
-        <svg height$="[[height]]" preserveAspectRatio="xMinYMid meet" viewBox="0 0 24 10">
-          <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#op-series-horizontal-stamp" fill$="[[_getFill(_computedFill, 'SERIES')]]" stroke$="[[_getStroke(_computedFill, 'SERIES')]]" class$="{{_fadedClass(renderInfo, 'series')}}" x="0" y="1"></use>
-        </svg>
-      </template>
-    </template>
-  </template>
-
-  <script>
-    (function() {
-      Polymer({
-        is: 'tf-graph-icon',
-
-        properties: {
-          /**
-           * Node to represent with an icon. Optional, but if specified, its
-           * properties override those defined in the type, vertical, const and
-           * summary properties.
-           * @type {tf.graph.Node}
-           */
-          node: {
-            type: Object,
-            value: null
-          },
-
-          /**
-           * Render node information associated with this node. Optional. If
-           * specified, this is only used when computing the fill of the icon
-           * element.
-           * @type {tf.graph.render.RenderNodeInfo}
-           */
-          renderInfo: {
-            type: Object,
-            value: null
-          },
-
-          /**
-           * String indicating the type of coloring to use for this node, used
-           * only for determining the fill.
-           */
-          colorBy: {
-            type: Object,
-            value: "structural"
-          },
-
-          /**
-           * Function used by structural coloring algorithm to determine which
-           * color to use based on the template ID of the node. Optional.
-           */
-          templateIndex: {
-            type: Function,
-            value: null
-          },
-
-          /** Type of node to draw (ignored if node is set). */
-          type: {
-            type: String,
-            value: null
-          },
-
-          /** Direction for series (ignored for other types). */
-          vertical: {
-            type: Boolean,
-            value: false
-          },
-
-          /** Whether the op is Const (ignored for non-ops). */
-          const: {
-            type: Boolean,
-            value: false
-          },
-
-          /** Whether the op is a Summary (ignored for non-ops). */
-          summary: {
-            type: Boolean,
-            value: false
-          },
-
-          /**
-           * Fill for the icon, optional. If fill is specified and node is not
-           * specified, then this value will override the default for the
-           * element. However, if node is specified, this value will be ignored.
-           */
-          fill: {
-            type: String,
-            value: null
-          },
-
-          /** Height of the SVG element in pixels, used for scaling. */
-          height: {
-            type: Number,
-            value: 20
-          },
-
-          /** The computed fill for the node. **/
-          _computedFill: {
-            type: String,
-            computed:
-              "_getComputedFill(node, renderInfo, colorBy, templateIndex, fill)"
-          }
-
-        },
-
-        /**
-         * Get the computed fill value for the element.
-         */
-        _getComputedFill: function(inputNode, inputRenderInfo, inputColorBy,
-            inputTemplateIndex, inputFill) {
-          if (inputNode && inputRenderInfo &&
-              inputColorBy && inputTemplateIndex) {
-            var ns = tf.graph.scene.node;
-            var colorBy = ns.ColorBy[inputColorBy.toUpperCase()];
-            return ns.getFillForNode(inputTemplateIndex, colorBy,
-                inputRenderInfo, false);
-          }
-          return inputFill;
-        },
-
-        /**
-         * Get the fill value for the element, or if that's not possible, return
-         * the default fill value for the node type.
-         */
-        _getFill: function(inputComputedFill, inputNodeType) {
-          return inputComputedFill || ({
-            OP: tf.graph.render.OpNodeColors.DEFAULT_FILL,
-            META: tf.graph.render.MetanodeColors.DEFAULT_FILL,
-            SERIES: tf.graph.render.SeriesNodeColors.DEFAULT_FILL
-          })[inputNodeType];
-        },
-
-        /**
-         * Get the stroke value for the element, or if that's not possible,
-         * return the default stroke value for the node type.
-         */
-        _getStroke: function(inputComputedFill, inputNodeType) {
-          return inputComputedFill ?
-            tf.graph.scene.node.getStrokeForFill(inputComputedFill) :
-            ({
-              OP: tf.graph.render.OpNodeColors.DEFAULT_STROKE,
-              META: tf.graph.render.MetanodeColors.DEFAULT_STROKE,
-              SERIES: tf.graph.render.SeriesNodeColors.DEFAULT_STROKE
-            })[inputNodeType];
-        },
-
-        /**
-         * Test whether the specified node's type, or the literal type string,
-         * match a particular other type.
-         */
-        _isType: function(inputNode, inputType, targetType) {
-          if (inputNode) {
-            return tf.graph.NodeType[inputNode.type] === targetType;
-          }
-          return inputType === targetType;
-        },
-
-        /**
-         * Test whether the specified node should be represented as a vertical
-         * series. Defaults to the value of the vertical property if node is
-         * not specified.
-         */
-        _isVertical: function(inputNode, inputVertical) {
-          if (inputNode) {
-            return inputNode.hasNonControlEdges;
-          }
-          return !!inputVertical;
-        },
-
-        /**
-         * Test whether the specified node is a constant. Defaults to the value
-         * of the const property if node is not specified.
-         */
-        _isConst: function(inputNode, inputConst) {
-          if (inputNode) {
-            return inputNode.op === 'Const';
-          }
-          return !!inputConst;
-        },
-
-        /**
-         * Test whether the specified node is a summary. Defaults to the value
-         * of the summary property if node is not specified.
-         */
-        _isSummary: function(inputNode, inputSummary) {
-          if (inputNode) {
-            return this._isType(inputNode, null, 'OP') &&
-                inputNode.op.substr(-7) === 'Summary';
-          }
-          return !!inputSummary;
-        },
-
-        /**
-         * Test whether the op node is a regular non-summary non-const node.
-         */
-        _isRegularOp: function(inputNode, inputConst, inputSummary) {
-          return !this._isConst(inputNode, inputConst) &&
-              !this._isSummary(inputNode, inputSummary);
-        },
-
-        _fadedClass: function(itemRenderInfo, shape) {
-          return itemRenderInfo && itemRenderInfo.isFadedOut ? 'faded-' + shape : '';
-        }
-      });
-    })();
-  </script>
-</dom-module>
-<dom-module id="tf-node-list-item" assetpath="../tf-graph-info/">
-  <style>
-  #list-item {
-    width: 100%;
-    color: #565656;
-    font-size: 11pt;
-    font-weight: 400;
-    position: relative;
-    display: inline-block;
-  }
-
-  #list-item:hover {
-    background-color: var(--google-yellow-100);
-  }
-
-  .clickable {
-    cursor: pointer;
-  }
-
-  #list-item span {
-    margin-left: 40px;
-  }
-
-  #list-item.excluded span {
-    color: #999;
-  }
-
-  #list-item span.edge-label {
-    float: right;
-    font-size: 10px;
-    margin-left: 3px;
-    margin-right: 5px;
-  }
-
-  .node-icon {
-    position: absolute;
-    top: 1px;
-    left: 2px;
-  }
-
-  .faded span {
-    color: var(--tb-graph-faded);
-  }
-  </style>
-  <template>
-    <div id="list-item" on-mouseover="_nodeListener" on-mouseout="_nodeListener" on-click="_nodeListener">
-      <div class$="{{_fadedClass(itemRenderInfo)}}">
-        <tf-graph-icon class="node-icon" height="12" color-by="[[colorBy]]" color-by-params="[[colorByParams]]" node="[[itemNode]]" render-info="[[itemRenderInfo]]" template-index="[[templateIndex]]"></tf-graph-icon>
-        <span title$="[[name]]">[[name]]</span>
-        <span class="edge-label">[[edgeLabel]]</span>
-      </div>
-    </div>
-  </template>
-
-  <script>
-    (function() {
-      Polymer({
-        is: 'tf-node-list-item',
-
-        properties: {
-          /**
-           * The Node for the card itself, on which this item is being drawn.
-           * @type {tf.graph.Node}
-           */
-          cardNode: Object,
-          /**
-           * The Node for the item within the card, somehow related to cardNode.
-           * @type {tf.graph.Node}
-           */
-          itemNode: Object,
-          /** The edge label associated with this item. */
-          edgeLabel: String,
-          /**
-           * The render node information for the item node. Used by the graph
-           * icon in determining fill color.
-           */
-          itemRenderInfo: Object,
-          name: String,
-          itemType: {
-            type: String,
-            observer: '_itemTypeChanged'
-          },
-          colorBy: String,
-          colorByParams: Object,
-          templateIndex: Function
-        },
-
-        _itemTypeChanged: function() {
-          if (this.itemType !== 'subnode') {
-            this.$['list-item'].classList.add('clickable');
-          } else {
-            this.$['list-item'].classList.remove('clickable');
-          }
-        },
-
-        _nodeListener: function(event) {
-          // fire node.click/mouseover/mouseout
-          this.fire('node-list-item-' + event.type, {
-            cardNode: this.cardNode.name,
-            nodeName: this.name,
-            type: this.itemType
-          });
-        },
-
-        _fadedClass: function(itemRenderInfo) {
-          return itemRenderInfo && itemRenderInfo.isFadedOut ? 'faded' : '';
-        }
-      });
-    })();
-  </script>
-</dom-module>
-<link rel="import" href="../iron-list/iron-list.html">
-<link rel="import" href="../paper-item/all-imports.html">
-<dom-module id="tf-node-info" assetpath="../tf-graph-info/">
-  <style>
-  .sub-list-group {
-    font-weight: 500;
-    font-size: 12pt;
-    padding-bottom: 8px;
-    width: 100%;
-  }
-
-  .sub-list {
-    max-height: 300px;
-    overflow-y: scroll;
-  }
-
-  .attr-left {
-    float: left;
-    width: 30%;
-    word-wrap: break-word;
-    color: #565656;
-    font-size: 11pt;
-    font-weight: 400;
-  }
-
-  .attr-right {
-    margin-left: 30%;
-    word-wrap: break-word;
-    color: #565656;
-    font-weight: 400;
-  }
-
-  .sub-list-table {
-    display: table;
-    width: 100%;
-  }
-
-  .sub-list-table-row {
-    display: table-row;
-  }
-
-  .sub-list-table-row .sub-list-table-cell:last-child {
-    text-align: right;
-  }
-
-  .sub-list-table-cell {
-    color: #565656;
-    display: table-cell;
-    font-size: 11pt;
-    font-weight: 400;
-    max-width: 200px;
-    padding: 0 4px;
-  }
-
-  paper-item {
-    padding: 0;
-    background: #e9e9e9;
-  }
-
-  paper-item-body[two-line] {
-    min-height: 0;
-    padding: 8px 12px 4px;
-  }
-
-  .expandedInfo {
-    padding: 8px 12px;
-  }
-
-  .controlDeps {
-    padding: 0 0 0 8px;
-  }
-
-  .node-name {
-    white-space: normal;
-    word-wrap: break-word;
-    font-size: 14pt;
-    font-weight: 500;
-  }
-
-  .node-icon {
-    float: right;
-  }
-
-  .subtitle {
-    font-size: 12pt;
-    color: #5e5e5e;
-  }
-
-  .controlLine {
-    font-size: 11pt;
-    font-weight: 400;
-  }
-
-  .toggle-button {
-    float: right;
-    max-height: 20px;
-    max-width: 20px;
-    padding: 0;
-  }
-
-  .control-toggle-button {
-    float: left;
-    max-height: 20px;
-    max-width: 20px;
-    padding: 0;
-  }
-
-  .toggle-include-group {
-    padding-top: 4px;
-  }
-
-  .toggle-include {
-    margin: 5px 6px;
-    text-transform: none;
-    padding: 4px 6px;
-    font-size: 10pt;
-    background-color: #fafafa;
-    color: #666;
-  }
-
-  .toggle-include:hover {
-    background-color: var(--google-yellow-100);
-  }
-
-  .non-control-list-item {
-    padding-left: 10px;
-  }
-  </style>
-  <template>
-    <paper-item>
-      <paper-item-body two-line="">
-        <div>
-          <paper-icon-button icon="{{_getToggleIcon(_expanded)}}" on-click="_toggleExpanded" class="toggle-button">
-          </paper-icon-button>
-          <div class="node-name" id="nodetitle"></div>
-        </div>
-        <div secondary="">
-          <tf-graph-icon class="node-icon" node="[[_node]]" render-info="[[_getRenderInfo(nodeName, renderHierarchy)]]" color-by="[[colorBy]]" template-index="[[_templateIndex]]"></tf-graph-icon>
-          <template is="dom-if" if="{{_node.op}}">
-            <div class="subtitle">
-              Operation:
-              <span>[[_node.op]]</span>
-            </div>
-          </template>
-          <template is="dom-if" if="{{_node.metagraph}}">
-            <div class="subtitle">
-              Subgraph:
-              <span>[[_node.cardinality]]</span> nodes
-            </div>
-          </template>
-        </div>
-      </paper-item-body>
-    </paper-item>
-    <iron-collapse opened="{{_expanded}}">
-    <template is="dom-if" if="{{_expanded}}" restamp="true">
-      <div class="expandedInfo">
-        <div class="sub-list-group attributes">
-          Attributes
-          (<span>[[_attributes.length]]</span>)
-          <iron-list class="sub-list" id="attributesList" items="[[_attributes]]">
-            <template>
-              <div>
-                <div class="attr-left">[[item.key]]</div>
-                <div class="attr-right">[[item.value]]</div>
-              </div>
-            </template>
-          </iron-list>
-        </div>
-
-        <template is="dom-if" if="{{_device}}">
-          <div class="sub-list-group device">
-            <div class="attr-left">Device</div>
-            <div class="attr-right">[[_device]]</div>
-          </div>
-        </template>
-
-        <div class="sub-list-group predecessors">
-          Inputs
-          (<span>[[_totalPredecessors]]</span>)
-          <iron-list class="sub-list" id="inputsList" items="[[_predecessors.regular]]">
-            <template>
-              <tf-node-list-item class="non-control-list-item" card-node="[[_node]]" item-node="[[item.node]]" edge-label="[[item.edgeLabel]]" item-render-info="[[item.renderInfo]]" name="[[item.name]]" item-type="predecessors" color-by="[[colorBy]]" template-index="[[_templateIndex]]">
-              </tf-node-list-item>
-            </template>
-          </iron-list>
-          <template is="dom-if" if="[[_predecessors.control.length]]">
-            <div class="controlDeps">
-              <div class="controlLine">
-                <paper-icon-button icon="{{_getToggleIcon(_openedControlPred)}}" on-click="_toggleControlPred" class="control-toggle-button">
-                </paper-icon-button>
-                Control dependencies
-              </div>
-              <iron-collapse opened="{{_openedControlPred}}" no-animation="">
-                <template is="dom-if" if="{{_openedControlPred}}" restamp="true">
-                  <iron-list class="sub-list" items="[[_predecessors.control]]">
-                    <template>
-                      <tf-node-list-item card-node="[[_node]]" item-node="[[item.node]]" item-render-info="[[item.renderInfo]]" name="[[item.name]]" item-type="predecessors" color-by="[[colorBy]]" template-index="[[_templateIndex]]">
-                      </tf-node-list-item>
-                    </template>
-                  </iron-list>
-                </template>
-              </iron-collapse>
-            </div>
-          </template>
-        </div>
-
-        <div class="sub-list-group successors">
-          Outputs
-          (<span>[[_totalSuccessors]]</span>)
-          <iron-list class="sub-list" id="outputsList" items="[[_successors.regular]]">
-            <template>
-              <tf-node-list-item class="non-control-list-item" card-node="[[_node]]" item-node="[[item.node]]" edge-label="[[item.edgeLabel]]" item-render-info="[[item.renderInfo]]" name="[[item.name]]" item-type="successor" color-by="[[colorBy]]" template-index="[[_templateIndex]]">
-              </tf-node-list-item>
-            </template>
-          </iron-list>
-          <template is="dom-if" if="[[_successors.control.length]]">
-            <div class="controlDeps">
-              <div class="controlLine">
-                <paper-icon-button icon="{{_getToggleIcon(_openedControlSucc)}}" on-click="_toggleControlSucc" class="control-toggle-button">
-                </paper-icon-button>
-                Control dependencies
-              </div>
-              <iron-collapse opened="{{_openedControlSucc}}" no-animation="">
-                <template is="dom-if" if="{{_openedControlSucc}}" restamp="true">
-                  <iron-list class="sub-list" items="[[_successors.control]]">
-                    <template>
-                      <tf-node-list-item card-node="[[_node]]" item-node="[[item.node]]" item-render-info="[[item.renderInfo]]" name="[[item.name]]" item-type="successors" color-by="[[colorBy]]" template-index="[[_templateIndex]]">
-                      </tf-node-list-item>
-                    </template>
-                  </iron-list>
-                </template>
-              </iron-collapse>
-            </div>
-          </template>
-        </div>
-        <template is="dom-if" if="{{_hasDisplayableNodeStats}}">
-          <div class="sub-list-group node-stats">
-            Node Stats
-            <div class="sub-list-table">
-              <template is="dom-if" if="{{_nodeStats.totalBytes}}">
-                <div class="sub-list-table-row">
-                  <div class="sub-list-table-cell">Memory</div>
-                  <div class="sub-list-table-cell">[[_nodeStatsFormattedBytes]]</div>
-                </div>
-              </template>
-              <template is="dom-if" if="{{_nodeStats.totalMicros}}">
-                <div class="sub-list-table-row">
-                  <div class="sub-list-table-cell">Compute Time</div>
-                  <div class="sub-list-table-cell">[[_nodeStatsFormattedComputeTime]]</div>
-                </div>
-              </template>
-              <template is="dom-if" if="{{_nodeStats.outputSize}}">
-                <div class="sub-list-table-row">
-                  <div class="sub-list-table-cell">Tensor Output Sizes</div>
-                  <div class="sub-list-table-cell">
-                    <template is="dom-repeat" items="{{_nodeStatsFormattedOutputSizes}}">
-                      [[item]] <br>
-                    </template>
-                  </div>
-                </div>
-              </template>
-            </div>
-          </div>
-        </template>
-        <div class="toggle-include-group">
-          <paper-button raised="" class="toggle-include" on-click="_toggleInclude">
-            <span>[[_auxButtonText]]</span>
-          </paper-button>
-        </div>
-        <template is="dom-if" if="{{_isInSeries(_node)}}">
-          <div class="toggle-include-group">
-            <paper-button raised="" class="toggle-include" on-click="_toggleGroup">
-              <span>[[_groupButtonText]]</span>
-            </paper-button>
-          </div>
-        </template>
-      </div>
-    </template>
-    </iron-collapse>
-  </template>
-
-  <script>
-    (function() {
-      Polymer({
-        is: 'tf-node-info',
-
-        properties: {
-          nodeName: String,
-          graphHierarchy: Object,
-          renderHierarchy: Object,
-          /** What to color the nodes by (compute time, memory, device etc.) */
-          colorBy: String,
-          _templateIndex: {
-            type: Function,
-            computed: '_getTemplateIndex(graphHierarchy)'
-          },
-          _node: {
-            type: Object,
-            computed: '_getNode(nodeName, graphHierarchy)',
-            observer: '_resetState'
-          },
-          _nodeStats: {
-            type: Object,
-            computed: '_getNodeStats(nodeName, graphHierarchy)',
-            observer: '_resetState'
-          },
-          _hasDisplayableNodeStats: {
-            type: Object,
-            computed: '_getHasDisplayableNodeStats(_nodeStats)',
-          },
-          _nodeStatsFormattedBytes: {
-            type: String,
-            computed: '_getNodeStatsFormattedBytes(_nodeStats)',
-          },
-          _nodeStatsFormattedComputeTime: {
-            type: String,
-            computed: '_getNodeStatsFormattedComputeTime(_nodeStats)',
-          },
-          _nodeStatsFormattedOutputSizes: {
-            type: Array,
-            computed: '_getNodeStatsFormattedOutputSizes(_nodeStats)',
-          },
-          // The enum value of the include property of the selected node.
-          nodeInclude: {
-            type: Number,
-            observer: '_nodeIncludeStateChanged'
-          },
-          _attributes: {
-            type: Array,
-            computed: '_getAttributes(_node)'
-          },
-          _device: {
-            type: String,
-            computed: '_getDevice(_node)'
-          },
-          _successors: {
-            type: Object,
-            computed: '_getSuccessors(_node, graphHierarchy)'
-          },
-          _predecessors: {
-            type: Object,
-            computed: '_getPredecessors(_node, graphHierarchy)'
-          },
-          _subnodes: {
-            type: Array,
-            computed: '_getSubnodes(_node)'
-          },
-          _expanded: {
-            type: Boolean,
-            value: true
-          },
-          _totalPredecessors: {
-            type: Number,
-            computed: '_getTotalPred(_predecessors)'
-          },
-          _totalSuccessors: {
-            type: Number,
-            computed: '_getTotalSucc(_successors)'
-          },
-          _openedControlPred: {
-            type: Boolean,
-            value: false
-          },
-          _openedControlSucc: {
-            type: Boolean,
-            value: false
-          },
-          _auxButtonText: String,
-          _groupButtonText: String
-        },
-        expandNode: function() {
-          this.fire('_node.expand', this.node);
-        },
-        _getTemplateIndex: function(graphHierarchy) {
-          return graphHierarchy.getTemplateIndex();
-        },
-        _getNode: function(nodeName, graphHierarchy) {
-          return graphHierarchy.node(nodeName);
-        },
-        _getNodeStats: function(nodeName, graphHierarchy) {
-          var node = this._getNode(nodeName, graphHierarchy);
-          if (node) {
-            return node.stats;
-          }
-          return null;
-        },
-        _getHasDisplayableNodeStats: function(stats) {
-          return tf.graph.util.hasDisplayableNodeStats(stats);
-        },
-        _getNodeStatsFormattedBytes: function(stats) {
-          if (!stats || !stats.totalBytes) {
-            return;
-          }
-
-          return tf.graph.util.convertUnitsToHumanReadable(
-              stats.totalBytes, tf.graph.util.MEMORY_UNITS);
-        },
-        _getNodeStatsFormattedComputeTime: function(stats) {
-          if (!stats || !stats.totalMicros) {
-            return;
-          }
-
-          return tf.graph.util.convertUnitsToHumanReadable(
-              stats.totalMicros, tf.graph.util.TIME_UNITS);
-        },
-        _getNodeStatsFormattedOutputSizes: function(stats) {
-          if (!stats || !stats.outputSize || !stats.outputSize.length) {
-            return;
-          }
-
-          return _.map(stats.outputSize, function(shape) {
-            if (shape.length === 0) {
-              return "scalar";
-            }
-            return "[" + shape.join(", ") + "]";
-          });
-        },
-        _getPrintableHTMLNodeName: function(nodeName) {
-          // Insert an optional line break before each slash so that
-          // long node names wrap cleanly at path boundaries.
-          return (nodeName || '').replace(/\//g, '<wbr>/');
-        },
-        _getRenderInfo: function(nodeName, renderHierarchy) {
-          return this.renderHierarchy.getOrCreateRenderNodeByName(nodeName);
-        },
-        _getAttributes: function(node) {
-          this.async(this._resizeList.bind(this, "#attributesList"));
-          if (!node || !node.attr) {
-            return [];
-          }
-          var attrs = [];
-          _.each(node.attr, function(entry) {
-            // Unpack the "too large" attributes into separate attributes
-            // in the info card, with values "too large to show".
-            if (entry.key === tf.graph.LARGE_ATTRS_KEY) {
-              attrs = attrs.concat(entry.value.list.s.map(function(key) {
-                return {key: key, value: "Too large to show..."};
-              }));
-            } else {
-              attrs.push({
-                key: entry.key,
-                value: JSON.stringify(entry.value)
-              });
-            }
-          });
-          return attrs;
-        },
-        _getDevice: function(node) {
-          return node ? node.device : null;
-        },
-        _getSuccessors: function(node, hierarchy) {
-          this.async(this._resizeList.bind(this, "#inputsList"));
-          if (!node) {
-            return {regular: [], control: []}
-          }
-          return this._convertEdgeListToEdgeInfoList(
-            hierarchy.getSuccessors(node.name), false, node.isGroupNode);
-        },
-        _getPredecessors: function(node, hierarchy) {
-          this.async(this._resizeList.bind(this, "#outputsList"));
-          if (!node) {
-            return {regular: [], control: []}
-          }
-          return this._convertEdgeListToEdgeInfoList(
-            hierarchy.getPredecessors(node.name), true, node.isGroupNode);
-        },
-        _convertEdgeListToEdgeInfoList: function(list, isPredecessor, isGroupNode) {
-
-          /**
-           * Unpacks the metaedge into a list of base edge information
-           * that can be rendered.
-           */
-          var unpackMetaedge = function(metaedge) {
-            return _.map(metaedge.baseEdgeList, function(baseEdge) {
-              name = isPredecessor ? baseEdge.v : baseEdge.w;
-              return {
-                name: name,
-                node: this._getNode(name, this.graphHierarchy),
-                edgeLabel: tf.graph.scene.edge.getLabelForBaseEdge(baseEdge,
-                    this.renderHierarchy),
-                renderInfo: this._getRenderInfo(name, this.renderHierarchy)
-              };
-            }, this);
-          }.bind(this);
-
-          /**
-           * Converts a list of metaedges to a list of edge information
-           * that can be rendered.
-           */
-          var toEdgeInfoList = function(edges) {
-            var edgeInfoList = [];
-            _.each(edges, function(metaedge) {
-              var name = isPredecessor ? metaedge.v : metaedge.w;
-              // Enumerate all the base edges if the node is an OpNode, or the
-              // metaedge has only 1 edge in it.
-              if (!isGroupNode || metaedge.baseEdgeList.length == 1) {
-                edgeInfoList = edgeInfoList.concat(unpackMetaedge(metaedge));
-              } else {
-                edgeInfoList.push({
-                  name: name,
-                  node: this._getNode(name, this.graphHierarchy),
-                  edgeLabel: tf.graph.scene.edge.getLabelForEdge(metaedge,
-                      this.renderHierarchy),
-                  renderInfo: this._getRenderInfo(name, this.renderHierarchy)
-                });
-              }
-            }, this);
-            return edgeInfoList;
-          }.bind(this);
-
-          return {
-            regular: toEdgeInfoList(list.regular),
-            control: toEdgeInfoList(list.control)
-          };
-        },
-        _getSubnodes: function(node) {
-          return node && node.metagraph ? node.metagraph.nodes() : null;
-        },
-        _getTotalPred: function(predecessors) {
-          return predecessors.regular.length + predecessors.control.length;
-        },
-        _getTotalSucc: function(successors) {
-          return successors.regular.length + successors.control.length;
-        },
-        _toggleControlPred: function() {
-          this._openedControlPred = !this._openedControlPred;
-        },
-        _toggleControlSucc: function() {
-          this._openedControlSucc = !this._openedControlSucc;
-        },
-        _toggleExpanded: function() {
-          this._expanded = !this._expanded;
-        },
-        _getToggleIcon: function(expanded) {
-          return expanded ? "expand-less" : "expand-more";
-        },
-        _resetState: function() {
-          this._openedControlPred = false;
-          this._openedControlSucc = false;
-
-          this.set("_groupButtonText",
-            tf.graph.scene.node.getGroupSettingLabel(this._node));
-
-          if (this._node) {
-            Polymer.dom(this.$.nodetitle).innerHTML =
-              this._getPrintableHTMLNodeName(this._node.name);
-          }
-        },
-        _resizeList: function(selector) {
-          var list = document.querySelector(selector);
-          if (list) {
-            list.fire('iron-resize');
-          }
-        },
-        _toggleInclude: function() {
-          var graphElem = document.querySelector("#graph");
-          graphElem.fire("node-toggle-extract", { name: this.nodeName });
-          var graphBoardElem = document.querySelector("#graphboard");
-          graphBoardElem.fire("node-toggle-extract");
-        },
-        _nodeIncludeStateChanged: function(include, oldInclude) {
-          this.set("_auxButtonText",
-            tf.graph.getIncludeNodeButtonString(include));
-        },
-        _toggleGroup: function() {
-          var graphElem = document.querySelector("#graph");
-          var seriesName = tf.graph.scene.node.getSeriesName(this._node);
-          graphElem.fire("node-toggle-seriesgroup", { name: seriesName });
-        },
-        _isInSeries: function(node) {
-          return tf.graph.scene.node.canBeInSeries(node);
-        }
-      });
-    })();
-  </script>
-</dom-module>
-<dom-module id="tf-graph-info" assetpath="../tf-graph-info/">
-<template>
-<style>
-:host {
-  font-size: 12px;
-  margin: 0;
-  padding: 0;
-  display: block;
-}
-
-h2 {
-  padding: 0;
-  text-align: center;
-  margin: 0;
-}
-
-.health-pill-legend {
-  padding: 15px;
-}
-
-.health-pill-legend h2 {
-  text-align: left;
-}
-
-.health-pill-entry {
-  margin: 10px 10px 10px 0;
-}
-
-.health-pill-entry .color-preview {
-  width: 26px;
-  height: 26px;
-  border-radius: 3px;
-  display: inline-block;
-  margin: 0 10px 0 0;
-}
-
-.health-pill-entry .color-label, .health-pill-entry .tensor-count {
-  color: #777;
-  display: inline-block;
-  height: 26px;
-  font-size: 22px;
-  line-height: 26px;
-  vertical-align: top;
-}
-
-.health-pill-entry .tensor-count {
-  float: right;
-}
-
-#health-pill-step-slider {
-  width: 100%;
-  margin: 0 0 0 -15px;
-  /* 31 comes from adding a padding of 15px from both sides of the paper-slider, subtracting
-   * 1px so that the slider width aligns with the image (the last slider marker takes up 1px),
-   * and adding 2px to account for a border of 1px on both sides of the image. 30 - 1 + 2.
-   * Apparently, the paper-slider lacks a mixin for those padding values. */
-  width: calc(100% + 31px);
-}
-
-#health-pills-loading-spinner {
-  width: 20px;
-  height: 20px;
-  vertical-align: top;
-}
-
-#health-pill-step-number-input {
-  text-align: center;
-  vertical-align: top;
-}
-</style>
-<template is="dom-if" if="{{selectedNode}}">
-  <paper-material elevation="1" class="card">
-    <tf-node-info graph-hierarchy="[[graphHierarchy]]" render-hierarchy="[[renderHierarchy]]" flat-graph="[[graph]]" node-name="[[selectedNode]]" node-include="[[selectedNodeInclude]]" highlighted-node="{{highlightedNode}}" color-by="[[colorBy]]">
-    </tf-node-info>
-  </paper-material>
-</template>
-<template is="dom-if" if="[[_healthPillsAvailable(debuggerDataEnabled, nodeNamesToHealthPills)]]">
-  <paper-material elevation="1" class="card health-pill-legend">
-    <div class="title">
-      Enable all (not just sampled) steps. Requires slow disk read.
-    </div>
-    <paper-toggle-button id="enableAllStepsModeToggle" checked="{{allStepsModeEnabled}}">
-    </paper-toggle-button>
-    <h2>
-      Step of Health Pills:
-      <template is="dom-if" if="[[allStepsModeEnabled]]">
-        <input type="number" id="health-pill-step-number-input" min="0" max="[[_biggestStepEverSeen]]" value="{{specificHealthPillStep::input}}">
-      </template>
-      <template is="dom-if" if="[[!allStepsModeEnabled]]">
-        [[_currentStepDisplayValue]]
-      </template>
-
-      <paper-spinner-lite active="" hidden$="[[!areHealthPillsLoading]]" id="health-pills-loading-spinner"></paper-spinner-lite>
-    </h2>
-    <template is="dom-if" if="[[allStepsModeEnabled]]">
-      <paper-slider id="health-pill-step-slider" immediate-value="{{specificHealthPillStep}}" max="[[_biggestStepEverSeen]]" snaps="" step="1" value="{{specificHealthPillStep}}"></paper-slider>
-    </template>
-    <template is="dom-if" if="[[!allStepsModeEnabled]]">
-      <template is="dom-if" if="[[_maxStepIndex]]">
-        <paper-slider id="health-pill-step-slider" immediate-value="{{healthPillStepIndex}}" max="[[_maxStepIndex]]" snaps="" step="1" value="{{healthPillStepIndex}}"></paper-slider>
-      </template>
-    </template>
-    <h2>
-      Health Pill
-      <template is="dom-if" if="[[healthPillValuesForSelectedNode]]">
-        Counts for Selected Node
-      </template>
-      <template is="dom-if" if="[[!healthPillValuesForSelectedNode]]">
-        Legend
-      </template>
-    </h2>
-    <template is="dom-repeat" items="[[healthPillEntries]]">
-      <div class="health-pill-entry">
-        <div class="color-preview" style="background:[[item.background_color]]"></div>
-        <div class="color-label">[[item.label]]</div>
-        <div class="tensor-count">
-          [[_computeTensorCountString(healthPillValuesForSelectedNode, index)]]
-        </div>
-      </div>
-    </template>
-  </paper-material>
-</template>
-</template>
-<script>
-(function() {
-  Polymer({
-    is: 'tf-graph-info',
-
-    properties: {
-      title: String,
-      graphHierarchy: Object,
-      graph: Object,
-      renderHierarchy: Object,
-      nodeNamesToHealthPills: Object,
-      healthPillStepIndex: {
-        type: Number,
-        notify: true,
-      },
-      // Only relevant if we are in all steps mode, in which case the user may want to view health
-      // pills for a specific step.
-      specificHealthPillStep: {
-        type: Number,
-        value: 0,
-        notify: true,
-      },
-      colorBy: String,
-      // Two-ways
-      selectedNode: {
-        type: String,
-        notify: true
-      },
-      highlightedNode: {
-        type: String,
-        notify: true
-      },
-      // The enum value of the include property of the selected node.
-      selectedNodeInclude: {
-        type: Number,
-        notify: true
-      },
-      // Whether debugger data is enabled for this instance of Tensorboard.
-      debuggerDataEnabled: Boolean,
-      // Whether health pills are currently being loaded, in which case we show a spinner (and the
-      // current health pills shown might be out of date).
-      areHealthPillsLoading: Boolean,
-      healthPillEntries: {
-        type: Array,
-        value: tf.graph.scene.healthPillEntries,
-        readOnly: true,
-      },
-      healthPillValuesForSelectedNode: {
-        type: Array,
-        computed: '_computeHealthPillForNode(nodeNamesToHealthPills, healthPillStepIndex, selectedNode, allStepsModeEnabled, areHealthPillsLoading)',
-      },
-      // When all-steps mode is enabled, the user can request health pills for any step. In this
-      // mode, Tensorboard makes a request every time the user drags the slider to a different step.
-      allStepsModeEnabled: {
-        type: Boolean,
-        notify: true,
-      },
-      // The biggest step value ever seen. Used to determine what steps of health pills to let the
-      // user fetch in all steps mode.
-      _biggestStepEverSeen: {
-        type: Number,
-        computed: '_computeBiggestStepEverSeen(nodeNamesToHealthPills)',
-      },
-      _maxStepIndex: {
-        type: Number,
-        computed: '_computeMaxStepIndex(nodeNamesToHealthPills)',
-      },
-      _currentStepDisplayValue: {
-        type: String,
-        computed: '_computeCurrentStepDisplayValue(nodeNamesToHealthPills, healthPillStepIndex, allStepsModeEnabled, specificHealthPillStep, areHealthPillsLoading)',
-      },
-    },
-    listeners: {
-      'node-list-item-click': '_nodeListItemClicked',
-      'node-list-item-mouseover': '_nodeListItemMouseover',
-      'node-list-item-mouseout': '_nodeListItemMouseout'
-    },
-    _nodeListItemClicked: function(event) {
-      this.selectedNode = event.detail.nodeName;
-    },
-    _nodeListItemMouseover: function(event) {
-      this.highlightedNode = event.detail.nodeName;
-    },
-    _nodeListItemMouseout: function() {
-      this.highlightedNode = null;
-    },
-    _healthPillsAvailable: function(debuggerDataEnabled, nodeNamesToHealthPills) {
-      // So long as there is a mapping (even if empty) from node name to health pills, show the
-      // legend and slider. We do that because, even if no health pills exist at the current step,
-      // the user may desire to change steps, and the slider must show for the user to do that.
-      return debuggerDataEnabled && nodeNamesToHealthPills;
-    },
-    _computeTensorCountString: function(healthPillValuesForSelectedNode, valueIndex) {
-      if (!healthPillValuesForSelectedNode) {
-        // No health pill data is available.
-        return '';
-      }
-
-      return healthPillValuesForSelectedNode[valueIndex].toFixed(0);
-    },
-    _computeHealthPillForNode: function(
-        nodeNamesToHealthPills, healthPillStepIndex, selectedNode, allStepsModeEnabled, areHealthPillsLoading) {
-      if (areHealthPillsLoading) {
-        // Health pills are loading. Do not render data that is out of date.
-        return null;
-      }
-
-      if (!selectedNode) {
-        // No node is selected.
-        return null;
-      }
-
-      const healthPills = nodeNamesToHealthPills[selectedNode];
-      if (!healthPills) {
-        // This node lacks a health pill.
-        return null;
-      }
-
-      // If all steps mode is enabled, we use the first health pill in the list because the JSON
-      // response from the server is a mapping between node name and a list of 1 health pill.
-      const healthPill = healthPills[allStepsModeEnabled ? 0 : healthPillStepIndex];
-      if (!healthPill) {
-        // This node lacks a health pill at the current step.
-        return null;
-      }
-
-      // The health pill count values start at 2. Each health pill contains 6 values.
-      return healthPill.value.slice(2, 8);
-    },
-    _computeCurrentStepDisplayValue: function(
-        nodeNamesToHealthPills,
-        healthPillStepIndex,
-        allStepsModeEnabled,
-        specificHealthPillStep,
-        areHealthPillsLoading) {
-      if (allStepsModeEnabled) {
-        // The user seeks health pills for specific step from the server.
-        return specificHealthPillStep.toFixed(0);
-      }
-
-      if (areHealthPillsLoading) {
-        // The current step is undefined.
-        return 0;
-      }
-
-      for (let nodeName in nodeNamesToHealthPills) {
-        // All nodes have the same number of steps stored, so only examine 1 node. We cannot
-        // directly index into the nodeNamesToHealthPills object because we do not have a key.
-        // If all steps mode is enabled, we only have 1 step to show.
-        return nodeNamesToHealthPills[nodeName][healthPillStepIndex].step.toFixed(0);
-      }
-
-      // The current step could not be computed.
-      return 0;
-    },
-    _computeBiggestStepEverSeen: function(nodeNamesToHealthPills) {
-      for (let nodeName in nodeNamesToHealthPills) {
-        // All nodes have the same number of steps stored, so only examine 1 node.
-        // The index is 1 less than the count. Tensorboard backend logic guarantees that the length
-        // of the array will be greater than 1.
-        var healthPills = nodeNamesToHealthPills[nodeName];
-        return Math.max(this._biggestStepEverSeen, healthPills[healthPills.length - 1].step);
-      }
-
-      // No steps seen so far. Default to 0.
-      return this._biggestStepEverSeen || 0;
-    },
-    _computeMaxStepIndex: function(nodeNamesToHealthPills) {
-      for (let nodeName in nodeNamesToHealthPills) {
-        // All nodes have the same number of steps stored, so only examine 1 node.
-        // The index is 1 less than the count. Tensorboard backend logic guarantees that the length
-        // of the array will be greater than 1.
-        return nodeNamesToHealthPills[nodeName].length - 1;
-      }
-
-      // Return a falsy value. The slider should be hidden.
-      return 0;
-    },
-  });
-})();
-</script>
-</dom-module>
-<link rel="import" href="../paper-progress/paper-progress.html">
-
-
-<dom-module id="tf-graph-board" assetpath="../tf-graph-board/">
-<template>
-<style>
-::host {
-  display: block;
-}
-
-/deep/ .close {
-  position: absolute;
-  cursor: pointer;
-  left: 15px;
-  bottom: 15px;
-}
-
-.container {
-  width: 100%;
-  height: 100%;
-  opacity: 1;
-}
-
-.container.loading {
-  cursor: progress;
-  opacity: 0.1;
-}
-
-.container.loading.error {
-  cursor: auto;
-}
-
-#info {
-  position: absolute;
-  right: 5px;
-  top: 5px;
-  padding: 0px;
-  max-width: 380px;
-  min-width: 320px;
-  background-color: rgba(255,255,255,0.9);
-  @apply(--shadow-elevation-2dp);
-}
-
-#main {
-  width: 100%;
-  height: 100%;
-}
-
-#progress-bar {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  justify-content: center;
-  width: 100%;
-  position: absolute;
-  top: 40px;
-  left: 0;
-  font-size: 13px;
-}
-
-#progress-msg {
-  width: 400px;
-  margin-bottom: 5px;
-}
-
-paper-progress {
-  width: 400px;
-  --paper-progress-height: 6px;
-  --paper-progress-active-color: #f3913e;
-}
-
-.context-menu {
-  position: absolute;
-  display: none;
-  background-color: #e2e2e2;
-  border-radius: 2px;
-  font-size: 14px;
-  min-width: 150px;
-  border: 1px solid #d4d4d4;
-}
-
-/deep/ .context-menu ul {
-  list-style-type: none;
-  margin: 0;
-  padding: 0;
-  cursor: default;
-}
-
-/deep/ .context-menu ul li {
-  padding: 4px 16px;
-}
-
-/deep/ .context-menu ul li:hover {
-  background-color: #f3913e;
-  color: white;
-}
-</style>
-<template is="dom-if" if="[[_isNotComplete(progress)]]">
-  <div id="progress-bar">
-    <div id="progress-msg">[[progress.msg]]</div>
-    <paper-progress value="[[progress.value]]"></paper-progress>
-  </div>
-</template>
-<div class$="[[_getContainerClass(progress)]]">
-  <div id="main">
-    <tf-graph id="graph" graph-hierarchy="{{graphHierarchy}}" basic-graph="[[graph]]" hierarchy-params="[[hierarchyParams]]" render-hierarchy="{{renderHierarchy}}" devices-for-stats="[[devicesForStats]]" stats="[[stats]]" selected-node="{{_selectedNode}}" highlighted-node="{{_highlightedNode}}" color-by="[[colorBy]]" color-by-params="{{colorByParams}}" progress="{{progress}}" node-names-to-health-pills="[[nodeNamesToHealthPills]]" health-pill-step-index="[[healthPillStepIndex]]"></tf-graph>
-  </div>
-  <div id="info">
-    <tf-graph-info id="graph-info" title="selected" graph-hierarchy="[[graphHierarchy]]" render-hierarchy="[[renderHierarchy]]" graph="[[graph]]" selected-node="{{_selectedNode}}" selected-node-include="{{_selectedNodeInclude}}" highlighted-node="{{_highlightedNode}}" color-by="[[colorBy]]" color-by-params="[[colorByParams]]" debugger-data-enabled="[[debuggerDataEnabled]]" are-health-pills-loading="[[areHealthPillsLoading]]" node-names-to-health-pills="[[nodeNamesToHealthPills]]" all-steps-mode-enabled="{{allStepsModeEnabled}}" specific-health-pill-step="{{specificHealthPillStep}}" health-pill-step-index="{{healthPillStepIndex}}"></tf-graph-info>
-  </div>
-  <div class="context-menu"></div>
-</div>
-</template>
-</dom-module>
-
-<script>
-Polymer({
-  is: 'tf-graph-board',
-  properties: {
-    // Public API.
-    graphHierarchy: Object,
-    graph: Object,
-    stats: Object,
-    /**
-     * @type {value: number, msg: string}
-     *
-     * A number between 0 and 100 denoting the % of progress
-     * for the progress bar and the displayed message.
-     */
-    progress: Object,
-    colorBy: String,
-    colorByParams: {
-      type: Object,
-      notify: true
-    },
-    renderHierarchy: {
-      type: Object,
-      notify: true
-    },
-    // Whether debugger data is enabled for this instance of Tensorboard.
-    debuggerDataEnabled: Boolean,
-    // Whether health pills are currently being loaded.
-    areHealthPillsLoading: Boolean,
-    // A mapping between node name to the tf.graph.scene.HealthPill to render.
-    nodeNamesToHealthPills: Object,
-    // Whether the user can request health pills for individual steps from the server. This can be
-    // slow compared the default of showing sampled health pills.
-    allStepsModeEnabled: {
-      type: Boolean,
-      notify: true,
-      value: false,
-    },
-    // Relevant if allStepsModeEnabled. The specific step for which to fetch health pills from the
-    // server for.
-    specificHealthPillStep: {
-      type: Number,
-      notify: true,
-      value: 0,
-    },
-    // The step of health pills to show throughout the graph.
-    healthPillStepIndex: Number,
-    // Private API: Data routing between child components.
-    _selectedNode: String,
-    // The enum value of the include property of the selected node.
-    _selectedNodeInclude: Number,
-    _highlightedNode: String
-  },
-  listeners: {
-    'node-toggle-extract': '_nodeToggleExtract'
-  },
-  observers: [
-    '_updateNodeInclude(_selectedNode)'
-  ],
-  /** True if the progress is not complete yet (< 100 %). */
-  _isNotComplete: function(progress) {
-    return progress.value < 100;
-  },
-  _getContainerClass: function(progress) {
-    var result = 'container';
-    if (progress.error) {
-      result += ' error';
-    }
-    if (this._isNotComplete(progress)) {
-      result += ' loading';
-    }
-    return result;
-  },
-  _updateNodeInclude: function(nodeName) {
-    var node = this.graphHierarchy.node(nodeName);
-    this.set("_selectedNodeInclude",
-      node ? node.include : tf.graph.InclusionType.UNSPECIFIED);
-  },
-  _nodeToggleExtract: function() {
-    this._updateNodeInclude(this._selectedNode);
-  }
-});
-</script>
-<link rel="import" href="../paper-radio-group/paper-radio-group.html">
-<link rel="import" href="../paper-tooltip/paper-tooltip.html">
-<dom-module id="tf-graph-controls" assetpath="../tf-graph/">
-<template>
-<style>
-:host {
-  font-size: 12px;
-  color: gray;
-  --paper-font-subhead: {
-    font-size: 14px;
-    color: gray;
-  };
-  --paper-dropdown-menu-icon: {
-    width: 15px;
-    height: 15px;
-  };
-  --paper-dropdown-menu-button: {
-    padding: 0;
-  };
-  --paper-dropdown-menu-input: {
-    padding: 0;
-  };
-  --paper-item-min-height: 30px;
-}
-
-paper-button[raised].keyboard-focus {
-  font-weight: normal;
-}
-
-.run-dropdown {
-  --paper-input-container: {
-    padding: 9px 0 0 25px;
-  };
-}
-
-.color-dropdown {
-  --paper-input-container: {
-    padding: 9px 0 0 13px;
-  };
-}
-
-table {
-  border-collapse: collapse;
-  border-spacing: 0;
-}
-
-table td {
-  padding: 0;
-  margin: 0;
-}
-
-.allcontrols {
-  width: 188px;
-  padding: 0 30px;
-}
-
-.legend-holder {
-  position: absolute;
-  bottom: 0;
-  padding-bottom: 10px;
-}
-
-paper-radio-button {
-  display: block;
-  padding: 5px;
-}
-svg.icon {
-  width: 60px;
-  height: 18px;
-}
-.icon ellipse {
-  rx: 10px;
-  ry: 5px;
-  stroke: #CCC;
-  stroke-width: 1px;
-  fill: #FFFFFF;
-  cy: 10px;
-}
-.icon rect {
-  height: 14px;
-  width: 35px;
-  rx: 5px;
-  ry: 5px;
-  stroke: #CCC;
-  stroke-width: 2px;
-  fill: #D9D9D9;
-}
-.domainValues {
-  margin-bottom: 10px;
-  width: 165px;
-}
-.domainStart {
-  float: left;
-}
-.domainEnd {
-  float: right;
-}
-.colorBox {
-  width: 20px;
-}
-
-.image-icon {
-  width: 24px;
-  height: 24px;
-}
-
-.help-icon {
-  height: 15px;
-  margin: 0;
-  padding: 0;
-}
-
-.gray {
-  color: #666;
-}
-
-.title {
-  font-size: 16px;
-  margin: 8px 5px 8px 0;
-  color: black;
-}
-.title small {
-  font-weight: normal;
-}
-.deviceList, .xlaClusterList {
-  max-height: 200px;
-  overflow-y: auto;
-}
-
-#file {
-  padding: 8px 0;
-}
-
-.color-legend-row {
-  clear: both;
-  height: 20px;
-  margin-top: 5px;
-  position: relative;
-}
-
-.color-legend-row svg {
-  position: absolute;
-  top: -1px;
-  width: 40px;
-}
-
-.color-legend-row span.color-legend-value {
-  margin-left: 60px;
-}
-
-#grey-rect {
-  fill: #eee;
-  stroke: #a6a6a6;
-}
-
-#faded-rect {
-  fill: url("#rectHatch");
-  stroke: var(--tb-graph-faded);
-}
-
-.button-text {
-  text-transform: none;
-  padding: 8px 18px 0 18px;
-  font-size: 14px
-}
-
-.upload-button {
-  width: 165px;
-  height: 25px;
-  text-transform: none;
-  margin-top: 4px;
-}
-
-.iconbutton {
-  padding: 2px;
-  width: 30px;
-  height: 30px;
-  color: var(--paper-orange-500);
-}
-
-.hidden-input {
-  height: 0px;
-  width: 0px;
-  overflow:hidden;
-}
-
-.allcontrols .control-holder {
-  display: flex;
-  clear: both;
-}
-
-.allcontrols .control-holder paper-radio-group {
-  margin-top: 5px;
-}
-
-span.counter {
-  font-size: 13px;
-  color: gray;
-}
-
-.runs paper-item {
-  --paper-item: {
-    white-space: nowrap;
-  }
-}
-
-table.control-holder {
-  border: 0;
-  border-collapse: collapse;
-}
-
-table.tf-graph-controls td.input-element-table-data {
-  padding: 0 0 0 20px;
-}
-
-/** Override inline styles that suppress pointer events for disabled buttons. Otherwise, the */
-/*  tooltips do not appear. */
-#color-by-radio-group paper-radio-button {
-  pointer-events: auto !important;
-}
-</style>
-<svg width="0" height="0">
-  <defs>
-    <g id="legend-rect">
-      <rect x="1" y="1" stroke-width="2px" height="14" width="35" rx="5" ry="5"></rect>
-    </g>
-    <g id="grey-rect">
-       <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#legend-rect"></use>
-     </g>
-     <g id="faded-rect">
-       <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#legend-rect"></use>
-     </g>
-  </defs>
-</svg>
-<div class="allcontrols">
-  <div class="control-holder">
-    <paper-icon-button icon="aspect-ratio" class="iconbutton" on-click="fit" alt="Fit to screen">
-    </paper-icon-button>
-    <paper-button class="button-text" on-click="fit">Fit to screen
-    </paper-button>
-  </div>
-  <div class="control-holder">
-    <paper-icon-button icon="file-download" class="iconbutton" on-click="download" alt="Download PNG">
-    </paper-icon-button>
-    <paper-button class="button-text" on-click="download">Download PNG
-    </paper-button>
-    <a href="#" id="graphdownload" class="title" download="graph.png">
-    </a>
-  </div>
-  <div class="control-holder runs">
-    <div class="title">Run <span class="counter">([[datasets.length]])</span></div>
-    <paper-dropdown-menu no-label-float="" no-animations="" noink="" class="run-dropdown">
-      <paper-menu id="select" class="dropdown-content" selected="{{selectedDataset}}">
-        <template is="dom-repeat" items="[[datasets]]">
-          <paper-item>[[item.name]]</paper-item>
-        </template>
-      </paper-menu>
-    </paper-dropdown-menu>
-  </div>
-  <template is="dom-if" if="[[showSessionRunsDropdown]]">
-    <div class="control-holder">
-      <div class="title">Session runs <span class="counter">([[_numSessionRuns(metadataTags)]])</span></div>
-      <paper-dropdown-menu no-label-float="" no-animations="" noink="" class="run-dropdown">
-        <paper-menu id="select" class="dropdown-content" selected="{{selectedMetadataTag}}">
-          <template is="dom-repeat" items="[[metadataTags]]">
-            <paper-item>[[item.tag]]</paper-item>
-          </template>
-          <paper-item>None</paper-item>
-        </paper-menu>
-      </paper-dropdown-menu>
-    </div>
-  </template>
-  <template is="dom-if" if="[[showUploadButton]]">
-    <div class="control-holder">
-      <div class="title">Upload</div>
-      <paper-button raised="" class="text-button upload-button" on-click="_getFile">Choose File</paper-button>
-      <div class="hidden-input">
-        <input type="file" id="file" name="file" on-change="_updateFileInput">
-      </div>
-    </div>
-  </template>
-  <table class="control-holder">
-    <tbody><tr>
-      <td class="title">Trace inputs</td>
-      <td class="input-element-table-data">
-        <paper-toggle-button id="trace-inputs"></paper-toggle-button>
-      </td>
-    </tr>
-    <template is="dom-if" if="[[healthPillsFeatureEnabled]]">
-      <tr>
-        <td class="title">Show health pills</td>
-        <td class="input-element-table-data">
-          <paper-toggle-button checked="{{healthPillsToggledOn}}"></paper-toggle-button>
-        </td>
-      </tr>
-    </template>
-  </tbody></table>
-  <div class="control-holder">
-    <div class="title">Color</div>
-    <paper-radio-group id="color-by-radio-group" selected="{{colorBy}}">
-      <paper-radio-button name="structure">Structure</paper-radio-button>
-
-      <paper-radio-button name="device">Device</paper-radio-button>
-
-      <paper-radio-button id="xla-cluster-radio-button" name="xla_cluster" disabled="[[!_xlaClustersProvided(renderHierarchy)]]">
-        XLA Cluster
-      </paper-radio-button>
-      <paper-tooltip for="xla-cluster-radio-button" position="right">
-        Coloring by XLA cluster is only enabled if at least 1 op specifies an XLA cluster.
-      </paper-tooltip>
-
-      <paper-radio-button id="compute-time-radio-button" name="compute_time" disabled="[[!stats]]">
-        Compute time
-      </paper-radio-button>
-      <paper-tooltip for="compute-time-radio-button" position="right">
-        Coloring by compute time is only enabled if the RunMetadata proto is passed to the
-        FileWriter when a specific session is run.
-      </paper-tooltip>
-
-      <paper-radio-button id="memory-radio-button" name="memory" disabled="[[!stats]]">
-        Memory
-      </paper-radio-button>
-      <paper-tooltip for="memory-radio-button" position="right">
-        Coloring by memory is only enabled if the RunMetadata proto is passed to the
-        FileWriter when a specific session is run.
-      </paper-tooltip>
-    </paper-radio-group>
-  </div>
-  <div>
-    <template is="dom-if" if="[[_isGradientColoring(stats, colorBy)]]">
-      <svg width="140" height="20" style="margin: 0 5px" class="color-text">
-        <defs>
-          <linearGradient id="linearGradient" x1="0%" y1="0%" x2="100%" y2="0%">
-            <stop class="start" offset="0%" stop-color$="[[_currentGradientParams.startColor]]"></stop>
-            <stop class="end" offset="100%" stop-color$="[[_currentGradientParams.endColor]]"></stop>
-          </linearGradient>
-        </defs>
-        <rect x="0" y="0" width="135" height="20" fill="url(#linearGradient)" stroke="black"></rect>
-      </svg>
-      <div class="domainValues color-text">
-        <div class="domainStart">[[_currentGradientParams.minValue]]</div>
-        <div class="domainEnd">[[_currentGradientParams.maxValue]]</div>
-      </div>
-      <br style="clear: both">
-      <div>Devices included in stats:</div>
-      <div class="deviceList">
-        <table>
-        <template is="dom-repeat" items="[[_getDevices(devicesForStats)]]">
-          <tr>
-            <td>
-              <input type="checkbox" value$="[[item.device]]" checked$="[[item.used]]" on-click="_deviceCheckboxClicked">
-            </td>
-            <td>
-              <div>
-                <span>[[item.suffix]]</span>
-                <template is="dom-if" if="[[item.ignoredMsg]]">
-                  <paper-icon-button icon="help" class="help-icon"></paper-icon-button>
-                  <paper-tooltip position="right" animation-delay="0">[[item.ignoredMsg]]</paper-tooltip>
-                </template>
-              </div>
-            </td>
-          </tr>
-        </template>
-        </table>
-      </div>
-    </template>
-    <template is="dom-if" if="[[_equals(colorBy, 'structure')]]">
-      <div class="color-text">
-        <div class="color-legend-row">
-          <div style="position: absolute;">
-            colors
-          </div>
-          <span class="color-legend-value">same substructure</span>
-        </div>
-        <div class="color-legend-row">
-          <svg>
-            <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#grey-rect" x="0" y="0"></use>
-          </svg>
-          <span class="color-legend-value">unique substructure</span>
-        </div>
-      </div>
-    </template>
-    <template is="dom-if" if="[[_equals(colorBy, 'device')]]">
-      <div class="color-text">
-        <div class="deviceList">
-          <table>
-          <template is="dom-repeat" items="[[colorByParams.device]]">
-            <tr>
-              <td style$="[[_getBackgroundColor(item.color)]]">
-                <div class="colorBox"></div>
-              </td>
-              <td>
-                <div>[[item.device]]</div>
-              </td>
-            </tr>
-          </template>
-          </table>
-        </div>
-        <br>
-        <div class="color-legend-row">
-          <svg>
-            <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#grey-rect" x="0" y="0"></use>
-          </svg>
-          <span class="color-legend-value">unknown device</span>
-        </div>
-      </div>
-    </template>
-    <template is="dom-if" if="[[_equals(colorBy, 'xla_cluster')]]">
-      <div class="color-text">
-        <div class="xlaClusterList">
-          <table>
-          <template is="dom-repeat" items="[[colorByParams.xla_cluster]]">
-            <tr>
-              <td style$="[[_getBackgroundColor(item.color)]]">
-                <div class="colorBox"></div>
-              </td>
-              <td>
-                <div>[[item.xla_cluster]]</div>
-              </td>
-            </tr>
-          </template>
-          </table>
-        </div>
-        <br>
-        <div class="color-legend-row">
-          <svg>
-            <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#grey-rect" x="0" y="0"></use>
-          </svg>
-          <span class="color-legend-value">unknown XLA cluster</span>
-        </div>
-      </div>
-    </template>
-    <template is="dom-if" if="[[_statsNotNull(stats)]]">
-      <div class="color-legend-row">
-        <svg>
-          <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#faded-rect" x="0" y="0"></use>
-        </svg>
-        <span class="color-legend-value">unused substructure</span>
-      </div>
-    </template>
-  </div>
-  
-  <template is="dom-if" if="[[!_isGradientColoring(stats, colorBy)]]">
-    <div class="legend-holder">
-      <table>
-        <tbody><tr>
-          <td><div class="title">Graph</div></td>
-          <td>(* = expandable)</td>
-        </tr>
-        <tr>
-          <td>
-            <svg class="icon">
-              <rect transform="translate(3, 1)" height="14" width="35" rx="5" ry="5"></rect>
-            </svg>
-          </td>
-          <td>Namespace<span class="gray">*</span></td>
-        </tr>
-        <tr>
-          <td>
-            <svg class="icon" preserveAspectRatio="xMinYMid meet" viewBox="0 0 10 10">
-              <use xlink:href="#op-node-stamp" fill="white" stroke="#ccc" x="9.5" y="6"></use>
-            </svg>
-          </td>
-          <td>OpNode</td>
-        </tr>
-        <tr>
-          <td>
-            <svg class="icon" height="15px" preserveAspectRatio="xMinYMid meet" viewBox="0 0 12 12">
-              <use xlink:href="#op-series-horizontal-stamp" fill="white" stroke="#ccc" x="2" y="2"></use>
-            </svg>
-          </td>
-          <td>Unconnected series<span class="gray">*</span></td>
-        </tr>
-        <tr>
-          <td>
-            <svg class="icon" height="15px" preserveAspectRatio="xMinYMid meet" viewBox="0 0 15 15">
-              <use xlink:href="#op-series-vertical-stamp" fill="white" stroke="#ccc" x="2" y="2"></use>
-            </svg>
-          </td>
-          <td>Connected series<span class="gray">*</span></td>
-        </tr>
-        <tr>
-          <td>
-            <svg class="icon">
-              <circle fill="white" stroke="#848484" cx="10" cy="10" r="5"></circle>
-            </svg>
-          </td>
-          <td>Constant</td>
-        </tr>
-        <tr>
-          <td>
-            <svg class="image-icon" viewBox="0 0 12 12" width="24" height="24">
-              <use x="0" y="0" class="image-icon" xlink:href="#summary-icon"></use>
-            </svg>
-          </td>
-          <td>Summary</td>
-        </tr>
-        <tr>
-          <td>
-            <svg class="icon" height="15px" preserveAspectRatio="xMinYMid meet" viewBox="0 0 15 15">
-              <defs>
-                <marker id="ref-arrowhead-legend" fill="#bbb" markerWidth="10" markerHeight="10" refX="1" refY="5" orient="auto">
-                  <path d="M 10,0 L 0,5 L 10,10 C 7,7 7,3 10,0"></path>
-                </marker>
-              </defs>
-              <path stroke="#bbb" d="M2 9 l 23 0" stroke-linecap="round"></path>
-            </svg>
-          </td>
-          <td>Dataflow edge</td>
-        </tr>
-        <tr>
-          <td>
-            <svg class="icon" height="15px" preserveAspectRatio="xMinYMid meet" viewBox="0 0 15 15">
-              <path stroke="#bbb" d="M2 9 l 23 0" stroke-linecap="round" stroke-dasharray="2, 2"></path>
-            </svg>
-          </td>
-          <td>Control dependency edge</td>
-        </tr>
-        <tr>
-          <td>
-            <svg class="icon" height="15px" preserveAspectRatio="xMinYMid meet" viewBox="0 0 15 15">
-              <path marker-start="url(#ref-arrowhead-legend)" stroke="#bbb" d="M2 9 l 23 0" stroke-linecap="round"></path>
-            </svg>
-          </td>
-          <td>Reference edge</td>
-        </tr>
-      </tbody></table>
-    </div>
-  </template>
-  </div>
-</template>
-<script>
-(function() { // Private scope.
-/**
- * Stats from device names that match these regexes will be excluded by default.
- * The user can still turn on a device by selecting the checkbox in the device list.
- * See b/29089982 for context.
- */
-var DEVICE_NAMES_EXCLUDE = [
-  {
-    regex: /gpu:[0-9]+$/,
-    msg: 'Excluded by default since this is a CPU thread setting up GPU kernels.'
-  }
-];
-
-Polymer({
-  is: 'tf-graph-controls',
-  properties: {
-    // Public API.
-    stats: {
-      value: null,
-      type: Object,
-      observer: '_statsChanged'
-    },
-    devicesForStats: {
-      value: null,
-      type: Object,
-      notify: true,
-      readonly: true,
-    },
-    colorBy: {
-      type: String,
-      value: 'structure',
-      notify: true,
-      readonly: true
-    },
-    colorByParams: Object,
-    datasets: {
-      type: Array,
-      observer: '_datasetsChanged'
-    },
-    renderHierarchy: {
-      type: Object,
-      notify: true,
-    },
-    metadataTags: {
-      type: Array,
-      computed: '_getMetadataTags(selectedDataset, datasets)'
-    },
-    selectedDataset: {
-      type: Number,
-      notify: true,
-      value: 0,
-      observer: '_selectedDatasetChanged'
-    },
-    selectedFile: {
-      type: Object,
-      notify: true
-    },
-    selectedMetadataTag: {
-      type: Number,
-      notify: true,
-      value: -1
-    },
-    _currentGradientParams: {
-      type: Object,
-      computed: '_getCurrentGradientParams(colorByParams, colorBy)'
-    },
-    showSessionRunsDropdown: {
-      type: Boolean,
-      value: true
-    },
-    showUploadButton: {
-      type: Boolean,
-      value: true
-    },
-    // This stores whether the feature for showing health pills is enabled in the first place.
-    healthPillsFeatureEnabled: Boolean,
-    // This stores whether to show health pills. Only relevant if healthPillsFeatureEnabled. The
-    // user can toggle this value.
-    healthPillsToggledOn: {
-      type: Boolean,
-      notify: true,
-    },
-  },
-  listeners: {
-    'trace-inputs.change': '_traceInputToggleChanged'
-  },
-  _traceInputToggleChanged: function(event) {
-    // Flip the state of the trace inputs flag.
-    this.renderHierarchy.traceInputs = event.target.active;
-    tf.graph.scene.node.traceInputs(this.renderHierarchy);
-  },
-  _xlaClustersProvided: function(renderHierarchy) {
-    return renderHierarchy &&
-        renderHierarchy.hierarchy &&
-        renderHierarchy.hierarchy.xlaClusters.length > 0;
-  },
-  _statsChanged: function(stats) {
-    if (stats == null) {
-      return;
-    }
-    var devicesForStats = {};
-    var devices = _.each(stats.dev_stats, function(d) {
-      // Avoid device names that are ignored by default.
-      var exclude = _.some(DEVICE_NAMES_EXCLUDE, function(rule) {
-        return rule.regex.test(d.device);
-      });
-      if (!exclude) {
-        devicesForStats[d.device] = true;
-      }
-    });
-    this.set('devicesForStats', devicesForStats);
-  },
-  _getDevices: function(devicesForStats) {
-    var devices = _.map(this.stats.dev_stats, function(d) {
-      return d.device;
-    });
-    // Devices names can be long so we remove the longest common prefix
-    // before showing the devices in a list.
-    var suffixes = tf.graph.util.removeCommonPrefix(devices);
-    return _.map(devices, function(device, i) {
-      var ignoredMsg = null;
-      _.each(DEVICE_NAMES_EXCLUDE, function(rule) {
-        if (rule.regex.test(device)) {
-          ignoredMsg = rule.msg;
-        }
-      });
-      return {
-        device: device,
-        suffix: suffixes[i],
-        used: devicesForStats[device],
-        ignoredMsg: ignoredMsg
-      };
-    });
-  },
-  _deviceCheckboxClicked: function(checkbox) {
-    // Update the device map.
-    var devicesForStats = _.extend({}, this.devicesForStats);
-    var device = checkbox.target.value;
-    if (checkbox.target.checked) {
-      devicesForStats[device] = true;
-    } else {
-      delete devicesForStats[device];
-    }
-    this.set('devicesForStats', devicesForStats);
-  },
-  _numSessionRuns: function(metadataTags) {
-    return metadataTags != null ? metadataTags.length : 0;
-  },
-  _getBackgroundColor: function(color) {
-    return 'background-color:' + color;
-  },
-  fit: function() {
-    document.querySelector('#scene').fit();
-  },
-  _isGradientColoring: function(stats, colorBy) {
-    return ["compute_time", "memory"].indexOf(colorBy) !== -1
-        && stats != null;
-  },
-  _equals: function(a, b) {
-    return a === b;
-  },
-  _getCurrentGradientParams: function(colorByParams, colorBy) {
-    if (!this._isGradientColoring(this.stats, colorBy)) {
-      return;
-    }
-    var params = colorByParams[colorBy];
-    var minValue = params.minValue;
-    var maxValue = params.maxValue;
-    if (colorBy === 'memory') {
-      minValue = tf.graph.util.convertUnitsToHumanReadable(
-          minValue, tf.graph.util.MEMORY_UNITS);
-      maxValue = tf.graph.util.convertUnitsToHumanReadable(
-          maxValue, tf.graph.util.MEMORY_UNITS);
-    } else if (colorBy === 'compute_time') {
-      minValue = tf.graph.util.convertUnitsToHumanReadable(
-          minValue, tf.graph.util.TIME_UNITS);
-      maxValue = tf.graph.util.convertUnitsToHumanReadable(
-          maxValue, tf.graph.util.TIME_UNITS);
-    }
-    return {
-      minValue: minValue,
-      maxValue: maxValue,
-      startColor: params.startColor,
-      endColor: params.endColor
-    };
-  },
-  download: function() {
-    this.$.graphdownload.click();
-  },
-  _updateFileInput: function(e) {
-    var file = e.target.files[0];
-    if (!file) {
-      return;
-    }
-    this._setDownloadFilename(file.name);
-    this.set('selectedFile', e);
-  },
-  _datasetsChanged: function(newDatasets, oldDatasets) {
-    if (oldDatasets != null || this.selected == null) {
-      // Select the first dataset by default.
-      this.set('selectedDataset', 0);
-      this._setDownloadFilename(this.datasets[this.selectedDataset].path);
-    }
-  },
-  _getMetadataTags: function(selectedDataset, datasets) {
-    return this.datasets[selectedDataset].runMetadata;
-  },
-  _selectedDatasetChanged: function(newDataset, oldDataset) {
-    if (this.datasets) {
-      this.set('selectedMetadataTag', -1);
-      this.set('colorBy', 'structure');
-      this.$['trace-inputs'].active = false; // Set trace input to off-state.
-      this._setDownloadFilename(this.datasets[newDataset].path);
-    }
-  },
-  _getFile: function() {
-    this.$$("#file").click();
-  },
-  _setDownloadFilename: function(graphPath) {
-    // Strip off everything before the last "/" and strip off the file
-    // extension in order to get the name of the PNG for the graph.
-    var dotIndex = graphPath.lastIndexOf('.');
-    if (dotIndex) {
-      graphPath = graphPath.substring(0, dotIndex);
-    }
-    var slashIndex = graphPath.lastIndexOf('/');
-    if (slashIndex) {
-      graphPath = graphPath.substring(slashIndex + 1);
-    }
-    this.$.graphdownload.setAttribute('download', graphPath + '.png');
-  }
-});
-})(); // Closing private scope.
-</script>
-</dom-module>
-
-<dom-module id="tf-graph-dashboard" assetpath="../tf-graph-dashboard/">
-<template>
-<tf-no-data-warning data-type="graph" show-warning="[[_datasetsEmpty(_datasets)]]"></tf-no-data-warning>
-<template is="dom-if" if="[[!_datasetsEmpty(_datasets)]]">
-<tf-dashboard-layout>
-<div class="sidebar">
-  <tf-graph-controls id="controls" devices-for-stats="{{_devicesForStats}}" color-by-params="[[_colorByParams]]" stats="[[_stats]]" color-by="{{_colorBy}}" datasets="[[_datasets]]" render-hierarchy="[[_renderHierarchy]]" selected-dataset="{{_selectedDataset}}" selected-file="{{_selectedFile}}" selected-metadata-tag="{{_selectedMetadataTag}}" health-pills-feature-enabled="[[debuggerDataEnabled]]" health-pills-toggled-on="{{healthPillsToggledOn}}"></tf-graph-controls>
-  <tf-graph-loader id="loader" datasets="[[_datasets]]" selected-dataset="[[_selectedDataset]]" selected-metadata-tag="[[_selectedMetadataTag]]" selected-file="[[_selectedFile]]" out-graph-hierarchy="{{_graphHierarchy}}" out-graph="{{_graph}}" out-stats="{{_stats}}" progress="{{_progress}}" out-hierarchy-params="{{_hierarchyParams}}"></tf-graph-loader>
-</div>
-<div class="center">
-    <tf-graph-board id="graphboard" devices-for-stats="[[_devicesForStats]]" color-by="[[_colorBy]]" color-by-params="{{_colorByParams}}" graph-hierarchy="[[_graphHierarchy]]" graph="[[_graph]]" hierarchy-params="[[_hierarchyParams]]" progress="[[_progress]]" debugger-data-enabled="[[debuggerDataEnabled]]" are-health-pills-loading="[[_areHealthPillsLoading]]" node-names-to-health-pills="[[_nodeNamesToHealthPills]]" all-steps-mode-enabled="{{allStepsModeEnabled}}" specific-health-pill-step="{{specificHealthPillStep}}" health-pill-step-index="[[_healthPillStepIndex]]" render-hierarchy="{{_renderHierarchy}}" stats="[[_stats]]"></tf-graph-board>
-</div>
-</tf-dashboard-layout>
-</template>
-<style>
-
-:host /deep/ {
-  font-family: 'Roboto', sans-serif;
-}
-
-.center {
-  position: relative;
-  height: 100%;
-}
-
-</style>
-</template>
-</dom-module>
-
-<script>
-(function() {
-TF.Dashboard.TfGraphDashboard = Polymer({
-  is: 'tf-graph-dashboard',
-  factoryImpl: function(backend, debuggerDataEnabled) {
-    this.backend = backend;
-    this.debuggerDataEnabled = debuggerDataEnabled;
-  },
-  behaviors: [
-    TF.Dashboard.DashboardBehavior("graphs"),
-    TF.Dashboard.ReloadBehavior("tf-graph-dashboard"),
-    TF.Backend.Behavior,
-  ],
-  properties: {
-    _datasets: Object,
-    _renderHierarchy: {type: Object, observer: '_renderHierarchyChanged'},
-    backend: Object,
-    debuggerDataEnabled: Boolean,
-    allStepsModeEnabled: Boolean,
-    specificHealthPillStep: {type: Number, value: 0},
-    healthPillsToggledOn: {type: Boolean, value: true, observer: '_healthPillsToggledOnChanged'},
-    _isAttached: Boolean,
-    // Whether this dashboard is initialized. This dashboard should only be initialized once.
-    _initialized: Boolean,
-    // Whether health pills are currently being loaded, in which case we may want to say show a
-    // spinner.
-    _areHealthPillsLoading: Boolean,
-    // Maps the names of nodes to an array of health pills (HealthPillDatums).
-    _nodeNamesToHealthPills: {
-      type: Object,
-      value: {},
-    },
-    _healthPillStepIndex: Number,
-    // A strictly increasing ID. Each request for health pills has a unique ID. This helps us
-    // identify stale requests.
-    _healthPillRequestId: {type: Number, value: 1},
-    // The setTimeout ID for the pending request for health pills at a specific step.
-    _healthPillStepRequestTimerId: Number,
-    // The request for health pills at a specific step (as opposed to all sampled health pills) may
-    // involve slow disk reads. Hence, we throttle to 1 of those requests every this many ms.
-    _healthPillStepRequestTimerDelay: {
-      type: Number,
-      value: 500,
-      readOnly: true,
-    },
-    runs: Array,
-  },
-  listeners: {
-    'node-toggle-expand': '_handleNodeToggleExpand',
-  },
-  observers: [
-    '_maybeFetchHealthPillsAtSpecificStep(allStepsModeEnabled, specificHealthPillStep)',
-    '_maybeInitializeDashboard(backend, _isAttached)',
-  ],
-  attached: function() {
-    this.set('_isAttached', true);
-  },
-  detached: function() {
-    this.set('_isAttached', false);
-  },
-  reload: function() {
-    if (!this.debuggerDataEnabled ||
-        !this.healthPillsToggledOn ||
-        !this._renderHierarchy ||
-        this._datasetsEmpty(this._datasets)) {
-      // Do not load debugger data if the feature is disabled, if the user toggled off the feature,
-      // or if the graph itself has not loaded yet. We need the graph to load so that we know which
-      // nodes to request health pills for.
-      return;
-    }
-
-    // Request debugger data on graph reloads, but do not re-request the graph itself. The graph
-    // would not change across reloads.
-    this._requestHealthPills();
-  },
-  _maybeInitializeDashboard: function(backend, isAttached) {
-    if (this._initialized || !backend || !isAttached) {
-      // Either this dashboard is already initialized ... or we are not yet ready to initialize.
-      return;
-    }
-
-    // Set this to true so we only initialize once.
-    this._initialized = true;
-    Promise.all([backend.graphRuns(), backend.runMetadataRuns()])
-      .then(function(result) {
-        var runsWithGraph = result[0].sort(VZ.Sorting.compareTagNames);
-        var runToMetadata = result[1];
-        var datasets = _.map(runsWithGraph, function(runName) {
-          return {
-            name: runName,
-            path: backend.router.graph(
-                runName, tf.graph.LIMIT_ATTR_SIZE, tf.graph.LARGE_ATTRS_KEY),
-            runMetadata: runToMetadata[runName] ? _.map(
-              runToMetadata[runName].sort(VZ.Sorting.compareTagNames), function(tag) {
-                return {
-                  tag: tag,
-                  path: backend.router.runMetadata(tag, runName)
-                };
-              }, this) : []
-          };
-        }, this);
-        this.set('_datasets', datasets);
-      }.bind(this));
-  },
-  _requestHealthPills: function() {
-    this.set('_areHealthPillsLoading', true);
-    const requestId = ++this._healthPillRequestId;
-
-    if (this._healthPillStepRequestTimerId !== null) {
-      // A request for health pills is already scheduled to be initiated. Clear it, and schedule a
-      // new request.
-      window.clearTimeout(this._healthPillStepRequestTimerId);
-      this._healthPillStepRequestTimerId = null;
-    }
-
-    if (this.allStepsModeEnabled) {
-      // This path may be slow. Schedule network requests to start some time later. If another
-      // request is scheduled in the mean time, drop this current request.
-      this._healthPillStepRequestTimerId = setTimeout(function() {
-        this._healthPillStepRequestTimerId = null;
-        this._initiateNetworkRequestForHealthPills(requestId);
-      }.bind(this), this._healthPillStepRequestTimerDelay);
-    } else {
-      // The user is fetching sampled steps. This path is fast, so no need to throttle. Directly
-      // fetch the health pills across the network.
-      this._initiateNetworkRequestForHealthPills(requestId);
-    }
-  },
-  // Initiates the network request for health pills. Do not directly call this method - network
-  // requests may be throttled. Instead, call _requestHealthPills, which uses this method.
-  _initiateNetworkRequestForHealthPills: function(requestId) {
-    if (this._healthPillRequestId !== requestId) {
-      // This possibly scheduled request was outdated before it was even sent across the network. Do
-      // not bother initiating it.
-      return;
-    }
-
-    const specificStep = this.allStepsModeEnabled ? this.specificHealthPillStep : undefined;
-    this.backend.healthPills(this._renderHierarchy.getNamesOfRenderedOps(), specificStep).then(
-        function(result) {
-      if (!this.healthPillsToggledOn) {
-        // The user has opted to hide health pills via the toggle button.
-        return;
-      }
-
-      if (requestId !== this._healthPillRequestId) {
-        // This response is no longer relevant.
-        return;
-      }
-
-      // Set the index for which step to show for the health pills. By default, show the last step.
-      // A precondition we assume (that Tensorboard's reservoir sampling guarantees) is that all
-      // node names should be mapped to the same number of steps.
-      for (let nodeName in result) {
-        this.set('_healthPillStepIndex', result[nodeName].length - 1);
-        break;
-      }
-
-      this.set('_nodeNamesToHealthPills', result);
-      this.set('_areHealthPillsLoading', false);
-      this.set('_healthPillStepRequestTimerId', null);
-    }.bind(this));
-  },
-  _datasetsEmpty: function(datasets) {
-    return !datasets || !datasets.length;
-  },
-  _renderHierarchyChanged: function(renderHierarchy) {
-    // Reload any data on the graph when the render hierarchy (which determines which nodes are
-    // rendered) changes.
-    this.reload();
-  },
-  _handleNodeToggleExpand: function() {
-    // Nodes were toggled. We may need to request health pills for more nodes.
-    this._requestHealthPills();
-  },
-  _healthPillsToggledOnChanged: function(healthPillsToggledOn) {
-    if (healthPillsToggledOn) {
-      // Load health pills.
-      this.reload();
-    } else {
-      // Remove all health pills by setting an empty mapping.
-      this.set('_nodeNamesToHealthPills', {});
-    }
-  },
-  // Fetch health pills for a specific step if applicable.
-  _maybeFetchHealthPillsAtSpecificStep: function(allStepsModeEnabled, specificHealthPillStep) {
-    if (!this._renderHierarchy) {
-      // The graph is not ready yet.
-      return;
-    }
-
-    this._requestHealthPills();
-  },
-});
-})();
-</script>
-<link rel="import" href="../paper-material/paper-material.html">
-
-
-<style>
-  tf-text-loader p {
-    margin: 0.3em 0;
-  }
-
-  tf-text-loader table {
-    border-collapse: collapse;
-  }
-
-  tf-text-loader table th {
-    font-weight: 600;
-  }
-
-  tf-text-loader table th,
-  tf-text-loader table td {
-    padding: 6px 13px;
-    border: 1px solid #dfe2e5;
-  }
-
-  tf-text-loader table tr {
-    background-color: #fff;
-    border-top: 1px solid #c6cbd1;
-  }
-
-</style>
-<dom-module id="tf-text-loader" assetpath="../tf-text-dashboard/">
-
-  
-  <template>
-    <style include="scrollbar-style"></style>
-    <paper-material elevation="1" id="outer" class="container scrollbar">
-      <template id="repeater" is="dom-repeat" items="[[_texts]]">
-      <paper-material elevation="1" class="step-container">
-        step <span class="step-value">[[_numfmt(item.step)]]</span>
-      </paper-material>
-      <paper-material elevation="1" inner-h-t-m-l="[[item.text]]" class="text">
-        </paper-material>
-      </template>
-    </paper-material>
-
-
-    <style>
-      #outer {
-        display: block;
-        overflow: auto;
-        max-height: 500px;
-        position: relative;
-        border-radius: 3px;
-        border: 2px solid black;
-      }
-      .text {
-        margin: 0 10px 10px 10px;
-        border-radius: 0 3px 3px 3px;
-        background-color: white;
-        padding: 5px;
-        word-break: break-word;
-      }
-      .step-container {
-        border-left: 1px solid #ccc;
-        border-right: 1px solid #ccc;
-        border-top: 1px solid #ccc;
-        border-radius: 3px 3px 0 0;
-        font-style: italic;
-        margin-top: 10px;
-        background-color: var(--tb-ui-light-accent);
-        display: inline-block;
-        margin-left: 9px;
-        padding: 3px;
-        font-size: 12px;
-      }
-
-    </style>
-
-  </template>
-  <script>
-    Polymer({
-      is: "tf-text-loader",
-      properties: {
-        colorScale: Object,
-        run: String,
-        // This is an array of Tensorboard Text&Datum objects (See backend.ts for details). The
-        // properties of objects in this array are
-        // {
-        //   wall_time: Date,
-        //   step: number,
-        //   text: string,
-        // }
-        // they are ordered from most recent to oldest
-        _texts: {
-          type: Array,
-          value: [],
-        },
-
-      },
-      redraw: function() {
-        // Other dashboards logic requires a redraw method to be defined.
-      },
-      setVisibleSeries: function(runs) {
-        // Do nothing.
-      },
-      setSeriesData: function(run, texts) {
-        this.set("run", run);
-        this.set("_texts", texts.reverse());
-
-        // Update the border color based on the run.
-        var color = this.colorScale.scale(run);
-        this.$$("#outer").style.borderColor = color;
-      },
-      _numfmt: function(n) {
-        return d3.format(",")(n);
-      }
-    });
-  </script>
-</dom-module>
-
-<dom-module id="tf-text-dashboard" assetpath="../tf-text-dashboard/">
-  <template>
-    <paper-dialog with-backdrop="" id="actual-text-size-dialog"></paper-dialog>
-    <div id="plumbing">
-      <tf-color-scale id="colorScale" runs="[[runs]]" out-color-scale="{{_colorScale}}"></tf-color-scale>
-    </div>
-
-    <tf-dashboard-layout>
-      <div class="sidebar">
-        <tf-sidebar-helper backend="[[backend]]" categories="{{_categories}}" color-scale="[[_colorScale]]" run2tag="[[run2tag]]" runs="[[runs]]" selected-runs="{{_selectedRuns}}">
-        </tf-sidebar-helper>
-      </div>
-      <div class="center">
-        <tf-panes-helper categories="[[_categories]]" color-scale="[[_colorScale]]" data-type="[[dataType]]" data-provider="[[dataProvider]]" data-not-found="[[dataNotFound]]" run2tag="[[run2tag]]" selected-runs="[[_selectedRuns]]" repeat-for-runs="">
-          <template>
-            <tf-text-loader color-scale="[[_colorScale]]"></tf-text-loader>
-          </template>
-        </tf-panes-helper>
-      </div>
-    </tf-dashboard-layout>
-    <style include="dashboard-style"></style>
-    <style>
-      tf-panes-helper {
-        --card-width: 100%;
-        --card-height: auto;
-        --card-expanded-width: 100%;
-        --card-expanded-height: 1000px;
-        --card-padding: 0 5px 5px 5px;
-        --show-expand-button: none;
-      }
-
-    </style>
-  </template>
-  <script>
-    TF.Dashboard.TfTextDashboard = Polymer({
-      is: "tf-text-dashboard",
-      factoryImpl: function(backend) {
-        this.backend = backend;
-      },
-      properties: {
-        backend: Object,
-        dataType: {
-          type: String,
-          value: "text"
-        },
-      },
-      behaviors: [
-        TF.Dashboard.DashboardBehavior("text"),
-        TF.Dashboard.ReloadBehavior("tf-chart-scaffold"),
-        TF.Backend.Behavior,
-      ],
-      attached: function() {
-        this.async(function() {
-          this.fire("rendered");
-        });
-      },
-    });
-  </script>
-</dom-module>
-<dom-module id="vz-projector-styles" assetpath="../vz-projector/">
-<template>
-<style>
-:host {
-  --paper-input-container-label: {
-    font-size: 14px;
-  };
-  --paper-input-container-input: {
-    font-size: 14px;
-  };
-  /* TODO: Figure out why this doesn't work */
-  --paper-dropdown-menu-input: {
-    font-size: 14px;
-  };
-}
-
-paper-button {
-  background: #e3e3e3;
-  margin-left: 0;
-  text-transform: none;
-}
-
-paper-dropdown-menu paper-item {
-  font-size: 13px;
-}
-
-paper-tooltip {
-  max-width: 200px;
-  --paper-tooltip: {
-    font-size: 12px;
-  };
-}
-
-paper-checkbox {
-  --paper-checkbox-checked-color: #880E4F;
-}
-
-paper-toggle-button {
-  --paper-toggle-button-checked-bar-color:  #880E4F;
-  --paper-toggle-button-checked-button-color:  #880E4F;
-  --paper-toggle-button-checked-ink-color: #880E4F;
-}
-
-paper-icon-button {
-  border-radius: 50%;
-}
-
-paper-icon-button[active] {
-  color: white;
-  background-color: #880E4F;
-}
-
-.slider {
-  display: flex;
-  align-items: center;
-  margin-bottom: 10px;
-  justify-content: space-between;
-}
-
-.slider span {
-  width: 35px;
-  text-align: right;
-}
-
-.slider label {
-  align-items: center;
-  display: flex;
-}
-
-.help-icon {
-  height: 15px;
-  left: 2px;
-  min-width: 15px;
-  min-height: 15px;
-  margin: 0;
-  padding: 0;
-  top: -2px;
-  width: 15px;
-}
-
-.ink-panel {
-  display: flex;
-  flex-direction: column;
-  font-size: 14px;
-}
-
-.ink-panel h4 {
-  border-bottom: 1px solid #ddd;
-  font-size: 14px;
-  font-weight: 500;
-  margin: 0;
-  margin-bottom: 10px;
-  padding-bottom: 5px;
-}
-
-.ink-panel-header {
-  border-bottom: 1px solid rgba(0, 0, 0, 0.1);
-  border-top: 1px solid rgba(0, 0, 0, 0.1);
-  height: 50px;
-}
-
-.ink-panel-content {
-  display: none;
-  height: 100%;
-}
-
-.ink-panel-content.active {
-  display: block;
-}
-
-.ink-panel-content h3 {
-  font-weight: 500;
-  font-size: 14px;
-  margin-top: 20px;
-  margin-bottom: 5px;
-  text-transform: uppercase;
-}
-
-.ink-panel-header h3 {
-  font-weight: 500;
-  font-size: 14px;
-  margin: 0;
-  padding: 0 24px;
-  text-transform: uppercase;
-}
-
-
-/* - Tabs */
-.ink-tab-group {
-  align-items: center;
-  box-sizing: border-box;
-  display: flex;
-  height: 100%;
-  justify-content: space-around;
-}
-
-.ink-tab-group .projection-tab {
-  color: rgba(0, 0, 0, 0.5);
-  cursor: pointer;
-  font-weight: 300;
-  line-height: 49px;
-  padding: 0 12px;
-  text-align: center;
-  text-transform: uppercase;
-}
-
-.ink-tab-group .projection-tab:hover {
-  color: black;
-}
-
-.ink-tab-group .projection-tab.active {
-  border-bottom: 2px solid black;
-  color: black;
-  font-weight: 500;
-}
-
-h4 {
-  margin: 30px 0 10px 0;
-}
-
-.dismiss-dialog-note {
-  margin-top: 25px;
-  font-size: 11px;
-  text-align: right;
-}
-</style>
-</template>
-</dom-module>
-<link rel="import" href="../paper-input/paper-textarea.html">
-<dom-module id="vz-projector-bookmark-panel" assetpath="../vz-projector/">
-<template>
-<style include="vz-projector-styles"></style>
-<style>
-#title {
-  background-color: #fafafa;
-  color: black;
-  font-weight: 500;
-  left: 0;
-  line-height: 60px;
-  padding-left: 24px;
-  position: absolute;
-  width: 276px;
-}
-#bookmark-container {
-  background-color: #fafafa;
-}
-#icon-container {
-  line-height: 60px;
-  position: absolute;
-  right: 0;
-}
-#header {
-  border-top: 1px solid rgba(0, 0, 0, 0.1);
-  position: relative;
-}
-#panel {
-  background-color: #fafafa;
-  position: relative;
-  overflow-y: scroll;
-  top: 60px;
-  max-height: 50vh;
-}
-
-#save-container {
-  text-align: center;
-}
-
-.state-radio {
-  display: table-cell;
-  vertical-align: middle;
-  padding-top: 16px;
-}
-
-.state-label {
-  display: table-cell;
-  vertical-align: middle;
-  top: 14px;
-}
-
-.state-label-input {
-  width: 194px;
-}
-
-.state-clear {
-  display: table-cell;
-  vertical-align: middle;
-  padding-top: 20px;
-}
-#state-file {
-  display: none;
-}
-#no-bookmarks {
-  padding: 0 24px;
-}
-#action-buttons-container .add-icon-button {
-  background-color: #03a9f4;
-  color: white;
-  margin: 0 4px 4px auto;
-  right: 7px;
-  top: -4px;
-}
-.upload-download-icon-button {
-  padding: 0;
-}
-#action-buttons-container {
-  display: flex;
-  margin-left: 34px;
-  margin-top: 6px;
-}
-.ink-fab {
-  border-radius: 50%;
-  background: white;
-  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
-}
-paper-textarea {
-  --paper-input-container-input: {
-    font-size: 12px;
-  }
-  --paper-font-caption: {
-    display: none
-  }
-}
-</style>
-
-
-<div id="bookmark-container">
-  <div id="header">
-    <div id="title">
-      BOOKMARKS ([[savedStates.length]])
-      <paper-icon-button icon="help" class="help-icon"></paper-icon-button>
-      <paper-tooltip animation-delay="0" position="top" offset="0">
-        Open this drawer to save a set of views of the projection, including
-        selected points. A file containing the bookmarks can then be saved and
-        later loaded to view them.
-      </paper-tooltip>
-    </div>
-    <div id="icon-container">
-      
-      <paper-icon-button id="expand-more" icon="expand-less" on-tap="_expandMore"></paper-icon-button>
-      <paper-icon-button id="expand-less" style="display: none" icon="expand-more" on-tap="_expandLess"></paper-icon-button>
-    </div>
-  </div>
-  <iron-collapse id="panel">
-    
-    <div id="state-section">
-      <template is="dom-if" if="[[!savedStates.length]]">
-        <p id="no-bookmarks">
-            No bookmarks yet, upload a bookmarks file or add a new bookmark by clicking the "+" below.
-        </p>
-      </template>
-
-      <template is="dom-repeat" items="{{savedStates}}">
-        <div class="state-row">
-          <div class="state-radio">
-            <template is="dom-if" if="{{item.isSelected}}">
-              <paper-icon-button icon="radio-button-checked"></paper-icon-button>
-            </template>
-            <template is="dom-if" if="{{!item.isSelected}}">
-              <paper-icon-button icon="radio-button-unchecked" data-index$="{{index}}" on-tap="_radioButtonHandler"></paper-icon-button>
-            </template>
-          </div>
-          <div class="state-label">
-            <paper-textarea value="[[item.label]]" class="state-label-input" on-keyup="_labelChange" data-index$="[[index]]" autoresizing="">
-          </paper-textarea></div>
-          <div class="state-clear">
-            <paper-icon-button icon="clear" data-index$="{{index}}" on-tap="_clearButtonHandler"></paper-icon-button>
-          </div>
-        </div>
-      </template>
-
-      <div id="action-buttons-container">
-        <paper-icon-button class="upload-download-icon-button" icon="save" title="Save bookmarks" disabled="[[!hasStates]]" on-tap="_downloadFile"></paper-icon-button>
-        <paper-icon-button class="upload-download-icon-button" icon="file-upload" title="Load bookmarks" on-tap="_uploadFile"></paper-icon-button>
-        <paper-icon-button class="add-icon-button ink-fab" icon="add" title="Add bookmark" on-tap="_addBookmark"></paper-icon-button>
-        <input type="file" id="state-file" name="state-file">
-      </div>
-    </div>
-  </iron-collapse>
-</div>
-
-</template>
-</dom-module>
-<dom-module id="vz-projector-legend" assetpath="../vz-projector/">
-<template>
-<style include="vz-projector-styles"></style>
-<style>
-.item {
-  display: flex;
-  align-items: flex-start;
-  margin-bottom: 10px;
-}
-
-.shape {
-  width: 10px;
-  height: 10px;
-  margin-right: 10px;
-  margin-top: 5px;
-  border-radius: 50%;
-}
-
-.label {
-  flex-grow: 1;
-}
-
-.gradient {
-  width: 100%;
-  height: 10px;
-}
-
-.gradient-boundaries {
-  display: flex;
-  justify-content: space-between;
-}
-</style>
-
-<template is="dom-repeat" items="[[renderInfo.items]]">
-  <div class="item">
-    <div class="shape" style="background-color: [[item.color]];"></div>
-    <div class="label">[[item.label]]</div>
-    <div class="info" style="color: [[item.color]];">[[item.count]]</div>
-  </div>
-</template>
-
-<template is="dom-if" if="[[renderInfo.thresholds]]">
-  <svg class="gradient">
-    <defs>
-      <linearGradient id="gradient" x1="0%" y1="100%" x2="100%" y2="100%"></linearGradient>
-    </defs>
-    <rect height="10" style="fill: url(&quot;#gradient&quot;);"></rect>
-  </svg>
-  <div class="gradient-boundaries">
-    <div>[[renderInfo.thresholds.0.value]]</div>
-    <div>[[_getLastThreshold(renderInfo.thresholds)]]</div>
-  </div>
-</template>
-
-</template>
-</dom-module><dom-module id="vz-projector-data-panel" assetpath="../vz-projector/">
-<template>
-<style include="vz-projector-styles"></style>
-<style>
-.container {
-  padding: 10px 20px 20px 20px;
-}
-
-input[type=file] {
-  display: none;
-}
-
-.file-name {
-  margin-right: 10px;
-}
-
-.dirs {
-  color: rgba(0, 0, 0, 0.7);
-  font-size: 12px;
-}
-
-.dirs table tr {
-  vertical-align: top;
-}
-
-.dirs table tr td {
-  padding-bottom: 10px;
-}
-
-paper-item {
-  --paper-item-disabled: {
-    border-bottom: 1px solid black;
-    justify-content: center;
-    font-size: 12px;
-    line-height: normal;
-    min-height: 0px;
-  };
-}
-
-.item-details {
-  margin-left: 5px;
-  color: gray;
-  font-size: 12px;
-}
-
-paper-dropdown-menu {
-  width: 100%;
-}
-
-paper-dropdown-menu paper-item {
-  justify-content: space-between;
-}
-
-.title {
-  align-items: center;
-  border-bottom: 1px solid rgba(0, 0, 0, 0.1);
-  color: black;
-  display: flex;
-  font-weight: 500;
-  height: 59px;
-  padding-left: 20px;
-}
-
-#normalize-data-checkbox {
-  margin: 10px 0;
-}
-
-#projector-config-template {
-  --paper-input-container-input: {
-    line-height: 13px;
-    font-family: monospace;
-    font-size: 12px;
-  };
-}
-
-#generate-share-url {
-  padding: 16px;
-  margin-left: 24px;
-}
-
-#projector-share-button-container {
-  margin: 10px 0;
-}
-
-.config-checkbox {
-  display: inline-block;
-  font-size: 11px;
-  margin-left: 10px;
-}
-
-.projector-config-options {
-  margin-top: 12px;
-}
-
-.projector-config-dialog-container {
-  padding: 24px;
-}
-
-.code {
-  background-color: #f7f7f7;
-  display: table;
-  font-family: monospace;
-  margin-top: 7px;
-  padding: 15px;
-}
-
-.delimiter {
-  color: #B71C1C;
-}
-
-.upload-step {
-  display: flex;
-  justify-content: space-between;
-  margin-bottom: 6px;
-}
-
-.upload-step paper-button {
-  margin-left: 30px;
-}
-
-.step-label {
-  color: rgb(38, 180, 226);
-}
-
-.scrollable-container {
-  margin-top: 0;
-  min-width: 400px;
-}
-
-#projectorConfigDialog p {
-  margin: 8px 0 8px;
-}
-
-.data-step {
-  margin-top: 40px;
-}
-
-.data-step-contents {
-  display: table;
-  width: 100%;
-}
-
-.data-step-contents-contents {
-  display: table-cell;
-  margin-top: 6px;
-}
-
-.data-step-contents-upload {
-  display: table-cell;
-  text-align: right;
-  vertical-align: bottom;
-}
-
-#demo-data-buttons-container {
-  display: none;
-}
-
-.colorby-container {
-  margin-bottom: 10px;
-}
-</style>
-<div class="title">DATA</div>
-<div class="container">
-  
-  <template is="dom-if" if="[[_hasChoices(runNames)]]">
-    <paper-dropdown-menu no-animations="" label="[[_getNumRunsLabel(runNames)]] found">
-      <paper-listbox attr-for-selected="value" class="dropdown-content" selected="{{selectedRun}}">
-        <template is="dom-repeat" items="[[runNames]]">
-          <paper-item value="[[item]]" label="[[item]]">
-            [[item]]
-          </paper-item>
-        </template>
-      </paper-listbox>
-    </paper-dropdown-menu>
-  </template>
-
-  <template is="dom-if" if="[[tensorNames]]">
-    
-    <paper-dropdown-menu no-animations="" label="[[_getNumTensorsLabel(tensorNames)]] found">
-      <paper-listbox attr-for-selected="value" class="dropdown-content" selected="{{selectedTensor}}">
-        <template is="dom-repeat" items="[[tensorNames]]">
-          <paper-item value="[[item.name]]" label="[[item.name]]">
-            [[item.name]]
-            <span class="item-details">
-              [[item.shape.0]]x[[item.shape.1]]
-            </span>
-          </paper-item>
-        </template>
-      </paper-listbox>
-    </paper-dropdown-menu>
-  </template>
-  
-  <template is="dom-if" if="[[_hasChoices(labelOptions)]]">
-    <paper-dropdown-menu no-animations="" label="Label by">
-      <paper-listbox attr-for-selected="value" class="dropdown-content" selected="{{selectedLabelOption}}">
-        <template is="dom-repeat" items="[[labelOptions]]">
-          <paper-item value="[[item]]" label="[[item]]">
-            [[item]]
-          </paper-item>
-        </template>
-      </paper-listbox>
-    </paper-dropdown-menu>
-  </template>
-
-  
-  <div hidden$="[[!_hasChoices(colorOptions)]]" class="colorby-container">
-    <paper-dropdown-menu id="colorby" no-animations="" label="Color by">
-      <paper-listbox attr-for-selected="value" class="dropdown-content" selected="{{selectedColorOptionName}}">
-        <template is="dom-repeat" items="[[colorOptions]]">
-          <paper-item class$="[[getSeparatorClass(item.isSeparator)]]" value="[[item.name]]" label="[[item.name]]" disabled="[[item.isSeparator]]">
-            [[item.name]]
-            <span class="item-details">[[item.desc]]</span>
-          </paper-item>
-        </template>
-      </paper-listbox>
-    </paper-dropdown-menu>
-    <div hidden$="[[!showForceCategoricalColorsCheckbox]]">
-      <paper-checkbox id="force-categorical-checkbox"></paper-checkbox>
-      Use categorical coloring
-      <paper-icon-button icon="help" class="help-icon"></paper-icon-button>
-      <paper-tooltip position="bottom" animation-delay="0" fit-to-visible-bounds="">
-        For metadata fields that have many unique values we use a gradient color map
-        by default. This checkbox allows you to force categorical coloring by a given
-        metadata field.
-      </paper-tooltip>
-    </div>
-    <template dom-if="[[colorLegendRenderInfo]]">
-      <vz-projector-legend render-info="[[colorLegendRenderInfo]]"></vz-projector-legend>
-    </template>
-  </div>
-  <paper-checkbox id="normalize-data-checkbox" checked="{{normalizeData}}">
-    Sphereize data
-    <paper-icon-button icon="help" class="help-icon"></paper-icon-button>
-    <paper-tooltip position="bottom" animation-delay="0" fit-to-visible-bounds="">
-      The data is normalized by shifting each point by the centroid and making
-      it unit norm.
-    </paper-tooltip>
-  </paper-checkbox>
-  <p id="demo-data-buttons-container">
-    <span>
-      <paper-tooltip position="bottom" animation-delay="0" fit-to-visible-bounds="">
-        Load data from your computer
-      </paper-tooltip>
-      <paper-button id="upload" class="ink-button" onclick="dataDialog.open()">Load data</paper-button>
-    </span>
-    <span id="publish-container">
-      <paper-tooltip position="bottom" animation-delay="0" fit-to-visible-bounds="">
-        Publish your embedding visualization and data
-      </paper-tooltip>
-      <paper-button id="host-embedding" class="ink-button" onclick="projectorConfigDialog.open()">Publish</paper-button>
-    </span>
-  </p>
-  <div>
-    <paper-dialog id="dataDialog" with-backdrop="">
-      <h2>Load data from your computer</h2>
-      <paper-dialog-scrollable class="scrollable-container">
-        <div class="data-step" id="upload-tensors-step-container">
-          <div class="upload-step">
-            <div>
-                <b><span class="step-label">Step 1:</span> Load a TSV file of vectors.</b>
-            </div>
-          </div>
-          <div class="data-step-contents">
-            <div class="data-step-contents-contents">
-              Example of 3 vectors with dimension 4:
-              <div class="code">
-                0.1<span class="delimiter">\t</span>0.2<span class="delimiter">\t</span>0.5<span class="delimiter">\t</span>0.9<br>
-                0.2<span class="delimiter">\t</span>0.1<span class="delimiter">\t</span>5.0<span class="delimiter">\t</span>0.2<br>
-                0.4<span class="delimiter">\t</span>0.1<span class="delimiter">\t</span>7.0<span class="delimiter">\t</span>0.8
-              </div>
-            </div>
-            <div class="data-step-contents-upload">
-              <paper-button id="upload-tensors" title="Choose a TSV tensor file">Choose file</paper-button>
-              <input type="file" id="file" name="file">
-            </div>
-          </div>
-        </div>
-        <div class="data-step">
-          <div class="upload-step">
-            <div>
-                <span class="step-label" id="upload-metadata-label"><b>Step 2</b> (optional):</span> <b>Load a TSV file of metadata.</b>
-            </div>
-          </div>
-          <div class="data-step-contents">
-            <div class="data-step-contents-contents">
-              Example of 3 data points and 2 columns.<br>
-              <i>Note: If there is more than one column, the first row will be parsed as column labels.</i>
-              <div class="code">
-                <b>Pokémon<span class="delimiter">\t</span>Species</b><br>
-                Wartortle<span class="delimiter">\t</span>Turtle<br>
-                Venusaur<span class="delimiter">\t</span>Seed<br>
-                Charmeleon<span class="delimiter">\t</span>Flame
-              </div>
-            </div>
-            <div class="data-step-contents-upload">
-              <paper-button id="upload-metadata" title="Choose a TSV metadata file" class="ink-button">Choose file</paper-button>
-              <input type="file" id="file-metadata" name="file-metadata">
-            </div>
-          </div>
-        </div>
-      </paper-dialog-scrollable>
-      <div class="dismiss-dialog-note">Click outside to dismiss.</div>
-    </paper-dialog>
-    <paper-dialog id="projectorConfigDialog" with-backdrop="">
-      <h2>Publish your embedding visualization and data</h2>
-      <paper-dialog-scrollable class="scrollable-container">
-        <div>
-          <p>
-            If you'd like to share your visualization with the world, follow these simple steps.
-            See <a target="_blank" href="https://www.tensorflow.org/get_started/embedding_viz">this tutorial</a> for more.
-          </p>
-          <h4><span class="step-label">Step 1:</span> Make data public</h4>
-          <p>
-            Host tensors, metadata, sprite image, and bookmarks TSV files <i>publicly</i> on the web.
-          </p>
-          <p>
-            One option is using a <a target="_blank" href="https://gist.github.com/">github gist</a>.
-            If you choose this approach, make sure to link directly to the raw file.
-          </p>
-        </div>
-        <div>
-          <h4><span class="step-label">Step 2:</span> Projector config</h4>
-          <div class="projector-config-options">
-            <i>Optional:</i>
-            <div class="config-checkbox">
-              <paper-checkbox id="config-metadata-checkbox" checked="">Metadata</paper-checkbox>
-            </div>
-            <div class="config-checkbox">
-              <paper-checkbox id="config-sprite-checkbox">Sprite</paper-checkbox>
-            </div>
-            <div class="config-checkbox">
-              <paper-checkbox id="config-bookmarks-checkbox">Bookmarks</paper-checkbox>
-            </div>
-          </div>
-        </div>
-        <paper-textarea id="projector-config-template" label="template_projector_config.json"></paper-textarea>
-        <div>
-          <h4><span class="step-label">Step 3:</span> Host projector config</h4>
-          After you have hosted the projector config JSON file you built above, paste the URL to the config below.
-        </div>
-        <paper-input id="projector-config-url" label="Path to projector config"></paper-input>
-        <paper-input id="projector-share-url" label="Your shareable URL" readonly=""></paper-input>
-        <div id="projector-share-button-container">
-          <a target="_blank" id="projector-share-url-link">
-            <paper-button title="Test your shareable URL" class="ink-button">Test your shareable URL</paper-button>
-          </a>
-        </div>
-      </paper-dialog-scrollable>
-      <div class="dismiss-dialog-note">Click outside to dismiss.</div>
-    </paper-dialog>
-  </div>
-  <div class="dirs">
-    <table>
-      <tbody><tr>
-        <td>Checkpoint:</td>
-        <td><span id="checkpoint-file"></span></td>
-      </tr>
-      <tr>
-        <td>Metadata:</td>
-        <td><span id="metadata-file"></span></td>
-      </tr>
-    </tbody></table>
-  </div>
-</div>
-
-</template>
-</dom-module>
-<dom-module id="vz-projector-input" assetpath="../vz-projector/">
-<template>
-<style include="vz-projector-styles"></style>
-<style>
-.info {
-  color: rgba(0, 0, 0, 0.5);
-  display: block;
-  font-size: 11px;
-}
-
-.toggle {
-  font-size: 12px;
-  height: 21px;
-  margin: 0px;
-  min-width: 0px;
-  min-height: 0px;
-  padding: 0;
-  width: 17px;
-}
-
-.toggle[active] {
-  background-color: #880E4F;
-  color: white;
-}
-</style>
-
-<paper-input label="[[label]]">
-  <div class="slash" prefix="">/</div>
-  <div class="slash" suffix="">/</div>
-  <div suffix="">
-    <paper-button id="regex" toggles="" class="toggle">.*</paper-button>
-  </div>
-</paper-input>
-<paper-tooltip for="regex" position="bottom" animation-delay="0" fit-to-visible-bounds="">
-  Enable/disable regex mode.
-</paper-tooltip>
-<span class="info">[[message]]</span>
-
-
-</template>
-</dom-module><dom-module id="vz-projector-inspector-panel" assetpath="../vz-projector/">
-<style include="vz-projector-styles"></style>
-<style>
-:host {
-   display: flex;
-   flex-direction: column;
-   /* Account for the bookmark pane at the bottom */
-   height: calc(100% - 55px);
-}
-
-.container {
-  display: block;
-  padding: 10px 20px 0 20px;
-}
-
-.buttons {
-  display: flex;
-  height: 60px;
-}
-
-.button {
-  margin-right: 10px;
-  border: none;
-  border-radius: 7px;
-  font-size: 13px;
-  padding: 10px;
-  background: #e3e3e3;
-}
-
-.button:last-child {
-  margin-right: 0;
-}
-
-.nn {
-  display: flex;
-  flex-direction: column;
-}
-
-.nn > * {
-  padding: 0 20px;
-}
-
-.nn-list {
-  overflow-y: auto;
-}
-
-.nn-list .neighbor {
-  font-size: 12px;
-  margin-bottom: 8px;
-}
-
-.nn-list .label-and-value {
-  display: flex;
-  justify-content: space-between;
-}
-
-.label {
-  overflow: hidden;
-  text-overflow: ellipsis;
-  white-space: nowrap;
-}
-
-.nn-list .value {
-  color: #666;
-  float: right;
-  font-weight: 300;
-  margin-left: 8px;
-}
-
-.nn-list .bar {
-  position: relative;
-  border-top: 1px solid rgba(0, 0, 0, 0.15);
-  margin: 2px 0;
-}
-
-.nn-list .bar .fill {
-  position: absolute;
-  top: -1px;
-  border-top: 1px solid white;
-}
-
-.nn-list .tick {
-  position: absolute;
-  top: 0px;
-  height: 3px;
-  border-left: 1px solid rgba(0, 0, 0, 0.15);
-}
-
-.nn-list .neighbor-link:hover {
-  cursor: pointer;
-}
-
-.search-by {
-  display: flex;
-}
-
-.search-by vz-projector-input {
-  width: 100%;
-}
-
-.search-by paper-dropdown-menu {
-  margin-left: 10px;
-  width: 100px;
-}
-
-.distance .options {
-  float: right;
-}
-
-.options a {
-  color: #727272;
-  font-size: 13px;
-  margin-left: 12px;
-  text-decoration: none;
-}
-
-.options a.selected {
-  color: #009EFE;
-}
-
-.neighbors {
-  margin-bottom: 30px;
-}
-
-.neighbors-options {
-  margin-top: 6px;
-}
-
-.neighbors-options .option-label, .distance .option-label {
-  color: #727272;
-  margin-right: 2px;
-  width: auto;
-}
-
-.num-neighbors-container {
-  display: inline-block;
-}
-
-#nn-slider {
-  margin: 0 -12px 0 10px;
-}
-
-.euclidian {
-  margin-right: 10px;
-}
-
-.matches-list {
-  padding: 0 20px;
-}
-
-.matches-list .row {
-  border-bottom: 1px solid #ddd;
-  cursor: pointer;
-  display: flex;
-  font-size: 12px;
-  margin: 5px 0;
-  padding: 4px 0;
-}
-
-.results {
-  display: flex;
-  flex-direction: column;
-}
-</style>
-<template>
-<div class="container">
-  <div class="buttons">
-    <button class="button reset-filter">Show All Data</button>
-    <button class="button set-filter">Isolate selection</button>
-    <button class="button clear-selection">Clear selection</button>
-  </div>
-  <div class="search-by">
-    <vz-projector-input id="search-box" label="Search"></vz-projector-input>
-    <paper-dropdown-menu no-animations="" label="by">
-      <paper-listbox attr-for-selected="value" class="dropdown-content" selected="{{selectedMetadataField}}">
-        <template is="dom-repeat" items="[[metadataFields]]">
-          <paper-item value="[[item]]" label="[[item]]">
-            [[item]]
-          </paper-item>
-        </template>
-      </paper-listbox>
-    </paper-dropdown-menu>
-  </div>
-</div>
-<div class="results">
-  <div class="nn" style="display: none">
-    <div class="neighbors">
-      <div class="neighbors-options">
-        <div class="slider num-nn">
-          <span class="option-label">neighbors</span>
-          <paper-icon-button icon="help" class="help-icon"></paper-icon-button>
-          <paper-tooltip position="bottom" animation-delay="0" fit-to-visible-bounds="">
-            The number of neighbors (in the original space) to show when clicking on a point.
-          </paper-tooltip>
-          <paper-slider id="nn-slider" pin="" min="5" max="1000" value="100"></paper-slider>
-          <span class="nn-count"></span>
-        </div>
-      </div>
-      <div class="distance">
-        <span class="option-label">distance</span>
-        <div class="options">
-          <a class="selected cosine" href="javascript:void(0);">COSINE</a>
-          <a class="euclidean" href="javascript:void(0);">EUCLIDIAN</a>
-        </div>
-      </div>
-    </div>
-    <p>Nearest points in the original space:
-    </p><div class="nn-list"></div>
-  </div>
-  <div class="matches-list" style="display: none">
-    <div class="list"></div>
-    <div class="limit-msg">Showing only the first 100 results...</div>
-  </div>
-</div>
-
-</template>
-</dom-module>
-<dom-module id="vz-projector-metadata-card" assetpath="../vz-projector/">
-<template>
-<style>
-#metadata-card {
-  background-color: rgba(255,255,255,0.9);
-  box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14),
-      0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2);
-  width: 280px;
-}
-
-#header {
-  background: #e9e9e9;
-}
-
-#icon-container {
-  position: absolute;
-  right: 0;
-  top: 4px;
-}
-
-#metadata-label {
-  font-weight: 400;
-  font-size: 14px;
-  line-height: 24px;
-  padding: 12px 12px 8px;
-  width: 230px;
-}
-
-#metadata-table {
-  display: table;
-  padding: 8px 12px 4px;
-}
-
-.metadata-row {
-  display: table-row;
-}
-
-.metadata-key {
-  font-weight: bold;
-}
-
-.metadata-key, .metadata-value {
-  display: table-cell;
-  font-size: 12px;
-  padding: 3px 3px;
-}
-</style>
-
-<template is="dom-if" if="[[hasMetadata]]">
-  <div id="metadata-card">
-    <div id="icon-container">
-      <paper-icon-button id="expand-more" style="display: none" icon="expand-more" on-tap="_expandMore"></paper-icon-button>
-      <paper-icon-button id="expand-less" on-tap="_expandLess" icon="expand-less"></paper-icon-button>
-    </div>
-    <div id="header">
-      <div id="metadata-label">[[label]]</div>
-    </div>
-    <iron-collapse id="metadata-container" opened="">
-      <div id="metadata-table">
-        <template is="dom-repeat" items="[[metadata]]">
-          <div class="metadata-row">
-            <div class="metadata-key">[[item.key]]</div>
-            <div class="metadata-value">[[item.value]]</div>
-          </div>
-        </template>
-      </div>
-    </iron-collapse>
-  </div>
-</template>
-</template>
-</dom-module>
-<dom-module id="vz-projector-projections-panel" assetpath="../vz-projector/">
-<template>
-<style include="vz-projector-styles"></style>
-<style>
-:host {
-  transition: height 0.2s;
-}
-
-.ink-button, ::shadow .ink-button {
-  border: none;
-  border-radius: 2px;
-  font-size: 13px;
-  padding: 10px;
-  min-width: 100px;
-  flex-shrink: 0;
-  background: #e3e3e3;
-}
-
-.ink-panel-buttons {
-  margin-bottom: 10px;
-}
-
-.two-way-toggle {
-  display: flex;
-  flex-direction: row;
-}
-
-.two-way-toggle span {
-  padding-right: 7px;
-}
-
-.has-border {
-  border: 1px solid rgba(0, 0, 0, 0.1);
-}
-
-.toggle {
-  min-width: 0px;
-  font-size: 12px;
-  width: 17px;
-  min-height: 0px;
-  height: 21px;
-  padding: 0;
-  margin: 0px;
-}
-
-.toggle[active] {
-  background-color: #880E4F;
-  color: white;
-}
-
-.two-columns {
-  display:flex;
-  justify-content: space-between;
-}
-
-.two-columns > :first-child {
-  margin-right: 15px;
-}
-
-.two-columns > div {
-  width: 50%;
-}
-
-.dropdown-item {
-  justify-content: space-between;
-  min-height: 35px;
-}
-
-#z-container {
-  display: flex;
-  align-items: center;
-  width: 50%;
-}
-
-#z-checkbox {
-  margin: 27px 0 0 5px;
-  width: 18px;
-}
-
-#z-dropdown {
-  flex-grow: 1;
-}
-
-.notice {
-  color: #880E4F;
-}
-
-.container {
-  padding: 20px;
-}
-
-.book-icon {
-  height: 20px;
-  color: rgba(0, 0, 0, 0.7);
-}
-
-.item-details {
-  color: gray;
-  font-size: 12px;
-  margin-left: 5px;
-}
-
-.pca-dropdown {
-  width: 100%;
-}
-
-.pca-dropdown paper-listbox {
-  width: 135px;
-}
-
-.dropdown-item.header {
-  border-bottom: 1px solid #aaa;
-  color: #333;
-  font-weight: bold;
-}
-
-#total-variance {
-  color: rgba(0, 0, 0, 0.7);
-}
-</style>
-<div id="main">
-  <div class="ink-panel-header">
-    <div class="ink-tab-group">
-
-      <div data-tab="tsne" id="tsne-tab" class="ink-tab projection-tab">t-SNE</div>
-      <paper-tooltip for="tsne-tab" position="bottom" animation-delay="0" fit-to-visible-bounds="">
-        t-distributed stochastic neighbor embedding
-      </paper-tooltip>
-
-      <div data-tab="pca" id="pca-tab" class="ink-tab projection-tab">PCA</div>
-      <paper-tooltip for="pca-tab" position="bottom" animation-delay="0" fit-to-visible-bounds="">
-        Principal component analysis
-      </paper-tooltip>
-
-      <div data-tab="custom" id="custom-tab" class="ink-tab projection-tab" title="Linear projection of two custom vectors">Custom</div>
-      <paper-tooltip for="custom-tab" position="bottom" animation-delay="0" fit-to-visible-bounds="">
-        Search for two vectors upon which to project all points.
-      </paper-tooltip>
-
-    </div>
-  </div>
-  <div class="container">
-    
-    <div data-panel="tsne" class="ink-panel-content">
-      <div class="slider">
-        <label>Dimension</label>
-        <div class="two-way-toggle">
-          <span>2D</span>
-          <paper-toggle-button id="tsne-toggle" checked="{{tSNEis3d}}">3D</paper-toggle-button>
-        </div>
-      </div>
-      <div class="slider tsne-perplexity">
-        <label>
-          Perplexity
-          <paper-icon-button icon="help" class="help-icon"></paper-icon-button>
-          <paper-tooltip position="right" animation-delay="0" fit-to-visible-bounds="">
-            The most appropriate perplexity value depends on the density of the
-            data. Loosely speaking, a larger / denser dataset
-            requires a larger perplexity. Typical values for perplexity range
-            between 5 and 50.
-          </paper-tooltip>
-        </label>
-        <paper-slider id="perplexity-slider" pin="" min="2" max="100" value="30"></paper-slider>
-        <span></span>
-      </div>
-      <div class="slider tsne-learning-rate">
-        <label>
-          Learning rate
-          <paper-icon-button icon="help" class="help-icon"></paper-icon-button>
-          <paper-tooltip position="right" animation-delay="0" fit-to-visible-bounds="">
-            The ideal learning rate often depends on the size of the data,
-            with smaller datasets requiring smaller learning rates.
-          </paper-tooltip>
-        </label>
-        <paper-slider id="learning-rate-slider" snaps="" min="-3" max="2" step="1" value="1" max-markers="6">
-        </paper-slider>
-        <span></span>
-      </div>
-      <p>
-        <button class="run-tsne ink-button" title="Re-run t-SNE">Re-run</button>
-        <button class="stop-tsne ink-button" title="Stop t-SNE">Stop</button>
-      </p>
-      <p>Iteration: <span class="run-tsne-iter">0</span></p>
-      <p id="tsne-sampling" class="notice">
-        For fast results, the data will be sampled down to [[getTsneSampleSizeText()]] points.
-      </p>
-      <p>
-        <iron-icon icon="book" class="book-icon"></iron-icon>
-        <a target="_blank" href="http://distill.pub/2016/misread-tsne/">
-          How to use t-SNE effectively.
-        </a>
-      </p>
-    </div>
-    
-    <div data-panel="pca" class="ink-panel-content">
-      <div class="two-columns">
-        <div> 
-          <paper-dropdown-menu class="pca-dropdown" vertical-align="bottom" no-animations="" label="X">
-            <paper-listbox attr-for-selected="value" class="dropdown-content" selected="{{pcaX}}">
-              <paper-item disabled="" class="dropdown-item header">
-                  <div>#</div>
-                  <div>Variance (%)</div>
-              </paper-item>
-              <template is="dom-repeat" items="[[pcaComponents]]">
-                <paper-item class="dropdown-item" value="[[item.id]]" label="Component #[[item.componentNumber]]">
-                  <div>[[item.componentNumber]]</div>
-                  <div class="item-details">[[item.percVariance]]</div>
-                </paper-item>
-              </template>
-            </paper-listbox>
-          </paper-dropdown-menu>
-          <paper-dropdown-menu class="pca-dropdown" no-animations="" vertical-align="bottom" label="Z" disabled="[[!hasPcaZ]]" id="z-dropdown">
-            <paper-listbox attr-for-selected="value" class="dropdown-content" selected="{{pcaZ}}">
-              <paper-item disabled="" class="dropdown-item header">
-                  <div>#</div>
-                  <div>Variance (%)</div>
-              </paper-item>
-              <template is="dom-repeat" items="[[pcaComponents]]">
-                <paper-item class="dropdown-item" value="[[item.id]]" label="Component #[[item.componentNumber]]">
-                  <div>[[item.componentNumber]]</div>
-                  <div class="item-details">[[item.percVariance]]</div>
-                </paper-item>
-              </template>
-            </paper-listbox>
-          </paper-dropdown-menu>
-        </div>
-        <div> 
-          <paper-dropdown-menu class="pca-dropdown" vertical-align="bottom" no-animations="" label="Y">
-            <paper-listbox attr-for-selected="value" class="dropdown-content" selected="{{pcaY}}">
-              <paper-item disabled="" class="dropdown-item header">
-                  <div>#</div>
-                  <div>Variance (%)</div>
-              </paper-item>
-              <template is="dom-repeat" items="[[pcaComponents]]">
-                <paper-item class="dropdown-item" value="[[item.id]]" label="Component #[[item.componentNumber]]">
-                  <div>[[item.componentNumber]]</div>
-                  <div class="item-details">[[item.percVariance]]</div>
-                </paper-item>
-              </template>
-            </paper-listbox>
-          </paper-dropdown-menu>
-          <paper-checkbox id="z-checkbox" checked="{{pcaIs3d}}"></paper-checkbox>
-        </div>
-      </div>
-      <p id="pca-sampling" class="notice">
-        PCA is approximate.
-        <paper-icon-button icon="help" class="help-icon"></paper-icon-button>
-      </p>
-      <div id="total-variance">Total variance</div>
-      <paper-tooltip for="pca-sampling" position="top" animation-delay="0" fit-to-visible-bounds="">
-        For fast results, the data was sampled to [[getPcaSampleSizeText()]] points and randomly projected down to [[getPcaSampledDimText()]] dimensions.
-      </paper-tooltip>
-    </div>
-    
-    <div data-panel="custom" class="ink-panel-content">
-      <paper-dropdown-menu style="width: 100%" no-animations="" label="Search by">
-        <paper-listbox attr-for-selected="value" class="dropdown-content" selected="{{customSelectedSearchByMetadataOption}}">
-          <template is="dom-repeat" items="[[searchByMetadataOptions]]">
-            <paper-item class="dropdown-item" value="[[item]]" label="[[item]]">
-              [[item]]
-            </paper-item>
-          </template>
-        </paper-listbox>
-      </paper-dropdown-menu>
-      <div class="two-columns">
-        <vz-projector-input id="xLeft" label="Left"></vz-projector-input>
-        <vz-projector-input id="xRight" label="Right"></vz-projector-input>
-      </div>
-      <div class="two-columns">
-        <vz-projector-input id="yUp" label="Up"></vz-projector-input>
-        <vz-projector-input id="yDown" label="Down"></vz-projector-input>
-      </div>
-    </div>
-  </div>
-</div>
-</template>
-</dom-module>
-<link rel="import" href="../paper-listbox/paper-listbox.html">
-<link rel="import" href="../iron-icons/image-icons.html">
-<link rel="import" href="../paper-toast/paper-toast.html">
-<link rel="import" href="../paper-styles/typography.html">
-<link rel="import" href="../paper-dialog-scrollable/paper-dialog-scrollable.html">
-
-<dom-module id="vz-projector" assetpath="../vz-projector/">
-<template>
-<style include="vz-projector-styles"></style>
-<style>
-:host {
-  display: flex;
-  width: 100%;
-  height: 100%;
-}
-
-#container {
-  display: flex;
-  width: 100%;
-  height: 100%;
-  overflow: hidden;
-}
-
-.hidden {
-  display: none !important;
-}
-
-/* Main */
-
-#main {
-  position: relative;
-  flex-grow: 2;
-}
-
-#main .stage {
-  position: relative;
-  flex-grow: 2;
-}
-
-#scatter {
-  position: absolute;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
-}
-
-#selector {
-  display: none;
-  height: 100%;
-  position: absolute;
-  width: 100%;
-}
-
-#left-pane {
-  display: flex;
-  flex-direction: column;
-  justify-content: space-between;
-  min-width: 312px;
-  width: 312px;
-  border-right: 1px solid rgba(0, 0, 0, 0.1);
-  background: #fafafa;
-}
-
-#right-pane {
-  border-left: 1px solid rgba(0, 0, 0, 0.1);
-  background: #fafafa;
-  display: flex;
-  height: 100%;
-  min-width: 300px;
-  width: 300px;
-}
-
-.file-name {
-  margin-right: 5px;
-}
-
-.control input[type=text]:focus {
-  outline: none;
-  border-bottom: 1px solid rgba(0, 0, 0, 1);
-}
-
-.control {
-  display: inline-block;
-  width: 45%;
-  vertical-align: top;
-  margin-right: 10px;
-  overflow-x: hidden;
-}
-
-.control.last {
-  margin-right: 0;
-}
-
-#notification-dialog {
-  width: 400px;
-  padding-bottom: 20px;
-}
-
-#notification-dialog paper-button {
-  background: none;
-  text-transform: uppercase;
-}
-
-#notification-dialog .progress {
-  --paper-spinner-color: #880E4F;
-  --paper-spinner-stroke-width: 2px;
-}
-
-#notify-msgs {
-  text-align: center;
-  display: block;
-}
-
-.notify-msg {
-  font-weight: 500;
-  margin: 0;
-  padding: 0;
-}
-
-.notify-msg.error {
-  text-align: left;
-}
-
-.brush .extent {
-  stroke: #fff;
-  fill-opacity: .125;
-  shape-rendering: crispEdges;
-}
-
-.origin text {
-  font-size: 12px;
-  font-weight: 500;
-}
-
-.origin line {
-  stroke: black;
-  stroke-opacity: 0.2;
-}
-
-/* Ink Framework */
-
-/* - Buttons */
-.ink-button, ::shadow .ink-button {
-  border: none;
-  border-radius: 2px;
-  font-size: 13px;
-  padding: 10px;
-  min-width: 100px;
-  flex-shrink: 0;
-  background: #e3e3e3;
-}
-
-.status-bar-panel {
-  display: flex;
-  align-items: center;
-}
-
-.status-bar-entry {
-  border-left: 1px solid rgba(0, 0, 0, 0.5);
-  margin-left: 5px;
-  padding-left: 5px;
-}
-
-/* - Menubar */
-
-.ink-panel-menubar {
-  align-items: center;
-  position: relative;
-  height: 60px;
-  border-bottom: solid 1px #eee;
-  padding: 0 24px;
-  display: flex;
-}
-
-.ink-panel-menubar .ink-fabs {
-  position: absolute;
-  right: 12px;
-  top: 40px;
-  z-index: 1;
-}
-
-#bookmark-panel {
-  bottom: 0;
-  position: absolute;
-  width: 300px;
-}
-#bookmark-panel-container {
-  bottom: 60px;
-  position: absolute;
-}
-
-.ink-fab {
-  margin-left: 8px;
-  border: 1px solid rgba(0, 0, 0, 0.02);
-  background: white;
-  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
-}
-
-#metadata-card {
-  position: absolute;
-  right: 5px;
-  top: 25px;
-}
-
-#help-3d-icon {
-  position: absolute;
-  top: 20px;
-  left: 20px;
-}
-
-#help3dDialog .main {
-  margin: 0;
-  padding: 20px;
-}
-
-#help3dDialog h3 {
-  margin-top: 20px;
-  margin-bottom: 5px;
-}
-
-#help3dDialog h3:first-child {
-  margin-top: 0;
-}
-
-#data-panel {
-  border-top: 1px solid rgba(0, 0, 0, 0.1);
-  overflow-y: auto;
-}
-
-#toast {
-  display: flex;
-  align-items: center;
-  --paper-toast-color: #eeff41;
-}
-</style>
-<paper-dialog id="notification-dialog" modal="">
-  <h2 id="notification-title"></h2>
-  <paper-dialog-scrollable>
-    <div id="notify-msgs"></div>
-  </paper-dialog-scrollable>
-  <div style="text-align: center;"><paper-spinner-lite active="" class="progress"></paper-spinner-lite></div>
-  <div class="buttons">
-    <paper-button class="close-button" dialog-confirm="" autofocus="">Close</paper-button>
-  </div>
-</paper-dialog>
-<div id="container">
-  <div id="left-pane" class="ink-panel">
-    <vz-projector-data-panel id="data-panel"></vz-projector-data-panel>
-    <vz-projector-projections-panel id="projections-panel"></vz-projector-projections-panel>
-  </div>
-  <div id="main" class="ink-panel">
-    <div class="ink-panel-menubar">
-      <paper-icon-button id="selectMode" alt="Bounding box selection" toggles="" icon="image:photo-size-select-small"></paper-icon-button>
-      <paper-tooltip for="selectMode" position="bottom" animation-delay="0" fit-to-visible-bounds="">Bounding box selection</paper-tooltip>
-
-      <paper-icon-button id="nightDayMode" alt="Enable/disable night mode" toggles="" icon="image:brightness-2"></paper-icon-button>
-      <paper-tooltip for="nightDayMode" position="bottom" animation-delay="0" fit-to-visible-bounds="">Enable/disable night mode</paper-tooltip>
-
-      <paper-icon-button id="labels3DMode" alt="Enable/disable 3D labels mode" toggles="" icon="font-download"></paper-icon-button>
-      <paper-tooltip for="labels3DMode" position="bottom" animation-delay="0" fit-to-visible-bounds="">Enable/disable 3D labels mode</paper-tooltip>
-      <div class="status-bar-panel">
-        <div class="status-bar-entry">Points: <span class="numDataPoints">Loading...</span></div>
-        <div class="status-bar-entry">Dimension: <span class="dim">Loading...</span></div>
-        <div id="status-bar" class="status-bar-entry" style="display: none;"></div>
-      </div>
-      <div class="ink-fabs">
-        <paper-icon-button id="reset-zoom" class="ink-fab" alt="Reset zoom to fit all points" icon="home"></paper-icon-button>
-        <paper-tooltip for="reset-zoom" position="left" animation-delay="0">Reset zoom to fit all points</paper-tooltip>
-      </div>
-    </div>
-    <div class="stage">
-      <div id="scatter">
-        <svg id="selector"></svg>
-      </div>
-      <vz-projector-metadata-card id="metadata-card"></vz-projector-metadata-card>
-      <paper-icon-button raised="" onclick="help3dDialog.open()" icon="help-outline" id="help-3d-icon"></paper-icon-button>
-      <paper-tooltip animation-delay="0" for="help-3d-icon">Help with interaction controls.</paper-tooltip>
-      <paper-dialog id="help3dDialog" with-backdrop="">
-        <div class="main" dialog-confirm="" autofocus="">
-          <h3>3D controls</h3>
-            <b>Rotate</b> Mouse left click.<br>
-            <b>Pan</b> Mouse right click.<br>
-            <b>Zoom</b> Mouse wheel.<br>
-            Holding <b>ctrl</b> reverses the mouse clicks.
-          <h3>2D controls</h3>
-            <b>Pan</b> Mouse left click.<br>
-            <b>Zoom</b> Mouse wheel.
-          <div class="dismiss-dialog-note"> Click anywhere to dismiss.</div>
-        </div>
-      </paper-dialog>
-    </div>
-  </div>
-  <div id="right-pane" class="ink-panel">
-    <div class="ink-panel-content active">
-      <vz-projector-inspector-panel id="inspector-panel"></vz-projector-inspector-panel>
-    </div>
-    <div id="bookmark-panel-container">
-      <vz-projector-bookmark-panel id="bookmark-panel"></vz-projector-bookmark-panel>
-    </div>
-  </div>
-</div>
-<paper-toast id="toast" always-on-top=""></paper-toast>
-
-</template> 
-</dom-module>
-<dom-module id="vz-projector-dashboard" assetpath="../vz-projector/">
-<template>
-  <tf-no-data-warning data-type="projector" show-warning="[[dataNotFound]]"></tf-no-data-warning>
-  <template is="dom-if" if="[[!dataNotFound]]">
-    <vz-projector id="projector" route-prefix="[[routePrefix]]" serving-mode="server" page-view-logging="" event-logging=""></vz-projector>
-  </template>
-</template>
-<script>
-(function() {
-TF.Dashboard.VzProjectorDashboard = Polymer({
-  is: 'vz-projector-dashboard',
-  factoryImpl: function(routePrefix) {
-    this.routePrefix = routePrefix;
-  },
-  properties: {
-    dataNotFound: Boolean,
-    routePrefix: String,
-    // Whether this dashboard is initialized. This dashboard should only be initialized once.
-    _initialized: Boolean,
-  },
-  behaviors: [
-    TF.Dashboard.DashboardBehavior("embeddings"),
-  ],
-  reload: function() {
-    // Do not reload the embedding projector. Reloading could take a long time.
-  },
-  attached: function() {
-    if (this._initialized) {
-      return;
-    }
-
-    // Set this to true so we only initialize once.
-    this._initialized = true;
-
-    d3.json(this.routePrefix + '/runs', (err, runs) => {
-      this.set('dataNotFound', runs.length === 0);
-    });
-  },
-});
-})();
-</script>
-</dom-module>
-<script>/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-var TF;
-(function (TF) {
-    var TensorBoard;
-    (function (TensorBoard) {
-        TensorBoard.AUTORELOAD_LOCALSTORAGE_KEY = 'TF.TensorBoard.autoReloadEnabled';
-        var getAutoReloadFromLocalStorage = function () {
-            var val = window.localStorage.getItem(TensorBoard.AUTORELOAD_LOCALSTORAGE_KEY);
-            return val === 'true' || val == null; // defaults to true
-        };
-        TensorBoard.AutoReloadBehavior = {
-            properties: {
-                autoReloadEnabled: {
-                    type: Boolean,
-                    observer: '_autoReloadObserver',
-                    value: getAutoReloadFromLocalStorage,
-                },
-                _autoReloadId: {
-                    type: Number,
-                },
-                autoReloadIntervalSecs: {
-                    type: Number,
-                    value: 30,
-                },
-            },
-            detached: function () {
-                window.clearTimeout(this._autoReloadId);
-            },
-            _autoReloadObserver: function (autoReload) {
-                window.localStorage.setItem(TensorBoard.AUTORELOAD_LOCALSTORAGE_KEY, autoReload);
-                if (autoReload) {
-                    var _this = this;
-                    this._autoReloadId = window.setTimeout(this._doAutoReload.bind(this), this.autoReloadIntervalSecs * 1000);
-                }
-                else {
-                    window.clearTimeout(this._autoReloadId);
-                }
-            },
-            _doAutoReload: function () {
-                if (this.reload == null) {
-                    throw new Error('AutoReloadBehavior requires a reload method');
-                }
-                this.reload();
-                this._autoReloadId = window.setTimeout(this._doAutoReload.bind(this), this.autoReloadIntervalSecs * 1000);
-            }
-        };
-    })(TensorBoard = TF.TensorBoard || (TF.TensorBoard = {}));
-})(TF || (TF = {}));
-</script></div><dom-module id="tf-tensorboard">
-  <template>
-    <paper-dialog with-backdrop="" id="settings">
-      <h2>Settings</h2>
-      <paper-checkbox id="auto-reload-checkbox" checked="{{autoReloadEnabled}}">
-        Reload data every <span>[[autoReloadIntervalSecs]]</span>s.
-      </paper-checkbox>
-    </paper-dialog>
-    <paper-header-panel>
-      <paper-toolbar id="toolbar">
-        <div id="toolbar-content">
-          <div class="toolbar-title">TensorBoard</div>
-          <paper-tabs selected="{{modeIndex}}" noink="" class="tabs" id="tabs">
-            <template is="dom-repeat" items="[[_dashboards]]">
-              <template is="dom-if" if="[[_isTabEnabled(item.name)]]">
-                <paper-tab data-mode="[[item.name]]">[[item.name]]</paper-tab>
-              </template>
-            </template>
-          </paper-tabs>
-          <div class="global-actions">
-            <paper-icon-button icon="refresh" on-tap="reload" disabled$="[[_isReloadDisabled(mode)]]" id="reload-button"></paper-icon-button>
-            <paper-icon-button icon="settings" on-tap="openSettings" id="settings-button"></paper-icon-button>
-            <a href="https://github.com/tensorflow/tensorflow/blob/master/tensorflow/tensorboard/README.md" tabindex="-1">
-              <paper-icon-button icon="help-outline"></paper-icon-button>
-            </a>
-          </div>
-        </div>
-      </paper-toolbar>
-
-      <div id="content" class="fit"></div>
-    </paper-header-panel>
-
-    <style>
-      :host {
-        height: 100%;
-        display: block;
-        background-color: var(--paper-grey-100);
-      }
-
-      #toolbar {
-        background-color: var(--tb-toolbar-background-color, --tb-orange-strong);
-        -webkit-font-smoothing: antialiased;
-      }
-
-      .toolbar-title {
-        font-size: 20px;
-        margin-left: 10px;
-        text-rendering: optimizeLegibility;
-        letter-spacing: -0.025em;
-        font-weight: 500;
-        flex-grow: 2;
-        display: var(--tb-toolbar-title-display, block);
-      }
-
-      .tabs {
-        flex-grow: 1;
-        text-transform: uppercase;
-        height: 100%;
-      }
-
-      paper-tabs {
-        --paper-tabs-selection-bar-color: white;
-      }
-
-      .global-actions {
-        flex-grow: 2;
-        display: inline-flex; /* Ensure that icons stay aligned */
-        justify-content: flex-end;
-        text-align: right;
-        color: white;
-      }
-
-      .global-actions a {
-        color: white;
-      }
-
-      #toolbar-content {
-        width: 100%;
-        height: 100%;
-        display: flex;
-        flex-direction: row;
-        justify-content: space-between;
-        align-items: center;
-      }
-
-      #content {
-        height: 100%;
-      }
-
-      [disabled] {
-        opacity: 0.2;
-        color: white;
-      }
-
-    </style>
-  </template>
-  <script>
-    Polymer({
-      is: "tf-tensorboard",
-      behaviors: [TF.TensorBoard.AutoReloadBehavior],
-      properties: {
-        router: {
-          type: Object,
-          value: function() {
-            return TF.Backend.router();
-          },
-        },
-        _backend: {
-          type: Object,
-          computed: "_makeBackend(router, demoDir)",
-        },
-        _debuggerDataEnabled: {
-          type: Boolean,
-          value: function() {
-            // For now, Tensorboard only shows debugger data if the debugger_data GET param is set
-            // to enabled.
-            let match = window.location.href.match(/[&\?]debugger_data=enabled/);
-            return match && match.length == 1;
-          },
-        },
-        _dashboards: {
-          type: Array,
-          computed: "_makeDashboardList(_backend, router, _debuggerDataEnabled)",
-        },
-        // Maps dashboard name to dashboard object.
-        _dashboardMapping: {
-          type: Object,
-          computed: "_makeDashboardMapping(_dashboards)",
-        },
-        // Which tab is selected (scalars, graph, images etc).
-        mode: {
-          type: String,
-          computed: '_getModeFromIndex(modeIndex)',
-          notify: true,
-          observer: '_modeChanged',
-        },
-        // If this is set to a string, TensorBoard will switch to "demo mode"
-        // and attempt to load serialized json data from that directory. You can
-        // generate conformant json using
-        // tensorboard/scripts/serialize_tensorboard.py
-        demoDir: {
-          type: String,
-          value: null,
-        },
-        // Set this to true to store state in URI hash. Should be true for all non-test purposes.
-        useHash: {
-          type: Boolean,
-          value: false,
-        },
-        disabledTabs: String,
-      },
-      _isTabEnabled: function(tab) {
-        if (this.disabledTabs != null &&
-            this.disabledTabs.split(',').indexOf(tab) >= 0) {
-          return false;
-        }
-        return true;
-      },
-      _getModeFromIndex: function(modeIndex) {
-        var mode = this._dashboards[modeIndex].name;
-        TF.URIStorage.setString(TF.URIStorage.TAB, mode);
-        return mode;
-      },
-      _makeBackend: function(router, demoDir) {
-        // use the demoDir if it is set, otherwise use the provided router
-        if (demoDir != null) {
-          router = TF.Backend.router(demoDir, true);
-        }
-        return new TF.Backend.Backend(router);
-      },
-      _isReloadDisabled: function(mode) {
-        return !this._debuggerDataEnabled && mode == 'graphs';
-      },
-      selectedDashboard: function() {
-        var dashboard = this._dashboardMapping[this.mode];
-        if (dashboard == null) {
-          throw new Error(`Unable to find dashboard for mode: ${this.mode}`);
-        }
-        return dashboard;
-      },
-      ready: function() {
-        TF.Globals.USE_HASH = this.useHash;
-
-        this._getModeFromHash();
-        window.addEventListener('hashchange', function() {
-          this._getModeFromHash();
-        }.bind(this));
-      },
-      _makeDashboardList: function(backend, router, debuggerDataEnabled) {
-        if (!backend || !router) {
-          // The dashboards require these entities. We are not ready to construct dashboards.
-          return null;
-        }
-
-        return [
-          new TF.Dashboard.TfScalarDashboard(backend, router),
-          new TF.Dashboard.TfImageDashboard(backend),
-          new TF.Dashboard.TfAudioDashboard(backend),
-          new TF.Dashboard.TfGraphDashboard(backend, debuggerDataEnabled),
-          new TF.Dashboard.TfDistributionDashboard(backend),
-          new TF.Dashboard.TfHistogramDashboard(backend),
-          new TF.Dashboard.VzProjectorDashboard('data/plugin/projector'),
-          new TF.Dashboard.TfTextDashboard(backend),
-        ];
-      },
-      _makeDashboardMapping: function(dashboards) {
-        if (!dashboards) {
-          return null;
-        }
-
-        let mapping = {};
-        dashboards.forEach(function(dashboard) {
-          mapping[dashboard.name] = dashboard;
-        });
-        return mapping;
-      },
-      _getModeFromHash: function() {
-        var tabName = TF.URIStorage.getString(TF.URIStorage.TAB);
-        var modeIndex;
-        for (var i = 0; i < this._dashboards.length; i++) {
-          if (this._dashboards[i].name == tabName) {
-            modeIndex = i;
-            break;
-          }
-        }
-
-        if (modeIndex === undefined && this.modeIndex == null) {
-          // Select the first tab as default.
-          this.set('modeIndex', 0);
-        }
-        if (modeIndex !== undefined && modeIndex != this.modeIndex) {
-          this.set('modeIndex', modeIndex);
-        }
-      },
-      _modeChanged: function(mode) {
-        let currentDashboard = this.$.content.firstChild;
-        if (currentDashboard) {
-          this.$.content.removeChild(currentDashboard);
-        }
-
-        if (!mode || !this._dashboardMapping) {
-          return;
-        }
-
-        // Append the new dashboard.
-        const newDashboard = this.selectedDashboard();
-        this.$.content.appendChild(newDashboard);
-      },
-      reload: function() {
-        this.selectedDashboard().reload();
-      },
-      openSettings: function() {
-        this.$.settings.open();
-      },
-    });
-  </script>
-  
-  <script>(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
-"use strict";
-var AnalyticsLogger = (function () {
-    /**
-     * Constructs an event logger using Google Analytics. It assumes there is a
-     * Google Analytics script added to the page elsewhere. If there is no such
-     * script, the logger acts as a no-op.
-     *
-     * @param pageViewLogging Whether to log page views.
-     * @param eventLogging Whether to log user interaction.
-     */
-    function AnalyticsLogger(pageViewLogging, eventLogging) {
-        if (typeof ga === 'undefined' || ga == null) {
-            this.eventLogging = false;
-            this.pageViewLogging = false;
-            return;
-        }
-        this.eventLogging = eventLogging;
-        this.pageViewLogging = pageViewLogging;
-    }
-    AnalyticsLogger.prototype.logPageView = function (pageTitle) {
-        if (this.pageViewLogging) {
-            // Always send a page view.
-            ga('send', { hitType: 'pageview', page: "/v/" + pageTitle });
-        }
-    };
-    AnalyticsLogger.prototype.logProjectionChanged = function (projection) {
-        if (this.eventLogging) {
-            ga('send', {
-                hitType: 'event',
-                eventCategory: 'Projection',
-                eventAction: 'click',
-                eventLabel: projection
-            });
-        }
-    };
-    AnalyticsLogger.prototype.logWebGLDisabled = function () {
-        if (this.eventLogging) {
-            ga('send', {
-                hitType: 'event',
-                eventCategory: 'Error',
-                eventAction: 'PageLoad',
-                eventLabel: 'WebGL_disabled'
-            });
-        }
-    };
-    return AnalyticsLogger;
-}());
-exports.AnalyticsLogger = AnalyticsLogger;
-
-},{}],2:[function(require,module,exports){
-/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-"use strict";
-/**
- * This is a fork of the Karpathy's TSNE.js (original license below).
- * This fork implements Barnes-Hut approximation and runs in O(NlogN)
- * time, as opposed to the Karpathy's O(N^2) version.
- *
- * @author smilkov@google.com (Daniel Smilkov)
- */
-/**
- * The MIT License (MIT)
- * Copyright (c) 2015 Andrej Karpathy
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-var sptree_1 = require('./sptree');
-/**
- * Barnes-hut approximation level. Higher means more approximation and faster
- * results. Recommended value mentioned in the paper is 0.8.
- */
-var THETA = 0.8;
-var MIN_POSSIBLE_PROB = 1E-9;
-// Variables used for memorizing the second random number since running
-// gaussRandom() generates two random numbers at the cost of 1 atomic
-// computation. This optimization results in 2X speed-up of the generator.
-var return_v = false;
-var v_val = 0.0;
-/** Returns the square euclidean distance between two vectors. */
-function dist2(a, b) {
-    if (a.length !== b.length) {
-        throw new Error('Vectors a and b must be of same length');
-    }
-    var result = 0;
-    for (var i = 0; i < a.length; ++i) {
-        var diff = a[i] - b[i];
-        result += diff * diff;
-    }
-    return result;
-}
-exports.dist2 = dist2;
-/** Returns the square euclidean distance between two 2D points. */
-function dist2_2D(a, b) {
-    var dX = a[0] - b[0];
-    var dY = a[1] - b[1];
-    return dX * dX + dY * dY;
-}
-exports.dist2_2D = dist2_2D;
-/** Returns the square euclidean distance between two 3D points. */
-function dist2_3D(a, b) {
-    var dX = a[0] - b[0];
-    var dY = a[1] - b[1];
-    var dZ = a[2] - b[2];
-    return dX * dX + dY * dY + dZ * dZ;
-}
-exports.dist2_3D = dist2_3D;
-function gaussRandom(rng) {
-    if (return_v) {
-        return_v = false;
-        return v_val;
-    }
-    var u = 2 * rng() - 1;
-    var v = 2 * rng() - 1;
-    var r = u * u + v * v;
-    if (r === 0 || r > 1) {
-        return gaussRandom(rng);
-    }
-    var c = Math.sqrt(-2 * Math.log(r) / r);
-    v_val = v * c; // cache this for next function call for efficiency
-    return_v = true;
-    return u * c;
-}
-;
-// return random normal number
-function randn(rng, mu, std) {
-    return mu + gaussRandom(rng) * std;
-}
-;
-// utilitity that creates contiguous vector of zeros of size n
-function zeros(n) {
-    return new Float64Array(n);
-}
-;
-// utility that returns a matrix filled with random numbers
-// generated by the provided generator.
-function randnMatrix(n, d, rng) {
-    var nd = n * d;
-    var x = zeros(nd);
-    for (var i = 0; i < nd; ++i) {
-        x[i] = randn(rng, 0.0, 1E-4);
-    }
-    return x;
-}
-;
-// utility that returns a matrix filled with the provided value.
-function arrayofs(n, d, val) {
-    var x = [];
-    for (var i = 0; i < n; ++i) {
-        x.push(d === 3 ? [val, val, val] : [val, val]);
-    }
-    return x;
-}
-;
-// compute (p_{i|j} + p_{j|i})/(2n)
-function nearest2P(nearest, perplexity, tol) {
-    var N = nearest.length;
-    var Htarget = Math.log(perplexity); // target entropy of distribution
-    var P = zeros(N * N); // temporary probability matrix
-    var K = nearest[0].length;
-    var pRow = new Array(K); // pij[].
-    for (var i = 0; i < N; ++i) {
-        var neighbors = nearest[i];
-        var betaMin = -Infinity;
-        var betaMax = Infinity;
-        var beta = 1; // initial value of precision
-        var maxTries = 50;
-        // perform binary search to find a suitable precision beta
-        // so that the entropy of the distribution is appropriate
-        var numTries = 0;
-        while (true) {
-            // compute entropy and kernel row with beta precision
-            var psum = 0.0;
-            for (var k = 0; k < neighbors.length; ++k) {
-                var neighbor = neighbors[k];
-                var pij = (i === neighbor.index) ? 0 : Math.exp(-neighbor.dist * beta);
-                pij = Math.max(pij, MIN_POSSIBLE_PROB);
-                pRow[k] = pij;
-                psum += pij;
-            }
-            // normalize p and compute entropy
-            var Hhere = 0.0;
-            for (var k = 0; k < pRow.length; ++k) {
-                pRow[k] /= psum;
-                var pij = pRow[k];
-                if (pij > 1E-7) {
-                    Hhere -= pij * Math.log(pij);
-                }
-                ;
-            }
-            // adjust beta based on result
-            if (Hhere > Htarget) {
-                // entropy was too high (distribution too diffuse)
-                // so we need to increase the precision for more peaky distribution
-                betaMin = beta; // move up the bounds
-                if (betaMax === Infinity) {
-                    beta = beta * 2;
-                }
-                else {
-                    beta = (beta + betaMax) / 2;
-                }
-            }
-            else {
-                // converse case. make distrubtion less peaky
-                betaMax = beta;
-                if (betaMin === -Infinity) {
-                    beta = beta / 2;
-                }
-                else {
-                    beta = (beta + betaMin) / 2;
-                }
-            }
-            numTries++;
-            // stopping conditions: too many tries or got a good precision
-            if (numTries >= maxTries || Math.abs(Hhere - Htarget) < tol) {
-                break;
-            }
-        }
-        // copy over the final prow to P at row i
-        for (var k = 0; k < pRow.length; ++k) {
-            var pij = pRow[k];
-            var j = neighbors[k].index;
-            P[i * N + j] = pij;
-        }
-    } // end loop over examples i
-    // symmetrize P and normalize it to sum to 1 over all ij
-    var N2 = N * 2;
-    for (var i = 0; i < N; ++i) {
-        for (var j = i + 1; j < N; ++j) {
-            var i_j = i * N + j;
-            var j_i = j * N + i;
-            var value = (P[i_j] + P[j_i]) / N2;
-            P[i_j] = value;
-            P[j_i] = value;
-        }
-    }
-    return P;
-}
-;
-// helper function
-function sign(x) {
-    return x > 0 ? 1 : x < 0 ? -1 : 0;
-}
-function computeForce_2d(force, mult, pointA, pointB) {
-    force[0] += mult * (pointA[0] - pointB[0]);
-    force[1] += mult * (pointA[1] - pointB[1]);
-}
-function computeForce_3d(force, mult, pointA, pointB) {
-    force[0] += mult * (pointA[0] - pointB[0]);
-    force[1] += mult * (pointA[1] - pointB[1]);
-    force[2] += mult * (pointA[2] - pointB[2]);
-}
-var TSNE = (function () {
-    function TSNE(opt) {
-        this.iter = 0;
-        opt = opt || { dim: 2 };
-        this.perplexity = opt.perplexity || 30;
-        this.epsilon = opt.epsilon || 10;
-        this.rng = opt.rng || Math.random;
-        this.dim = opt.dim;
-        if (opt.dim === 2) {
-            this.dist2 = dist2_2D;
-            this.computeForce = computeForce_2d;
-        }
-        else if (opt.dim === 3) {
-            this.dist2 = dist2_3D;
-            this.computeForce = computeForce_3d;
-        }
-        else {
-            throw new Error('Only 2D and 3D is supported');
-        }
-    }
-    // this function takes a fattened distance matrix and creates
-    // matrix P from them.
-    // D is assumed to be provided as an array of size N^2.
-    TSNE.prototype.initDataDist = function (nearest) {
-        var N = nearest.length;
-        this.nearest = nearest;
-        this.P = nearest2P(nearest, this.perplexity, 1E-4);
-        this.N = N;
-        this.initSolution(); // refresh this
-    };
-    // (re)initializes the solution to random
-    TSNE.prototype.initSolution = function () {
-        // generate random solution to t-SNE
-        this.Y = randnMatrix(this.N, this.dim, this.rng); // the solution
-        this.gains = arrayofs(this.N, this.dim, 1.0); // step gains
-        // to accelerate progress in unchanging directions
-        this.ystep = arrayofs(this.N, this.dim, 0.0); // momentum accumulator
-        this.iter = 0;
-    };
-    // return pointer to current solution
-    TSNE.prototype.getSolution = function () { return this.Y; };
-    // perform a single step of optimization to improve the embedding
-    TSNE.prototype.step = function () {
-        this.iter += 1;
-        var N = this.N;
-        var grad = this.costGrad(this.Y); // evaluate gradient
-        // perform gradient step
-        var ymean = this.dim === 3 ? [0, 0, 0] : [0, 0];
-        for (var i = 0; i < N; ++i) {
-            for (var d = 0; d < this.dim; ++d) {
-                var gid = grad[i][d];
-                var sid = this.ystep[i][d];
-                var gainid = this.gains[i][d];
-                // compute gain update
-                var newgain = sign(gid) === sign(sid) ? gainid * 0.8 : gainid + 0.2;
-                if (newgain < 0.01) {
-                    newgain = 0.01; // clamp
-                }
-                this.gains[i][d] = newgain; // store for next turn
-                // compute momentum step direction
-                var momval = this.iter < 250 ? 0.5 : 0.8;
-                var newsid = momval * sid - this.epsilon * newgain * grad[i][d];
-                this.ystep[i][d] = newsid; // remember the step we took
-                // step!
-                var i_d = i * this.dim + d;
-                this.Y[i_d] += newsid;
-                ymean[d] += this.Y[i_d]; // accumulate mean so that we
-            }
-        }
-        // reproject Y to be zero mean
-        for (var i = 0; i < N; ++i) {
-            for (var d = 0; d < this.dim; ++d) {
-                this.Y[i * this.dim + d] -= ymean[d] / N;
-            }
-        }
-    };
-    // return cost and gradient, given an arrangement
-    TSNE.prototype.costGrad = function (Y) {
-        var _this = this;
-        var N = this.N;
-        var P = this.P;
-        // Trick that helps with local optima.
-        var alpha = this.iter < 100 ? 4 : 1;
-        // Make data for the SP tree.
-        var points = new Array(N); // (x, y)[]
-        for (var i = 0; i < N; ++i) {
-            var iTimesD = i * this.dim;
-            var row = new Array(this.dim);
-            for (var d = 0; d < this.dim; ++d) {
-                row[d] = Y[iTimesD + d];
-            }
-            points[i] = row;
-        }
-        // Make a tree.
-        var tree = new sptree_1.SPTree(points);
-        var root = tree.root;
-        // Annotate the tree.
-        var annotateTree = function (node) {
-            var numCells = 1;
-            if (node.children == null) {
-                // Update the current node and tell the parent.
-                node.numCells = numCells;
-                node.yCell = node.point;
-                return { numCells: numCells, yCell: node.yCell };
-            }
-            // node.point is a 2 or 3-dim number[], so slice() makes a copy.
-            var yCell = node.point.slice();
-            for (var i = 0; i < node.children.length; ++i) {
-                var child = node.children[i];
-                if (child == null) {
-                    continue;
-                }
-                var result = annotateTree(child);
-                numCells += result.numCells;
-                for (var d = 0; d < _this.dim; ++d) {
-                    yCell[d] += result.yCell[d];
-                }
-            }
-            // Update the node and tell the parent.
-            node.numCells = numCells;
-            node.yCell = yCell.map(function (v) { return v / numCells; });
-            return { numCells: numCells, yCell: yCell };
-        };
-        // Augment the tree with more info.
-        annotateTree(root);
-        tree.visit(function (node, low, high) {
-            node.rCell = high[0] - low[0];
-            return false;
-        });
-        // compute current Q distribution, unnormalized first
-        var grad = [];
-        var Z = 0;
-        var forces = new Array(N);
-        var _loop_1 = function(i) {
-            var pointI = points[i];
-            // Compute the positive forces for the i-th node.
-            var Fpos = this_1.dim === 3 ? [0, 0, 0] : [0, 0];
-            var neighbors = this_1.nearest[i];
-            for (var k = 0; k < neighbors.length; ++k) {
-                var j = neighbors[k].index;
-                var pij = P[i * N + j];
-                var pointJ = points[j];
-                var squaredDistItoJ = this_1.dist2(pointI, pointJ);
-                var premult = pij / (1 + squaredDistItoJ);
-                this_1.computeForce(Fpos, premult, pointI, pointJ);
-            }
-            // Compute the negative forces for the i-th node.
-            var FnegZ = this_1.dim === 3 ? [0, 0, 0] : [0, 0];
-            tree.visit(function (node) {
-                var squaredDistToCell = _this.dist2(pointI, node.yCell);
-                // Squared distance from point i to cell.
-                if (node.children == null ||
-                    (squaredDistToCell > 0 &&
-                        node.rCell / Math.sqrt(squaredDistToCell) < THETA)) {
-                    var qijZ_1 = 1 / (1 + squaredDistToCell);
-                    var dZ = node.numCells * qijZ_1;
-                    Z += dZ;
-                    dZ *= qijZ_1;
-                    _this.computeForce(FnegZ, dZ, pointI, node.yCell);
-                    return true;
-                }
-                // Cell is too close to approximate.
-                var squaredDistToPoint = _this.dist2(pointI, node.point);
-                var qijZ = 1 / (1 + squaredDistToPoint);
-                Z += qijZ;
-                qijZ *= qijZ;
-                _this.computeForce(FnegZ, qijZ, pointI, node.point);
-                return false;
-            }, true);
-            forces[i] = [Fpos, FnegZ];
-        };
-        var this_1 = this;
-        for (var i = 0; i < N; ++i) {
-            _loop_1(i);
-        }
-        // Normalize the negative forces and compute the gradient.
-        var A = 4 * alpha;
-        var B = 4 / Z;
-        for (var i = 0; i < N; ++i) {
-            var _a = forces[i], FPos = _a[0], FNegZ = _a[1];
-            var gsum = new Array(this.dim);
-            for (var d = 0; d < this.dim; ++d) {
-                gsum[d] = A * FPos[d] - B * FNegZ[d];
-            }
-            grad.push(gsum);
-        }
-        return grad;
-    };
-    return TSNE;
-}());
-exports.TSNE = TSNE;
-
-},{"./sptree":23}],3:[function(require,module,exports){
-/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-"use strict";
-var data_1 = require('./data');
-var data_provider_1 = require('./data-provider');
-var dataProvider = require('./data-provider');
-var logging = require('./logging');
-var BYTES_EXTENSION = '.bytes';
-/** Data provider that loads data from a demo folder. */
-var DemoDataProvider = (function () {
-    function DemoDataProvider(projectorConfigPath) {
-        this.projectorConfigPath = projectorConfigPath;
-    }
-    DemoDataProvider.prototype.getEmbeddingInfo = function (tensorName) {
-        var embeddings = this.projectorConfig.embeddings;
-        for (var i = 0; i < embeddings.length; i++) {
-            var embedding = embeddings[i];
-            if (embedding.tensorName === tensorName) {
-                return embedding;
-            }
-        }
-        return null;
-    };
-    DemoDataProvider.prototype.retrieveRuns = function (callback) {
-        callback(['Demo']);
-    };
-    DemoDataProvider.prototype.retrieveProjectorConfig = function (run, callback) {
-        var _this = this;
-        var msgId = logging.setModalMessage('Fetching projector config...');
-        d3.json(this.projectorConfigPath, function (err, projectorConfig) {
-            if (err) {
-                var errorMessage = err;
-                // If the error is a valid XMLHttpResponse, it's possible this is a
-                // cross-origin error.
-                if (err.responseText != null) {
-                    errorMessage = 'Cannot fetch projector config, possibly a ' +
-                        'Cross-Origin request error.';
-                }
-                logging.setErrorMessage(errorMessage, 'fetching projector config');
-                return;
-            }
-            logging.setModalMessage(null, msgId);
-            _this.projectorConfig = projectorConfig;
-            callback(projectorConfig);
-        });
-    };
-    DemoDataProvider.prototype.retrieveTensor = function (run, tensorName, callback) {
-        var embedding = this.getEmbeddingInfo(tensorName);
-        var url = "" + embedding.tensorPath;
-        if (embedding.tensorPath.substr(-1 * BYTES_EXTENSION.length) ===
-            BYTES_EXTENSION) {
-            dataProvider.retrieveTensorAsBytes(this, this.getEmbeddingInfo(tensorName), run, tensorName, url, callback);
-        }
-        else {
-            logging.setModalMessage('Fetching tensors...', data_provider_1.TENSORS_MSG_ID);
-            var request_1 = new XMLHttpRequest();
-            request_1.open('GET', url);
-            request_1.responseType = 'arraybuffer';
-            request_1.onerror = function () {
-                logging.setErrorMessage(request_1.responseText, 'fetching tensors');
-            };
-            request_1.onload = function () {
-                dataProvider.parseTensors(request_1.response).then(function (points) {
-                    callback(new data_1.DataSet(points));
-                });
-            };
-            request_1.send();
-        }
-    };
-    DemoDataProvider.prototype.retrieveSpriteAndMetadata = function (run, tensorName, callback) {
-        var embedding = this.getEmbeddingInfo(tensorName);
-        var spriteImagePath = null;
-        if (embedding.sprite && embedding.sprite.imagePath) {
-            spriteImagePath = embedding.sprite.imagePath;
-        }
-        dataProvider.retrieveSpriteAndMetadataInfo(embedding.metadataPath, spriteImagePath, embedding.sprite, callback);
-    };
-    DemoDataProvider.prototype.getBookmarks = function (run, tensorName, callback) {
-        var embedding = this.getEmbeddingInfo(tensorName);
-        var msgId = logging.setModalMessage('Fetching bookmarks...');
-        d3.json(embedding.bookmarksPath, function (err, bookmarks) {
-            if (err) {
-                logging.setErrorMessage(err.responseText);
-                return;
-            }
-            logging.setModalMessage(null, msgId);
-            callback(bookmarks);
-        });
-    };
-    return DemoDataProvider;
-}());
-exports.DemoDataProvider = DemoDataProvider;
-
-},{"./data":7,"./data-provider":6,"./logging":12}],4:[function(require,module,exports){
-/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-"use strict";
-var data_1 = require('./data');
-var data_provider_1 = require('./data-provider');
-var ProtoDataProvider = (function () {
-    function ProtoDataProvider(dataProto) {
-        this.dataProto = dataProto;
-    }
-    ProtoDataProvider.prototype.retrieveRuns = function (callback) {
-        callback(['proto']);
-    };
-    ProtoDataProvider.prototype.retrieveProjectorConfig = function (run, callback) {
-        callback({
-            modelCheckpointPath: 'proto',
-            embeddings: [{
-                    tensorName: 'proto',
-                    tensorShape: this.dataProto.shape,
-                    metadataPath: 'proto'
-                }]
-        });
-    };
-    ProtoDataProvider.prototype.retrieveTensor = function (run, tensorName, callback) {
-        callback(this.flatArrayToDataset(this.dataProto.tensor));
-    };
-    ProtoDataProvider.prototype.retrieveSpriteAndMetadata = function (run, tensorName, callback) {
-        var columnNames = this.dataProto.metadata.columns.map(function (c) { return c.name; });
-        var n = this.dataProto.shape[0];
-        var pointsMetadata = new Array(n);
-        this.dataProto.metadata.columns.forEach(function (c) {
-            var values = c.numericValues || c.stringValues;
-            for (var i = 0; i < n; i++) {
-                pointsMetadata[i] = pointsMetadata[i] || {};
-                pointsMetadata[i][c.name] = values[i];
-            }
-        });
-        callback({
-            stats: data_provider_1.analyzeMetadata(columnNames, pointsMetadata),
-            pointsInfo: pointsMetadata
-        });
-    };
-    ProtoDataProvider.prototype.getBookmarks = function (run, tensorName, callback) {
-        return callback([]);
-    };
-    ProtoDataProvider.prototype.flatArrayToDataset = function (tensor) {
-        var points = [];
-        var n = this.dataProto.shape[0];
-        var d = this.dataProto.shape[1];
-        if (n * d !== tensor.length) {
-            throw 'The shape doesn\'t match the length of the flattened array';
-        }
-        for (var i = 0; i < n; i++) {
-            var offset = i * d;
-            points.push({
-                vector: new Float32Array(tensor.slice(offset, offset + d)),
-                metadata: {},
-                projections: null,
-                index: i
-            });
-        }
-        return new data_1.DataSet(points);
-    };
-    return ProtoDataProvider;
-}());
-exports.ProtoDataProvider = ProtoDataProvider;
-
-},{"./data":7,"./data-provider":6}],5:[function(require,module,exports){
-/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-"use strict";
-var dataProvider = require('./data-provider');
-var logging = require('./logging');
-// Limit for the number of data points we receive from the server.
-exports.LIMIT_NUM_POINTS = 100000;
-/**
- * Data provider that loads data provided by a python server (usually backed
- * by a checkpoint file).
- */
-var ServerDataProvider = (function () {
-    function ServerDataProvider(routePrefix) {
-        this.runProjectorConfigCache = {};
-        this.routePrefix = routePrefix;
-    }
-    ServerDataProvider.prototype.getEmbeddingInfo = function (run, tensorName, callback) {
-        this.retrieveProjectorConfig(run, function (config) {
-            var embeddings = config.embeddings;
-            for (var i = 0; i < embeddings.length; i++) {
-                var embedding = embeddings[i];
-                if (embedding.tensorName === tensorName) {
-                    callback(embedding);
-                    return;
-                }
-            }
-            callback(null);
-        });
-    };
-    ServerDataProvider.prototype.retrieveRuns = function (callback) {
-        var msgId = logging.setModalMessage('Fetching runs...');
-        d3.json(this.routePrefix + "/runs", function (err, runs) {
-            if (err) {
-                logging.setErrorMessage(err.responseText, 'fetching runs');
-                return;
-            }
-            logging.setModalMessage(null, msgId);
-            callback(runs);
-        });
-    };
-    ServerDataProvider.prototype.retrieveProjectorConfig = function (run, callback) {
-        var _this = this;
-        if (run in this.runProjectorConfigCache) {
-            callback(this.runProjectorConfigCache[run]);
-            return;
-        }
-        var msgId = logging.setModalMessage('Fetching projector config...');
-        d3.json(this.routePrefix + "/info?run=" + run, function (err, config) {
-            if (err) {
-                logging.setErrorMessage(err.responseText, 'fetching projector config');
-                return;
-            }
-            logging.setModalMessage(null, msgId);
-            _this.runProjectorConfigCache[run] = config;
-            callback(config);
-        });
-    };
-    ServerDataProvider.prototype.retrieveTensor = function (run, tensorName, callback) {
-        var _this = this;
-        this.getEmbeddingInfo(run, tensorName, function (embedding) {
-            dataProvider.retrieveTensorAsBytes(_this, embedding, run, tensorName, (_this.routePrefix + "/tensor?run=" + run + "&name=" + tensorName) +
-                ("&num_rows=" + exports.LIMIT_NUM_POINTS), callback);
-        });
-    };
-    ServerDataProvider.prototype.retrieveSpriteAndMetadata = function (run, tensorName, callback) {
-        var _this = this;
-        this.getEmbeddingInfo(run, tensorName, function (embedding) {
-            var metadataPath = null;
-            if (embedding.metadataPath) {
-                metadataPath =
-                    (_this.routePrefix + "/metadata?") +
-                        ("run=" + run + "&name=" + tensorName + "&num_rows=" + exports.LIMIT_NUM_POINTS);
-            }
-            var spriteImagePath = null;
-            if (embedding.sprite && embedding.sprite.imagePath) {
-                spriteImagePath =
-                    _this.routePrefix + "/sprite_image?run=" + run + "&name=" + tensorName;
-            }
-            dataProvider.retrieveSpriteAndMetadataInfo(metadataPath, spriteImagePath, embedding.sprite, callback);
-        });
-    };
-    ServerDataProvider.prototype.getBookmarks = function (run, tensorName, callback) {
-        var msgId = logging.setModalMessage('Fetching bookmarks...');
-        d3.json(this.routePrefix + "/bookmarks?run=" + run + "&name=" + tensorName, function (err, bookmarks) {
-            logging.setModalMessage(null, msgId);
-            if (!err) {
-                callback(bookmarks);
-            }
-        });
-    };
-    return ServerDataProvider;
-}());
-exports.ServerDataProvider = ServerDataProvider;
-
-},{"./data-provider":6,"./logging":12}],6:[function(require,module,exports){
-/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-"use strict";
-var data_1 = require('./data');
-var logging = require('./logging');
-var util_1 = require('./util');
-/** Maximum number of colors supported in the color map. */
-var NUM_COLORS_COLOR_MAP = 50;
-var MAX_SPRITE_IMAGE_SIZE_PX = 8192;
-exports.METADATA_MSG_ID = 'metadata';
-exports.TENSORS_MSG_ID = 'tensors';
-function retrieveTensorAsBytes(dp, embedding, run, tensorName, tensorsPath, callback) {
-    // Get the tensor.
-    logging.setModalMessage('Fetching tensor values...', exports.TENSORS_MSG_ID);
-    var xhr = new XMLHttpRequest();
-    xhr.open('GET', tensorsPath);
-    xhr.responseType = 'arraybuffer';
-    xhr.onprogress = function (ev) {
-        if (ev.lengthComputable) {
-            var percent = (ev.loaded * 100 / ev.total).toFixed(1);
-            logging.setModalMessage('Fetching tensor values: ' + percent + '%', exports.TENSORS_MSG_ID);
-        }
-    };
-    xhr.onload = function () {
-        if (xhr.status !== 200) {
-            var msg = String.fromCharCode.apply(null, new Uint8Array(xhr.response));
-            logging.setErrorMessage(msg, 'fetching tensors');
-            return;
-        }
-        var data;
-        try {
-            data = new Float32Array(xhr.response);
-        }
-        catch (e) {
-            logging.setErrorMessage(e, 'parsing tensor bytes');
-            return;
-        }
-        var dim = embedding.tensorShape[1];
-        var N = data.length / dim;
-        if (embedding.tensorShape[0] > N) {
-            logging.setWarningMessage(("Showing the first " + N.toLocaleString()) +
-                (" of " + embedding.tensorShape[0].toLocaleString() + " data points"));
-        }
-        parseTensorsFromFloat32Array(data, dim).then(function (dataPoints) {
-            callback(new data_1.DataSet(dataPoints));
-        });
-    };
-    xhr.send();
-}
-exports.retrieveTensorAsBytes = retrieveTensorAsBytes;
-function parseRawTensors(content, callback) {
-    parseTensors(content).then(function (data) {
-        callback(new data_1.DataSet(data));
-    });
-}
-exports.parseRawTensors = parseRawTensors;
-function parseRawMetadata(contents, callback) {
-    parseMetadata(contents).then(function (result) { return callback(result); });
-}
-exports.parseRawMetadata = parseRawMetadata;
-/**
- * Parse an ArrayBuffer in a streaming fashion line by line (or custom delim).
- * Can handle very large files.
- *
- * @param content The array buffer.
- * @param callback The callback called on each line.
- * @param chunkSize The size of each read chunk, defaults to ~1MB. (optional)
- * @param delim The delimiter used to split a line, defaults to '\n'. (optional)
- * @returns A promise for when it is finished.
- */
-function streamParse(content, callback, chunkSize, delim) {
-    if (chunkSize === void 0) { chunkSize = 1000000; }
-    if (delim === void 0) { delim = '\n'; }
-    return new Promise(function (resolve, reject) {
-        var offset = 0;
-        var bufferSize = content.byteLength - 1;
-        var data = '';
-        function readHandler(str) {
-            offset += chunkSize;
-            var parts = str.split(delim);
-            var first = data + parts[0];
-            if (parts.length === 1) {
-                data = first;
-                readChunk(offset, chunkSize);
-                return;
-            }
-            data = parts[parts.length - 1];
-            callback(first);
-            for (var i = 1; i < parts.length - 1; i++) {
-                callback(parts[i]);
-            }
-            if (offset >= bufferSize) {
-                if (data) {
-                    callback(data);
-                }
-                resolve();
-                return;
-            }
-            readChunk(offset, chunkSize);
-        }
-        function readChunk(offset, size) {
-            var contentChunk = content.slice(offset, offset + size);
-            var blob = new Blob([contentChunk]);
-            var file = new FileReader();
-            file.onload = function (e) { return readHandler(e.target.result); };
-            file.readAsText(blob);
-        }
-        readChunk(offset, chunkSize);
-    });
-}
-/** Parses a tsv text file. */
-function parseTensors(content, valueDelim) {
-    if (valueDelim === void 0) { valueDelim = '\t'; }
-    logging.setModalMessage('Parsing tensors...', exports.TENSORS_MSG_ID);
-    return new Promise(function (resolve, reject) {
-        var data = [];
-        var numDim;
-        streamParse(content, function (line) {
-            line = line.trim();
-            if (line === '') {
-                return;
-            }
-            var row = line.split(valueDelim);
-            var dataPoint = {
-                metadata: {},
-                vector: null,
-                index: data.length,
-                projections: null,
-            };
-            // If the first label is not a number, take it as the label.
-            if (isNaN(row[0]) || numDim === row.length - 1) {
-                dataPoint.metadata['label'] = row[0];
-                dataPoint.vector = new Float32Array(row.slice(1).map(Number));
-            }
-            else {
-                dataPoint.vector = new Float32Array(row.map(Number));
-            }
-            data.push(dataPoint);
-            if (numDim == null) {
-                numDim = dataPoint.vector.length;
-            }
-            if (numDim !== dataPoint.vector.length) {
-                logging.setModalMessage('Parsing failed. Vector dimensions do not match');
-                throw Error('Parsing failed');
-            }
-            if (numDim <= 1) {
-                logging.setModalMessage('Parsing failed. Found a vector with only one dimension?');
-                throw Error('Parsing failed');
-            }
-        }).then(function () {
-            logging.setModalMessage(null, exports.TENSORS_MSG_ID);
-            resolve(data);
-        });
-    });
-}
-exports.parseTensors = parseTensors;
-/** Parses a tsv text file. */
-function parseTensorsFromFloat32Array(data, dim) {
-    return util_1.runAsyncTask('Parsing tensors...', function () {
-        var N = data.length / dim;
-        var dataPoints = [];
-        var offset = 0;
-        for (var i = 0; i < N; ++i) {
-            dataPoints.push({
-                metadata: {},
-                vector: data.subarray(offset, offset + dim),
-                index: i,
-                projections: null,
-            });
-            offset += dim;
-        }
-        return dataPoints;
-    }, exports.TENSORS_MSG_ID).then(function (dataPoints) {
-        logging.setModalMessage(null, exports.TENSORS_MSG_ID);
-        return dataPoints;
-    });
-}
-exports.parseTensorsFromFloat32Array = parseTensorsFromFloat32Array;
-function analyzeMetadata(columnNames, pointsMetadata) {
-    var columnStats = columnNames.map(function (name) {
-        return {
-            name: name,
-            isNumeric: true,
-            tooManyUniqueValues: false,
-            min: Number.POSITIVE_INFINITY,
-            max: Number.NEGATIVE_INFINITY
-        };
-    });
-    var mapOfValues = columnNames.map(function () { return d3.map(); });
-    pointsMetadata.forEach(function (metadata) {
-        columnNames.forEach(function (name, colIndex) {
-            var stats = columnStats[colIndex];
-            var map = mapOfValues[colIndex];
-            var value = metadata[name];
-            // Skip missing values.
-            if (value == null) {
-                return;
-            }
-            if (!stats.tooManyUniqueValues) {
-                if (map.has(value)) {
-                    map.set(value, map.get(value) + 1);
-                }
-                else {
-                    map.set(value, 1);
-                }
-                if (map.size() > NUM_COLORS_COLOR_MAP) {
-                    stats.tooManyUniqueValues = true;
-                }
-            }
-            if (isNaN(value)) {
-                stats.isNumeric = false;
-            }
-            else {
-                metadata[name] = +value;
-                stats.min = Math.min(stats.min, +value);
-                stats.max = Math.max(stats.max, +value);
-            }
-        });
-    });
-    columnStats.forEach(function (stats, colIndex) {
-        stats.uniqueEntries = mapOfValues[colIndex].entries().map(function (e) {
-            return { label: e.key, count: e.value };
-        });
-    });
-    return columnStats;
-}
-exports.analyzeMetadata = analyzeMetadata;
-function parseMetadata(content) {
-    logging.setModalMessage('Parsing metadata...', exports.METADATA_MSG_ID);
-    return new Promise(function (resolve, reject) {
-        var pointsMetadata = [];
-        var hasHeader = false;
-        var lineNumber = 0;
-        var columnNames = ['label'];
-        streamParse(content, function (line) {
-            if (line.trim().length === 0) {
-                return;
-            }
-            if (lineNumber === 0) {
-                hasHeader = line.indexOf('\t') >= 0;
-                // If the first row doesn't contain metadata keys, we assume that the
-                // values are labels.
-                if (hasHeader) {
-                    columnNames = line.split('\t');
-                    lineNumber++;
-                    return;
-                }
-            }
-            lineNumber++;
-            var rowValues = line.split('\t');
-            var metadata = {};
-            pointsMetadata.push(metadata);
-            columnNames.forEach(function (name, colIndex) {
-                var value = rowValues[colIndex];
-                // Normalize missing values.
-                value = (value === '' ? null : value);
-                metadata[name] = value;
-            });
-        }).then(function () {
-            logging.setModalMessage(null, exports.METADATA_MSG_ID);
-            resolve({
-                stats: analyzeMetadata(columnNames, pointsMetadata),
-                pointsInfo: pointsMetadata
-            });
-        });
-    });
-}
-exports.parseMetadata = parseMetadata;
-function fetchImage(url) {
-    return new Promise(function (resolve, reject) {
-        var image = new Image();
-        image.onload = function () { return resolve(image); };
-        image.onerror = function (err) { return reject(err); };
-        image.crossOrigin = '';
-        image.src = url;
-    });
-}
-exports.fetchImage = fetchImage;
-function retrieveSpriteAndMetadataInfo(metadataPath, spriteImagePath, spriteMetadata, callback) {
-    var metadataPromise = Promise.resolve({});
-    if (metadataPath) {
-        metadataPromise = new Promise(function (resolve, reject) {
-            logging.setModalMessage('Fetching metadata...', exports.METADATA_MSG_ID);
-            var request = new XMLHttpRequest();
-            request.open('GET', metadataPath);
-            request.responseType = 'arraybuffer';
-            request.onerror = function () {
-                logging.setErrorMessage(request.responseText, 'fetching metadata');
-                reject();
-            };
-            request.onload = function () {
-                resolve(parseMetadata(request.response));
-            };
-            request.send(null);
-        });
-    }
-    var spriteMsgId = null;
-    var spritesPromise = null;
-    if (spriteImagePath) {
-        spriteMsgId = logging.setModalMessage('Fetching sprite image...');
-        spritesPromise = fetchImage(spriteImagePath);
-    }
-    // Fetch the metadata and the image in parallel.
-    Promise.all([metadataPromise, spritesPromise]).then(function (values) {
-        if (spriteMsgId) {
-            logging.setModalMessage(null, spriteMsgId);
-        }
-        var metadata = values[0], spriteImage = values[1];
-        if (spriteImage && (spriteImage.height > MAX_SPRITE_IMAGE_SIZE_PX ||
-            spriteImage.width > MAX_SPRITE_IMAGE_SIZE_PX)) {
-            logging.setModalMessage(("Error: Sprite image of dimensions " + spriteImage.width + "px x ") +
-                (spriteImage.height + "px exceeds maximum dimensions ") +
-                (MAX_SPRITE_IMAGE_SIZE_PX + "px x " + MAX_SPRITE_IMAGE_SIZE_PX + "px"));
-        }
-        else {
-            metadata.spriteImage = spriteImage;
-            metadata.spriteMetadata = spriteMetadata;
-            callback(metadata);
-        }
-    });
-}
-exports.retrieveSpriteAndMetadataInfo = retrieveSpriteAndMetadataInfo;
-
-},{"./data":7,"./logging":12,"./util":24}],7:[function(require,module,exports){
-/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-"use strict";
-var bh_tsne_1 = require('./bh_tsne');
-var knn = require('./knn');
-var logging = require('./logging');
-var util = require('./util');
-var vector = require('./vector');
-var IS_FIREFOX = navigator.userAgent.toLowerCase().indexOf('firefox') >= 0;
-/** Controls whether nearest neighbors computation is done on the GPU or CPU. */
-var KNN_GPU_ENABLED = util.hasWebGLSupport() && !IS_FIREFOX;
-exports.TSNE_SAMPLE_SIZE = 10000;
-exports.PCA_SAMPLE_SIZE = 50000;
-/** Number of dimensions to sample when doing approximate PCA. */
-exports.PCA_SAMPLE_DIM = 200;
-/** Number of pca components to compute. */
-var NUM_PCA_COMPONENTS = 10;
-/**
- * Reserved metadata attributes used for sequence information
- * NOTE: Use "__seq_next__" as "__next__" is deprecated.
- */
-var SEQUENCE_METADATA_ATTRS = ['__next__', '__seq_next__'];
-function getSequenceNextPointIndex(pointMetadata) {
-    var sequenceAttr = null;
-    for (var _i = 0, SEQUENCE_METADATA_ATTRS_1 = SEQUENCE_METADATA_ATTRS; _i < SEQUENCE_METADATA_ATTRS_1.length; _i++) {
-        var metadataAttr = SEQUENCE_METADATA_ATTRS_1[_i];
-        if (metadataAttr in pointMetadata && pointMetadata[metadataAttr] !== '') {
-            sequenceAttr = pointMetadata[metadataAttr];
-            break;
-        }
-    }
-    if (sequenceAttr == null) {
-        return null;
-    }
-    return +sequenceAttr;
-}
-/**
- * Dataset contains a DataPoints array that should be treated as immutable. This
- * acts as a working subset of the original data, with cached properties
- * from computationally expensive operations. Because creating a subset
- * requires normalizing and shifting the vector space, we make a copy of the
- * data so we can still always create new subsets based on the original data.
- */
-var DataSet = (function () {
-    /** Creates a new Dataset */
-    function DataSet(points, spriteAndMetadataInfo) {
-        this.shuffledDataIndices = [];
-        /**
-         * This keeps a list of all current projections so you can easily test to see
-         * if it's been calculated already.
-         */
-        this.projections = d3.set();
-        this.tSNEIteration = 0;
-        this.tSNEShouldStop = true;
-        this.dim = [0, 0];
-        this.hasTSNERun = false;
-        this.points = points;
-        this.shuffledDataIndices = util.shuffle(d3.range(this.points.length));
-        this.sequences = this.computeSequences(points);
-        this.dim = [this.points.length, this.points[0].vector.length];
-        this.spriteAndMetadataInfo = spriteAndMetadataInfo;
-    }
-    DataSet.prototype.computeSequences = function (points) {
-        // Keep a list of indices seen so we don't compute sequences for a given
-        // point twice.
-        var indicesSeen = new Int8Array(points.length);
-        // Compute sequences.
-        var indexToSequence = {};
-        var sequences = [];
-        for (var i = 0; i < points.length; i++) {
-            if (indicesSeen[i]) {
-                continue;
-            }
-            indicesSeen[i] = 1;
-            // Ignore points without a sequence attribute.
-            var next = getSequenceNextPointIndex(points[i].metadata);
-            if (next == null) {
-                continue;
-            }
-            if (next in indexToSequence) {
-                var existingSequence = indexToSequence[next];
-                // Pushing at the beginning of the array.
-                existingSequence.pointIndices.unshift(i);
-                indexToSequence[i] = existingSequence;
-                continue;
-            }
-            // The current point is pointing to a new/unseen sequence.
-            var newSequence = { pointIndices: [] };
-            indexToSequence[i] = newSequence;
-            sequences.push(newSequence);
-            var currentIndex = i;
-            while (points[currentIndex]) {
-                newSequence.pointIndices.push(currentIndex);
-                var next_1 = getSequenceNextPointIndex(points[currentIndex].metadata);
-                if (next_1 != null) {
-                    indicesSeen[next_1] = 1;
-                    currentIndex = next_1;
-                }
-                else {
-                    currentIndex = -1;
-                }
-            }
-        }
-        return sequences;
-    };
-    DataSet.prototype.projectionCanBeRendered = function (projection) {
-        if (projection !== 'tsne') {
-            return true;
-        }
-        return this.tSNEIteration > 0;
-    };
-    /**
-     * Returns a new subset dataset by copying out data. We make a copy because
-     * we have to modify the vectors by normalizing them.
-     *
-     * @param subset Array of indices of points that we want in the subset.
-     *
-     * @return A subset of the original dataset.
-     */
-    DataSet.prototype.getSubset = function (subset) {
-        var _this = this;
-        var pointsSubset = ((subset != null) && (subset.length > 0)) ?
-            subset.map(function (i) { return _this.points[i]; }) :
-            this.points;
-        var points = pointsSubset.map(function (dp) {
-            return {
-                metadata: dp.metadata,
-                index: dp.index,
-                vector: dp.vector.slice(),
-                projections: {}
-            };
-        });
-        return new DataSet(points, this.spriteAndMetadataInfo);
-    };
-    /**
-     * Computes the centroid, shifts all points to that centroid,
-     * then makes them all unit norm.
-     */
-    DataSet.prototype.normalize = function () {
-        // Compute the centroid of all data points.
-        var centroid = vector.centroid(this.points, function (a) { return a.vector; });
-        if (centroid == null) {
-            throw Error('centroid should not be null');
-        }
-        // Shift all points by the centroid and make them unit norm.
-        for (var id = 0; id < this.points.length; ++id) {
-            var dataPoint = this.points[id];
-            dataPoint.vector = vector.sub(dataPoint.vector, centroid);
-            vector.unit(dataPoint.vector);
-        }
-    };
-    /** Projects the dataset onto a given vector and caches the result. */
-    DataSet.prototype.projectLinear = function (dir, label) {
-        this.projections.add(label);
-        this.points.forEach(function (dataPoint) {
-            dataPoint.projections[label] = vector.dot(dataPoint.vector, dir);
-        });
-    };
-    /** Projects the dataset along the top 10 principal components. */
-    DataSet.prototype.projectPCA = function () {
-        var _this = this;
-        if (this.projections.has('pca-0')) {
-            return Promise.resolve(null);
-        }
-        return util.runAsyncTask('Computing PCA...', function () {
-            // Approximate pca vectors by sampling the dimensions.
-            var dim = _this.points[0].vector.length;
-            var vectors = _this.shuffledDataIndices.map(function (i) { return _this.points[i].vector; });
-            if (dim > exports.PCA_SAMPLE_DIM) {
-                vectors = vector.projectRandom(vectors, exports.PCA_SAMPLE_DIM);
-            }
-            var sampledVectors = vectors.slice(0, exports.PCA_SAMPLE_SIZE);
-            var sigma = numeric.div(numeric.dot(numeric.transpose(sampledVectors), sampledVectors), sampledVectors.length);
-            var svd = numeric.svd(sigma);
-            var variances = svd.S;
-            var totalVariance = 0;
-            for (var i = 0; i < variances.length; ++i) {
-                totalVariance += variances[i];
-            }
-            for (var i = 0; i < variances.length; ++i) {
-                variances[i] /= totalVariance;
-            }
-            _this.fracVariancesExplained = variances;
-            var U = svd.U;
-            var pcaVectors = vectors.map(function (vector) {
-                var newV = new Float32Array(NUM_PCA_COMPONENTS);
-                for (var newDim = 0; newDim < NUM_PCA_COMPONENTS; newDim++) {
-                    var dot = 0;
-                    for (var oldDim = 0; oldDim < vector.length; oldDim++) {
-                        dot += vector[oldDim] * U[oldDim][newDim];
-                    }
-                    newV[newDim] = dot;
-                }
-                return newV;
-            });
-            for (var d = 0; d < NUM_PCA_COMPONENTS; d++) {
-                var label = 'pca-' + d;
-                _this.projections.add(label);
-                for (var i = 0; i < pcaVectors.length; i++) {
-                    var pointIndex = _this.shuffledDataIndices[i];
-                    _this.points[pointIndex].projections[label] = pcaVectors[i][d];
-                }
-            }
-        });
-    };
-    /** Runs tsne on the data. */
-    DataSet.prototype.projectTSNE = function (perplexity, learningRate, tsneDim, stepCallback) {
-        var _this = this;
-        this.hasTSNERun = true;
-        var k = Math.floor(3 * perplexity);
-        var opt = { epsilon: learningRate, perplexity: perplexity, dim: tsneDim };
-        this.tsne = new bh_tsne_1.TSNE(opt);
-        this.tSNEShouldStop = false;
-        this.tSNEIteration = 0;
-        var sampledIndices = this.shuffledDataIndices.slice(0, exports.TSNE_SAMPLE_SIZE);
-        var step = function () {
-            if (_this.tSNEShouldStop) {
-                stepCallback(null);
-                _this.tsne = null;
-                return;
-            }
-            _this.tsne.step();
-            var result = _this.tsne.getSolution();
-            sampledIndices.forEach(function (index, i) {
-                var dataPoint = _this.points[index];
-                dataPoint.projections['tsne-0'] = result[i * tsneDim + 0];
-                dataPoint.projections['tsne-1'] = result[i * tsneDim + 1];
-                if (tsneDim === 3) {
-                    dataPoint.projections['tsne-2'] = result[i * tsneDim + 2];
-                }
-            });
-            _this.tSNEIteration++;
-            stepCallback(_this.tSNEIteration);
-            requestAnimationFrame(step);
-        };
-        // Nearest neighbors calculations.
-        var knnComputation;
-        if (this.nearest != null && k === this.nearestK) {
-            // We found the nearest neighbors before and will reuse them.
-            knnComputation = Promise.resolve(this.nearest);
-        }
-        else {
-            var sampledData = sampledIndices.map(function (i) { return _this.points[i]; });
-            this.nearestK = k;
-            knnComputation = KNN_GPU_ENABLED ?
-                knn.findKNNGPUCosine(sampledData, k, (function (d) { return d.vector; })) :
-                knn.findKNN(sampledData, k, (function (d) { return d.vector; }), function (a, b, limit) { return vector.cosDistNorm(a, b); });
-        }
-        knnComputation.then(function (nearest) {
-            _this.nearest = nearest;
-            util.runAsyncTask('Initializing T-SNE...', function () {
-                _this.tsne.initDataDist(_this.nearest);
-            }).then(step);
-        });
-    };
-    /**
-     * Merges metadata to the dataset and returns whether it succeeded.
-     */
-    DataSet.prototype.mergeMetadata = function (metadata) {
-        var _this = this;
-        if (metadata.pointsInfo.length !== this.points.length) {
-            var errorMessage = ("Number of tensors (" + this.points.length + ") do not") +
-                " match the number of lines in metadata" +
-                (" (" + metadata.pointsInfo.length + ").");
-            if (metadata.stats.length === 1 &&
-                this.points.length + 1 === metadata.pointsInfo.length) {
-                // If there is only one column of metadata and the number of points is
-                // exactly one less than the number of metadata lines, this is due to an
-                // unnecessary header line in the metadata and we can show a meaningful
-                // error.
-                logging.setErrorMessage(errorMessage + ' Single column metadata should not have a header ' +
-                    'row.', 'merging metadata');
-                return false;
-            }
-            else if (metadata.stats.length > 1 &&
-                this.points.length - 1 === metadata.pointsInfo.length) {
-                // If there are multiple columns of metadata and the number of points is
-                // exactly one greater than the number of lines in the metadata, this
-                // means there is a missing metadata header.
-                logging.setErrorMessage(errorMessage + ' Multi-column metadata should have a header ' +
-                    'row with column labels.', 'merging metadata');
-                return false;
-            }
-            logging.setWarningMessage(errorMessage);
-        }
-        this.spriteAndMetadataInfo = metadata;
-        metadata.pointsInfo.slice(0, this.points.length)
-            .forEach(function (m, i) { return _this.points[i].metadata = m; });
-        return true;
-    };
-    DataSet.prototype.stopTSNE = function () {
-        this.tSNEShouldStop = true;
-    };
-    /**
-     * Finds the nearest neighbors of the query point using a
-     * user-specified distance metric.
-     */
-    DataSet.prototype.findNeighbors = function (pointIndex, distFunc, numNN) {
-        // Find the nearest neighbors of a particular point.
-        var neighbors = knn.findKNNofPoint(this.points, pointIndex, numNN, (function (d) { return d.vector; }), distFunc);
-        // TODO(smilkov): Figure out why we slice.
-        var result = neighbors.slice(0, numNN);
-        return result;
-    };
-    /**
-     * Search the dataset based on a metadata field.
-     */
-    DataSet.prototype.query = function (query, inRegexMode, fieldName) {
-        var predicate = util.getSearchPredicate(query, inRegexMode, fieldName);
-        var matches = [];
-        this.points.forEach(function (point, id) {
-            if (predicate(point)) {
-                matches.push(id);
-            }
-        });
-        return matches;
-    };
-    return DataSet;
-}());
-exports.DataSet = DataSet;
-var Projection = (function () {
-    function Projection(projectionType, projectionComponents, dimensionality, dataSet) {
-        this.projectionType = projectionType;
-        this.projectionComponents = projectionComponents;
-        this.dimensionality = dimensionality;
-        this.dataSet = dataSet;
-    }
-    return Projection;
-}());
-exports.Projection = Projection;
-/**
- * An interface that holds all the data for serializing the current state of
- * the world.
- */
-var State = (function () {
-    function State() {
-        /** A label identifying this state. */
-        this.label = '';
-        /** Whether this State is selected in the bookmarks pane. */
-        this.isSelected = false;
-        /** t-SNE parameters */
-        this.tSNEIteration = 0;
-        this.tSNEPerplexity = 0;
-        this.tSNELearningRate = 0;
-        this.tSNEis3d = true;
-        /** PCA projection component dimensions */
-        this.pcaComponentDimensions = [];
-        /** The computed projections of the tensors. */
-        this.projections = [];
-        /** The indices of selected points. */
-        this.selectedPoints = [];
-    }
-    return State;
-}());
-exports.State = State;
-function getProjectionComponents(projection, components) {
-    if (components.length > 3) {
-        throw new RangeError('components length must be <= 3');
-    }
-    var projectionComponents = [null, null, null];
-    var prefix = (projection === 'custom') ? 'linear' : projection;
-    for (var i = 0; i < components.length; ++i) {
-        if (components[i] == null) {
-            continue;
-        }
-        projectionComponents[i] = prefix + '-' + components[i];
-    }
-    return projectionComponents;
-}
-exports.getProjectionComponents = getProjectionComponents;
-function stateGetAccessorDimensions(state) {
-    var dimensions;
-    switch (state.selectedProjection) {
-        case 'pca':
-            dimensions = state.pcaComponentDimensions.slice();
-            break;
-        case 'tsne':
-            dimensions = [0, 1];
-            if (state.tSNEis3d) {
-                dimensions.push(2);
-            }
-            break;
-        case 'custom':
-            dimensions = ['x', 'y'];
-            break;
-        default:
-            throw new Error('Unexpected fallthrough');
-    }
-    return dimensions;
-}
-exports.stateGetAccessorDimensions = stateGetAccessorDimensions;
-
-},{"./bh_tsne":2,"./knn":10,"./logging":12,"./util":24,"./vector":25}],8:[function(require,module,exports){
-
-},{}],9:[function(require,module,exports){
-/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-"use strict";
-/**
- * Min-heap data structure. Provides O(1) for peek, returning the smallest key.
- */
-// TODO(jart): Rename to Heap and use Comparator.
-var MinHeap = (function () {
-    function MinHeap() {
-        this.arr = [];
-    }
-    /** Push an element with the provided key. */
-    MinHeap.prototype.push = function (key, value) {
-        this.arr.push({ key: key, value: value });
-        this.bubbleUp(this.arr.length - 1);
-    };
-    /** Pop the element with the smallest key. */
-    MinHeap.prototype.pop = function () {
-        if (this.arr.length === 0) {
-            throw new Error('pop() called on empty binary heap');
-        }
-        var item = this.arr[0];
-        var last = this.arr.length - 1;
-        this.arr[0] = this.arr[last];
-        this.arr.pop();
-        if (last > 0) {
-            this.bubbleDown(0);
-        }
-        return item;
-    };
-    ;
-    /** Returns, but doesn't remove the element with the smallest key */
-    MinHeap.prototype.peek = function () { return this.arr[0]; };
-    /**
-     * Pops the element with the smallest key and at the same time
-     * adds the newly provided element. This is faster than calling
-     * pop() and push() separately.
-     */
-    MinHeap.prototype.popPush = function (key, value) {
-        if (this.arr.length === 0) {
-            throw new Error('pop() called on empty binary heap');
-        }
-        var item = this.arr[0];
-        this.arr[0] = { key: key, value: value };
-        if (this.arr.length > 0) {
-            this.bubbleDown(0);
-        }
-        return item;
-    };
-    /** Returns the number of elements in the heap. */
-    MinHeap.prototype.size = function () { return this.arr.length; };
-    /** Returns all the items in the heap. */
-    MinHeap.prototype.items = function () { return this.arr; };
-    MinHeap.prototype.swap = function (a, b) {
-        var temp = this.arr[a];
-        this.arr[a] = this.arr[b];
-        this.arr[b] = temp;
-    };
-    MinHeap.prototype.bubbleDown = function (pos) {
-        var left = (pos << 1) + 1;
-        var right = left + 1;
-        var largest = pos;
-        if (left < this.arr.length && this.arr[left].key < this.arr[largest].key) {
-            largest = left;
-        }
-        if (right < this.arr.length &&
-            this.arr[right].key < this.arr[largest].key) {
-            largest = right;
-        }
-        if (largest !== pos) {
-            this.swap(largest, pos);
-            this.bubbleDown(largest);
-        }
-    };
-    MinHeap.prototype.bubbleUp = function (pos) {
-        if (pos <= 0) {
-            return;
-        }
-        var parent = ((pos - 1) >> 1);
-        if (this.arr[pos].key < this.arr[parent].key) {
-            this.swap(pos, parent);
-            this.bubbleUp(parent);
-        }
-    };
-    return MinHeap;
-}());
-exports.MinHeap = MinHeap;
-/** List that keeps the K elements with the smallest keys. */
-var KMin = (function () {
-    /** Constructs a new k-min data structure with the provided k. */
-    function KMin(k) {
-        this.maxHeap = new MinHeap();
-        this.k = k;
-    }
-    /** Adds an element to the list. */
-    KMin.prototype.add = function (key, value) {
-        if (this.maxHeap.size() < this.k) {
-            this.maxHeap.push(-key, value);
-            return;
-        }
-        var largest = this.maxHeap.peek();
-        // If the new element is smaller, replace the largest with the new element.
-        if (key < -largest.key) {
-            this.maxHeap.popPush(-key, value);
-        }
-    };
-    /** Returns the k items with the smallest keys. */
-    KMin.prototype.getMinKItems = function () {
-        var items = this.maxHeap.items();
-        items.sort(function (a, b) { return b.key - a.key; });
-        return items.map(function (a) { return a.value; });
-    };
-    /** Returns the size of the list. */
-    KMin.prototype.getSize = function () { return this.maxHeap.size(); };
-    /** Returns the largest key in the list. */
-    KMin.prototype.getLargestKey = function () {
-        return this.maxHeap.size() === 0 ? null : -this.maxHeap.peek().key;
-    };
-    return KMin;
-}());
-exports.KMin = KMin;
-
-},{}],10:[function(require,module,exports){
-/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-"use strict";
-var util_1 = require('./util');
-var logging = require('./logging');
-var heap_1 = require('./heap');
-var vector = require('./vector');
-/**
- * Optimal size for the height of the matrix when doing computation on the GPU
- * using WebGL. This was found experimentally.
- *
- * This also guarantees that for computing pair-wise distance for up to 10K
- * vectors, no more than 40MB will be allocated in the GPU. Without the
- * allocation limit, we can freeze the graphics of the whole OS.
- */
-var OPTIMAL_GPU_BLOCK_SIZE = 256;
-/** Id of message box used for knn gpu progress bar. */
-var KNN_GPU_MSG_ID = 'knn-gpu';
-/**
- * Returns the K nearest neighbors for each vector where the distance
- * computation is done on the GPU (WebGL) using cosine distance.
- *
- * @param dataPoints List of data points, where each data point holds an
- *   n-dimensional vector.
- * @param k Number of nearest neighbors to find.
- * @param accessor A method that returns the vector, given the data point.
- */
-function findKNNGPUCosine(dataPoints, k, accessor) {
-    var N = dataPoints.length;
-    var dim = accessor(dataPoints[0]).length;
-    // The goal is to compute a large matrix multiplication A*A.T where A is of
-    // size NxD and A.T is its transpose. This results in a NxN matrix which
-    // could be too big to store on the GPU memory. To avoid memory overflow, we
-    // compute multiple A*partial_A.T where partial_A is of size BxD (B is much
-    // smaller than N). This results in storing only NxB size matrices on the GPU
-    // at a given time.
-    // A*A.T will give us NxN matrix holding the cosine distance between every
-    // pair of points, which we sort using KMin data structure to obtain the
-    // K nearest neighbors for each point.
-    var typedArray = vector.toTypedArray(dataPoints, accessor);
-    var bigMatrix = new weblas.pipeline.Tensor([N, dim], typedArray);
-    var nearest = new Array(N);
-    var numPieces = Math.ceil(N / OPTIMAL_GPU_BLOCK_SIZE);
-    var M = Math.floor(N / numPieces);
-    var modulo = N % numPieces;
-    var offset = 0;
-    var progress = 0;
-    var progressDiff = 1 / (2 * numPieces);
-    var piece = 0;
-    function step(resolve) {
-        var progressMsg = 'Finding nearest neighbors: ' + (progress * 100).toFixed() + '%';
-        util_1.runAsyncTask(progressMsg, function () {
-            var B = piece < modulo ? M + 1 : M;
-            var typedB = new Float32Array(B * dim);
-            for (var i = 0; i < B; ++i) {
-                var vector_1 = accessor(dataPoints[offset + i]);
-                for (var d = 0; d < dim; ++d) {
-                    typedB[i * dim + d] = vector_1[d];
-                }
-            }
-            var partialMatrix = new weblas.pipeline.Tensor([B, dim], typedB);
-            // Result is N x B matrix.
-            var result = weblas.pipeline.sgemm(1, bigMatrix, partialMatrix, null, null);
-            var partial = result.transfer();
-            partialMatrix.delete();
-            result.delete();
-            progress += progressDiff;
-            for (var i = 0; i < B; i++) {
-                var kMin = new heap_1.KMin(k);
-                var iReal = offset + i;
-                for (var j = 0; j < N; j++) {
-                    if (j === iReal) {
-                        continue;
-                    }
-                    var cosDist = 1 - partial[j * B + i]; // [j, i];
-                    kMin.add(cosDist, { index: j, dist: cosDist });
-                }
-                nearest[iReal] = kMin.getMinKItems();
-            }
-            progress += progressDiff;
-            offset += B;
-            piece++;
-        }, KNN_GPU_MSG_ID).then(function () {
-            if (piece < numPieces) {
-                step(resolve);
-            }
-            else {
-                logging.setModalMessage(null, KNN_GPU_MSG_ID);
-                bigMatrix.delete();
-                resolve(nearest);
-            }
-        }, function (error) {
-            // GPU failed. Reverting back to CPU.
-            logging.setModalMessage(null, KNN_GPU_MSG_ID);
-            var distFunc = function (a, b, limit) { return vector.cosDistNorm(a, b); };
-            findKNN(dataPoints, k, accessor, distFunc).then(function (nearest) {
-                resolve(nearest);
-            });
-        });
-    }
-    return new Promise(function (resolve) { return step(resolve); });
-}
-exports.findKNNGPUCosine = findKNNGPUCosine;
-/**
- * Returns the K nearest neighbors for each vector where the distance
- * computation is done on the CPU using a user-specified distance method.
- *
- * @param dataPoints List of data points, where each data point holds an
- *   n-dimensional vector.
- * @param k Number of nearest neighbors to find.
- * @param accessor A method that returns the vector, given the data point.
- * @param dist Method that takes two vectors and a limit, and computes the
- *   distance between two vectors, with the ability to stop early if the
- *   distance is above the limit.
- */
-function findKNN(dataPoints, k, accessor, dist) {
-    return util_1.runAsyncTask('Finding nearest neighbors...', function () {
-        var N = dataPoints.length;
-        var nearest = new Array(N);
-        // Find the distances from node i.
-        var kMin = new Array(N);
-        for (var i = 0; i < N; i++) {
-            kMin[i] = new heap_1.KMin(k);
-        }
-        for (var i = 0; i < N; i++) {
-            var a = accessor(dataPoints[i]);
-            var kMinA = kMin[i];
-            for (var j = i + 1; j < N; j++) {
-                var kMinB = kMin[j];
-                var limitI = kMinA.getSize() === k ?
-                    kMinA.getLargestKey() || Number.MAX_VALUE :
-                    Number.MAX_VALUE;
-                var limitJ = kMinB.getSize() === k ?
-                    kMinB.getLargestKey() || Number.MAX_VALUE :
-                    Number.MAX_VALUE;
-                var limit = Math.max(limitI, limitJ);
-                var dist2ItoJ = dist(a, accessor(dataPoints[j]), limit);
-                if (dist2ItoJ >= 0) {
-                    kMinA.add(dist2ItoJ, { index: j, dist: dist2ItoJ });
-                    kMinB.add(dist2ItoJ, { index: i, dist: dist2ItoJ });
-                }
-            }
-        }
-        for (var i = 0; i < N; i++) {
-            nearest[i] = kMin[i].getMinKItems();
-        }
-        return nearest;
-    });
-}
-exports.findKNN = findKNN;
-/** Calculates the minimum distance between a search point and a rectangle. */
-function minDist(point, x1, y1, x2, y2) {
-    var x = point[0];
-    var y = point[1];
-    var dx1 = x - x1;
-    var dx2 = x - x2;
-    var dy1 = y - y1;
-    var dy2 = y - y2;
-    if (dx1 * dx2 <= 0) {
-        if (dy1 * dy2 <= 0) {
-            return 0; // return 0 as point is in rect
-        }
-        return Math.min(Math.abs(dy1), Math.abs(dy2));
-    }
-    if (dy1 * dy2 <= 0) {
-        // We know it is already inside the rectangle
-        return Math.min(Math.abs(dx1), Math.abs(dx2));
-    }
-    var corner;
-    if (x > x2) {
-        // Upper-right vs lower-right.
-        corner = y > y2 ? [x2, y2] : [x2, y1];
-    }
-    else {
-        // Upper-left vs lower-left.
-        corner = y > y2 ? [x1, y2] : [x1, y1];
-    }
-    return Math.sqrt(vector.dist22D([x, y], corner));
-}
-/**
- * Returns the nearest neighbors of a particular point.
- *
- * @param dataPoints List of data points.
- * @param pointIndex The index of the point we need the nearest neighbors of.
- * @param k Number of nearest neighbors to search for.
- * @param accessor Method that maps a data point => vector (array of numbers).
- * @param distance Method that takes two vectors and returns their distance.
- */
-function findKNNofPoint(dataPoints, pointIndex, k, accessor, distance) {
-    var kMin = new heap_1.KMin(k);
-    var a = accessor(dataPoints[pointIndex]);
-    for (var i = 0; i < dataPoints.length; ++i) {
-        if (i === pointIndex) {
-            continue;
-        }
-        var b = accessor(dataPoints[i]);
-        var dist = distance(a, b);
-        kMin.add(dist, { index: i, dist: dist });
-    }
-    return kMin.getMinKItems();
-}
-exports.findKNNofPoint = findKNNofPoint;
-
-},{"./heap":9,"./logging":12,"./util":24,"./vector":25}],11:[function(require,module,exports){
-/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-"use strict";
-/**
- * Accelerates label placement by dividing the view into a uniform grid.
- * Labels only need to be tested for collision with other labels that overlap
- * the same grid cells. This is a fork of {@code amoeba.CollisionGrid}.
- */
-var CollisionGrid = (function () {
-    /**
-     * Constructs a new Collision grid.
-     *
-     * @param bound The bound of the grid. Labels out of bounds will be rejected.
-     * @param cellWidth Width of a cell in the grid.
-     * @param cellHeight Height of a cell in the grid.
-     */
-    function CollisionGrid(bound, cellWidth, cellHeight) {
-        /** The bound of the grid. Labels out of bounds will be rejected. */
-        this.bound = bound;
-        /** Width of a cell in the grid. */
-        this.cellWidth = cellWidth;
-        /** Height of a cell in the grid. */
-        this.cellHeight = cellHeight;
-        /** Number of grid cells along the x axis. */
-        this.numHorizCells = Math.ceil(this.boundWidth(bound) / cellWidth);
-        /** Number of grid cells along the y axis. */
-        this.numVertCells = Math.ceil(this.boundHeight(bound) / cellHeight);
-        /**
-         * The 2d grid (stored as a 1d array.) Each cell consists of an array of
-         * BoundingBoxes for objects that are in the cell.
-         */
-        this.grid = new Array(this.numHorizCells * this.numVertCells);
-    }
-    CollisionGrid.prototype.boundWidth = function (bound) { return bound.hiX - bound.loX; };
-    CollisionGrid.prototype.boundHeight = function (bound) { return bound.hiY - bound.loY; };
-    CollisionGrid.prototype.boundsIntersect = function (a, b) {
-        return !(a.loX > b.hiX || a.loY > b.hiY || a.hiX < b.loX || a.hiY < b.loY);
-    };
-    /**
-     * Checks if a given bounding box has any conflicts in the grid and inserts it
-     * if none are found.
-     *
-     * @param bound The bound to insert.
-     * @param justTest If true, just test if it conflicts, without inserting.
-     * @return True if the bound was successfully inserted; false if it
-     *         could not be inserted due to a conflict.
-     */
-    CollisionGrid.prototype.insert = function (bound, justTest) {
-        if (justTest === void 0) { justTest = false; }
-        // Reject if the label is out of bounds.
-        if ((bound.hiX < this.bound.loX) || (bound.loX > this.bound.hiX) ||
-            (bound.hiY < this.bound.loY) || (bound.loY > this.bound.hiY)) {
-            return false;
-        }
-        var minCellX = this.getCellX(bound.loX);
-        var maxCellX = this.getCellX(bound.hiX);
-        var minCellY = this.getCellY(bound.loY);
-        var maxCellY = this.getCellY(bound.hiY);
-        // Check all overlapped cells to verify that we can insert.
-        var baseIdx = minCellY * this.numHorizCells + minCellX;
-        var idx = baseIdx;
-        for (var j = minCellY; j <= maxCellY; j++) {
-            for (var i = minCellX; i <= maxCellX; i++) {
-                var cell = this.grid[idx++];
-                if (cell) {
-                    for (var k = 0; k < cell.length; k++) {
-                        if (this.boundsIntersect(bound, cell[k])) {
-                            return false;
-                        }
-                    }
-                }
-            }
-            idx += this.numHorizCells - (maxCellX - minCellX + 1);
-        }
-        if (justTest) {
-            return true;
-        }
-        // Insert into the overlapped cells.
-        idx = baseIdx;
-        for (var j = minCellY; j <= maxCellY; j++) {
-            for (var i = minCellX; i <= maxCellX; i++) {
-                if (!this.grid[idx]) {
-                    this.grid[idx] = [bound];
-                }
-                else {
-                    this.grid[idx].push(bound);
-                }
-                idx++;
-            }
-            idx += this.numHorizCells - (maxCellX - minCellX + 1);
-        }
-        return true;
-    };
-    /**
-     * Returns the x index of the grid cell where the given x coordinate falls.
-     *
-     * @param x the coordinate, in world space.
-     * @return the x index of the cell.
-     */
-    CollisionGrid.prototype.getCellX = function (x) {
-        return Math.floor((x - this.bound.loX) / this.cellWidth);
-    };
-    ;
-    /**
-     * Returns the y index of the grid cell where the given y coordinate falls.
-     *
-     * @param y the coordinate, in world space.
-     * @return the y index of the cell.
-     */
-    CollisionGrid.prototype.getCellY = function (y) {
-        return Math.floor((y - this.bound.loY) / this.cellHeight);
-    };
-    ;
-    return CollisionGrid;
-}());
-exports.CollisionGrid = CollisionGrid;
-
-},{}],12:[function(require,module,exports){
-/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-"use strict";
-/** Duration in ms for showing warning messages to the user */
-var WARNING_DURATION_MS = 10000;
-var dom = null;
-var msgId = 0;
-var numActiveMessages = 0;
-function setDomContainer(domElement) {
-    dom = domElement;
-}
-exports.setDomContainer = setDomContainer;
-/**
- * Updates the user message with the provided id.
- *
- * @param msg The message shown to the user. If null, the message is removed.
- * @param id The id of an existing message. If no id is provided, a unique id
- *     is assigned.
- * @param title The title of the notification.
- * @param isErrorMsg If true, the message is error and the dialog will have a
- *                   close button.
- * @return The id of the message.
- */
-function setModalMessage(msg, id, title, isErrorMsg) {
-    if (id === void 0) { id = null; }
-    if (title === void 0) { title = null; }
-    if (isErrorMsg === void 0) { isErrorMsg = false; }
-    if (dom == null) {
-        console.warn('Can\'t show modal message before the dom is initialized');
-        return;
-    }
-    if (id == null) {
-        id = (msgId++).toString();
-    }
-    var dialog = dom.querySelector('#notification-dialog');
-    dialog.querySelector('.close-button').style.display =
-        isErrorMsg ? null : 'none';
-    var spinner = dialog.querySelector('.progress');
-    spinner.style.display = isErrorMsg ? 'none' : null;
-    spinner.active = isErrorMsg ? null : true;
-    dialog.querySelector('#notification-title').innerHTML = title;
-    var msgsContainer = dialog.querySelector('#notify-msgs');
-    if (isErrorMsg) {
-        d3.select(msgsContainer).html('');
-    }
-    else {
-        d3.select(msgsContainer).selectAll('.error').remove();
-    }
-    var divId = "notify-msg-" + id;
-    var msgDiv = d3.select(dialog.querySelector('#' + divId));
-    var exists = msgDiv.size() > 0;
-    if (!exists) {
-        msgDiv = d3.select(msgsContainer)
-            .insert('div', ':first-child')
-            .attr('class', 'notify-msg')
-            .classed('error', isErrorMsg)
-            .attr('id', divId);
-        if (!isErrorMsg) {
-            numActiveMessages++;
-        }
-        else {
-            numActiveMessages = 0;
-        }
-    }
-    if (msg == null) {
-        numActiveMessages--;
-        if (numActiveMessages === 0) {
-            dialog.close();
-        }
-        msgDiv.remove();
-    }
-    else {
-        msgDiv.text(msg);
-        dialog.open();
-    }
-    return id;
-}
-exports.setModalMessage = setModalMessage;
-function setErrorMessage(errMsg, task) {
-    setModalMessage(errMsg, null, 'Error ' + (task != null ? task : ''), true);
-}
-exports.setErrorMessage = setErrorMessage;
-/**
- * Shows a warning message to the user for a certain amount of time.
- */
-function setWarningMessage(msg) {
-    var toast = dom.querySelector('#toast');
-    toast.text = msg;
-    toast.duration = WARNING_DURATION_MS;
-    toast.open();
-}
-exports.setWarningMessage = setWarningMessage;
-
-},{}],13:[function(require,module,exports){
-/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-"use strict";
-
-},{}],14:[function(require,module,exports){
-/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-"use strict";
-var renderContext_1 = require('./renderContext');
-var scatterPlot_1 = require('./scatterPlot');
-var scatterPlotVisualizer3DLabels_1 = require('./scatterPlotVisualizer3DLabels');
-var scatterPlotVisualizerCanvasLabels_1 = require('./scatterPlotVisualizerCanvasLabels');
-var scatterPlotVisualizerPolylines_1 = require('./scatterPlotVisualizerPolylines');
-var scatterPlotVisualizerSprites_1 = require('./scatterPlotVisualizerSprites');
-var vector = require('./vector');
-var LABEL_FONT_SIZE = 10;
-var LABEL_SCALE_DEFAULT = 1.0;
-var LABEL_SCALE_LARGE = 2;
-var LABEL_FILL_COLOR_SELECTED = 0x000000;
-var LABEL_FILL_COLOR_HOVER = 0x000000;
-var LABEL_FILL_COLOR_NEIGHBOR = 0x000000;
-var LABEL_STROKE_COLOR_SELECTED = 0xFFFFFF;
-var LABEL_STROKE_COLOR_HOVER = 0xFFFFFF;
-var LABEL_STROKE_COLOR_NEIGHBOR = 0xFFFFFF;
-var POINT_COLOR_UNSELECTED = 0xE3E3E3;
-var POINT_COLOR_NO_SELECTION = 0x7575D9;
-var POINT_COLOR_SELECTED = 0xFA6666;
-var POINT_COLOR_HOVER = 0x760B4F;
-var POINT_SCALE_DEFAULT = 1.0;
-var POINT_SCALE_SELECTED = 1.2;
-var POINT_SCALE_NEIGHBOR = 1.2;
-var POINT_SCALE_HOVER = 1.2;
-var LABELS_3D_COLOR_UNSELECTED = 0xFFFFFF;
-var LABELS_3D_COLOR_NO_SELECTION = 0xFFFFFF;
-var SPRITE_IMAGE_COLOR_UNSELECTED = 0xFFFFFF;
-var SPRITE_IMAGE_COLOR_NO_SELECTION = 0xFFFFFF;
-var POLYLINE_START_HUE = 60;
-var POLYLINE_END_HUE = 360;
-var POLYLINE_SATURATION = 1;
-var POLYLINE_LIGHTNESS = .3;
-var POLYLINE_DEFAULT_OPACITY = .2;
-var POLYLINE_DEFAULT_LINEWIDTH = 2;
-var POLYLINE_SELECTED_OPACITY = .9;
-var POLYLINE_SELECTED_LINEWIDTH = 3;
-var POLYLINE_DESELECTED_OPACITY = .05;
-var SCATTER_PLOT_CUBE_LENGTH = 2;
-/** Color scale for nearest neighbors. */
-var NN_COLOR_SCALE = d3.scale.linear()
-    .domain([1, 0.7, 0.4])
-    .range(['hsl(285, 80%, 40%)', 'hsl(0, 80%, 65%)', 'hsl(40, 70%, 60%)'])
-    .clamp(true);
-/**
- * Interprets projector events and assembes the arrays and commands necessary
- * to use the ScatterPlot to render the current projected data set.
- */
-var ProjectorScatterPlotAdapter = (function () {
-    function ProjectorScatterPlotAdapter(scatterPlotContainer, projectorEventContext) {
-        var _this = this;
-        this.renderLabelsIn3D = false;
-        this.scatterPlot =
-            new scatterPlot_1.ScatterPlot(scatterPlotContainer, projectorEventContext);
-        this.scatterPlotContainer = scatterPlotContainer;
-        projectorEventContext.registerProjectionChangedListener(function (projection) {
-            _this.projection = projection;
-            _this.updateScatterPlotWithNewProjection(projection);
-        });
-        projectorEventContext.registerSelectionChangedListener(function (selectedPointIndices, neighbors) {
-            _this.selectedPointIndices = selectedPointIndices;
-            _this.neighborsOfFirstSelectedPoint = neighbors;
-            _this.updateScatterPlotPositions();
-            _this.updateScatterPlotAttributes();
-            _this.scatterPlot.render();
-        });
-        projectorEventContext.registerHoverListener(function (hoverPointIndex) {
-            _this.hoverPointIndex = hoverPointIndex;
-            _this.updateScatterPlotAttributes();
-            _this.scatterPlot.render();
-        });
-        projectorEventContext.registerDistanceMetricChangedListener(function (distanceMetric) {
-            _this.distanceMetric = distanceMetric;
-            _this.updateScatterPlotAttributes();
-            _this.scatterPlot.render();
-        });
-        this.createVisualizers(false);
-    }
-    ProjectorScatterPlotAdapter.prototype.notifyProjectionPositionsUpdated = function () {
-        this.updateScatterPlotPositions();
-        this.scatterPlot.render();
-    };
-    ProjectorScatterPlotAdapter.prototype.setDataSet = function (dataSet) {
-        if (this.projection != null) {
-            // TODO(nicholsonc): setDataSet needs to go away, the projection is the
-            // atomic unit of update.
-            this.projection.dataSet = dataSet;
-        }
-        if (this.polylineVisualizer != null) {
-            this.polylineVisualizer.setDataSet(dataSet);
-        }
-        if (this.labels3DVisualizer != null) {
-            this.labels3DVisualizer.setLabelStrings(this.generate3DLabelsArray(dataSet, this.labelPointAccessor));
-        }
-        if (this.spriteVisualizer == null) {
-            return;
-        }
-        this.spriteVisualizer.clearSpriteAtlas();
-        if ((dataSet == null) || (dataSet.spriteAndMetadataInfo == null)) {
-            return;
-        }
-        var metadata = dataSet.spriteAndMetadataInfo;
-        if ((metadata.spriteImage == null) || (metadata.spriteMetadata == null)) {
-            return;
-        }
-        var n = dataSet.points.length;
-        var spriteIndices = new Float32Array(n);
-        for (var i = 0; i < n; ++i) {
-            spriteIndices[i] = dataSet.points[i].index;
-        }
-        this.spriteVisualizer.setSpriteAtlas(metadata.spriteImage, metadata.spriteMetadata.singleImageDim, spriteIndices);
-    };
-    ProjectorScatterPlotAdapter.prototype.set3DLabelMode = function (renderLabelsIn3D) {
-        this.renderLabelsIn3D = renderLabelsIn3D;
-        this.createVisualizers(renderLabelsIn3D);
-        this.updateScatterPlotAttributes();
-        this.scatterPlot.render();
-    };
-    ProjectorScatterPlotAdapter.prototype.setLegendPointColorer = function (legendPointColorer) {
-        this.legendPointColorer = legendPointColorer;
-    };
-    ProjectorScatterPlotAdapter.prototype.setLabelPointAccessor = function (labelPointAccessor) {
-        this.labelPointAccessor = labelPointAccessor;
-        if (this.labels3DVisualizer != null) {
-            var ds = (this.projection == null) ? null : this.projection.dataSet;
-            this.labels3DVisualizer.setLabelStrings(this.generate3DLabelsArray(ds, labelPointAccessor));
-        }
-    };
-    ProjectorScatterPlotAdapter.prototype.resize = function () {
-        this.scatterPlot.resize();
-    };
-    ProjectorScatterPlotAdapter.prototype.populateBookmarkFromUI = function (state) {
-        state.cameraDef = this.scatterPlot.getCameraDef();
-    };
-    ProjectorScatterPlotAdapter.prototype.restoreUIFromBookmark = function (state) {
-        this.scatterPlot.setCameraParametersForNextCameraCreation(state.cameraDef, false);
-    };
-    ProjectorScatterPlotAdapter.prototype.updateScatterPlotPositions = function () {
-        var ds = (this.projection == null) ? null : this.projection.dataSet;
-        var projectionComponents = (this.projection == null) ? null : this.projection.projectionComponents;
-        var newPositions = this.generatePointPositionArray(ds, projectionComponents);
-        this.scatterPlot.setPointPositions(newPositions);
-    };
-    ProjectorScatterPlotAdapter.prototype.updateScatterPlotAttributes = function () {
-        if (this.projection == null) {
-            return;
-        }
-        var dataSet = this.projection.dataSet;
-        var selectedSet = this.selectedPointIndices;
-        var hoverIndex = this.hoverPointIndex;
-        var neighbors = this.neighborsOfFirstSelectedPoint;
-        var pointColorer = this.legendPointColorer;
-        var pointColors = this.generatePointColorArray(dataSet, pointColorer, this.distanceMetric, selectedSet, neighbors, hoverIndex, this.renderLabelsIn3D, this.getSpriteImageMode());
-        var pointScaleFactors = this.generatePointScaleFactorArray(dataSet, selectedSet, neighbors, hoverIndex);
-        var labels = this.generateVisibleLabelRenderParams(dataSet, selectedSet, neighbors, hoverIndex);
-        var polylineColors = this.generateLineSegmentColorMap(dataSet, pointColorer);
-        var polylineOpacities = this.generateLineSegmentOpacityArray(dataSet, selectedSet);
-        var polylineWidths = this.generateLineSegmentWidthArray(dataSet, selectedSet);
-        this.scatterPlot.setPointColors(pointColors);
-        this.scatterPlot.setPointScaleFactors(pointScaleFactors);
-        this.scatterPlot.setLabels(labels);
-        this.scatterPlot.setPolylineColors(polylineColors);
-        this.scatterPlot.setPolylineOpacities(polylineOpacities);
-        this.scatterPlot.setPolylineWidths(polylineWidths);
-    };
-    ProjectorScatterPlotAdapter.prototype.render = function () {
-        this.scatterPlot.render();
-    };
-    ProjectorScatterPlotAdapter.prototype.generatePointPositionArray = function (ds, projectionComponents) {
-        if (ds == null) {
-            return null;
-        }
-        var xScaler = d3.scale.linear();
-        var yScaler = d3.scale.linear();
-        var zScaler = null;
-        {
-            // Determine max and min of each axis of our data.
-            var xExtent = d3.extent(ds.points, function (p, i) { return ds.points[i].projections[projectionComponents[0]]; });
-            var yExtent = d3.extent(ds.points, function (p, i) { return ds.points[i].projections[projectionComponents[1]]; });
-            var range = [-SCATTER_PLOT_CUBE_LENGTH / 2, SCATTER_PLOT_CUBE_LENGTH / 2];
-            xScaler.domain(xExtent).range(range);
-            yScaler.domain(yExtent).range(range);
-            if (projectionComponents[2] != null) {
-                var zExtent = d3.extent(ds.points, function (p, i) { return ds.points[i].projections[projectionComponents[2]]; });
-                zScaler = d3.scale.linear();
-                zScaler.domain(zExtent).range(range);
-            }
-        }
-        var positions = new Float32Array(ds.points.length * 3);
-        var dst = 0;
-        ds.points.forEach(function (d, i) {
-            positions[dst++] =
-                xScaler(ds.points[i].projections[projectionComponents[0]]);
-            positions[dst++] =
-                yScaler(ds.points[i].projections[projectionComponents[1]]);
-            positions[dst++] = 0.0;
-        });
-        if (zScaler) {
-            dst = 2;
-            ds.points.forEach(function (d, i) {
-                positions[dst] =
-                    zScaler(ds.points[i].projections[projectionComponents[2]]);
-                dst += 3;
-            });
-        }
-        return positions;
-    };
-    ProjectorScatterPlotAdapter.prototype.generateVisibleLabelRenderParams = function (ds, selectedPointIndices, neighborsOfFirstPoint, hoverPointIndex) {
-        if (ds == null) {
-            return null;
-        }
-        var selectedPointCount = (selectedPointIndices == null) ? 0 : selectedPointIndices.length;
-        var neighborCount = (neighborsOfFirstPoint == null) ? 0 : neighborsOfFirstPoint.length;
-        var n = selectedPointCount + neighborCount +
-            ((hoverPointIndex != null) ? 1 : 0);
-        var visibleLabels = new Uint32Array(n);
-        var scale = new Float32Array(n);
-        var opacityFlags = new Int8Array(n);
-        var fillColors = new Uint8Array(n * 3);
-        var strokeColors = new Uint8Array(n * 3);
-        var labelStrings = [];
-        scale.fill(LABEL_SCALE_DEFAULT);
-        opacityFlags.fill(1);
-        var dst = 0;
-        if (hoverPointIndex != null) {
-            labelStrings.push(this.getLabelText(ds, hoverPointIndex, this.labelPointAccessor));
-            visibleLabels[dst] = hoverPointIndex;
-            scale[dst] = LABEL_SCALE_LARGE;
-            opacityFlags[dst] = 0;
-            var fillRgb = styleRgbFromHexColor(LABEL_FILL_COLOR_HOVER);
-            packRgbIntoUint8Array(fillColors, dst, fillRgb[0], fillRgb[1], fillRgb[2]);
-            var strokeRgb = styleRgbFromHexColor(LABEL_STROKE_COLOR_HOVER);
-            packRgbIntoUint8Array(strokeColors, dst, strokeRgb[0], strokeRgb[1], strokeRgb[1]);
-            ++dst;
-        }
-        // Selected points
-        {
-            var n_1 = selectedPointCount;
-            var fillRgb = styleRgbFromHexColor(LABEL_FILL_COLOR_SELECTED);
-            var strokeRgb = styleRgbFromHexColor(LABEL_STROKE_COLOR_SELECTED);
-            for (var i = 0; i < n_1; ++i) {
-                var labelIndex = selectedPointIndices[i];
-                labelStrings.push(this.getLabelText(ds, labelIndex, this.labelPointAccessor));
-                visibleLabels[dst] = labelIndex;
-                scale[dst] = LABEL_SCALE_LARGE;
-                opacityFlags[dst] = (n_1 === 1) ? 0 : 1;
-                packRgbIntoUint8Array(fillColors, dst, fillRgb[0], fillRgb[1], fillRgb[2]);
-                packRgbIntoUint8Array(strokeColors, dst, strokeRgb[0], strokeRgb[1], strokeRgb[2]);
-                ++dst;
-            }
-        }
-        // Neighbors
-        {
-            var n_2 = neighborCount;
-            var fillRgb = styleRgbFromHexColor(LABEL_FILL_COLOR_NEIGHBOR);
-            var strokeRgb = styleRgbFromHexColor(LABEL_STROKE_COLOR_NEIGHBOR);
-            for (var i = 0; i < n_2; ++i) {
-                var labelIndex = neighborsOfFirstPoint[i].index;
-                labelStrings.push(this.getLabelText(ds, labelIndex, this.labelPointAccessor));
-                visibleLabels[dst] = labelIndex;
-                packRgbIntoUint8Array(fillColors, dst, fillRgb[0], fillRgb[1], fillRgb[2]);
-                packRgbIntoUint8Array(strokeColors, dst, strokeRgb[0], strokeRgb[1], strokeRgb[2]);
-                ++dst;
-            }
-        }
-        return new renderContext_1.LabelRenderParams(visibleLabels, labelStrings, scale, opacityFlags, LABEL_FONT_SIZE, fillColors, strokeColors);
-    };
-    ProjectorScatterPlotAdapter.prototype.generatePointScaleFactorArray = function (ds, selectedPointIndices, neighborsOfFirstPoint, hoverPointIndex) {
-        if (ds == null) {
-            return new Float32Array(0);
-        }
-        var scale = new Float32Array(ds.points.length);
-        scale.fill(POINT_SCALE_DEFAULT);
-        var selectedPointCount = (selectedPointIndices == null) ? 0 : selectedPointIndices.length;
-        var neighborCount = (neighborsOfFirstPoint == null) ? 0 : neighborsOfFirstPoint.length;
-        // Scale up all selected points.
-        {
-            var n = selectedPointCount;
-            for (var i = 0; i < n; ++i) {
-                var p = selectedPointIndices[i];
-                scale[p] = POINT_SCALE_SELECTED;
-            }
-        }
-        // Scale up the neighbor points.
-        {
-            var n = neighborCount;
-            for (var i = 0; i < n; ++i) {
-                var p = neighborsOfFirstPoint[i].index;
-                scale[p] = POINT_SCALE_NEIGHBOR;
-            }
-        }
-        // Scale up the hover point.
-        if (hoverPointIndex != null) {
-            scale[hoverPointIndex] = POINT_SCALE_HOVER;
-        }
-        return scale;
-    };
-    ProjectorScatterPlotAdapter.prototype.generateLineSegmentColorMap = function (ds, legendPointColorer) {
-        var polylineColorArrayMap = {};
-        if (ds == null) {
-            return polylineColorArrayMap;
-        }
-        for (var i = 0; i < ds.sequences.length; i++) {
-            var sequence = ds.sequences[i];
-            var colors = new Float32Array(2 * (sequence.pointIndices.length - 1) * 3);
-            var colorIndex = 0;
-            if (legendPointColorer) {
-                for (var j = 0; j < sequence.pointIndices.length - 1; j++) {
-                    var c1 = new THREE.Color(legendPointColorer(ds, sequence.pointIndices[j]));
-                    var c2 = new THREE.Color(legendPointColorer(ds, sequence.pointIndices[j + 1]));
-                    colors[colorIndex++] = c1.r;
-                    colors[colorIndex++] = c1.g;
-                    colors[colorIndex++] = c1.b;
-                    colors[colorIndex++] = c2.r;
-                    colors[colorIndex++] = c2.g;
-                    colors[colorIndex++] = c2.b;
-                }
-            }
-            else {
-                for (var j = 0; j < sequence.pointIndices.length - 1; j++) {
-                    var c1 = getDefaultPointInPolylineColor(j, sequence.pointIndices.length);
-                    var c2 = getDefaultPointInPolylineColor(j + 1, sequence.pointIndices.length);
-                    colors[colorIndex++] = c1.r;
-                    colors[colorIndex++] = c1.g;
-                    colors[colorIndex++] = c1.b;
-                    colors[colorIndex++] = c2.r;
-                    colors[colorIndex++] = c2.g;
-                    colors[colorIndex++] = c2.b;
-                }
-            }
-            polylineColorArrayMap[i] = colors;
-        }
-        return polylineColorArrayMap;
-    };
-    ProjectorScatterPlotAdapter.prototype.generateLineSegmentOpacityArray = function (ds, selectedPoints) {
-        if (ds == null) {
-            return new Float32Array(0);
-        }
-        var opacities = new Float32Array(ds.sequences.length);
-        var selectedPointCount = (selectedPoints == null) ? 0 : selectedPoints.length;
-        if (selectedPointCount > 0) {
-            opacities.fill(POLYLINE_DESELECTED_OPACITY);
-            var i = ds.points[selectedPoints[0]].sequenceIndex;
-            opacities[i] = POLYLINE_SELECTED_OPACITY;
-        }
-        else {
-            opacities.fill(POLYLINE_DEFAULT_OPACITY);
-        }
-        return opacities;
-    };
-    ProjectorScatterPlotAdapter.prototype.generateLineSegmentWidthArray = function (ds, selectedPoints) {
-        if (ds == null) {
-            return new Float32Array(0);
-        }
-        var widths = new Float32Array(ds.sequences.length);
-        widths.fill(POLYLINE_DEFAULT_LINEWIDTH);
-        var selectedPointCount = (selectedPoints == null) ? 0 : selectedPoints.length;
-        if (selectedPointCount > 0) {
-            var i = ds.points[selectedPoints[0]].sequenceIndex;
-            widths[i] = POLYLINE_SELECTED_LINEWIDTH;
-        }
-        return widths;
-    };
-    ProjectorScatterPlotAdapter.prototype.generatePointColorArray = function (ds, legendPointColorer, distFunc, selectedPointIndices, neighborsOfFirstPoint, hoverPointIndex, label3dMode, spriteImageMode) {
-        if (ds == null) {
-            return new Float32Array(0);
-        }
-        var selectedPointCount = (selectedPointIndices == null) ? 0 : selectedPointIndices.length;
-        var neighborCount = (neighborsOfFirstPoint == null) ? 0 : neighborsOfFirstPoint.length;
-        var colors = new Float32Array(ds.points.length * 3);
-        var unselectedColor = POINT_COLOR_UNSELECTED;
-        var noSelectionColor = POINT_COLOR_NO_SELECTION;
-        if (label3dMode) {
-            unselectedColor = LABELS_3D_COLOR_UNSELECTED;
-            noSelectionColor = LABELS_3D_COLOR_NO_SELECTION;
-        }
-        if (spriteImageMode) {
-            unselectedColor = SPRITE_IMAGE_COLOR_UNSELECTED;
-            noSelectionColor = SPRITE_IMAGE_COLOR_NO_SELECTION;
-        }
-        // Give all points the unselected color.
-        {
-            var n = ds.points.length;
-            var dst = 0;
-            if (selectedPointCount > 0) {
-                var c = new THREE.Color(unselectedColor);
-                for (var i = 0; i < n; ++i) {
-                    colors[dst++] = c.r;
-                    colors[dst++] = c.g;
-                    colors[dst++] = c.b;
-                }
-            }
-            else {
-                if (legendPointColorer != null) {
-                    for (var i = 0; i < n; ++i) {
-                        var c = new THREE.Color(legendPointColorer(ds, i));
-                        colors[dst++] = c.r;
-                        colors[dst++] = c.g;
-                        colors[dst++] = c.b;
-                    }
-                }
-                else {
-                    var c = new THREE.Color(noSelectionColor);
-                    for (var i = 0; i < n; ++i) {
-                        colors[dst++] = c.r;
-                        colors[dst++] = c.g;
-                        colors[dst++] = c.b;
-                    }
-                }
-            }
-        }
-        // Color the selected points.
-        {
-            var n = selectedPointCount;
-            var c = new THREE.Color(POINT_COLOR_SELECTED);
-            for (var i = 0; i < n; ++i) {
-                var dst = selectedPointIndices[i] * 3;
-                colors[dst++] = c.r;
-                colors[dst++] = c.g;
-                colors[dst++] = c.b;
-            }
-        }
-        // Color the neighbors.
-        {
-            var n = neighborCount;
-            var minDist = n > 0 ? neighborsOfFirstPoint[0].dist : 0;
-            for (var i = 0; i < n; ++i) {
-                var c = new THREE.Color(dist2color(distFunc, neighborsOfFirstPoint[i].dist, minDist));
-                var dst = neighborsOfFirstPoint[i].index * 3;
-                colors[dst++] = c.r;
-                colors[dst++] = c.g;
-                colors[dst++] = c.b;
-            }
-        }
-        // Color the hover point.
-        if (hoverPointIndex != null) {
-            var c = new THREE.Color(POINT_COLOR_HOVER);
-            var dst = hoverPointIndex * 3;
-            colors[dst++] = c.r;
-            colors[dst++] = c.g;
-            colors[dst++] = c.b;
-        }
-        return colors;
-    };
-    ProjectorScatterPlotAdapter.prototype.generate3DLabelsArray = function (ds, accessor) {
-        if ((ds == null) || (accessor == null)) {
-            return null;
-        }
-        var labels = [];
-        var n = ds.points.length;
-        for (var i = 0; i < n; ++i) {
-            labels.push(this.getLabelText(ds, i, accessor));
-        }
-        return labels;
-    };
-    ProjectorScatterPlotAdapter.prototype.getLabelText = function (ds, i, accessor) {
-        return ds.points[i].metadata[accessor].toString();
-    };
-    ProjectorScatterPlotAdapter.prototype.updateScatterPlotWithNewProjection = function (projection) {
-        if (projection == null) {
-            this.createVisualizers(this.renderLabelsIn3D);
-            this.scatterPlot.render();
-            return;
-        }
-        this.setDataSet(projection.dataSet);
-        this.scatterPlot.setDimensions(projection.dimensionality);
-        if (projection.dataSet.projectionCanBeRendered(projection.projectionType)) {
-            this.updateScatterPlotAttributes();
-            this.notifyProjectionPositionsUpdated();
-        }
-        this.scatterPlot.setCameraParametersForNextCameraCreation(null, false);
-    };
-    ProjectorScatterPlotAdapter.prototype.createVisualizers = function (inLabels3DMode) {
-        var ds = (this.projection == null) ? null : this.projection.dataSet;
-        var scatterPlot = this.scatterPlot;
-        scatterPlot.removeAllVisualizers();
-        this.labels3DVisualizer = null;
-        this.canvasLabelsVisualizer = null;
-        this.spriteVisualizer = null;
-        this.polylineVisualizer = null;
-        if (inLabels3DMode) {
-            this.labels3DVisualizer = new scatterPlotVisualizer3DLabels_1.ScatterPlotVisualizer3DLabels();
-            this.labels3DVisualizer.setLabelStrings(this.generate3DLabelsArray(ds, this.labelPointAccessor));
-        }
-        else {
-            this.spriteVisualizer = new scatterPlotVisualizerSprites_1.ScatterPlotVisualizerSprites();
-            scatterPlot.addVisualizer(this.spriteVisualizer);
-            this.canvasLabelsVisualizer =
-                new scatterPlotVisualizerCanvasLabels_1.ScatterPlotVisualizerCanvasLabels(this.scatterPlotContainer);
-        }
-        this.polylineVisualizer = new scatterPlotVisualizerPolylines_1.ScatterPlotVisualizerPolylines();
-        this.setDataSet(ds);
-        if (this.spriteVisualizer) {
-            scatterPlot.addVisualizer(this.spriteVisualizer);
-        }
-        if (this.labels3DVisualizer) {
-            scatterPlot.addVisualizer(this.labels3DVisualizer);
-        }
-        if (this.canvasLabelsVisualizer) {
-            scatterPlot.addVisualizer(this.canvasLabelsVisualizer);
-        }
-        scatterPlot.addVisualizer(this.polylineVisualizer);
-    };
-    ProjectorScatterPlotAdapter.prototype.getSpriteImageMode = function () {
-        if (this.projection == null) {
-            return false;
-        }
-        var ds = this.projection.dataSet;
-        if ((ds == null) || (ds.spriteAndMetadataInfo == null)) {
-            return false;
-        }
-        return ds.spriteAndMetadataInfo.spriteImage != null;
-    };
-    return ProjectorScatterPlotAdapter;
-}());
-exports.ProjectorScatterPlotAdapter = ProjectorScatterPlotAdapter;
-function packRgbIntoUint8Array(rgbArray, labelIndex, r, g, b) {
-    rgbArray[labelIndex * 3] = r;
-    rgbArray[labelIndex * 3 + 1] = g;
-    rgbArray[labelIndex * 3 + 2] = b;
-}
-function styleRgbFromHexColor(hex) {
-    var c = new THREE.Color(hex);
-    return [(c.r * 255) | 0, (c.g * 255) | 0, (c.b * 255) | 0];
-}
-function getDefaultPointInPolylineColor(index, totalPoints) {
-    var hue = POLYLINE_START_HUE +
-        (POLYLINE_END_HUE - POLYLINE_START_HUE) * index / totalPoints;
-    var rgb = d3.hsl(hue, POLYLINE_SATURATION, POLYLINE_LIGHTNESS).rgb();
-    return new THREE.Color(rgb.r / 255, rgb.g / 255, rgb.b / 255);
-}
-/**
- * Normalizes the distance so it can be visually encoded with color.
- * The normalization depends on the distance metric (cosine vs euclidean).
- */
-function normalizeDist(distFunc, d, minDist) {
-    return (distFunc === vector.dist) ? (minDist / d) : (1 - d);
-}
-exports.normalizeDist = normalizeDist;
-/** Normalizes and encodes the provided distance with color. */
-function dist2color(distFunc, d, minDist) {
-    return NN_COLOR_SCALE(normalizeDist(distFunc, d, minDist));
-}
-exports.dist2color = dist2color;
-
-},{"./renderContext":15,"./scatterPlot":16,"./scatterPlotVisualizer3DLabels":19,"./scatterPlotVisualizerCanvasLabels":20,"./scatterPlotVisualizerPolylines":21,"./scatterPlotVisualizerSprites":22,"./vector":25}],15:[function(require,module,exports){
-/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-"use strict";
-/**
- * LabelRenderParams describes the set of points that should have labels
- * rendered next to them.
- */
-var LabelRenderParams = (function () {
-    function LabelRenderParams(pointIndices, labelStrings, scaleFactors, useSceneOpacityFlags, defaultFontSize, fillColors, strokeColors) {
-        this.pointIndices = pointIndices;
-        this.labelStrings = labelStrings;
-        this.scaleFactors = scaleFactors;
-        this.useSceneOpacityFlags = useSceneOpacityFlags;
-        this.defaultFontSize = defaultFontSize;
-        this.fillColors = fillColors;
-        this.strokeColors = strokeColors;
-    }
-    return LabelRenderParams;
-}());
-exports.LabelRenderParams = LabelRenderParams;
-/** Details about the camera projection being used to render the scene. */
-(function (CameraType) {
-    CameraType[CameraType["Perspective"] = 0] = "Perspective";
-    CameraType[CameraType["Orthographic"] = 1] = "Orthographic";
-})(exports.CameraType || (exports.CameraType = {}));
-var CameraType = exports.CameraType;
-/**
- * RenderContext contains all of the state required to color and render the data
- * set. ScatterPlot passes this to every attached visualizer as part of the
- * render callback.
- * TODO(nicholsonc): This should only contain the data that's changed between
- * each frame. Data like colors / scale factors / labels should be reapplied
- * only when they change.
- */
-var RenderContext = (function () {
-    function RenderContext(camera, cameraType, cameraTarget, screenWidth, screenHeight, nearestCameraSpacePointZ, farthestCameraSpacePointZ, backgroundColor, pointColors, pointScaleFactors, labels, polylineColors, polylineOpacities, polylineWidths) {
-        this.camera = camera;
-        this.cameraType = cameraType;
-        this.cameraTarget = cameraTarget;
-        this.screenWidth = screenWidth;
-        this.screenHeight = screenHeight;
-        this.nearestCameraSpacePointZ = nearestCameraSpacePointZ;
-        this.farthestCameraSpacePointZ = farthestCameraSpacePointZ;
-        this.backgroundColor = backgroundColor;
-        this.pointColors = pointColors;
-        this.pointScaleFactors = pointScaleFactors;
-        this.labels = labels;
-        this.polylineColors = polylineColors;
-        this.polylineOpacities = polylineOpacities;
-        this.polylineWidths = polylineWidths;
-    }
-    return RenderContext;
-}());
-exports.RenderContext = RenderContext;
-
-},{}],16:[function(require,module,exports){
-/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-"use strict";
-var renderContext_1 = require('./renderContext');
-var scatterPlotRectangleSelector_1 = require('./scatterPlotRectangleSelector');
-var util = require('./util');
-var BACKGROUND_COLOR = 0xffffff;
-/**
- * The length of the cube (diameter of the circumscribing sphere) where all the
- * points live.
- */
-var CUBE_LENGTH = 2;
-var MAX_ZOOM = 5 * CUBE_LENGTH;
-var MIN_ZOOM = 0.025 * CUBE_LENGTH;
-// Constants relating to the camera parameters.
-var PERSP_CAMERA_FOV_VERTICAL = 70;
-var PERSP_CAMERA_NEAR_CLIP_PLANE = 0.01;
-var PERSP_CAMERA_FAR_CLIP_PLANE = 100;
-var ORTHO_CAMERA_FRUSTUM_HALF_EXTENT = 1.2;
-// Key presses.
-var SHIFT_KEY = 16;
-var CTRL_KEY = 17;
-var START_CAMERA_POS_3D = new THREE.Vector3(0.45, 0.9, 1.6);
-var START_CAMERA_TARGET_3D = new THREE.Vector3(0, 0, 0);
-var START_CAMERA_POS_2D = new THREE.Vector3(0, 0, 4);
-var START_CAMERA_TARGET_2D = new THREE.Vector3(0, 0, 0);
-var ORBIT_MOUSE_ROTATION_SPEED = 1;
-var ORBIT_ANIMATION_ROTATION_CYCLE_IN_SECONDS = 7;
-/** Supported modes of interaction. */
-(function (MouseMode) {
-    MouseMode[MouseMode["AREA_SELECT"] = 0] = "AREA_SELECT";
-    MouseMode[MouseMode["CAMERA_AND_CLICK_SELECT"] = 1] = "CAMERA_AND_CLICK_SELECT";
-})(exports.MouseMode || (exports.MouseMode = {}));
-var MouseMode = exports.MouseMode;
-/** Defines a camera, suitable for serialization. */
-var CameraDef = (function () {
-    function CameraDef() {
-        this.orthographic = false;
-    }
-    return CameraDef;
-}());
-exports.CameraDef = CameraDef;
-/**
- * Maintains a three.js instantiation and context,
- * animation state, and all other logic that's
- * independent of how a 3D scatter plot is actually rendered. Also holds an
- * array of visualizers and dispatches application events to them.
- */
-var ScatterPlot = (function () {
-    function ScatterPlot(container, projectorEventContext) {
-        var _this = this;
-        this.visualizers = [];
-        this.onCameraMoveListeners = [];
-        this.backgroundColor = BACKGROUND_COLOR;
-        this.dimensionality = 3;
-        this.cameraDef = null;
-        this.orbitAnimationOnNextCameraCreation = false;
-        this.selecting = false;
-        this.mouseIsDown = false;
-        this.isDragSequence = false;
-        this.containerNode = container.node();
-        this.projectorEventContext = projectorEventContext;
-        this.getLayoutValues();
-        this.scene = new THREE.Scene();
-        this.renderer = new THREE.WebGLRenderer({ alpha: true, premultipliedAlpha: false, antialias: false });
-        this.renderer.setClearColor(BACKGROUND_COLOR, 1);
-        this.containerNode.appendChild(this.renderer.domElement);
-        this.light = new THREE.PointLight(0xFFECBF, 1, 0);
-        this.scene.add(this.light);
-        this.setDimensions(3);
-        this.recreateCamera(this.makeDefaultCameraDef(this.dimensionality));
-        this.renderer.render(this.scene, this.camera);
-        this.rectangleSelector = new scatterPlotRectangleSelector_1.ScatterPlotRectangleSelector(this.containerNode, function (boundingBox) { return _this.selectBoundingBox(boundingBox); });
-        this.addInteractionListeners();
-    }
-    ScatterPlot.prototype.addInteractionListeners = function () {
-        this.containerNode.addEventListener('mousemove', this.onMouseMove.bind(this));
-        this.containerNode.addEventListener('mousedown', this.onMouseDown.bind(this));
-        this.containerNode.addEventListener('mouseup', this.onMouseUp.bind(this));
-        this.containerNode.addEventListener('click', this.onClick.bind(this));
-        window.addEventListener('keydown', this.onKeyDown.bind(this), false);
-        window.addEventListener('keyup', this.onKeyUp.bind(this), false);
-    };
-    ScatterPlot.prototype.addCameraControlsEventListeners = function (cameraControls) {
-        var _this = this;
-        // Start is called when the user stars interacting with
-        // controls.
-        cameraControls.addEventListener('start', function () {
-            _this.stopOrbitAnimation();
-            _this.onCameraMoveListeners.forEach(function (l) { return l(_this.camera.position, cameraControls.target); });
-        });
-        // Change is called everytime the user interacts with the controls.
-        cameraControls.addEventListener('change', function () {
-            _this.render();
-        });
-        // End is called when the user stops interacting with the
-        // controls (e.g. on mouse up, after dragging).
-        cameraControls.addEventListener('end', function () { });
-    };
-    ScatterPlot.prototype.makeOrbitControls = function (camera, cameraDef, cameraIs3D) {
-        if (this.orbitCameraControls != null) {
-            this.orbitCameraControls.dispose();
-        }
-        var occ = new THREE.OrbitControls(camera, this.renderer.domElement);
-        occ.target0 = new THREE.Vector3(cameraDef.target[0], cameraDef.target[1], cameraDef.target[2]);
-        occ.position0 = new THREE.Vector3().copy(camera.position);
-        occ.zoom0 = cameraDef.zoom;
-        occ.enableRotate = cameraIs3D;
-        occ.autoRotate = false;
-        occ.rotateSpeed = ORBIT_MOUSE_ROTATION_SPEED;
-        if (cameraIs3D) {
-            occ.mouseButtons.ORBIT = THREE.MOUSE.LEFT;
-            occ.mouseButtons.PAN = THREE.MOUSE.RIGHT;
-        }
-        else {
-            occ.mouseButtons.ORBIT = null;
-            occ.mouseButtons.PAN = THREE.MOUSE.LEFT;
-        }
-        occ.reset();
-        this.camera = camera;
-        this.orbitCameraControls = occ;
-        this.addCameraControlsEventListeners(this.orbitCameraControls);
-    };
-    ScatterPlot.prototype.makeCamera3D = function (cameraDef, w, h) {
-        var camera;
-        {
-            var aspectRatio = w / h;
-            camera = new THREE.PerspectiveCamera(PERSP_CAMERA_FOV_VERTICAL, aspectRatio, PERSP_CAMERA_NEAR_CLIP_PLANE, PERSP_CAMERA_FAR_CLIP_PLANE);
-            camera.position.set(cameraDef.position[0], cameraDef.position[1], cameraDef.position[2]);
-            var at = new THREE.Vector3(cameraDef.target[0], cameraDef.target[1], cameraDef.target[2]);
-            camera.lookAt(at);
-            camera.zoom = cameraDef.zoom;
-            camera.updateProjectionMatrix();
-        }
-        this.camera = camera;
-        this.makeOrbitControls(camera, cameraDef, true);
-    };
-    ScatterPlot.prototype.makeCamera2D = function (cameraDef, w, h) {
-        var camera;
-        var target = new THREE.Vector3(cameraDef.target[0], cameraDef.target[1], cameraDef.target[2]);
-        {
-            var aspectRatio = w / h;
-            var left = -ORTHO_CAMERA_FRUSTUM_HALF_EXTENT;
-            var right = ORTHO_CAMERA_FRUSTUM_HALF_EXTENT;
-            var bottom = -ORTHO_CAMERA_FRUSTUM_HALF_EXTENT;
-            var top_1 = ORTHO_CAMERA_FRUSTUM_HALF_EXTENT;
-            // Scale up the larger of (w, h) to match the aspect ratio.
-            if (aspectRatio > 1) {
-                left *= aspectRatio;
-                right *= aspectRatio;
-            }
-            else {
-                top_1 /= aspectRatio;
-                bottom /= aspectRatio;
-            }
-            camera =
-                new THREE.OrthographicCamera(left, right, top_1, bottom, -1000, 1000);
-            camera.position.set(cameraDef.position[0], cameraDef.position[1], cameraDef.position[2]);
-            camera.up = new THREE.Vector3(0, 1, 0);
-            camera.lookAt(target);
-            camera.zoom = cameraDef.zoom;
-            camera.updateProjectionMatrix();
-        }
-        this.camera = camera;
-        this.makeOrbitControls(camera, cameraDef, false);
-    };
-    ScatterPlot.prototype.makeDefaultCameraDef = function (dimensionality) {
-        var def = new CameraDef();
-        def.orthographic = (dimensionality === 2);
-        def.zoom = 1.0;
-        if (def.orthographic) {
-            def.position =
-                [START_CAMERA_POS_2D.x, START_CAMERA_POS_2D.y, START_CAMERA_POS_2D.z];
-            def.target = [
-                START_CAMERA_TARGET_2D.x, START_CAMERA_TARGET_2D.y,
-                START_CAMERA_TARGET_2D.z
-            ];
-        }
-        else {
-            def.position =
-                [START_CAMERA_POS_3D.x, START_CAMERA_POS_3D.y, START_CAMERA_POS_3D.z];
-            def.target = [
-                START_CAMERA_TARGET_3D.x, START_CAMERA_TARGET_3D.y,
-                START_CAMERA_TARGET_3D.z
-            ];
-        }
-        return def;
-    };
-    /** Recreate the scatter plot camera from a definition structure. */
-    ScatterPlot.prototype.recreateCamera = function (cameraDef) {
-        if (cameraDef.orthographic) {
-            this.makeCamera2D(cameraDef, this.width, this.height);
-        }
-        else {
-            this.makeCamera3D(cameraDef, this.width, this.height);
-        }
-        this.orbitCameraControls.minDistance = MIN_ZOOM;
-        this.orbitCameraControls.maxDistance = MAX_ZOOM;
-        this.orbitCameraControls.update();
-        if (this.orbitAnimationOnNextCameraCreation) {
-            this.startOrbitAnimation();
-        }
-    };
-    ScatterPlot.prototype.onClick = function (e, notify) {
-        if (notify === void 0) { notify = true; }
-        if (e && this.selecting) {
-            return;
-        }
-        // Only call event handlers if the click originated from the scatter plot.
-        if (!this.isDragSequence && notify) {
-            var selection = (this.nearestPoint != null) ? [this.nearestPoint] : [];
-            this.projectorEventContext.notifySelectionChanged(selection);
-        }
-        this.isDragSequence = false;
-        this.render();
-    };
-    ScatterPlot.prototype.onMouseDown = function (e) {
-        this.isDragSequence = false;
-        this.mouseIsDown = true;
-        if (this.selecting) {
-            this.orbitCameraControls.enabled = false;
-            this.rectangleSelector.onMouseDown(e.offsetX, e.offsetY);
-            this.setNearestPointToMouse(e);
-        }
-        else if (!e.ctrlKey && this.sceneIs3D() &&
-            this.orbitCameraControls.mouseButtons.ORBIT === THREE.MOUSE.RIGHT) {
-            // The user happened to press the ctrl key when the tab was active,
-            // unpressed the ctrl when the tab was inactive, and now he/she
-            // is back to the projector tab.
-            this.orbitCameraControls.mouseButtons.ORBIT = THREE.MOUSE.LEFT;
-            this.orbitCameraControls.mouseButtons.PAN = THREE.MOUSE.RIGHT;
-        }
-        else if (e.ctrlKey && this.sceneIs3D() &&
-            this.orbitCameraControls.mouseButtons.ORBIT === THREE.MOUSE.LEFT) {
-            // Similarly to the situation above.
-            this.orbitCameraControls.mouseButtons.ORBIT = THREE.MOUSE.RIGHT;
-            this.orbitCameraControls.mouseButtons.PAN = THREE.MOUSE.LEFT;
-        }
-    };
-    /** When we stop dragging/zooming, return to normal behavior. */
-    ScatterPlot.prototype.onMouseUp = function (e) {
-        if (this.selecting) {
-            this.orbitCameraControls.enabled = true;
-            this.rectangleSelector.onMouseUp();
-            this.render();
-        }
-        this.mouseIsDown = false;
-    };
-    /**
-     * When the mouse moves, find the nearest point (if any) and send it to the
-     * hoverlisteners (usually called from embedding.ts)
-     */
-    ScatterPlot.prototype.onMouseMove = function (e) {
-        this.isDragSequence = this.mouseIsDown;
-        // Depending if we're selecting or just navigating, handle accordingly.
-        if (this.selecting && this.mouseIsDown) {
-            this.rectangleSelector.onMouseMove(e.offsetX, e.offsetY);
-            this.render();
-        }
-        else if (!this.mouseIsDown) {
-            this.setNearestPointToMouse(e);
-            this.projectorEventContext.notifyHoverOverPoint(this.nearestPoint);
-        }
-    };
-    /** For using ctrl + left click as right click, and for circle select */
-    ScatterPlot.prototype.onKeyDown = function (e) {
-        // If ctrl is pressed, use left click to orbit
-        if (e.keyCode === CTRL_KEY && this.sceneIs3D()) {
-            this.orbitCameraControls.mouseButtons.ORBIT = THREE.MOUSE.RIGHT;
-            this.orbitCameraControls.mouseButtons.PAN = THREE.MOUSE.LEFT;
-        }
-        // If shift is pressed, start selecting
-        if (e.keyCode === SHIFT_KEY) {
-            this.selecting = true;
-            this.containerNode.style.cursor = 'crosshair';
-        }
-    };
-    /** For using ctrl + left click as right click, and for circle select */
-    ScatterPlot.prototype.onKeyUp = function (e) {
-        if (e.keyCode === CTRL_KEY && this.sceneIs3D()) {
-            this.orbitCameraControls.mouseButtons.ORBIT = THREE.MOUSE.LEFT;
-            this.orbitCameraControls.mouseButtons.PAN = THREE.MOUSE.RIGHT;
-        }
-        // If shift is released, stop selecting
-        if (e.keyCode === SHIFT_KEY) {
-            this.selecting = (this.getMouseMode() === MouseMode.AREA_SELECT);
-            if (!this.selecting) {
-                this.containerNode.style.cursor = 'default';
-            }
-            this.render();
-        }
-    };
-    /**
-     * Returns a list of indices of points in a bounding box from the picking
-     * texture.
-     * @param boundingBox The bounding box to select from.
-     */
-    ScatterPlot.prototype.getPointIndicesFromPickingTexture = function (boundingBox) {
-        if (this.worldSpacePointPositions == null) {
-            return null;
-        }
-        var pointCount = this.worldSpacePointPositions.length / 3;
-        var dpr = window.devicePixelRatio || 1;
-        var x = Math.floor(boundingBox.x * dpr);
-        var y = Math.floor(boundingBox.y * dpr);
-        var width = Math.floor(boundingBox.width * dpr);
-        var height = Math.floor(boundingBox.height * dpr);
-        // Create buffer for reading all of the pixels from the texture.
-        var pixelBuffer = new Uint8Array(width * height * 4);
-        // Read the pixels from the bounding box.
-        this.renderer.readRenderTargetPixels(this.pickingTexture, x, this.pickingTexture.height - y, width, height, pixelBuffer);
-        // Keep a flat list of each point and whether they are selected or not. This
-        // approach is more efficient than using an object keyed by the index.
-        var pointIndicesSelection = new Uint8Array(this.worldSpacePointPositions.length);
-        for (var i = 0; i < width * height; i++) {
-            var id = (pixelBuffer[i * 4] << 16) | (pixelBuffer[i * 4 + 1] << 8) |
-                pixelBuffer[i * 4 + 2];
-            if (id !== 0xffffff && (id < pointCount)) {
-                pointIndicesSelection[id] = 1;
-            }
-        }
-        var pointIndices = [];
-        for (var i = 0; i < pointIndicesSelection.length; i++) {
-            if (pointIndicesSelection[i] === 1) {
-                pointIndices.push(i);
-            }
-        }
-        return pointIndices;
-    };
-    ScatterPlot.prototype.selectBoundingBox = function (boundingBox) {
-        var pointIndices = this.getPointIndicesFromPickingTexture(boundingBox);
-        this.projectorEventContext.notifySelectionChanged(pointIndices);
-    };
-    ScatterPlot.prototype.setNearestPointToMouse = function (e) {
-        if (this.pickingTexture == null) {
-            this.nearestPoint = null;
-            return;
-        }
-        var boundingBox = { x: e.offsetX, y: e.offsetY, width: 1, height: 1 };
-        var pointIndices = this.getPointIndicesFromPickingTexture(boundingBox);
-        this.nearestPoint = (pointIndices != null) ? pointIndices[0] : null;
-    };
-    ScatterPlot.prototype.getLayoutValues = function () {
-        this.width = this.containerNode.offsetWidth;
-        this.height = Math.max(1, this.containerNode.offsetHeight);
-        return [this.width, this.height];
-    };
-    ScatterPlot.prototype.sceneIs3D = function () {
-        return this.dimensionality === 3;
-    };
-    ScatterPlot.prototype.remove3dAxisFromScene = function () {
-        var axes = this.scene.getObjectByName('axes');
-        if (axes != null) {
-            this.scene.remove(axes);
-        }
-        return axes;
-    };
-    ScatterPlot.prototype.add3dAxis = function () {
-        var axes = new THREE.AxisHelper();
-        axes.name = 'axes';
-        this.scene.add(axes);
-    };
-    /** Set 2d vs 3d mode. */
-    ScatterPlot.prototype.setDimensions = function (dimensionality) {
-        if ((dimensionality !== 2) && (dimensionality !== 3)) {
-            throw new RangeError('dimensionality must be 2 or 3');
-        }
-        this.dimensionality = dimensionality;
-        var def = this.cameraDef || this.makeDefaultCameraDef(dimensionality);
-        this.recreateCamera(def);
-        this.remove3dAxisFromScene();
-        if (dimensionality === 3) {
-            this.add3dAxis();
-        }
-    };
-    /** Gets the current camera information, suitable for serialization. */
-    ScatterPlot.prototype.getCameraDef = function () {
-        var def = new CameraDef();
-        var pos = this.camera.position;
-        var tgt = this.orbitCameraControls.target;
-        def.orthographic = !this.sceneIs3D();
-        def.position = [pos.x, pos.y, pos.z];
-        def.target = [tgt.x, tgt.y, tgt.z];
-        def.zoom = this.camera.zoom;
-        return def;
-    };
-    /** Sets parameters for the next camera recreation. */
-    ScatterPlot.prototype.setCameraParametersForNextCameraCreation = function (def, orbitAnimation) {
-        this.cameraDef = def;
-        this.orbitAnimationOnNextCameraCreation = orbitAnimation;
-    };
-    /** Gets the current camera position. */
-    ScatterPlot.prototype.getCameraPosition = function () {
-        var currPos = this.camera.position;
-        return [currPos.x, currPos.y, currPos.z];
-    };
-    /** Gets the current camera target. */
-    ScatterPlot.prototype.getCameraTarget = function () {
-        var currTarget = this.orbitCameraControls.target;
-        return [currTarget.x, currTarget.y, currTarget.z];
-    };
-    /** Sets up the camera from given position and target coordinates. */
-    ScatterPlot.prototype.setCameraPositionAndTarget = function (position, target) {
-        this.stopOrbitAnimation();
-        this.camera.position.set(position[0], position[1], position[2]);
-        this.orbitCameraControls.target.set(target[0], target[1], target[2]);
-        this.orbitCameraControls.update();
-        this.render();
-    };
-    /** Starts orbiting the camera around its current lookat target. */
-    ScatterPlot.prototype.startOrbitAnimation = function () {
-        if (!this.sceneIs3D()) {
-            return;
-        }
-        if (this.orbitAnimationId != null) {
-            this.stopOrbitAnimation();
-        }
-        this.orbitCameraControls.autoRotate = true;
-        this.orbitCameraControls.rotateSpeed =
-            ORBIT_ANIMATION_ROTATION_CYCLE_IN_SECONDS;
-        this.updateOrbitAnimation();
-    };
-    ScatterPlot.prototype.updateOrbitAnimation = function () {
-        var _this = this;
-        this.orbitCameraControls.update();
-        this.orbitAnimationId =
-            requestAnimationFrame(function () { return _this.updateOrbitAnimation(); });
-    };
-    /** Stops the orbiting animation on the camera. */
-    ScatterPlot.prototype.stopOrbitAnimation = function () {
-        this.orbitCameraControls.autoRotate = false;
-        this.orbitCameraControls.rotateSpeed = ORBIT_MOUSE_ROTATION_SPEED;
-        if (this.orbitAnimationId != null) {
-            cancelAnimationFrame(this.orbitAnimationId);
-            this.orbitAnimationId = null;
-        }
-    };
-    /** Adds a visualizer to the set, will start dispatching events to it */
-    ScatterPlot.prototype.addVisualizer = function (visualizer) {
-        if (this.scene) {
-            visualizer.setScene(this.scene);
-        }
-        visualizer.onResize(this.width, this.height);
-        visualizer.onPointPositionsChanged(this.worldSpacePointPositions);
-        this.visualizers.push(visualizer);
-    };
-    /** Removes all visualizers attached to this scatter plot. */
-    ScatterPlot.prototype.removeAllVisualizers = function () {
-        this.visualizers.forEach(function (v) { return v.dispose(); });
-        this.visualizers = [];
-    };
-    /** Update scatter plot with a new array of packed xyz point positions. */
-    ScatterPlot.prototype.setPointPositions = function (worldSpacePointPositions) {
-        this.worldSpacePointPositions = worldSpacePointPositions;
-        this.visualizers.forEach(function (v) { return v.onPointPositionsChanged(worldSpacePointPositions); });
-    };
-    ScatterPlot.prototype.render = function () {
-        {
-            var lightPos = this.camera.position.clone();
-            lightPos.x += 1;
-            lightPos.y += 1;
-            this.light.position.set(lightPos.x, lightPos.y, lightPos.z);
-        }
-        var cameraType = (this.camera instanceof THREE.PerspectiveCamera) ?
-            renderContext_1.CameraType.Perspective :
-            renderContext_1.CameraType.Orthographic;
-        var cameraSpacePointExtents = [0, 0];
-        if (this.worldSpacePointPositions != null) {
-            cameraSpacePointExtents = util.getNearFarPoints(this.worldSpacePointPositions, this.camera.position, this.orbitCameraControls.target);
-        }
-        var rc = new renderContext_1.RenderContext(this.camera, cameraType, this.orbitCameraControls.target, this.width, this.height, cameraSpacePointExtents[0], cameraSpacePointExtents[1], this.backgroundColor, this.pointColors, this.pointScaleFactors, this.labels, this.polylineColors, this.polylineOpacities, this.polylineWidths);
-        // Render first pass to picking target. This render fills pickingTexture
-        // with colors that are actually point ids, so that sampling the texture at
-        // the mouse's current x,y coordinates will reveal the data point that the
-        // mouse is over.
-        this.visualizers.forEach(function (v) { return v.onPickingRender(rc); });
-        {
-            var axes = this.remove3dAxisFromScene();
-            this.renderer.render(this.scene, this.camera, this.pickingTexture);
-            if (axes != null) {
-                this.scene.add(axes);
-            }
-        }
-        // Render second pass to color buffer, to be displayed on the canvas.
-        this.visualizers.forEach(function (v) { return v.onRender(rc); });
-        this.renderer.render(this.scene, this.camera);
-    };
-    ScatterPlot.prototype.setMouseMode = function (mouseMode) {
-        this.mouseMode = mouseMode;
-        if (mouseMode === MouseMode.AREA_SELECT) {
-            this.selecting = true;
-            this.containerNode.style.cursor = 'crosshair';
-        }
-        else {
-            this.selecting = false;
-            this.containerNode.style.cursor = 'default';
-        }
-    };
-    /** Set the colors for every data point. (RGB triplets) */
-    ScatterPlot.prototype.setPointColors = function (colors) {
-        this.pointColors = colors;
-    };
-    /** Set the scale factors for every data point. (scalars) */
-    ScatterPlot.prototype.setPointScaleFactors = function (scaleFactors) {
-        this.pointScaleFactors = scaleFactors;
-    };
-    /** Set the labels to rendered */
-    ScatterPlot.prototype.setLabels = function (labels) {
-        this.labels = labels;
-    };
-    /** Set the colors for every data polyline. (RGB triplets) */
-    ScatterPlot.prototype.setPolylineColors = function (colors) {
-        this.polylineColors = colors;
-    };
-    ScatterPlot.prototype.setPolylineOpacities = function (opacities) {
-        this.polylineOpacities = opacities;
-    };
-    ScatterPlot.prototype.setPolylineWidths = function (widths) {
-        this.polylineWidths = widths;
-    };
-    ScatterPlot.prototype.getMouseMode = function () {
-        return this.mouseMode;
-    };
-    ScatterPlot.prototype.resetZoom = function () {
-        this.recreateCamera(this.makeDefaultCameraDef(this.dimensionality));
-        this.render();
-    };
-    ScatterPlot.prototype.setDayNightMode = function (isNight) {
-        d3.select(this.containerNode)
-            .selectAll('canvas')
-            .style('filter', isNight ? 'invert(100%)' : null);
-    };
-    ScatterPlot.prototype.resize = function (render) {
-        if (render === void 0) { render = true; }
-        var _a = [this.width, this.height], oldW = _a[0], oldH = _a[1];
-        var _b = this.getLayoutValues(), newW = _b[0], newH = _b[1];
-        if (this.dimensionality === 3) {
-            var camera = this.camera;
-            camera.aspect = newW / newH;
-            camera.updateProjectionMatrix();
-        }
-        else {
-            var camera = this.camera;
-            // Scale the ortho frustum by however much the window changed.
-            var scaleW = newW / oldW;
-            var scaleH = newH / oldH;
-            var newCamHalfWidth = ((camera.right - camera.left) * scaleW) / 2;
-            var newCamHalfHeight = ((camera.top - camera.bottom) * scaleH) / 2;
-            camera.top = newCamHalfHeight;
-            camera.bottom = -newCamHalfHeight;
-            camera.left = -newCamHalfWidth;
-            camera.right = newCamHalfWidth;
-            camera.updateProjectionMatrix();
-        }
-        // Accouting for retina displays.
-        var dpr = window.devicePixelRatio || 1;
-        this.renderer.setPixelRatio(dpr);
-        this.renderer.setSize(newW, newH);
-        // the picking texture needs to be exactly the same as the render texture.
-        {
-            var renderCanvasSize = this.renderer.getSize();
-            var pixelRatio = this.renderer.getPixelRatio();
-            this.pickingTexture = new THREE.WebGLRenderTarget(renderCanvasSize.width * pixelRatio, renderCanvasSize.height * pixelRatio);
-            this.pickingTexture.texture.minFilter = THREE.LinearFilter;
-        }
-        this.visualizers.forEach(function (v) { return v.onResize(newW, newH); });
-        if (render) {
-            this.render();
-        }
-        ;
-    };
-    ScatterPlot.prototype.onCameraMove = function (listener) {
-        this.onCameraMoveListeners.push(listener);
-    };
-    ScatterPlot.prototype.clickOnPoint = function (pointIndex) {
-        this.nearestPoint = pointIndex;
-        this.onClick(null, false);
-    };
-    return ScatterPlot;
-}());
-exports.ScatterPlot = ScatterPlot;
-
-},{"./renderContext":15,"./scatterPlotRectangleSelector":17,"./util":24}],17:[function(require,module,exports){
-/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-"use strict";
-var FILL = '#dddddd';
-var FILL_OPACITY = .2;
-var STROKE = '#aaaaaa';
-var STROKE_WIDTH = 2;
-var STROKE_DASHARRAY = '10 5';
-/**
- * A class that manages and renders a data selection rectangle.
- */
-var ScatterPlotRectangleSelector = (function () {
-    /**
-     * @param container The container HTML element that the selection SVG rect
-     *     will be a child of.
-     * @param selectionCallback The callback that accepts a bounding box to be
-     *     called when selection changes. Currently, we only call the callback on
-     *     mouseUp.
-     */
-    function ScatterPlotRectangleSelector(container, selectionCallback) {
-        this.svgElement = d3.select(container).select('#selector');
-        this.rectElement = this.svgElement.append('rect')
-            .style('stroke', STROKE)
-            .style('stroke-dasharray', STROKE_DASHARRAY)
-            .style('stroke-width', STROKE_WIDTH)
-            .style('fill', FILL)
-            .style('fill-opacity', FILL_OPACITY);
-        this.selectionCallback = selectionCallback;
-        this.isMouseDown = false;
-    }
-    ScatterPlotRectangleSelector.prototype.onMouseDown = function (offsetX, offsetY) {
-        this.isMouseDown = true;
-        this.svgElement.style('display', 'block');
-        this.startCoordinates = [offsetX, offsetY];
-        this.lastBoundingBox = {
-            x: this.startCoordinates[0],
-            y: this.startCoordinates[1],
-            width: 1,
-            height: 1
-        };
-    };
-    ScatterPlotRectangleSelector.prototype.onMouseMove = function (offsetX, offsetY) {
-        if (!this.isMouseDown) {
-            return;
-        }
-        this.lastBoundingBox.x = Math.min(offsetX, this.startCoordinates[0]);
-        this.lastBoundingBox.y = Math.max(offsetY, this.startCoordinates[1]);
-        this.lastBoundingBox.width =
-            Math.max(offsetX, this.startCoordinates[0]) - this.lastBoundingBox.x;
-        this.lastBoundingBox.height =
-            this.lastBoundingBox.y - Math.min(offsetY, this.startCoordinates[1]);
-        this.rectElement.attr({
-            x: this.lastBoundingBox.x,
-            y: this.lastBoundingBox.y - this.lastBoundingBox.height,
-            width: this.lastBoundingBox.width,
-            height: this.lastBoundingBox.height
-        });
-    };
-    ScatterPlotRectangleSelector.prototype.onMouseUp = function () {
-        this.isMouseDown = false;
-        this.svgElement.style('display', 'none');
-        this.rectElement.attr('width', 0);
-        this.rectElement.attr('height', 0);
-        this.selectionCallback(this.lastBoundingBox);
-    };
-    return ScatterPlotRectangleSelector;
-}());
-exports.ScatterPlotRectangleSelector = ScatterPlotRectangleSelector;
-
-},{}],18:[function(require,module,exports){
-arguments[4][13][0].apply(exports,arguments)
-},{"dup":13}],19:[function(require,module,exports){
-/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-"use strict";
-var util = require('./util');
-var FONT_SIZE = 80;
-var ONE_OVER_FONT_SIZE = 1 / FONT_SIZE;
-var LABEL_SCALE = 2.2; // at 1:1 texel/pixel ratio
-var LABEL_COLOR = 'black';
-var LABEL_BACKGROUND = 'white';
-var MAX_CANVAS_DIMENSION = 8192;
-var NUM_GLYPHS = 256;
-var RGB_ELEMENTS_PER_ENTRY = 3;
-var XYZ_ELEMENTS_PER_ENTRY = 3;
-var UV_ELEMENTS_PER_ENTRY = 2;
-var VERTICES_PER_GLYPH = 2 * 3; // 2 triangles, 3 verts per triangle
-/**
- * Each label is made up of triangles (two per letter.) Each vertex, then, is
- * the corner of one of these triangles (and thus the corner of a letter
- * rectangle.)
- * Each has the following attributes:
- *    posObj: The (x, y) position of the vertex within the label, where the
- *            bottom center of the word is positioned at (0, 0);
- *    position: The position of the label in worldspace.
- *    vUv: The (u, v) coordinates that index into the glyphs sheet (range 0, 1.)
- *    color: The color of the label (matches the cooresponding point's color.)
- *    wordShown: Boolean. Whether or not the label is visible.
- */
-var VERTEX_SHADER = "\n    attribute vec2 posObj;\n    attribute vec3 color;\n    varying vec2 vUv;\n    varying vec3 vColor;\n\n    void main() {\n      vUv = uv;\n      vColor = color;\n\n      // Rotate label to face camera.\n\n      vec4 vRight = vec4(\n        modelViewMatrix[0][0], modelViewMatrix[1][0], modelViewMatrix[2][0], 0);\n\n      vec4 vUp = vec4(\n        modelViewMatrix[0][1], modelViewMatrix[1][1], modelViewMatrix[2][1], 0);\n\n      vec4 vAt = -vec4(\n        modelViewMatrix[0][2], modelViewMatrix[1][2], modelViewMatrix[2][2], 0);\n\n      mat4 pointToCamera = mat4(vRight, vUp, vAt, vec4(0, 0, 0, 1));\n\n      vec2 scaledPos = posObj * " + ONE_OVER_FONT_SIZE + " * " + LABEL_SCALE + ";\n\n      vec4 posRotated = pointToCamera * vec4(scaledPos, 0, 1);\n      vec4 mvPosition = modelViewMatrix * (vec4(position, 0) + posRotated);\n      gl_Position = projectionMatrix * mvPosition;\n    }";
-var FRAGMENT_SHADER = "\n    uniform sampler2D texture;\n    uniform bool picking;\n    varying vec2 vUv;\n    varying vec3 vColor;\n\n    void main() {\n      if (picking) {\n        gl_FragColor = vec4(vColor, 1.0);\n      } else {\n        vec4 fromTexture = texture2D(texture, vUv);\n        gl_FragColor = vec4(vColor, 1.0) * fromTexture;\n      }\n    }";
-/**
- * Renders the text labels as 3d geometry in the world.
- */
-var ScatterPlotVisualizer3DLabels = (function () {
-    function ScatterPlotVisualizer3DLabels() {
-    }
-    ScatterPlotVisualizer3DLabels.prototype.createGlyphTexture = function () {
-        var canvas = document.createElement('canvas');
-        canvas.width = MAX_CANVAS_DIMENSION;
-        canvas.height = FONT_SIZE;
-        var ctx = canvas.getContext('2d');
-        ctx.font = 'bold ' + FONT_SIZE * 0.75 + 'px roboto';
-        ctx.textBaseline = 'top';
-        ctx.fillStyle = LABEL_BACKGROUND;
-        ctx.rect(0, 0, canvas.width, canvas.height);
-        ctx.fill();
-        ctx.fillStyle = LABEL_COLOR;
-        var spaceOffset = ctx.measureText(' ').width;
-        // For each letter, store length, position at the encoded index.
-        var glyphLengths = new Float32Array(NUM_GLYPHS);
-        var glyphOffset = new Float32Array(NUM_GLYPHS);
-        var leftCoord = 0;
-        for (var i = 0; i < NUM_GLYPHS; i++) {
-            var text = ' ' + String.fromCharCode(i);
-            var textLength = ctx.measureText(text).width;
-            glyphLengths[i] = textLength - spaceOffset;
-            glyphOffset[i] = leftCoord;
-            ctx.fillText(text, leftCoord - spaceOffset, 0);
-            leftCoord += textLength;
-        }
-        var tex = util.createTexture(canvas);
-        return { texture: tex, lengths: glyphLengths, offsets: glyphOffset };
-    };
-    ScatterPlotVisualizer3DLabels.prototype.processLabelVerts = function (pointCount) {
-        var numTotalLetters = 0;
-        this.labelVertexMap = [];
-        for (var i = 0; i < pointCount; i++) {
-            var label = this.labelStrings[i];
-            var vertsArray = [];
-            for (var j = 0; j < label.length; j++) {
-                for (var k = 0; k < VERTICES_PER_GLYPH; k++) {
-                    vertsArray.push(numTotalLetters * VERTICES_PER_GLYPH + k);
-                }
-                numTotalLetters++;
-            }
-            this.labelVertexMap.push(vertsArray);
-        }
-        this.totalVertexCount = numTotalLetters * VERTICES_PER_GLYPH;
-    };
-    ScatterPlotVisualizer3DLabels.prototype.createColorBuffers = function (pointCount) {
-        var _this = this;
-        this.pickingColors =
-            new Float32Array(this.totalVertexCount * RGB_ELEMENTS_PER_ENTRY);
-        this.renderColors =
-            new Float32Array(this.totalVertexCount * RGB_ELEMENTS_PER_ENTRY);
-        var _loop_1 = function(i) {
-            var color = new THREE.Color(i);
-            this_1.labelVertexMap[i].forEach(function (j) {
-                _this.pickingColors[RGB_ELEMENTS_PER_ENTRY * j] = color.r;
-                _this.pickingColors[RGB_ELEMENTS_PER_ENTRY * j + 1] = color.g;
-                _this.pickingColors[RGB_ELEMENTS_PER_ENTRY * j + 2] = color.b;
-                _this.renderColors[RGB_ELEMENTS_PER_ENTRY * j] = 1.0;
-                _this.renderColors[RGB_ELEMENTS_PER_ENTRY * j + 1] = 1.0;
-                _this.renderColors[RGB_ELEMENTS_PER_ENTRY * j + 2] = 1.0;
-            });
-        };
-        var this_1 = this;
-        for (var i = 0; i < pointCount; i++) {
-            _loop_1(i);
-        }
-    };
-    ScatterPlotVisualizer3DLabels.prototype.createLabels = function () {
-        var _this = this;
-        if ((this.labelStrings == null) ||
-            (this.worldSpacePointPositions == null)) {
-            return;
-        }
-        var pointCount = this.worldSpacePointPositions.length / XYZ_ELEMENTS_PER_ENTRY;
-        if (pointCount !== this.labelStrings.length) {
-            return;
-        }
-        this.glyphTexture = this.createGlyphTexture();
-        this.uniforms = {
-            texture: { type: 't' },
-            picking: { type: 'bool' },
-        };
-        this.material = new THREE.ShaderMaterial({
-            uniforms: this.uniforms,
-            transparent: true,
-            vertexShader: VERTEX_SHADER,
-            fragmentShader: FRAGMENT_SHADER,
-        });
-        this.processLabelVerts(pointCount);
-        this.createColorBuffers(pointCount);
-        var positionArray = new Float32Array(this.totalVertexCount * XYZ_ELEMENTS_PER_ENTRY);
-        this.positions =
-            new THREE.BufferAttribute(positionArray, XYZ_ELEMENTS_PER_ENTRY);
-        var posArray = new Float32Array(this.totalVertexCount * XYZ_ELEMENTS_PER_ENTRY);
-        var uvArray = new Float32Array(this.totalVertexCount * UV_ELEMENTS_PER_ENTRY);
-        var colorsArray = new Float32Array(this.totalVertexCount * RGB_ELEMENTS_PER_ENTRY);
-        var positionObject = new THREE.BufferAttribute(posArray, 2);
-        var uv = new THREE.BufferAttribute(uvArray, UV_ELEMENTS_PER_ENTRY);
-        var colors = new THREE.BufferAttribute(colorsArray, RGB_ELEMENTS_PER_ENTRY);
-        this.geometry = new THREE.BufferGeometry();
-        this.geometry.addAttribute('posObj', positionObject);
-        this.geometry.addAttribute('position', this.positions);
-        this.geometry.addAttribute('uv', uv);
-        this.geometry.addAttribute('color', colors);
-        var lettersSoFar = 0;
-        for (var i = 0; i < pointCount; i++) {
-            var label = this.labelStrings[i];
-            var leftOffset = 0;
-            // Determine length of word in pixels.
-            for (var j = 0; j < label.length; j++) {
-                var letterCode = label.charCodeAt(j);
-                leftOffset += this.glyphTexture.lengths[letterCode];
-            }
-            leftOffset /= -2; // centers text horizontally around the origin
-            for (var j = 0; j < label.length; j++) {
-                var letterCode = label.charCodeAt(j);
-                var letterWidth = this.glyphTexture.lengths[letterCode];
-                var scale = FONT_SIZE;
-                var right = (leftOffset + letterWidth) / scale;
-                var left = (leftOffset) / scale;
-                var top_1 = FONT_SIZE / scale;
-                // First triangle
-                positionObject.setXY(lettersSoFar * VERTICES_PER_GLYPH + 0, left, 0);
-                positionObject.setXY(lettersSoFar * VERTICES_PER_GLYPH + 1, right, 0);
-                positionObject.setXY(lettersSoFar * VERTICES_PER_GLYPH + 2, left, top_1);
-                // Second triangle
-                positionObject.setXY(lettersSoFar * VERTICES_PER_GLYPH + 3, left, top_1);
-                positionObject.setXY(lettersSoFar * VERTICES_PER_GLYPH + 4, right, 0);
-                positionObject.setXY(lettersSoFar * VERTICES_PER_GLYPH + 5, right, top_1);
-                // Set UVs based on letter.
-                var uLeft = (this.glyphTexture.offsets[letterCode]);
-                var uRight = (this.glyphTexture.offsets[letterCode] + letterWidth);
-                // Scale so that uvs lie between 0 and 1 on the texture.
-                uLeft /= MAX_CANVAS_DIMENSION;
-                uRight /= MAX_CANVAS_DIMENSION;
-                var vTop = 1;
-                var vBottom = 0;
-                uv.setXY(lettersSoFar * VERTICES_PER_GLYPH + 0, uLeft, vTop);
-                uv.setXY(lettersSoFar * VERTICES_PER_GLYPH + 1, uRight, vTop);
-                uv.setXY(lettersSoFar * VERTICES_PER_GLYPH + 2, uLeft, vBottom);
-                uv.setXY(lettersSoFar * VERTICES_PER_GLYPH + 3, uLeft, vBottom);
-                uv.setXY(lettersSoFar * VERTICES_PER_GLYPH + 4, uRight, vTop);
-                uv.setXY(lettersSoFar * VERTICES_PER_GLYPH + 5, uRight, vBottom);
-                lettersSoFar++;
-                leftOffset += letterWidth;
-            }
-        }
-        var _loop_2 = function(i) {
-            var p = util.vector3FromPackedArray(this_2.worldSpacePointPositions, i);
-            this_2.labelVertexMap[i].forEach(function (j) {
-                _this.positions.setXYZ(j, p.x, p.y, p.z);
-            });
-        };
-        var this_2 = this;
-        for (var i = 0; i < pointCount; i++) {
-            _loop_2(i);
-        }
-        ;
-        this.labelsMesh = new THREE.Mesh(this.geometry, this.material);
-        this.labelsMesh.frustumCulled = false;
-        this.scene.add(this.labelsMesh);
-    };
-    ScatterPlotVisualizer3DLabels.prototype.colorLabels = function (pointColors) {
-        if (this.labelStrings == null || this.geometry == null ||
-            pointColors == null) {
-            return;
-        }
-        var colors = this.geometry.getAttribute('color');
-        colors.array = this.renderColors;
-        var n = pointColors.length / XYZ_ELEMENTS_PER_ENTRY;
-        var src = 0;
-        for (var i = 0; i < n; ++i) {
-            var c = new THREE.Color(pointColors[src], pointColors[src + 1], pointColors[src + 2]);
-            var m = this.labelVertexMap[i].length;
-            for (var j = 0; j < m; ++j) {
-                colors.setXYZ(this.labelVertexMap[i][j], c.r, c.g, c.b);
-            }
-            src += RGB_ELEMENTS_PER_ENTRY;
-        }
-        colors.needsUpdate = true;
-    };
-    ScatterPlotVisualizer3DLabels.prototype.setScene = function (scene) {
-        this.scene = scene;
-    };
-    ScatterPlotVisualizer3DLabels.prototype.dispose = function () {
-        if (this.labelsMesh) {
-            if (this.scene) {
-                this.scene.remove(this.labelsMesh);
-            }
-            this.labelsMesh = null;
-        }
-        if (this.geometry) {
-            this.geometry.dispose();
-            this.geometry = null;
-        }
-        if ((this.glyphTexture != null) && (this.glyphTexture.texture != null)) {
-            this.glyphTexture.texture.dispose();
-            this.glyphTexture.texture = null;
-        }
-    };
-    ScatterPlotVisualizer3DLabels.prototype.onPickingRender = function (rc) {
-        if (this.geometry == null) {
-            this.createLabels();
-        }
-        if (this.geometry == null) {
-            return;
-        }
-        this.material.uniforms.texture.value = this.glyphTexture.texture;
-        this.material.uniforms.picking.value = true;
-        var colors = this.geometry.getAttribute('color');
-        colors.array = this.pickingColors;
-        colors.needsUpdate = true;
-    };
-    ScatterPlotVisualizer3DLabels.prototype.onRender = function (rc) {
-        if (this.geometry == null) {
-            this.createLabels();
-        }
-        if (this.geometry == null) {
-            return;
-        }
-        this.colorLabels(rc.pointColors);
-        this.material.uniforms.texture.value = this.glyphTexture.texture;
-        this.material.uniforms.picking.value = false;
-        var colors = this.geometry.getAttribute('color');
-        colors.array = this.renderColors;
-        colors.needsUpdate = true;
-    };
-    ScatterPlotVisualizer3DLabels.prototype.onPointPositionsChanged = function (newPositions) {
-        this.worldSpacePointPositions = newPositions;
-        this.dispose();
-    };
-    ScatterPlotVisualizer3DLabels.prototype.setLabelStrings = function (labelStrings) {
-        this.labelStrings = labelStrings;
-        this.dispose();
-    };
-    ScatterPlotVisualizer3DLabels.prototype.onResize = function (newWidth, newHeight) { };
-    return ScatterPlotVisualizer3DLabels;
-}());
-exports.ScatterPlotVisualizer3DLabels = ScatterPlotVisualizer3DLabels;
-
-},{"./util":24}],20:[function(require,module,exports){
-/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-"use strict";
-var label_1 = require('./label');
-var renderContext_1 = require('./renderContext');
-var util = require('./util');
-var MAX_LABELS_ON_SCREEN = 10000;
-var LABEL_STROKE_WIDTH = 3;
-var LABEL_FILL_WIDTH = 6;
-/**
- * Creates and maintains a 2d canvas on top of the GL canvas. All labels, when
- * active, are rendered to the 2d canvas as part of the visible render pass.
- */
-var ScatterPlotVisualizerCanvasLabels = (function () {
-    function ScatterPlotVisualizerCanvasLabels(container) {
-        this.labelsActive = true;
-        this.canvas = container.append('canvas').node();
-        this.gc = this.canvas.getContext('2d');
-        d3.select(this.canvas).style({ position: 'absolute', left: 0, top: 0 });
-        this.canvas.style.pointerEvents = 'none';
-    }
-    ScatterPlotVisualizerCanvasLabels.prototype.removeAllLabels = function () {
-        var pixelWidth = this.canvas.width * window.devicePixelRatio;
-        var pixelHeight = this.canvas.height * window.devicePixelRatio;
-        this.gc.clearRect(0, 0, pixelWidth, pixelHeight);
-    };
-    /** Render all of the non-overlapping visible labels to the canvas. */
-    ScatterPlotVisualizerCanvasLabels.prototype.makeLabels = function (rc) {
-        if ((rc.labels == null) || (rc.labels.pointIndices.length === 0)) {
-            return;
-        }
-        if (this.worldSpacePointPositions == null) {
-            return;
-        }
-        var lrc = rc.labels;
-        var sceneIs3D = (rc.cameraType === renderContext_1.CameraType.Perspective);
-        var labelHeight = parseInt(this.gc.font, 10);
-        var dpr = window.devicePixelRatio;
-        var grid;
-        {
-            var pixw = this.canvas.width * dpr;
-            var pixh = this.canvas.height * dpr;
-            var bb = { loX: 0, hiX: pixw, loY: 0, hiY: pixh };
-            grid = new label_1.CollisionGrid(bb, pixw / 25, pixh / 50);
-        }
-        var opacityMap = d3.scale.pow()
-            .exponent(Math.E)
-            .domain([rc.farthestCameraSpacePointZ, rc.nearestCameraSpacePointZ])
-            .range([0.1, 1]);
-        var camPos = rc.camera.position;
-        var camToTarget = camPos.clone().sub(rc.cameraTarget);
-        var camToPoint = new THREE.Vector3();
-        this.gc.textBaseline = 'middle';
-        this.gc.miterLimit = 2;
-        // Have extra space between neighboring labels. Don't pack too tightly.
-        var labelMargin = 2;
-        // Shift the label to the right of the point circle.
-        var xShift = 4;
-        var n = Math.min(MAX_LABELS_ON_SCREEN, lrc.pointIndices.length);
-        for (var i = 0; i < n; ++i) {
-            var point = void 0;
-            {
-                var pi = lrc.pointIndices[i];
-                point = util.vector3FromPackedArray(this.worldSpacePointPositions, pi);
-            }
-            // discard points that are behind the camera
-            camToPoint.copy(camPos).sub(point);
-            if (camToTarget.dot(camToPoint) < 0) {
-                continue;
-            }
-            var _a = util.vector3DToScreenCoords(rc.camera, rc.screenWidth, rc.screenHeight, point), x = _a[0], y = _a[1];
-            x += xShift;
-            // Computing the width of the font is expensive,
-            // so we assume width of 1 at first. Then, if the label doesn't
-            // conflict with other labels, we measure the actual width.
-            var textBoundingBox = {
-                loX: x - labelMargin,
-                hiX: x + 1 + labelMargin,
-                loY: y - labelHeight / 2 - labelMargin,
-                hiY: y + labelHeight / 2 + labelMargin
-            };
-            if (grid.insert(textBoundingBox, true)) {
-                var text = lrc.labelStrings[i];
-                var fontSize = lrc.defaultFontSize * lrc.scaleFactors[i] * dpr;
-                this.gc.font = fontSize + 'px roboto';
-                // Now, check with properly computed width.
-                textBoundingBox.hiX += this.gc.measureText(text).width - 1;
-                if (grid.insert(textBoundingBox)) {
-                    var opacity = 1;
-                    if (sceneIs3D && (lrc.useSceneOpacityFlags[i] === 1)) {
-                        opacity = opacityMap(camToPoint.length());
-                    }
-                    this.gc.fillStyle =
-                        this.styleStringFromPackedRgba(lrc.fillColors, i, opacity);
-                    this.gc.strokeStyle =
-                        this.styleStringFromPackedRgba(lrc.strokeColors, i, opacity);
-                    this.gc.lineWidth = LABEL_STROKE_WIDTH;
-                    this.gc.strokeText(text, x, y);
-                    this.gc.lineWidth = LABEL_FILL_WIDTH;
-                    this.gc.fillText(text, x, y);
-                }
-            }
-        }
-    };
-    ScatterPlotVisualizerCanvasLabels.prototype.styleStringFromPackedRgba = function (packedRgbaArray, colorIndex, opacity) {
-        var offset = colorIndex * 3;
-        var r = packedRgbaArray[offset];
-        var g = packedRgbaArray[offset + 1];
-        var b = packedRgbaArray[offset + 2];
-        return 'rgba(' + r + ',' + g + ',' + b + ',' + opacity + ')';
-    };
-    ScatterPlotVisualizerCanvasLabels.prototype.onResize = function (newWidth, newHeight) {
-        var dpr = window.devicePixelRatio;
-        d3.select(this.canvas)
-            .attr('width', newWidth * dpr)
-            .attr('height', newHeight * dpr)
-            .style({ width: newWidth + 'px', height: newHeight + 'px' });
-    };
-    ScatterPlotVisualizerCanvasLabels.prototype.dispose = function () {
-        this.removeAllLabels();
-        this.canvas = null;
-        this.gc = null;
-    };
-    ScatterPlotVisualizerCanvasLabels.prototype.onPointPositionsChanged = function (newPositions) {
-        this.worldSpacePointPositions = newPositions;
-        this.removeAllLabels();
-    };
-    ScatterPlotVisualizerCanvasLabels.prototype.onRender = function (rc) {
-        if (!this.labelsActive) {
-            return;
-        }
-        this.removeAllLabels();
-        this.makeLabels(rc);
-    };
-    ScatterPlotVisualizerCanvasLabels.prototype.setScene = function (scene) { };
-    ScatterPlotVisualizerCanvasLabels.prototype.onPickingRender = function (renderContext) { };
-    return ScatterPlotVisualizerCanvasLabels;
-}());
-exports.ScatterPlotVisualizerCanvasLabels = ScatterPlotVisualizerCanvasLabels;
-
-},{"./label":11,"./renderContext":15,"./util":24}],21:[function(require,module,exports){
-/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-"use strict";
-var util = require('./util');
-var RGB_NUM_ELEMENTS = 3;
-var XYZ_NUM_ELEMENTS = 3;
-/**
- * Renders polylines that connect multiple points in the dataset.
- */
-var ScatterPlotVisualizerPolylines = (function () {
-    function ScatterPlotVisualizerPolylines() {
-        this.polylinePositionBuffer = {};
-        this.polylineColorBuffer = {};
-    }
-    ScatterPlotVisualizerPolylines.prototype.updateSequenceIndicesInDataSet = function (ds) {
-        for (var i = 0; i < ds.sequences.length; i++) {
-            var sequence = ds.sequences[i];
-            for (var j = 0; j < sequence.pointIndices.length - 1; j++) {
-                ds.points[sequence.pointIndices[j]].sequenceIndex = i;
-                ds.points[sequence.pointIndices[j + 1]].sequenceIndex = i;
-            }
-        }
-    };
-    ScatterPlotVisualizerPolylines.prototype.createPolylines = function (scene) {
-        if (!this.dataSet || !this.dataSet.sequences) {
-            return;
-        }
-        this.updateSequenceIndicesInDataSet(this.dataSet);
-        this.polylines = [];
-        for (var i = 0; i < this.dataSet.sequences.length; i++) {
-            var geometry = new THREE.BufferGeometry();
-            geometry.addAttribute('position', this.polylinePositionBuffer[i]);
-            geometry.addAttribute('color', this.polylineColorBuffer[i]);
-            var material = new THREE.LineBasicMaterial({
-                linewidth: 1,
-                opacity: 1.0,
-                transparent: true,
-                vertexColors: THREE.VertexColors
-            });
-            var polyline = new THREE.LineSegments(geometry, material);
-            polyline.frustumCulled = false;
-            this.polylines.push(polyline);
-            scene.add(polyline);
-        }
-    };
-    ScatterPlotVisualizerPolylines.prototype.dispose = function () {
-        if (this.polylines == null) {
-            return;
-        }
-        for (var i = 0; i < this.polylines.length; i++) {
-            this.scene.remove(this.polylines[i]);
-            this.polylines[i].geometry.dispose();
-        }
-        this.polylines = null;
-        this.polylinePositionBuffer = {};
-        this.polylineColorBuffer = {};
-    };
-    ScatterPlotVisualizerPolylines.prototype.setScene = function (scene) {
-        this.scene = scene;
-    };
-    ScatterPlotVisualizerPolylines.prototype.setDataSet = function (dataSet) {
-        this.dataSet = dataSet;
-    };
-    ScatterPlotVisualizerPolylines.prototype.onPointPositionsChanged = function (newPositions) {
-        if ((newPositions == null) || (this.polylines != null)) {
-            this.dispose();
-        }
-        if ((newPositions == null) || (this.dataSet == null)) {
-            return;
-        }
-        // Set up the position buffer arrays for each polyline.
-        for (var i = 0; i < this.dataSet.sequences.length; i++) {
-            var sequence = this.dataSet.sequences[i];
-            var vertexCount = 2 * (sequence.pointIndices.length - 1);
-            var polylines = new Float32Array(vertexCount * XYZ_NUM_ELEMENTS);
-            this.polylinePositionBuffer[i] =
-                new THREE.BufferAttribute(polylines, XYZ_NUM_ELEMENTS);
-            var colors = new Float32Array(vertexCount * RGB_NUM_ELEMENTS);
-            this.polylineColorBuffer[i] =
-                new THREE.BufferAttribute(colors, RGB_NUM_ELEMENTS);
-        }
-        for (var i = 0; i < this.dataSet.sequences.length; i++) {
-            var sequence = this.dataSet.sequences[i];
-            var src = 0;
-            for (var j = 0; j < sequence.pointIndices.length - 1; j++) {
-                var p1Index = sequence.pointIndices[j];
-                var p2Index = sequence.pointIndices[j + 1];
-                var p1 = util.vector3FromPackedArray(newPositions, p1Index);
-                var p2 = util.vector3FromPackedArray(newPositions, p2Index);
-                this.polylinePositionBuffer[i].setXYZ(src, p1.x, p1.y, p1.z);
-                this.polylinePositionBuffer[i].setXYZ(src + 1, p2.x, p2.y, p2.z);
-                src += 2;
-            }
-            this.polylinePositionBuffer[i].needsUpdate = true;
-        }
-        if (this.polylines == null) {
-            this.createPolylines(this.scene);
-        }
-    };
-    ScatterPlotVisualizerPolylines.prototype.onRender = function (renderContext) {
-        if (this.polylines == null) {
-            return;
-        }
-        for (var i = 0; i < this.polylines.length; i++) {
-            this.polylines[i].material.opacity = renderContext.polylineOpacities[i];
-            this.polylines[i].material.linewidth =
-                renderContext.polylineWidths[i];
-            this.polylineColorBuffer[i].array = renderContext.polylineColors[i];
-            this.polylineColorBuffer[i].needsUpdate = true;
-        }
-    };
-    ScatterPlotVisualizerPolylines.prototype.onPickingRender = function (renderContext) { };
-    ScatterPlotVisualizerPolylines.prototype.onResize = function (newWidth, newHeight) { };
-    return ScatterPlotVisualizerPolylines;
-}());
-exports.ScatterPlotVisualizerPolylines = ScatterPlotVisualizerPolylines;
-
-},{"./util":24}],22:[function(require,module,exports){
-/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-"use strict";
-var renderContext_1 = require('./renderContext');
-var util = require('./util');
-var NUM_POINTS_FOG_THRESHOLD = 5000;
-var MIN_POINT_SIZE = 5.0;
-var IMAGE_SIZE = 30;
-// Constants relating to the indices of buffer arrays.
-var RGB_NUM_ELEMENTS = 3;
-var INDEX_NUM_ELEMENTS = 1;
-var XYZ_NUM_ELEMENTS = 3;
-var VERTEX_SHADER = "\n  // Index of the specific vertex (passed in as bufferAttribute), and the\n  // variable that will be used to pass it to the fragment shader.\n  attribute float spriteIndex;\n  attribute vec3 color;\n  attribute float scaleFactor;\n\n  varying vec2 xyIndex;\n  varying vec3 vColor;\n\n  uniform bool sizeAttenuation;\n  uniform float pointSize;\n  uniform float spritesPerRow;\n  uniform float spritesPerColumn;\n\n  void main() {\n    // Pass index and color values to fragment shader.\n    vColor = color;\n    xyIndex = vec2(mod(spriteIndex, spritesPerRow),\n              floor(spriteIndex / spritesPerColumn));\n\n    // Transform current vertex by modelViewMatrix (model world position and\n    // camera world position matrix).\n    vec4 cameraSpacePos = modelViewMatrix * vec4(position, 1.0);\n\n    // Project vertex in camera-space to screen coordinates using the camera's\n    // projection matrix.\n    gl_Position = projectionMatrix * cameraSpacePos;\n\n    // Create size attenuation (if we're in 3D mode) by making the size of\n    // each point inversly proportional to its distance to the camera.\n    float outputPointSize = pointSize;\n    if (sizeAttenuation) {\n      outputPointSize = -pointSize / cameraSpacePos.z;\n    }\n\n    gl_PointSize =\n      max(outputPointSize * scaleFactor, " + MIN_POINT_SIZE.toFixed(1) + ");\n  }";
-var FRAGMENT_SHADER_POINT_TEST_CHUNK = "\n  bool point_in_unit_circle(vec2 spriteCoord) {\n    vec2 centerToP = spriteCoord - vec2(0.5, 0.5);\n    return dot(centerToP, centerToP) < (0.5 * 0.5);\n  }\n\n  bool point_in_unit_equilateral_triangle(vec2 spriteCoord) {\n    vec3 v0 = vec3(0, 1, 0);\n    vec3 v1 = vec3(0.5, 0, 0);\n    vec3 v2 = vec3(1, 1, 0);\n    vec3 p = vec3(spriteCoord, 0);\n    float p_in_v0_v1 = cross(v1 - v0, p - v0).z;\n    float p_in_v1_v2 = cross(v2 - v1, p - v1).z;\n    return (p_in_v0_v1 > 0.0) && (p_in_v1_v2 > 0.0);\n  }\n\n  bool point_in_unit_square(vec2 spriteCoord) {\n    return true;\n  }\n";
-var FRAGMENT_SHADER = "\n  varying vec2 xyIndex;\n  varying vec3 vColor;\n\n  uniform sampler2D texture;\n  uniform float spritesPerRow;\n  uniform float spritesPerColumn;\n  uniform bool isImage;\n\n  " + THREE.ShaderChunk['common'] + "\n  " + THREE.ShaderChunk['fog_pars_fragment'] + "\n  " + FRAGMENT_SHADER_POINT_TEST_CHUNK + "\n\n  void main() {\n    if (isImage) {\n      // Coordinates of the vertex within the entire sprite image.\n      vec2 coords =\n        (gl_PointCoord + xyIndex) / vec2(spritesPerRow, spritesPerColumn);\n      gl_FragColor = vec4(vColor, 1.0) * texture2D(texture, coords);\n    } else {\n      bool inside = point_in_unit_circle(gl_PointCoord);\n      if (!inside) {\n        discard;\n      }\n      gl_FragColor = vec4(vColor, 1);\n    }\n    " + THREE.ShaderChunk['fog_fragment'] + "\n  }";
-var FRAGMENT_SHADER_PICKING = "\n  varying vec2 xyIndex;\n  varying vec3 vColor;\n  uniform bool isImage;\n\n  " + FRAGMENT_SHADER_POINT_TEST_CHUNK + "\n\n  void main() {\n    xyIndex; // Silence 'unused variable' warning.\n    if (isImage) {\n      gl_FragColor = vec4(vColor, 1);\n    } else {\n      bool inside = point_in_unit_circle(gl_PointCoord);\n      if (!inside) {\n        discard;\n      }\n      gl_FragColor = vec4(vColor, 1);\n    }\n  }";
-/**
- * Uses GL point sprites to render the dataset.
- */
-var ScatterPlotVisualizerSprites = (function () {
-    function ScatterPlotVisualizerSprites() {
-        this.texture = null;
-        this.standinTextureForPoints =
-            util.createTexture(document.createElement('canvas'));
-        this.renderMaterial = this.createRenderMaterial(false);
-        this.pickingMaterial = this.createPickingMaterial(false);
-    }
-    ScatterPlotVisualizerSprites.prototype.createTextureFromSpriteAtlas = function (spriteAtlas, spriteDimensions, spriteIndices) {
-        this.texture = util.createTexture(spriteAtlas);
-        this.spritesPerRow = spriteAtlas.width / spriteDimensions[0];
-        this.spritesPerColumn = spriteAtlas.height / spriteDimensions[1];
-        this.spriteDimensions = spriteDimensions;
-        this.spriteIndexBufferAttribute =
-            new THREE.BufferAttribute(spriteIndices, INDEX_NUM_ELEMENTS);
-        if (this.points != null) {
-            this.points.geometry
-                .addAttribute('spriteIndex', this.spriteIndexBufferAttribute);
-        }
-    };
-    ScatterPlotVisualizerSprites.prototype.createUniforms = function () {
-        return {
-            texture: { type: 't' },
-            spritesPerRow: { type: 'f' },
-            spritesPerColumn: { type: 'f' },
-            fogColor: { type: 'c' },
-            fogNear: { type: 'f' },
-            fogFar: { type: 'f' },
-            isImage: { type: 'bool' },
-            sizeAttenuation: { type: 'bool' },
-            pointSize: { type: 'f' }
-        };
-    };
-    ScatterPlotVisualizerSprites.prototype.createRenderMaterial = function (haveImage) {
-        var uniforms = this.createUniforms();
-        return new THREE.ShaderMaterial({
-            uniforms: uniforms,
-            vertexShader: VERTEX_SHADER,
-            fragmentShader: FRAGMENT_SHADER,
-            transparent: !haveImage,
-            depthTest: haveImage,
-            depthWrite: haveImage,
-            fog: true,
-            blending: THREE.MultiplyBlending,
-        });
-    };
-    ScatterPlotVisualizerSprites.prototype.createPickingMaterial = function (haveImage) {
-        var uniforms = this.createUniforms();
-        return new THREE.ShaderMaterial({
-            uniforms: uniforms,
-            vertexShader: VERTEX_SHADER,
-            fragmentShader: FRAGMENT_SHADER_PICKING,
-            transparent: true,
-            depthTest: true,
-            depthWrite: true,
-            fog: false,
-            blending: THREE.NormalBlending,
-        });
-    };
-    /**
-     * Create points, set their locations and actually instantiate the
-     * geometry.
-     */
-    ScatterPlotVisualizerSprites.prototype.createPointSprites = function (scene, positions) {
-        var pointCount = (positions != null) ? (positions.length / XYZ_NUM_ELEMENTS) : 0;
-        var geometry = this.createGeometry(pointCount);
-        this.fog = new THREE.Fog(0xFFFFFF); // unused value, gets overwritten.
-        this.points = new THREE.Points(geometry, this.renderMaterial);
-        this.points.frustumCulled = false;
-        if (this.spriteIndexBufferAttribute != null) {
-            this.points.geometry
-                .addAttribute('spriteIndex', this.spriteIndexBufferAttribute);
-        }
-        scene.add(this.points);
-    };
-    ScatterPlotVisualizerSprites.prototype.calculatePointSize = function (sceneIs3D) {
-        if (this.texture != null) {
-            return sceneIs3D ? IMAGE_SIZE : this.spriteDimensions[0];
-        }
-        var n = (this.worldSpacePointPositions != null) ?
-            (this.worldSpacePointPositions.length / XYZ_NUM_ELEMENTS) :
-            1;
-        var SCALE = 200;
-        var LOG_BASE = 8;
-        var DIVISOR = 1.5;
-        // Scale point size inverse-logarithmically to the number of points.
-        var pointSize = SCALE / Math.log(n) / Math.log(LOG_BASE);
-        return sceneIs3D ? pointSize : (pointSize / DIVISOR);
-    };
-    /**
-     * Set up buffer attributes to be used for the points/images.
-     */
-    ScatterPlotVisualizerSprites.prototype.createGeometry = function (pointCount) {
-        var n = pointCount;
-        // Fill pickingColors with each point's unique id as its color.
-        this.pickingColors = new Float32Array(n * RGB_NUM_ELEMENTS);
-        {
-            var dst = 0;
-            for (var i = 0; i < n; i++) {
-                var c = new THREE.Color(i);
-                this.pickingColors[dst++] = c.r;
-                this.pickingColors[dst++] = c.g;
-                this.pickingColors[dst++] = c.b;
-            }
-        }
-        var geometry = new THREE.BufferGeometry();
-        geometry.addAttribute('position', new THREE.BufferAttribute(null, XYZ_NUM_ELEMENTS));
-        geometry.addAttribute('color', new THREE.BufferAttribute(null, RGB_NUM_ELEMENTS));
-        geometry.addAttribute('scaleFactor', new THREE.BufferAttribute(null, INDEX_NUM_ELEMENTS));
-        return geometry;
-    };
-    ScatterPlotVisualizerSprites.prototype.setFogDistances = function (sceneIs3D, nearestPointZ, farthestPointZ) {
-        if (sceneIs3D) {
-            var n = this.worldSpacePointPositions.length / XYZ_NUM_ELEMENTS;
-            this.fog.near = nearestPointZ;
-            // If there are fewer points we want less fog. We do this
-            // by making the "far" value (that is, the distance from the camera to the
-            // far edge of the fog) proportional to the number of points.
-            var multiplier = 2 - Math.min(n, NUM_POINTS_FOG_THRESHOLD) / NUM_POINTS_FOG_THRESHOLD;
-            this.fog.far = farthestPointZ * multiplier;
-        }
-        else {
-            this.fog.near = Infinity;
-            this.fog.far = Infinity;
-        }
-    };
-    ScatterPlotVisualizerSprites.prototype.dispose = function () {
-        this.disposeGeometry();
-        this.disposeTextureAtlas();
-    };
-    ScatterPlotVisualizerSprites.prototype.disposeGeometry = function () {
-        if (this.points != null) {
-            this.scene.remove(this.points);
-            this.points.geometry.dispose();
-            this.points = null;
-            this.worldSpacePointPositions = null;
-        }
-    };
-    ScatterPlotVisualizerSprites.prototype.disposeTextureAtlas = function () {
-        if (this.texture != null) {
-            this.texture.dispose();
-        }
-        this.texture = null;
-        this.renderMaterial = null;
-        this.pickingMaterial = null;
-    };
-    ScatterPlotVisualizerSprites.prototype.setScene = function (scene) {
-        this.scene = scene;
-    };
-    ScatterPlotVisualizerSprites.prototype.setSpriteAtlas = function (spriteImage, spriteDimensions, spriteIndices) {
-        this.disposeTextureAtlas();
-        this.createTextureFromSpriteAtlas(spriteImage, spriteDimensions, spriteIndices);
-        this.renderMaterial = this.createRenderMaterial(true);
-        this.pickingMaterial = this.createPickingMaterial(true);
-    };
-    ScatterPlotVisualizerSprites.prototype.clearSpriteAtlas = function () {
-        this.disposeTextureAtlas();
-        this.renderMaterial = this.createRenderMaterial(false);
-        this.pickingMaterial = this.createPickingMaterial(false);
-    };
-    ScatterPlotVisualizerSprites.prototype.onPointPositionsChanged = function (newPositions) {
-        if ((newPositions == null) || (newPositions.length === 0)) {
-            this.dispose();
-            return;
-        }
-        if (this.points != null) {
-            if (this.worldSpacePointPositions.length !== newPositions.length) {
-                this.disposeGeometry();
-            }
-        }
-        this.worldSpacePointPositions = newPositions;
-        if (this.points == null) {
-            this.createPointSprites(this.scene, newPositions);
-        }
-        var positions = this.points.geometry
-            .getAttribute('position');
-        positions.array = newPositions;
-        positions.needsUpdate = true;
-    };
-    ScatterPlotVisualizerSprites.prototype.onPickingRender = function (rc) {
-        if (this.points == null) {
-            return;
-        }
-        var sceneIs3D = (rc.cameraType === renderContext_1.CameraType.Perspective);
-        this.pickingMaterial.uniforms.spritesPerRow.value = this.spritesPerRow;
-        this.pickingMaterial.uniforms.spritesPerRow.value = this.spritesPerColumn;
-        this.pickingMaterial.uniforms.sizeAttenuation.value = sceneIs3D;
-        this.pickingMaterial.uniforms.pointSize.value =
-            this.calculatePointSize(sceneIs3D);
-        this.points.material = this.pickingMaterial;
-        var colors = this.points.geometry
-            .getAttribute('color');
-        colors.array = this.pickingColors;
-        colors.needsUpdate = true;
-        var scaleFactors = this.points.geometry
-            .getAttribute('scaleFactor');
-        scaleFactors.array = rc.pointScaleFactors;
-        scaleFactors.needsUpdate = true;
-    };
-    ScatterPlotVisualizerSprites.prototype.onRender = function (rc) {
-        if (!this.points) {
-            return;
-        }
-        var sceneIs3D = (rc.camera instanceof THREE.PerspectiveCamera);
-        this.setFogDistances(sceneIs3D, rc.nearestCameraSpacePointZ, rc.farthestCameraSpacePointZ);
-        this.scene.fog = this.fog;
-        this.scene.fog.color = new THREE.Color(rc.backgroundColor);
-        this.renderMaterial.uniforms.fogColor.value = this.scene.fog.color;
-        this.renderMaterial.uniforms.fogNear.value = this.fog.near;
-        this.renderMaterial.uniforms.fogFar.value = this.fog.far;
-        this.renderMaterial.uniforms.spritesPerRow.value = this.spritesPerRow;
-        this.renderMaterial.uniforms.spritesPerColumn.value = this.spritesPerColumn;
-        this.renderMaterial.uniforms.isImage.value = (this.texture != null);
-        this.renderMaterial.uniforms.texture.value =
-            (this.texture != null) ? this.texture : this.standinTextureForPoints;
-        this.renderMaterial.uniforms.sizeAttenuation.value = sceneIs3D;
-        this.renderMaterial.uniforms.pointSize.value =
-            this.calculatePointSize(sceneIs3D);
-        this.points.material = this.renderMaterial;
-        var colors = this.points.geometry
-            .getAttribute('color');
-        this.renderColors = rc.pointColors;
-        colors.array = this.renderColors;
-        colors.needsUpdate = true;
-        var scaleFactors = this.points.geometry
-            .getAttribute('scaleFactor');
-        scaleFactors.array = rc.pointScaleFactors;
-        scaleFactors.needsUpdate = true;
-    };
-    ScatterPlotVisualizerSprites.prototype.onResize = function (newWidth, newHeight) { };
-    return ScatterPlotVisualizerSprites;
-}());
-exports.ScatterPlotVisualizerSprites = ScatterPlotVisualizerSprites;
-
-},{"./renderContext":15,"./util":24}],23:[function(require,module,exports){
-/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-"use strict";
-/**
- * A Space-partitioning tree (https://en.wikipedia.org/wiki/Space_partitioning)
- * that recursively divides the space into regions of equal sizes. This data
- * structure can act both as a Quad tree and an Octree when the data is 2 or
- * 3 dimensional respectively. One usage is in t-SNE in order to do Barnes-Hut
- * approximation.
- */
-var SPTree = (function () {
-    /**
-     * Constructs a new tree with the provided data.
-     *
-     * @param data List of n-dimensional data points.
-     * @param capacity Number of data points to store in a single node.
-     */
-    function SPTree(data) {
-        if (data.length < 1) {
-            throw new Error('There should be at least 1 data point');
-        }
-        // Make a bounding box based on the extent of the data.
-        this.dim = data[0].length;
-        // Each node has 2^d children, where d is the dimension of the space.
-        // Binary masks (e.g. 000, 001, ... 111 in 3D) are used to determine in
-        // which child (e.g. quadron in 2D) the new point is going to be assigned.
-        // For more details, see the insert() method and its comments.
-        this.masks = new Array(Math.pow(2, this.dim));
-        for (var d = 0; d < this.masks.length; ++d) {
-            this.masks[d] = (1 << d);
-        }
-        var min = new Array(this.dim);
-        fillArray(min, Number.POSITIVE_INFINITY);
-        var max = new Array(this.dim);
-        fillArray(max, Number.NEGATIVE_INFINITY);
-        for (var i = 0; i < data.length; ++i) {
-            // For each dim get the min and max.
-            // E.g. For 2-D, get the x_min, x_max, y_min, y_max.
-            for (var d = 0; d < this.dim; ++d) {
-                min[d] = Math.min(min[d], data[i][d]);
-                max[d] = Math.max(max[d], data[i][d]);
-            }
-        }
-        // Create a bounding box with the center of the largest span.
-        var center = new Array(this.dim);
-        var halfDim = 0;
-        for (var d = 0; d < this.dim; ++d) {
-            var span = max[d] - min[d];
-            center[d] = min[d] + span / 2;
-            halfDim = Math.max(halfDim, span / 2);
-        }
-        this.root = { box: { center: center, halfDim: halfDim }, point: data[0] };
-        for (var i = 1; i < data.length; ++i) {
-            this.insert(this.root, data[i]);
-        }
-    }
-    /**
-     * Visits every node in the tree. Each node can store 1 or more points,
-     * depending on the node capacity provided in the constructor.
-     *
-     * @param accessor Method that takes the currently visited node, and the
-     * low and high point of the region that this node occupies. E.g. in 2D,
-     * the low and high points will be the lower-left corner and the upper-right
-     * corner.
-     */
-    SPTree.prototype.visit = function (accessor, noBox) {
-        if (noBox === void 0) { noBox = false; }
-        this.visitNode(this.root, accessor, noBox);
-    };
-    SPTree.prototype.visitNode = function (node, accessor, noBox) {
-        var skipChildren;
-        if (noBox) {
-            skipChildren = accessor(node);
-        }
-        else {
-            var lowPoint = new Array(this.dim);
-            var highPoint = new Array(this.dim);
-            for (var d = 0; d < this.dim; ++d) {
-                lowPoint[d] = node.box.center[d] - node.box.halfDim;
-                highPoint[d] = node.box.center[d] + node.box.halfDim;
-            }
-            skipChildren = accessor(node, lowPoint, highPoint);
-        }
-        if (!node.children || skipChildren) {
-            return;
-        }
-        for (var i = 0; i < node.children.length; ++i) {
-            var child = node.children[i];
-            if (child) {
-                this.visitNode(child, accessor, noBox);
-            }
-        }
-    };
-    SPTree.prototype.insert = function (node, p) {
-        // Subdivide and then add the point to whichever node will accept it.
-        if (node.children == null) {
-            node.children = new Array(this.masks.length);
-        }
-        // Decide which child will get the new point by constructing a D-bits binary
-        // signature (D=3 for 3D) where the k-th bit is 1 if the point's k-th
-        // coordinate is greater than the node's k-th coordinate, 0 otherwise.
-        // Then the binary signature in decimal system gives us the index of the
-        // child where the new point should be.
-        var index = 0;
-        for (var d = 0; d < this.dim; ++d) {
-            if (p[d] > node.box.center[d]) {
-                index |= this.masks[d];
-            }
-        }
-        if (node.children[index] == null) {
-            this.makeChild(node, index, p);
-        }
-        else {
-            this.insert(node.children[index], p);
-        }
-    };
-    SPTree.prototype.makeChild = function (node, index, p) {
-        var oldC = node.box.center;
-        var h = node.box.halfDim / 2;
-        var newC = new Array(this.dim);
-        for (var d = 0; d < this.dim; ++d) {
-            newC[d] = (index & (1 << d)) ? oldC[d] + h : oldC[d] - h;
-        }
-        node.children[index] = { box: { center: newC, halfDim: h }, point: p };
-    };
-    return SPTree;
-}());
-exports.SPTree = SPTree;
-function fillArray(arr, value) {
-    for (var i = 0; i < arr.length; ++i) {
-        arr[i] = value;
-    }
-}
-
-},{}],24:[function(require,module,exports){
-/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-"use strict";
-var logging = require('./logging');
-/**
- * Delay for running expensive tasks, in milliseconds.
- * The duration was empirically found so that it leaves enough time for the
- * browser to update its UI state before starting an expensive UI-blocking task.
- */
-var TASK_DELAY_MS = 200;
-/** Shuffles the array in-place in O(n) time using Fisher-Yates algorithm. */
-function shuffle(array) {
-    var m = array.length;
-    var t;
-    var i;
-    // While there remain elements to shuffle.
-    while (m) {
-        // Pick a remaining element
-        i = Math.floor(Math.random() * m--);
-        // And swap it with the current element.
-        t = array[m];
-        array[m] = array[i];
-        array[i] = t;
-    }
-    return array;
-}
-exports.shuffle = shuffle;
-/** Projects a 3d point into screen space */
-function vector3DToScreenCoords(cam, w, h, v) {
-    var dpr = window.devicePixelRatio;
-    var pv = new THREE.Vector3().copy(v).project(cam);
-    // The screen-space origin is at the middle of the screen, with +y up.
-    var coords = [((pv.x + 1) / 2 * w) * dpr, -((pv.y - 1) / 2 * h) * dpr];
-    return coords;
-}
-exports.vector3DToScreenCoords = vector3DToScreenCoords;
-/** Loads 3 contiguous elements from a packed xyz array into a Vector3. */
-function vector3FromPackedArray(a, pointIndex) {
-    var offset = pointIndex * 3;
-    return new THREE.Vector3(a[offset], a[offset + 1], a[offset + 2]);
-}
-exports.vector3FromPackedArray = vector3FromPackedArray;
-/**
- * Gets the camera-space z coordinates of the nearest and farthest points.
- * Ignores points that are behind the camera.
- */
-function getNearFarPoints(worldSpacePoints, cameraPos, cameraTarget) {
-    var shortestDist = Infinity;
-    var furthestDist = 0;
-    var camToTarget = new THREE.Vector3().copy(cameraTarget).sub(cameraPos);
-    var camPlaneNormal = new THREE.Vector3().copy(camToTarget).normalize();
-    var n = worldSpacePoints.length / 3;
-    var src = 0;
-    var p = new THREE.Vector3();
-    var camToPoint = new THREE.Vector3();
-    for (var i = 0; i < n; i++) {
-        p.x = worldSpacePoints[src];
-        p.y = worldSpacePoints[src + 1];
-        p.z = worldSpacePoints[src + 2];
-        src += 3;
-        camToPoint.copy(p).sub(cameraPos);
-        var dist = camPlaneNormal.dot(camToPoint);
-        if (dist < 0) {
-            continue;
-        }
-        furthestDist = (dist > furthestDist) ? dist : furthestDist;
-        shortestDist = (dist < shortestDist) ? dist : shortestDist;
-    }
-    return [shortestDist, furthestDist];
-}
-exports.getNearFarPoints = getNearFarPoints;
-/**
- * Generate a texture for the points/images and sets some initial params
- */
-function createTexture(image) {
-    var tex = new THREE.Texture(image);
-    tex.needsUpdate = true;
-    // Used if the texture isn't a power of 2.
-    tex.minFilter = THREE.LinearFilter;
-    tex.generateMipmaps = false;
-    tex.flipY = false;
-    return tex;
-}
-exports.createTexture = createTexture;
-/**
- * Assert that the condition is satisfied; if not, log user-specified message
- * to the console.
- */
-function assert(condition, message) {
-    if (!condition) {
-        message = message || 'Assertion failed';
-        throw new Error(message);
-    }
-}
-exports.assert = assert;
-function getSearchPredicate(query, inRegexMode, fieldName) {
-    var predicate;
-    if (inRegexMode) {
-        var regExp_1 = new RegExp(query, 'i');
-        predicate = function (p) { return regExp_1.test(p.metadata[fieldName].toString()); };
-    }
-    else {
-        // Doing a case insensitive substring match.
-        query = query.toLowerCase();
-        predicate = function (p) {
-            var label = p.metadata[fieldName].toString().toLowerCase();
-            return label.indexOf(query) >= 0;
-        };
-    }
-    return predicate;
-}
-exports.getSearchPredicate = getSearchPredicate;
-/**
- * Runs an expensive task asynchronously with some delay
- * so that it doesn't block the UI thread immediately.
- *
- * @param message The message to display to the user.
- * @param task The expensive task to run.
- * @param msgId Optional. ID of an existing message. If provided, will overwrite
- *     an existing message and won't automatically clear the message when the
- *     task is done.
- * @return The value returned by the task.
- */
-function runAsyncTask(message, task, msgId) {
-    if (msgId === void 0) { msgId = null; }
-    var autoClear = (msgId == null);
-    msgId = logging.setModalMessage(message, msgId);
-    return new Promise(function (resolve, reject) {
-        d3.timer(function () {
-            try {
-                var result = task();
-                // Clearing the old message.
-                if (autoClear) {
-                    logging.setModalMessage(null, msgId);
-                }
-                resolve(result);
-            }
-            catch (ex) {
-                reject(ex);
-            }
-            return true;
-        }, TASK_DELAY_MS);
-    });
-}
-exports.runAsyncTask = runAsyncTask;
-/**
- * Parses the URL for query parameters, e.g. ?foo=1&bar=2 will return
- *   {'foo': '1', 'bar': '2'}.
- * @param url The URL to parse.
- * @return A map of queryParam key to its value.
- */
-function getURLParams(url) {
-    if (!url) {
-        return {};
-    }
-    var queryString = url.indexOf('?') !== -1 ? url.split('?')[1] : url;
-    if (queryString.indexOf('#')) {
-        queryString = queryString.split('#')[0];
-    }
-    var queryEntries = queryString.split('&');
-    var queryParams = {};
-    for (var i = 0; i < queryEntries.length; i++) {
-        var queryEntryComponents = queryEntries[i].split('=');
-        queryParams[queryEntryComponents[0].toLowerCase()] =
-            decodeURIComponent(queryEntryComponents[1]);
-    }
-    return queryParams;
-}
-exports.getURLParams = getURLParams;
-/** List of substrings that auto generated tensors have in their name. */
-var SUBSTR_GEN_TENSORS = ['/Adagrad'];
-/** Returns true if the tensor was automatically generated by TF API calls. */
-function tensorIsGenerated(tensorName) {
-    for (var i = 0; i < SUBSTR_GEN_TENSORS.length; i++) {
-        if (tensorName.indexOf(SUBSTR_GEN_TENSORS[i]) >= 0) {
-            return true;
-        }
-    }
-    return false;
-}
-exports.tensorIsGenerated = tensorIsGenerated;
-function xor(cond1, cond2) {
-    return (cond1 || cond2) && !(cond1 && cond2);
-}
-exports.xor = xor;
-/** Checks to see if the browser supports webgl. */
-function hasWebGLSupport() {
-    try {
-        var c = document.createElement('canvas');
-        var gl = c.getContext('webgl') || c.getContext('experimental-webgl');
-        return gl != null && typeof weblas !== 'undefined';
-    }
-    catch (e) {
-        return false;
-    }
-}
-exports.hasWebGLSupport = hasWebGLSupport;
-
-},{"./logging":12}],25:[function(require,module,exports){
-/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-"use strict";
-var util_1 = require('./util');
-/** Returns the dot product of two vectors. */
-function dot(a, b) {
-    util_1.assert(a.length === b.length, 'Vectors a and b must be of same length');
-    var result = 0;
-    for (var i = 0; i < a.length; ++i) {
-        result += a[i] * b[i];
-    }
-    return result;
-}
-exports.dot = dot;
-/** Sums all the elements in the vector */
-function sum(a) {
-    var result = 0;
-    for (var i = 0; i < a.length; ++i) {
-        result += a[i];
-    }
-    return result;
-}
-exports.sum = sum;
-/** Returns the sum of two vectors, i.e. a + b */
-function add(a, b) {
-    util_1.assert(a.length === b.length, 'Vectors a and b must be of same length');
-    var result = new Float32Array(a.length);
-    for (var i = 0; i < a.length; ++i) {
-        result[i] = a[i] + b[i];
-    }
-    return result;
-}
-exports.add = add;
-/** Subtracts vector b from vector a, i.e. returns a - b */
-function sub(a, b) {
-    util_1.assert(a.length === b.length, 'Vectors a and b must be of same length');
-    var result = new Float32Array(a.length);
-    for (var i = 0; i < a.length; ++i) {
-        result[i] = a[i] - b[i];
-    }
-    return result;
-}
-exports.sub = sub;
-/** Returns the square norm of the vector */
-function norm2(a) {
-    var result = 0;
-    for (var i = 0; i < a.length; ++i) {
-        result += a[i] * a[i];
-    }
-    return result;
-}
-exports.norm2 = norm2;
-/** Returns the euclidean distance between two vectors. */
-function dist(a, b) {
-    return Math.sqrt(dist2(a, b));
-}
-exports.dist = dist;
-/** Returns the square euclidean distance between two vectors. */
-function dist2(a, b) {
-    util_1.assert(a.length === b.length, 'Vectors a and b must be of same length');
-    var result = 0;
-    for (var i = 0; i < a.length; ++i) {
-        var diff = a[i] - b[i];
-        result += diff * diff;
-    }
-    return result;
-}
-exports.dist2 = dist2;
-/** Returns the square euclidean distance between two 2D points. */
-function dist2_2D(a, b) {
-    var dX = a[0] - b[0];
-    var dY = a[1] - b[1];
-    return dX * dX + dY * dY;
-}
-exports.dist2_2D = dist2_2D;
-/** Returns the square euclidean distance between two 3D points. */
-function dist2_3D(a, b) {
-    var dX = a[0] - b[0];
-    var dY = a[1] - b[1];
-    var dZ = a[2] - b[2];
-    return dX * dX + dY * dY + dZ * dZ;
-}
-exports.dist2_3D = dist2_3D;
-/** Returns the euclidean distance between 2 3D points. */
-function dist_3D(a, b) {
-    return Math.sqrt(dist2_3D(a, b));
-}
-exports.dist_3D = dist_3D;
-/**
- * Returns the square euclidean distance between two vectors, with an early
- * exit (returns -1) if the distance is >= to the provided limit.
- */
-function dist2WithLimit(a, b, limit) {
-    util_1.assert(a.length === b.length, 'Vectors a and b must be of same length');
-    var result = 0;
-    for (var i = 0; i < a.length; ++i) {
-        var diff = a[i] - b[i];
-        result += diff * diff;
-        if (result >= limit) {
-            return -1;
-        }
-    }
-    return result;
-}
-exports.dist2WithLimit = dist2WithLimit;
-/** Returns the square euclidean distance between two 2D points. */
-function dist22D(a, b) {
-    var dX = a[0] - b[0];
-    var dY = a[1] - b[1];
-    return dX * dX + dY * dY;
-}
-exports.dist22D = dist22D;
-/** Modifies the vector in-place to have unit norm. */
-function unit(a) {
-    var norm = Math.sqrt(norm2(a));
-    util_1.assert(norm >= 0, 'Norm of the vector must be > 0');
-    for (var i = 0; i < a.length; ++i) {
-        a[i] /= norm;
-    }
-}
-exports.unit = unit;
-/**
- *  Projects the vectors to a lower dimension
- *
- * @param vectors Array of vectors to be projected.
- * @param newDim The resulting dimension of the vectors.
- */
-function projectRandom(vectors, newDim) {
-    var dim = vectors[0].length;
-    var N = vectors.length;
-    var newVectors = new Array(N);
-    for (var i = 0; i < N; ++i) {
-        newVectors[i] = new Float32Array(newDim);
-    }
-    // Make nDim projections.
-    for (var k = 0; k < newDim; ++k) {
-        var randomVector = rn(dim);
-        for (var i = 0; i < N; ++i) {
-            newVectors[i][k] = dot(vectors[i], randomVector);
-        }
-    }
-    return newVectors;
-}
-exports.projectRandom = projectRandom;
-/**
- * Projects a vector onto a 2D plane specified by the two direction vectors.
- */
-function project2d(a, dir1, dir2) {
-    return [dot(a, dir1), dot(a, dir2)];
-}
-exports.project2d = project2d;
-/**
- * Computes the centroid of the data points. If the provided data points are not
- * vectors, an accessor function needs to be provided.
- */
-function centroid(dataPoints, accessor) {
-    if (dataPoints.length === 0) {
-        return null;
-    }
-    if (accessor == null) {
-        accessor = function (a) { return a; };
-    }
-    util_1.assert(dataPoints.length >= 0, '`vectors` must be of length >= 1');
-    var centroid = new Float32Array(accessor(dataPoints[0]).length);
-    for (var i = 0; i < dataPoints.length; ++i) {
-        var dataPoint = dataPoints[i];
-        var vector = accessor(dataPoint);
-        for (var j = 0; j < centroid.length; ++j) {
-            centroid[j] += vector[j];
-        }
-    }
-    for (var j = 0; j < centroid.length; ++j) {
-        centroid[j] /= dataPoints.length;
-    }
-    return centroid;
-}
-exports.centroid = centroid;
-/**
- * Generates a vector of the specified size where each component is drawn from
- * a random (0, 1) gaussian distribution.
- */
-function rn(size) {
-    var normal = d3.random.normal();
-    var result = new Float32Array(size);
-    for (var i = 0; i < size; ++i) {
-        result[i] = normal();
-    }
-    return result;
-}
-exports.rn = rn;
-/**
- * Returns the cosine distance ([0, 2]) between two vectors
- * that have been normalized to unit norm.
- */
-function cosDistNorm(a, b) {
-    return 1 - dot(a, b);
-}
-exports.cosDistNorm = cosDistNorm;
-/**
- * Returns the cosine distance ([0, 2]) between two vectors.
- */
-function cosDist(a, b) {
-    return 1 - cosSim(a, b);
-}
-exports.cosDist = cosDist;
-/** Returns the cosine similarity ([-1, 1]) between two vectors. */
-function cosSim(a, b) {
-    return dot(a, b) / Math.sqrt(norm2(a) * norm2(b));
-}
-exports.cosSim = cosSim;
-/**
- * Converts list of vectors (matrix) into a 1-dimensional
- * typed array with row-first order.
- */
-function toTypedArray(dataPoints, accessor) {
-    var N = dataPoints.length;
-    var dim = accessor(dataPoints[0]).length;
-    var result = new Float32Array(N * dim);
-    for (var i = 0; i < N; ++i) {
-        var vector = accessor(dataPoints[i]);
-        for (var d = 0; d < dim; ++d) {
-            result[i * dim + d] = vector[d];
-        }
-    }
-    return result;
-}
-exports.toTypedArray = toTypedArray;
-/**
- * Transposes an RxC matrix represented as a flat typed array
- * into a CxR matrix, again represented as a flat typed array.
- */
-function transposeTypedArray(r, c, typedArray) {
-    var result = new Float32Array(r * c);
-    for (var i = 0; i < r; ++i) {
-        for (var j = 0; j < c; ++j) {
-            result[j * r + i] = typedArray[i * c + j];
-        }
-    }
-    return result;
-}
-exports.transposeTypedArray = transposeTypedArray;
-
-},{"./util":24}],26:[function(require,module,exports){
-"use strict";
-var __extends = (this && this.__extends) || function (d, b) {
-    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
-    function __() { this.constructor = d; }
-    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
-};
-var logging = require('./logging');
-// tslint:disable-next-line:no-unused-variable
-var vz_projector_util_1 = require('./vz-projector-util');
-// tslint:disable-next-line
-exports.BookmarkPanelPolymer = vz_projector_util_1.PolymerElement({
-    is: 'vz-projector-bookmark-panel',
-    properties: {
-        savedStates: Object,
-        // Keep a separate polymer property because the savedStates doesn't change
-        // when adding and removing states.
-        hasStates: { type: Boolean, value: false },
-        selectedState: Number
-    }
-});
-var BookmarkPanel = (function (_super) {
-    __extends(BookmarkPanel, _super);
-    function BookmarkPanel() {
-        _super.apply(this, arguments);
-        this.hasStates = false;
-    }
-    BookmarkPanel.prototype.ready = function () {
-        this.dom = d3.select(this);
-        this.savedStates = [];
-        this.setupUploadButton();
-        this.ignoreNextProjectionEvent = false;
-    };
-    BookmarkPanel.prototype.initialize = function (projector, projectorEventContext) {
-        var _this = this;
-        this.projector = projector;
-        projectorEventContext.registerProjectionChangedListener(function () {
-            if (_this.ignoreNextProjectionEvent) {
-                _this.ignoreNextProjectionEvent = false;
-            }
-            else {
-                _this.clearStateSelection();
-            }
-        });
-    };
-    BookmarkPanel.prototype.setSelectedTensor = function (run, tensorInfo, dataProvider) {
-        var _this = this;
-        // Clear any existing bookmarks.
-        this.addStates(null);
-        if (tensorInfo && tensorInfo.bookmarksPath) {
-            // Get any bookmarks that may come when the projector starts up.
-            dataProvider.getBookmarks(run, tensorInfo.tensorName, function (bookmarks) {
-                _this.addStates(bookmarks);
-                _this._expandMore();
-            });
-        }
-        else {
-            this._expandLess();
-        }
-    };
-    /** Handles a click on show bookmarks tray button. */
-    BookmarkPanel.prototype._expandMore = function () {
-        this.$.panel.show();
-        this.dom.select('#expand-more').style('display', 'none');
-        this.dom.select('#expand-less').style('display', '');
-    };
-    /** Handles a click on hide bookmarks tray button. */
-    BookmarkPanel.prototype._expandLess = function () {
-        this.$.panel.hide();
-        this.dom.select('#expand-more').style('display', '');
-        this.dom.select('#expand-less').style('display', 'none');
-    };
-    /** Handles a click on the add bookmark button. */
-    BookmarkPanel.prototype._addBookmark = function () {
-        var currentState = this.projector.getCurrentState();
-        currentState.label = 'State ' + this.savedStates.length;
-        currentState.isSelected = true;
-        this.selectedState = this.savedStates.length;
-        for (var i = 0; i < this.savedStates.length; i++) {
-            this.savedStates[i].isSelected = false;
-            // We have to call notifyPath so that polymer knows this element was
-            // updated.
-            this.notifyPath('savedStates.' + i + '.isSelected', false, false);
-        }
-        this.push('savedStates', currentState);
-        this.updateHasStates();
-    };
-    /** Handles a click on the download bookmarks button. */
-    BookmarkPanel.prototype._downloadFile = function () {
-        var serializedState = this.serializeAllSavedStates();
-        var blob = new Blob([serializedState], { type: 'text/plain' });
-        var textFile = window.URL.createObjectURL(blob);
-        // Force a download.
-        var a = document.createElement('a');
-        document.body.appendChild(a);
-        a.style.display = 'none';
-        a.href = textFile;
-        a.download = 'state';
-        a.click();
-        document.body.removeChild(a);
-        window.URL.revokeObjectURL(textFile);
-    };
-    /** Handles a click on the upload bookmarks button. */
-    BookmarkPanel.prototype._uploadFile = function () {
-        var fileInput = this.dom.select('#state-file');
-        fileInput.node().click();
-    };
-    BookmarkPanel.prototype.setupUploadButton = function () {
-        var _this = this;
-        // Show and setup the load view button.
-        var fileInput = this.dom.select('#state-file');
-        fileInput.on('change', function () {
-            var file = d3.event.target.files[0];
-            // Clear out the value of the file chooser. This ensures that if the user
-            // selects the same file, we'll re-read it.
-            d3.event.target.value = '';
-            var fileReader = new FileReader();
-            fileReader.onload = function (evt) {
-                var str = evt.target.result;
-                var savedStates = JSON.parse(str);
-                // Verify the bookmarks match.
-                if (_this.savedStatesValid(savedStates)) {
-                    _this.addStates(savedStates);
-                    _this.loadSavedState(0);
-                }
-                else {
-                    logging.setWarningMessage("Unable to load bookmarks: wrong dataset, expected dataset " +
-                        ("with shape (" + savedStates[0].dataSetDimensions + ")."));
-                }
-            };
-            fileReader.readAsText(file);
-        });
-    };
-    BookmarkPanel.prototype.addStates = function (savedStates) {
-        if (savedStates == null) {
-            this.savedStates = [];
-        }
-        else {
-            for (var i = 0; i < savedStates.length; i++) {
-                savedStates[i].isSelected = false;
-                this.push('savedStates', savedStates[i]);
-            }
-        }
-        this.updateHasStates();
-    };
-    /** Deselects any selected state selection. */
-    BookmarkPanel.prototype.clearStateSelection = function () {
-        for (var i = 0; i < this.savedStates.length; i++) {
-            this.setSelectionState(i, false);
-        }
-    };
-    /** Handles a radio button click on a saved state. */
-    BookmarkPanel.prototype._radioButtonHandler = function (evt) {
-        var index = this.getParentDataIndex(evt);
-        this.loadSavedState(index);
-        this.setSelectionState(index, true);
-    };
-    BookmarkPanel.prototype.loadSavedState = function (index) {
-        for (var i = 0; i < this.savedStates.length; i++) {
-            if (this.savedStates[i].isSelected) {
-                this.setSelectionState(i, false);
-            }
-            else if (index === i) {
-                this.setSelectionState(i, true);
-                this.ignoreNextProjectionEvent = true;
-                this.projector.loadState(this.savedStates[i]);
-            }
-        }
-    };
-    BookmarkPanel.prototype.setSelectionState = function (stateIndex, selected) {
-        this.savedStates[stateIndex].isSelected = selected;
-        var path = 'savedStates.' + stateIndex + '.isSelected';
-        this.notifyPath(path, selected, false);
-    };
-    /**
-     * Crawls up the DOM to find an ancestor with a data-index attribute. This is
-     * used to match events to their bookmark index.
-     */
-    BookmarkPanel.prototype.getParentDataIndex = function (evt) {
-        for (var i = 0; i < evt.path.length; i++) {
-            var dataIndex = evt.path[i].getAttribute('data-index');
-            if (dataIndex != null) {
-                return +dataIndex;
-            }
-        }
-        return -1;
-    };
-    /** Handles a clear button click on a bookmark. */
-    BookmarkPanel.prototype._clearButtonHandler = function (evt) {
-        var index = this.getParentDataIndex(evt);
-        this.splice('savedStates', index, 1);
-        this.updateHasStates();
-    };
-    /** Handles a label change event on a bookmark. */
-    BookmarkPanel.prototype._labelChange = function (evt) {
-        var index = this.getParentDataIndex(evt);
-        this.savedStates[index].label = evt.target.value;
-    };
-    /**
-     * Used to determine whether to select the radio button for a given bookmark.
-     */
-    BookmarkPanel.prototype._isSelectedState = function (index) {
-        return index === this.selectedState;
-    };
-    BookmarkPanel.prototype._isNotSelectedState = function (index) {
-        return index !== this.selectedState;
-    };
-    /**
-     * Gets all of the saved states as a serialized string.
-     */
-    BookmarkPanel.prototype.serializeAllSavedStates = function () {
-        return JSON.stringify(this.savedStates);
-    };
-    /**
-     * Loads all of the serialized states and shows them in the list of
-     * viewable states.
-     */
-    BookmarkPanel.prototype.loadSavedStates = function (serializedStates) {
-        this.savedStates = JSON.parse(serializedStates);
-        this.updateHasStates();
-    };
-    /**
-     * Updates the hasState polymer property.
-     */
-    BookmarkPanel.prototype.updateHasStates = function () {
-        this.hasStates = (this.savedStates.length !== 0);
-    };
-    /** Sanity checks a State array to ensure it matches the current dataset. */
-    BookmarkPanel.prototype.savedStatesValid = function (states) {
-        for (var i = 0; i < states.length; i++) {
-            if (states[i].dataSetDimensions[0] !== this.projector.dataSet.dim[0] ||
-                states[i].dataSetDimensions[1] !== this.projector.dataSet.dim[1]) {
-                return false;
-            }
-        }
-        return true;
-    };
-    return BookmarkPanel;
-}(exports.BookmarkPanelPolymer));
-exports.BookmarkPanel = BookmarkPanel;
-document.registerElement(BookmarkPanel.prototype.is, BookmarkPanel);
-
-},{"./logging":12,"./vz-projector-util":33}],27:[function(require,module,exports){
-/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-"use strict";
-var __extends = (this && this.__extends) || function (d, b) {
-    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
-    function __() { this.constructor = d; }
-    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
-};
-var data_provider_1 = require('./data-provider');
-var util = require('./util');
-// tslint:disable-next-line:no-unused-variable
-var vz_projector_util_1 = require('./vz-projector-util');
-exports.DataPanelPolymer = vz_projector_util_1.PolymerElement({
-    is: 'vz-projector-data-panel',
-    properties: {
-        selectedTensor: { type: String, observer: '_selectedTensorChanged' },
-        selectedRun: { type: String, observer: '_selectedRunChanged' },
-        selectedColorOptionName: {
-            type: String,
-            notify: true,
-            observer: '_selectedColorOptionNameChanged'
-        },
-        selectedLabelOption: { type: String, notify: true, observer: '_selectedLabelOptionChanged' },
-        normalizeData: Boolean,
-        showForceCategoricalColorsCheckbox: Boolean
-    }
-});
-var DataPanel = (function (_super) {
-    __extends(DataPanel, _super);
-    function DataPanel() {
-        _super.apply(this, arguments);
-        this.forceCategoricalColoring = false;
-    }
-    DataPanel.prototype.ready = function () {
-        this.dom = d3.select(this);
-        this.normalizeData = true;
-    };
-    DataPanel.prototype.initialize = function (projector, dp) {
-        var _this = this;
-        this.projector = projector;
-        this.dataProvider = dp;
-        this.setupUploadButtons();
-        // Tell the projector whenever the data normalization changes.
-        // Unknown why, but the polymer checkbox button stops working as soon as
-        // you do d3.select() on it.
-        this.querySelector('#normalize-data-checkbox')
-            .addEventListener('change', function () {
-            _this.projector.setNormalizeData(_this.normalizeData);
-        });
-        var forceCategoricalColoringCheckbox = this.querySelector('#force-categorical-checkbox');
-        forceCategoricalColoringCheckbox.addEventListener('change', function () {
-            _this.setForceCategoricalColoring(forceCategoricalColoringCheckbox.checked);
-        });
-        // Get all the runs.
-        this.dataProvider.retrieveRuns(function (runs) {
-            _this.runNames = runs;
-            // Choose the first run by default.
-            if (_this.runNames.length > 0) {
-                _this.selectedRun = runs[0];
-            }
-        });
-    };
-    DataPanel.prototype.setForceCategoricalColoring = function (forceCategoricalColoring) {
-        this.forceCategoricalColoring = forceCategoricalColoring;
-        this.querySelector('#force-categorical-checkbox')
-            .checked = this.forceCategoricalColoring;
-        this.updateMetadataUI(this.spriteAndMetadata.stats, this.metadataFile);
-        // The selected color option name doesn't change when we switch to using
-        // categorical coloring for stats with too many unique values, so we
-        // manually call this polymer observer so that we update the UI.
-        this._selectedColorOptionNameChanged();
-    };
-    DataPanel.prototype.getSeparatorClass = function (isSeparator) {
-        return isSeparator ? 'separator' : null;
-    };
-    DataPanel.prototype.metadataChanged = function (spriteAndMetadata, metadataFile) {
-        this.spriteAndMetadata = spriteAndMetadata;
-        this.metadataFile = metadataFile;
-        this.updateMetadataUI(this.spriteAndMetadata.stats, this.metadataFile);
-        this.selectedColorOptionName = this.colorOptions[0].name;
-    };
-    DataPanel.prototype.addWordBreaks = function (longString) {
-        if (longString == null) {
-            return '';
-        }
-        return longString.replace(/([\/=-_,])/g, '$1<wbr>');
-    };
-    DataPanel.prototype.updateMetadataUI = function (columnStats, metadataFile) {
-        var _this = this;
-        this.dom.select('#metadata-file')
-            .html(this.addWordBreaks(metadataFile))
-            .attr('title', metadataFile);
-        // Label by options.
-        var labelIndex = -1;
-        this.labelOptions = columnStats.map(function (stats, i) {
-            // Make the default label by the first non-numeric column.
-            if (!stats.isNumeric && labelIndex === -1) {
-                labelIndex = i;
-            }
-            return stats.name;
-        });
-        this.selectedLabelOption = this.labelOptions[Math.max(0, labelIndex)];
-        // Color by options.
-        var standardColorOption = [
-            { name: 'No color map' },
-        ];
-        var metadataColorOption = columnStats
-            .filter(function (stats) {
-            return !stats.tooManyUniqueValues || stats.isNumeric;
-        })
-            .map(function (stats) {
-            var map;
-            var items;
-            var thresholds;
-            var isCategorical = _this.forceCategoricalColoring || !stats.tooManyUniqueValues;
-            if (isCategorical) {
-                var scale = d3.scale.category20();
-                var range_1 = scale.range();
-                // Re-order the range.
-                var newRange = range_1.map(function (color, i) {
-                    var index = (i * 3) % range_1.length;
-                    return range_1[index];
-                });
-                items = stats.uniqueEntries;
-                scale.range(newRange).domain(items.map(function (x) { return x.label; }));
-                map = scale;
-            }
-            else {
-                thresholds = [
-                    { color: '#ffffdd', value: stats.min },
-                    { color: '#1f2d86', value: stats.max }
-                ];
-                map = d3.scale.linear()
-                    .domain(thresholds.map(function (t) { return t.value; }))
-                    .range(thresholds.map(function (t) { return t.color; }));
-            }
-            var desc = !isCategorical ? 'gradient' :
-                stats.uniqueEntries.length +
-                    ((stats.uniqueEntries.length > 20) ? ' non-unique' : '') +
-                    ' colors';
-            return {
-                name: stats.name,
-                desc: desc,
-                map: map,
-                items: items,
-                thresholds: thresholds,
-                tooManyUniqueValues: stats.tooManyUniqueValues
-            };
-        });
-        if (metadataColorOption.length > 0) {
-            // Add a separator line between built-in color maps
-            // and those based on metadata columns.
-            standardColorOption.push({ name: 'Metadata', isSeparator: true });
-        }
-        this.colorOptions = standardColorOption.concat(metadataColorOption);
-    };
-    DataPanel.prototype.setNormalizeData = function (normalizeData) {
-        this.normalizeData = normalizeData;
-    };
-    DataPanel.prototype._selectedTensorChanged = function () {
-        var _this = this;
-        this.projector.updateDataSet(null, null, null);
-        if (this.selectedTensor == null) {
-            return;
-        }
-        this.dataProvider.retrieveTensor(this.selectedRun, this.selectedTensor, function (ds) {
-            var metadataFile = _this.getEmbeddingInfoByName(_this.selectedTensor).metadataPath;
-            _this.dataProvider.retrieveSpriteAndMetadata(_this.selectedRun, _this.selectedTensor, function (metadata) {
-                _this.projector.updateDataSet(ds, metadata, metadataFile);
-            });
-        });
-        this.projector.setSelectedTensor(this.selectedRun, this.getEmbeddingInfoByName(this.selectedTensor));
-    };
-    DataPanel.prototype._selectedRunChanged = function () {
-        var _this = this;
-        this.dataProvider.retrieveProjectorConfig(this.selectedRun, function (info) {
-            _this.projectorConfig = info;
-            var names = _this.projectorConfig.embeddings.map(function (e) { return e.tensorName; })
-                .filter(function (name) {
-                var shape = _this.getEmbeddingInfoByName(name).tensorShape;
-                return shape.length === 2 && shape[0] > 1 && shape[1] > 1;
-            })
-                .sort(function (a, b) {
-                var embA = _this.getEmbeddingInfoByName(a);
-                var embB = _this.getEmbeddingInfoByName(b);
-                // Prefer tensors with metadata.
-                if (util.xor(!!embA.metadataPath, !!embB.metadataPath)) {
-                    return embA.metadataPath ? -1 : 1;
-                }
-                // Prefer non-generated tensors.
-                var isGenA = util.tensorIsGenerated(a);
-                var isGenB = util.tensorIsGenerated(b);
-                if (util.xor(isGenA, isGenB)) {
-                    return isGenB ? -1 : 1;
-                }
-                // Prefer bigger tensors.
-                var sizeA = embA.tensorShape[0];
-                var sizeB = embB.tensorShape[0];
-                if (sizeA !== sizeB) {
-                    return sizeB - sizeA;
-                }
-                // Sort alphabetically by tensor name.
-                return a <= b ? -1 : 1;
-            });
-            _this.tensorNames = names.map(function (name) {
-                return { name: name, shape: _this.getEmbeddingInfoByName(name).tensorShape };
-            });
-            var wordBreakablePath = _this.addWordBreaks(_this.projectorConfig.modelCheckpointPath);
-            _this.dom.select('#checkpoint-file')
-                .html(wordBreakablePath)
-                .attr('title', _this.projectorConfig.modelCheckpointPath);
-            // If in demo mode, let the order decide which tensor to load by default.
-            var defaultTensor = _this.projector.servingMode === 'demo' ?
-                _this.projectorConfig.embeddings[0].tensorName :
-                names[0];
-            if (_this.selectedTensor === defaultTensor) {
-                // Explicitly call the observer. Polymer won't call it if the previous
-                // string matches the current string.
-                _this._selectedTensorChanged();
-            }
-            else {
-                _this.selectedTensor = defaultTensor;
-            }
-        });
-    };
-    DataPanel.prototype._selectedLabelOptionChanged = function () {
-        this.projector.setSelectedLabelOption(this.selectedLabelOption);
-    };
-    DataPanel.prototype._selectedColorOptionNameChanged = function () {
-        var colorOption;
-        for (var i = 0; i < this.colorOptions.length; i++) {
-            if (this.colorOptions[i].name === this.selectedColorOptionName) {
-                colorOption = this.colorOptions[i];
-                break;
-            }
-        }
-        if (!colorOption) {
-            return;
-        }
-        this.showForceCategoricalColorsCheckbox = !!colorOption.tooManyUniqueValues;
-        if (colorOption.map == null) {
-            this.colorLegendRenderInfo = null;
-        }
-        else if (colorOption.items) {
-            var items = colorOption.items.map(function (item) {
-                return {
-                    color: colorOption.map(item.label),
-                    label: item.label,
-                    count: item.count
-                };
-            });
-            this.colorLegendRenderInfo = { items: items, thresholds: null };
-        }
-        else {
-            this.colorLegendRenderInfo = {
-                items: null,
-                thresholds: colorOption.thresholds
-            };
-        }
-        this.projector.setSelectedColorOption(colorOption);
-    };
-    DataPanel.prototype.tensorWasReadFromFile = function (rawContents, fileName) {
-        var _this = this;
-        data_provider_1.parseRawTensors(rawContents, function (ds) {
-            _this.dom.select('#checkpoint-file')
-                .text(fileName)
-                .attr('title', fileName);
-            _this.projector.updateDataSet(ds);
-        });
-    };
-    DataPanel.prototype.metadataWasReadFromFile = function (rawContents, fileName) {
-        var _this = this;
-        data_provider_1.parseRawMetadata(rawContents, function (metadata) {
-            _this.projector.updateDataSet(_this.projector.dataSet, metadata, fileName);
-        });
-    };
-    DataPanel.prototype.getEmbeddingInfoByName = function (tensorName) {
-        for (var i = 0; i < this.projectorConfig.embeddings.length; i++) {
-            var e = this.projectorConfig.embeddings[i];
-            if (e.tensorName === tensorName) {
-                return e;
-            }
-        }
-    };
-    DataPanel.prototype.setupUploadButtons = function () {
-        var _this = this;
-        // Show and setup the upload button.
-        var fileInput = this.dom.select('#file');
-        fileInput.on('change', function () {
-            var file = d3.event.target.files[0];
-            // Clear out the value of the file chooser. This ensures that if the user
-            // selects the same file, we'll re-read it.
-            d3.event.target.value = '';
-            var fileReader = new FileReader();
-            fileReader.onload = function (evt) {
-                var content = evt.target.result;
-                _this.tensorWasReadFromFile(content, file.name);
-            };
-            fileReader.readAsArrayBuffer(file);
-        });
-        var uploadButton = this.dom.select('#upload-tensors');
-        uploadButton.on('click', function () {
-            fileInput.node().click();
-        });
-        // Show and setup the upload metadata button.
-        var fileMetadataInput = this.dom.select('#file-metadata');
-        fileMetadataInput.on('change', function () {
-            var file = d3.event.target.files[0];
-            // Clear out the value of the file chooser. This ensures that if the user
-            // selects the same file, we'll re-read it.
-            d3.event.target.value = '';
-            var fileReader = new FileReader();
-            fileReader.onload = function (evt) {
-                var contents = evt.target.result;
-                _this.metadataWasReadFromFile(contents, file.name);
-            };
-            fileReader.readAsArrayBuffer(file);
-        });
-        var uploadMetadataButton = this.dom.select('#upload-metadata');
-        uploadMetadataButton.on('click', function () {
-            fileMetadataInput.node().click();
-        });
-        if (this.projector.servingMode !== 'demo') {
-            this.$$('#publish-container').style.display = 'none';
-            this.$$('#upload-tensors-step-container').style.display =
-                'none';
-            this.$$('#upload-metadata-label').style.display = 'none';
-        }
-        this.$$('#demo-data-buttons-container').style.display =
-            'block';
-        // Fill out the projector config.
-        var projectorConfigTemplate = this.$$('#projector-config-template');
-        var projectorConfigTemplateJson = {
-            embeddings: [{
-                    tensorName: 'My tensor',
-                    tensorShape: [1000, 50],
-                    tensorPath: 'https://raw.githubusercontent.com/.../tensors.tsv',
-                    metadataPath: 'https://raw.githubusercontent.com/.../optional.metadata.tsv',
-                }],
-        };
-        this.setProjectorConfigTemplateJson(projectorConfigTemplate, projectorConfigTemplateJson);
-        // Set up optional field checkboxes.
-        var spriteFieldCheckbox = this.$$('#config-sprite-checkbox');
-        spriteFieldCheckbox.addEventListener('change', function () {
-            if (spriteFieldCheckbox.checked) {
-                projectorConfigTemplateJson.embeddings[0].sprite = {
-                    imagePath: 'https://github.com/.../optional.sprite.png',
-                    singleImageDim: [32, 32]
-                };
-            }
-            else {
-                delete projectorConfigTemplateJson.embeddings[0].sprite;
-            }
-            _this.setProjectorConfigTemplateJson(projectorConfigTemplate, projectorConfigTemplateJson);
-        });
-        var bookmarksFieldCheckbox = this.$$('#config-bookmarks-checkbox');
-        bookmarksFieldCheckbox.addEventListener('change', function () {
-            if (bookmarksFieldCheckbox.checked) {
-                projectorConfigTemplateJson.embeddings[0].bookmarksPath =
-                    'https://raw.githubusercontent.com/.../bookmarks.txt';
-            }
-            else {
-                delete projectorConfigTemplateJson.embeddings[0].bookmarksPath;
-            }
-            _this.setProjectorConfigTemplateJson(projectorConfigTemplate, projectorConfigTemplateJson);
-        });
-        var metadataFieldCheckbox = this.$$('#config-metadata-checkbox');
-        metadataFieldCheckbox.addEventListener('change', function () {
-            if (metadataFieldCheckbox.checked) {
-                projectorConfigTemplateJson.embeddings[0].metadataPath =
-                    'https://raw.githubusercontent.com/.../optional.metadata.tsv';
-            }
-            else {
-                delete projectorConfigTemplateJson.embeddings[0].metadataPath;
-            }
-            _this.setProjectorConfigTemplateJson(projectorConfigTemplate, projectorConfigTemplateJson);
-        });
-        // Update the link and the readonly shareable URL.
-        var projectorConfigUrlInput = this.$$('#projector-config-url');
-        var projectorConfigDemoUrlInput = this.$$('#projector-share-url');
-        var projectorConfigDemoUrlLink = this.$$('#projector-share-url-link');
-        projectorConfigUrlInput.addEventListener('input', function () {
-            var projectorDemoUrl = location.protocol + '//' + location.host +
-                location.pathname +
-                '?config=' + projectorConfigUrlInput.value;
-            projectorConfigDemoUrlInput.value =
-                projectorDemoUrl;
-            projectorConfigDemoUrlLink.href = projectorDemoUrl;
-        });
-    };
-    DataPanel.prototype.setProjectorConfigTemplateJson = function (projectorConfigTemplate, config) {
-        projectorConfigTemplate.value =
-            JSON.stringify(config, null, /** replacer */ 2 /** white space */);
-    };
-    DataPanel.prototype._getNumTensorsLabel = function () {
-        return this.tensorNames.length === 1 ? '1 tensor' :
-            this.tensorNames.length + ' tensors';
-    };
-    DataPanel.prototype._getNumRunsLabel = function () {
-        return this.runNames.length === 1 ? '1 run' :
-            this.runNames.length + ' runs';
-    };
-    DataPanel.prototype._hasChoices = function (choices) {
-        return choices.length > 1;
-    };
-    return DataPanel;
-}(exports.DataPanelPolymer));
-exports.DataPanel = DataPanel;
-document.registerElement(DataPanel.prototype.is, DataPanel);
-
-},{"./data-provider":6,"./util":24,"./vz-projector-util":33}],28:[function(require,module,exports){
-/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-"use strict";
-var __extends = (this && this.__extends) || function (d, b) {
-    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
-    function __() { this.constructor = d; }
-    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
-};
-// tslint:disable-next-line:no-unused-variable
-var vz_projector_util_1 = require('./vz-projector-util');
-// tslint:disable-next-line
-exports.PolymerClass = vz_projector_util_1.PolymerElement({ is: 'vz-projector-input', properties: { label: String, message: String } });
-/** Input control with custom capabilities (e.g. regex). */
-var ProjectorInput = (function (_super) {
-    __extends(ProjectorInput, _super);
-    function ProjectorInput() {
-        _super.apply(this, arguments);
-    }
-    /** Subscribe to be called everytime the input changes. */
-    ProjectorInput.prototype.registerInputChangedListener = function (listener) {
-        this.textChangedListeners.push(listener);
-    };
-    ProjectorInput.prototype.ready = function () {
-        var _this = this;
-        this.inRegexMode = false;
-        this.textChangedListeners = [];
-        this.dom = d3.select(this);
-        this.paperInput = this.querySelector('paper-input');
-        this.inRegexModeButton =
-            this.querySelector('paper-button');
-        this.paperInput.setAttribute('error-message', 'Invalid regex');
-        this.paperInput.addEventListener('input', function () {
-            _this.onTextChanged();
-        });
-        this.paperInput.addEventListener('keydown', function (event) {
-            event.stopPropagation();
-        });
-        this.inRegexModeButton.addEventListener('click', function () { return _this.onClickRegexModeButton(); });
-        this.updateRegexModeDisplaySlashes();
-        this.onTextChanged();
-    };
-    ProjectorInput.prototype.onClickRegexModeButton = function () {
-        this.inRegexMode = this.inRegexModeButton.active;
-        this.updateRegexModeDisplaySlashes();
-        this.onTextChanged();
-    };
-    ProjectorInput.prototype.notifyInputChanged = function (value, inRegexMode) {
-        this.textChangedListeners.forEach(function (l) { return l(value, inRegexMode); });
-    };
-    ProjectorInput.prototype.onTextChanged = function () {
-        try {
-            if (this.inRegexMode) {
-                new RegExp(this.paperInput.value);
-            }
-        }
-        catch (invalidRegexException) {
-            this.paperInput.setAttribute('invalid', 'true');
-            this.message = '';
-            this.notifyInputChanged(null, true);
-            return;
-        }
-        this.paperInput.removeAttribute('invalid');
-        this.notifyInputChanged(this.paperInput.value, this.inRegexMode);
-    };
-    ProjectorInput.prototype.updateRegexModeDisplaySlashes = function () {
-        d3.select(this.paperInput)
-            .selectAll('.slash')
-            .style('display', this.inRegexMode ? null : 'none');
-    };
-    ProjectorInput.prototype.getValue = function () {
-        return this.paperInput.value;
-    };
-    ProjectorInput.prototype.getInRegexMode = function () {
-        return this.inRegexMode;
-    };
-    ProjectorInput.prototype.set = function (value, inRegexMode) {
-        this.inRegexModeButton.active = inRegexMode;
-        this.paperInput.value = value;
-        this.onClickRegexModeButton();
-    };
-    return ProjectorInput;
-}(exports.PolymerClass));
-exports.ProjectorInput = ProjectorInput;
-document.registerElement(ProjectorInput.prototype.is, ProjectorInput);
-
-},{"./vz-projector-util":33}],29:[function(require,module,exports){
-/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-"use strict";
-var __extends = (this && this.__extends) || function (d, b) {
-    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
-    function __() { this.constructor = d; }
-    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
-};
-var adapter = require('./projectorScatterPlotAdapter');
-var vector = require('./vector');
-// tslint:disable-next-line:no-unused-variable
-var vz_projector_util_1 = require('./vz-projector-util');
-/** Limit the number of search results we show to the user. */
-var LIMIT_RESULTS = 100;
-// tslint:disable-next-line
-exports.PolymerClass = vz_projector_util_1.PolymerElement({
-    is: 'vz-projector-inspector-panel',
-    properties: { selectedMetadataField: String, metadataFields: Array }
-});
-var InspectorPanel = (function (_super) {
-    __extends(InspectorPanel, _super);
-    function InspectorPanel() {
-        _super.apply(this, arguments);
-    }
-    InspectorPanel.prototype.ready = function () {
-        this.dom = d3.select(this);
-        this.resetFilterButton = this.dom.select('.reset-filter');
-        this.setFilterButton = this.dom.select('.set-filter');
-        this.clearSelectionButton = this.dom.select('.clear-selection');
-        this.limitMessage = this.dom.select('.limit-msg');
-        this.searchBox = this.querySelector('#search-box');
-        // https://www.polymer-project.org/1.0/docs/devguide/styling#scope-subtree
-        this.scopeSubtree(this, true);
-    };
-    InspectorPanel.prototype.initialize = function (projector, projectorEventContext) {
-        var _this = this;
-        this.projector = projector;
-        this.projectorEventContext = projectorEventContext;
-        this.setupUI(projector);
-        projectorEventContext.registerSelectionChangedListener(function (selection, neighbors) {
-            return _this.updateInspectorPane(selection, neighbors);
-        });
-    };
-    /** Updates the nearest neighbors list in the inspector. */
-    InspectorPanel.prototype.updateInspectorPane = function (indices, neighbors) {
-        this.neighborsOfFirstPoint = neighbors;
-        this.selectedPointIndices = indices;
-        this.updateFilterButtons(indices.length + neighbors.length);
-        this.updateNeighborsList(neighbors);
-        if (neighbors.length === 0) {
-            this.updateSearchResults(indices);
-        }
-        else {
-            this.updateSearchResults([]);
-        }
-    };
-    InspectorPanel.prototype.enableResetFilterButton = function (enabled) {
-        this.resetFilterButton.attr('disabled', enabled ? null : true);
-    };
-    InspectorPanel.prototype.restoreUIFromBookmark = function (bookmark) {
-        this.enableResetFilterButton(bookmark.filteredPoints != null);
-    };
-    InspectorPanel.prototype.metadataChanged = function (spriteAndMetadata) {
-        var labelIndex = -1;
-        this.metadataFields = spriteAndMetadata.stats.map(function (stats, i) {
-            if (!stats.isNumeric && labelIndex === -1) {
-                labelIndex = i;
-            }
-            return stats.name;
-        });
-        labelIndex = Math.max(0, labelIndex);
-        // Make the default label the first non-numeric column.
-        this.selectedMetadataField = spriteAndMetadata.stats[labelIndex].name;
-    };
-    InspectorPanel.prototype.datasetChanged = function () {
-        this.enableResetFilterButton(false);
-    };
-    InspectorPanel.prototype.updateSearchResults = function (indices) {
-        var _this = this;
-        var container = this.dom.select('.matches-list');
-        container.style('display', indices.length ? null : 'none');
-        var list = container.select('.list');
-        list.html('');
-        if (indices.length === 0) {
-            return;
-        }
-        this.limitMessage.style('display', indices.length <= LIMIT_RESULTS ? 'none' : null);
-        indices = indices.slice(0, LIMIT_RESULTS);
-        var rows = list.selectAll('.row').data(indices).enter().append('div').attr('class', 'row');
-        rows.append('a')
-            .attr('class', 'label')
-            .attr('title', function (index) { return _this.getLabelFromIndex(index); })
-            .text(function (index) { return _this.getLabelFromIndex(index); });
-        rows.on('mouseenter', function (index) {
-            _this.projectorEventContext.notifyHoverOverPoint(index);
-        });
-        rows.on('mouseleave', function () {
-            _this.projectorEventContext.notifyHoverOverPoint(null);
-        });
-        rows.on('click', function (index) {
-            _this.projectorEventContext.notifySelectionChanged([index]);
-        });
-    };
-    InspectorPanel.prototype.getLabelFromIndex = function (pointIndex) {
-        var point = this.projector.dataSet.points[pointIndex];
-        return point.metadata[this.selectedMetadataField].toString();
-    };
-    InspectorPanel.prototype.updateNeighborsList = function (neighbors) {
-        var _this = this;
-        var nnlist = this.dom.select('.nn-list');
-        nnlist.html('');
-        this.dom.select('.nn').style('display', neighbors.length ? null : 'none');
-        if (neighbors.length === 0) {
-            return;
-        }
-        this.searchBox.message = '';
-        var minDist = neighbors.length > 0 ? neighbors[0].dist : 0;
-        var n = nnlist.selectAll('.neighbor')
-            .data(neighbors)
-            .enter()
-            .append('div')
-            .attr('class', 'neighbor')
-            .append('a')
-            .attr('class', 'neighbor-link')
-            .attr('title', function (d) { return _this.getLabelFromIndex(d.index); });
-        var labelValue = n.append('div').attr('class', 'label-and-value');
-        labelValue.append('div')
-            .attr('class', 'label')
-            .style('color', function (d) { return adapter.dist2color(_this.distFunc, d.dist, minDist); })
-            .text(function (d) { return _this.getLabelFromIndex(d.index); });
-        labelValue.append('div')
-            .attr('class', 'value')
-            .text(function (d) { return d.dist.toFixed(3); });
-        var bar = n.append('div').attr('class', 'bar');
-        bar.append('div')
-            .attr('class', 'fill')
-            .style('border-top-color', function (d) {
-            return adapter.dist2color(_this.distFunc, d.dist, minDist);
-        })
-            .style('width', function (d) { return adapter.normalizeDist(_this.distFunc, d.dist, minDist) * 100 +
-            '%'; });
-        bar.selectAll('.tick')
-            .data(d3.range(1, 4))
-            .enter()
-            .append('div')
-            .attr('class', 'tick')
-            .style('left', function (d) { return d * 100 / 4 + '%'; });
-        n.on('mouseenter', function (d) {
-            _this.projectorEventContext.notifyHoverOverPoint(d.index);
-        });
-        n.on('mouseleave', function () {
-            _this.projectorEventContext.notifyHoverOverPoint(null);
-        });
-        n.on('click', function (d) {
-            _this.projectorEventContext.notifySelectionChanged([d.index]);
-        });
-    };
-    InspectorPanel.prototype.updateFilterButtons = function (numPoints) {
-        if (numPoints > 1) {
-            this.setFilterButton.text("Isolate " + numPoints + " points")
-                .attr('disabled', null);
-            this.clearSelectionButton.attr('disabled', null);
-        }
-        else {
-            this.setFilterButton.attr('disabled', true);
-            this.clearSelectionButton.attr('disabled', true);
-        }
-    };
-    InspectorPanel.prototype.setupUI = function (projector) {
-        var _this = this;
-        this.distFunc = vector.cosDist;
-        var eucDist = this.dom.select('.distance a.euclidean');
-        eucDist.on('click', function () {
-            _this.dom.selectAll('.distance a').classed('selected', false);
-            eucDist.classed('selected', true);
-            _this.distFunc = vector.dist;
-            _this.projectorEventContext.notifyDistanceMetricChanged(_this.distFunc);
-            var neighbors = projector.dataSet.findNeighbors(_this.selectedPointIndices[0], _this.distFunc, _this.numNN);
-            _this.updateNeighborsList(neighbors);
-        });
-        var cosDist = this.dom.select('.distance a.cosine');
-        cosDist.on('click', function () {
-            _this.dom.selectAll('.distance a').classed('selected', false);
-            cosDist.classed('selected', true);
-            _this.distFunc = vector.cosDist;
-            _this.projectorEventContext.notifyDistanceMetricChanged(_this.distFunc);
-            var neighbors = projector.dataSet.findNeighbors(_this.selectedPointIndices[0], _this.distFunc, _this.numNN);
-            _this.updateNeighborsList(neighbors);
-        });
-        // Called whenever the search text input changes.
-        var updateInput = function (value, inRegexMode) {
-            if (value == null || value.trim() === '') {
-                _this.searchBox.message = '';
-                _this.projectorEventContext.notifySelectionChanged([]);
-                return;
-            }
-            var indices = projector.dataSet.query(value, inRegexMode, _this.selectedMetadataField);
-            if (indices.length === 0) {
-                _this.searchBox.message = '0 matches.';
-            }
-            else {
-                _this.searchBox.message = indices.length + " matches.";
-            }
-            _this.projectorEventContext.notifySelectionChanged(indices);
-        };
-        this.searchBox.registerInputChangedListener(function (value, inRegexMode) {
-            updateInput(value, inRegexMode);
-        });
-        // Nearest neighbors controls.
-        var numNNInput = this.$$('#nn-slider');
-        var updateNumNN = function () {
-            _this.numNN = +numNNInput.value;
-            _this.dom.select('.num-nn .nn-count').text(_this.numNN);
-            if (_this.selectedPointIndices != null) {
-                _this.projectorEventContext.notifySelectionChanged([_this.selectedPointIndices[0]]);
-            }
-        };
-        numNNInput.addEventListener('change', updateNumNN);
-        updateNumNN();
-        // Filtering dataset.
-        this.setFilterButton.on('click', function () {
-            var indices = _this.selectedPointIndices.concat(_this.neighborsOfFirstPoint.map(function (n) { return n.index; }));
-            projector.filterDataset(indices);
-            _this.enableResetFilterButton(true);
-            _this.updateFilterButtons(0);
-        });
-        this.resetFilterButton.on('click', function () {
-            projector.resetFilterDataset();
-            _this.enableResetFilterButton(false);
-        });
-        this.clearSelectionButton.on('click', function () {
-            projector.adjustSelectionAndHover([]);
-        });
-        this.enableResetFilterButton(false);
-    };
-    return InspectorPanel;
-}(exports.PolymerClass));
-exports.InspectorPanel = InspectorPanel;
-document.registerElement(InspectorPanel.prototype.is, InspectorPanel);
-
-},{"./projectorScatterPlotAdapter":14,"./vector":25,"./vz-projector-util":33}],30:[function(require,module,exports){
-/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-"use strict";
-var __extends = (this && this.__extends) || function (d, b) {
-    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
-    function __() { this.constructor = d; }
-    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
-};
-// tslint:disable-next-line:no-unused-variable
-var vz_projector_util_1 = require('./vz-projector-util');
-// tslint:disable-next-line
-exports.LegendPolymer = vz_projector_util_1.PolymerElement({
-    is: 'vz-projector-legend',
-    properties: { renderInfo: { type: Object, observer: '_renderInfoChanged' } }
-});
-var Legend = (function (_super) {
-    __extends(Legend, _super);
-    function Legend() {
-        _super.apply(this, arguments);
-    }
-    Legend.prototype.ready = function () {
-        this.dom = d3.select(this);
-    };
-    Legend.prototype._renderInfoChanged = function () {
-        var _this = this;
-        if (this.renderInfo == null) {
-            return;
-        }
-        if (this.renderInfo.thresholds) {
-            // <linearGradient> is under dom-if so we should wait for it to be
-            // inserted in the dom tree using async().
-            this.async(function () { return _this.setupLinearGradient(); });
-        }
-    };
-    Legend.prototype._getLastThreshold = function () {
-        if (this.renderInfo == null || this.renderInfo.thresholds == null) {
-            return;
-        }
-        return this.renderInfo.thresholds[this.renderInfo.thresholds.length - 1]
-            .value;
-    };
-    Legend.prototype.getOffset = function (value) {
-        var min = this.renderInfo.thresholds[0].value;
-        var max = this.renderInfo.thresholds[this.renderInfo.thresholds.length - 1].value;
-        return (100 * (value - min) / (max - min)).toFixed(2) + '%';
-    };
-    Legend.prototype.setupLinearGradient = function () {
-        var _this = this;
-        var linearGradient = this.dom.select('#gradient');
-        var width = this.dom.select('svg.gradient').node().clientWidth;
-        // Set the svg <rect> to be the width of its <svg> parent.
-        this.dom.select('svg.gradient rect').attr('width', width);
-        // Remove all <stop> children from before.
-        linearGradient.selectAll('*').remove();
-        // Add a <stop> child in <linearGradient> for each gradient threshold.
-        this.renderInfo.thresholds.forEach(function (t) {
-            linearGradient.append('stop')
-                .attr('offset', _this.getOffset(t.value))
-                .attr('stop-color', t.color);
-        });
-    };
-    return Legend;
-}(exports.LegendPolymer));
-exports.Legend = Legend;
-document.registerElement(Legend.prototype.is, Legend);
-
-},{"./vz-projector-util":33}],31:[function(require,module,exports){
-/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-"use strict";
-var __extends = (this && this.__extends) || function (d, b) {
-    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
-    function __() { this.constructor = d; }
-    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
-};
-// tslint:disable-next-line:no-unused-variable
-var vz_projector_util_1 = require('./vz-projector-util');
-// tslint:disable-next-line
-exports.MetadataCardPolymer = vz_projector_util_1.PolymerElement({
-    is: 'vz-projector-metadata-card',
-    properties: {
-        hasMetadata: { type: Boolean, value: false },
-        metadata: { type: Array },
-        label: String
-    }
-});
-var MetadataCard = (function (_super) {
-    __extends(MetadataCard, _super);
-    function MetadataCard() {
-        _super.apply(this, arguments);
-    }
-    MetadataCard.prototype.ready = function () {
-        this.dom = d3.select(this);
-    };
-    /** Handles a click on the expand more icon. */
-    MetadataCard.prototype._expandMore = function () {
-        this.$$('#metadata-container').toggle();
-        this.dom.select('#expand-more').style('display', 'none');
-        this.dom.select('#expand-less').style('display', '');
-    };
-    /** Handles a click on the expand less icon. */
-    MetadataCard.prototype._expandLess = function () {
-        this.$$('#metadata-container').toggle();
-        this.dom.select('#expand-more').style('display', '');
-        this.dom.select('#expand-less').style('display', 'none');
-    };
-    MetadataCard.prototype.updateMetadata = function (pointMetadata) {
-        this.pointMetadata = pointMetadata;
-        this.hasMetadata = (pointMetadata != null);
-        if (pointMetadata) {
-            var metadata = [];
-            for (var metadataKey in pointMetadata) {
-                if (!pointMetadata.hasOwnProperty(metadataKey)) {
-                    continue;
-                }
-                metadata.push({ key: metadataKey, value: pointMetadata[metadataKey] });
-            }
-            this.metadata = metadata;
-            this.label = '' + this.pointMetadata[this.labelOption];
-        }
-    };
-    MetadataCard.prototype.setLabelOption = function (labelOption) {
-        this.labelOption = labelOption;
-        if (this.pointMetadata) {
-            this.label = '' + this.pointMetadata[this.labelOption];
-        }
-    };
-    return MetadataCard;
-}(exports.MetadataCardPolymer));
-exports.MetadataCard = MetadataCard;
-document.registerElement(MetadataCard.prototype.is, MetadataCard);
-
-},{"./vz-projector-util":33}],32:[function(require,module,exports){
-/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-"use strict";
-var __extends = (this && this.__extends) || function (d, b) {
-    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
-    function __() { this.constructor = d; }
-    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
-};
-var data = require('./data');
-var data_1 = require('./data');
-var vector = require('./vector');
-// tslint:disable-next-line:no-unused-variable
-var vz_projector_util_1 = require('./vz-projector-util');
-var NUM_PCA_COMPONENTS = 10;
-// tslint:disable-next-line
-exports.ProjectionsPanelPolymer = vz_projector_util_1.PolymerElement({
-    is: 'vz-projector-projections-panel',
-    properties: {
-        pcaIs3d: { type: Boolean, value: true, observer: '_pcaDimensionToggleObserver' },
-        tSNEis3d: { type: Boolean, value: true, observer: '_tsneDimensionToggleObserver' },
-        // PCA projection.
-        pcaComponents: Array,
-        pcaX: { type: Number, value: 0, observer: 'showPCAIfEnabled' },
-        pcaY: { type: Number, value: 1, observer: 'showPCAIfEnabled' },
-        pcaZ: { type: Number, value: 2, observer: 'showPCAIfEnabled' },
-        // Custom projection.
-        customSelectedSearchByMetadataOption: {
-            type: String,
-            observer: '_customSelectedSearchByMetadataOptionChanged'
-        },
-    }
-});
-/**
- * A polymer component which handles the projection tabs in the projector.
- */
-var ProjectionsPanel = (function (_super) {
-    __extends(ProjectionsPanel, _super);
-    function ProjectionsPanel() {
-        _super.apply(this, arguments);
-    }
-    ProjectionsPanel.prototype.initialize = function (projector) {
-        this.polymerChangesTriggerReprojection = true;
-        this.projector = projector;
-        // Set up TSNE projections.
-        this.perplexity = 30;
-        this.learningRate = 10;
-        // Setup Custom projections.
-        this.centroidValues = { xLeft: null, xRight: null, yUp: null, yDown: null };
-        this.clearCentroids();
-        this.setupUIControls();
-    };
-    ProjectionsPanel.prototype.ready = function () {
-        this.dom = d3.select(this);
-        this.zDropdown = this.dom.select('#z-dropdown');
-        this.runTsneButton = this.dom.select('.run-tsne');
-        this.stopTsneButton = this.dom.select('.stop-tsne');
-        this.perplexitySlider = this.$$('#perplexity-slider');
-        this.learningRateInput =
-            this.$$('#learning-rate-slider');
-        this.iterationLabel = this.dom.select('.run-tsne-iter');
-    };
-    ProjectionsPanel.prototype.disablePolymerChangesTriggerReprojection = function () {
-        this.polymerChangesTriggerReprojection = false;
-    };
-    ProjectionsPanel.prototype.enablePolymerChangesTriggerReprojection = function () {
-        this.polymerChangesTriggerReprojection = true;
-    };
-    ProjectionsPanel.prototype.updateTSNEPerplexityFromSliderChange = function () {
-        if (this.perplexitySlider) {
-            this.perplexity = +this.perplexitySlider.value;
-        }
-        this.dom.select('.tsne-perplexity span').text(this.perplexity);
-    };
-    ProjectionsPanel.prototype.updateTSNELearningRateFromUIChange = function () {
-        if (this.learningRateInput) {
-            this.learningRate = Math.pow(10, +this.learningRateInput.value);
-        }
-        this.dom.select('.tsne-learning-rate span').text(this.learningRate);
-    };
-    ProjectionsPanel.prototype.setupUIControls = function () {
-        var _this = this;
-        {
-            var self_1 = this;
-            this.dom.selectAll('.ink-tab').on('click', function () {
-                var id = this.getAttribute('data-tab');
-                self_1.showTab(id);
-            });
-        }
-        this.runTsneButton.on('click', function () { return _this.runTSNE(); });
-        this.stopTsneButton.on('click', function () { return _this.dataSet.stopTSNE(); });
-        this.perplexitySlider.value = this.perplexity.toString();
-        this.perplexitySlider.addEventListener('change', function () { return _this.updateTSNEPerplexityFromSliderChange(); });
-        this.updateTSNEPerplexityFromSliderChange();
-        this.learningRateInput.addEventListener('change', function () { return _this.updateTSNELearningRateFromUIChange(); });
-        this.updateTSNELearningRateFromUIChange();
-        this.setupCustomProjectionInputFields();
-        // TODO: figure out why `--paper-input-container-input` css mixin didn't
-        // work.
-        this.dom.selectAll('paper-dropdown-menu paper-input input')
-            .style('font-size', '14px');
-    };
-    ProjectionsPanel.prototype.restoreUIFromBookmark = function (bookmark) {
-        this.disablePolymerChangesTriggerReprojection();
-        // PCA
-        this.pcaX = bookmark.pcaComponentDimensions[0];
-        this.pcaY = bookmark.pcaComponentDimensions[1];
-        if (bookmark.pcaComponentDimensions.length === 3) {
-            this.pcaZ = bookmark.pcaComponentDimensions[2];
-        }
-        this.pcaIs3d = (bookmark.pcaComponentDimensions.length === 3);
-        // t-SNE
-        if (this.perplexitySlider) {
-            this.perplexitySlider.value = bookmark.tSNEPerplexity.toString();
-        }
-        if (this.learningRateInput) {
-            this.learningRateInput.value = bookmark.tSNELearningRate.toString();
-        }
-        this.tSNEis3d = bookmark.tSNEis3d;
-        // custom
-        this.customSelectedSearchByMetadataOption =
-            bookmark.customSelectedSearchByMetadataOption;
-        if (this.customProjectionXLeftInput) {
-            this.customProjectionXLeftInput.set(bookmark.customXLeftText, bookmark.customXLeftRegex);
-        }
-        if (this.customProjectionXRightInput) {
-            this.customProjectionXRightInput.set(bookmark.customXRightText, bookmark.customXRightRegex);
-        }
-        if (this.customProjectionYUpInput) {
-            this.customProjectionYUpInput.set(bookmark.customYUpText, bookmark.customYUpRegex);
-        }
-        if (this.customProjectionYDownInput) {
-            this.customProjectionYDownInput.set(bookmark.customYDownText, bookmark.customYDownRegex);
-        }
-        this.computeAllCentroids();
-        this.setZDropdownEnabled(this.pcaIs3d);
-        this.updateTSNEPerplexityFromSliderChange();
-        this.updateTSNELearningRateFromUIChange();
-        if (this.iterationLabel) {
-            this.iterationLabel.text(bookmark.tSNEIteration.toString());
-        }
-        this.showTab(bookmark.selectedProjection);
-        this.enablePolymerChangesTriggerReprojection();
-    };
-    ProjectionsPanel.prototype.populateBookmarkFromUI = function (bookmark) {
-        this.disablePolymerChangesTriggerReprojection();
-        // PCA
-        bookmark.pcaComponentDimensions = [this.pcaX, this.pcaY];
-        if (this.pcaIs3d) {
-            bookmark.pcaComponentDimensions.push(this.pcaZ);
-        }
-        // t-SNE
-        if (this.perplexitySlider != null) {
-            bookmark.tSNEPerplexity = +this.perplexitySlider.value;
-        }
-        if (this.learningRateInput != null) {
-            bookmark.tSNELearningRate = +this.learningRateInput.value;
-        }
-        bookmark.tSNEis3d = this.tSNEis3d;
-        // custom
-        bookmark.customSelectedSearchByMetadataOption =
-            this.customSelectedSearchByMetadataOption;
-        if (this.customProjectionXLeftInput != null) {
-            bookmark.customXLeftText = this.customProjectionXLeftInput.getValue();
-            bookmark.customXLeftRegex =
-                this.customProjectionXLeftInput.getInRegexMode();
-        }
-        if (this.customProjectionXRightInput != null) {
-            bookmark.customXRightText = this.customProjectionXRightInput.getValue();
-            bookmark.customXRightRegex =
-                this.customProjectionXRightInput.getInRegexMode();
-        }
-        if (this.customProjectionYUpInput != null) {
-            bookmark.customYUpText = this.customProjectionYUpInput.getValue();
-            bookmark.customYUpRegex = this.customProjectionYUpInput.getInRegexMode();
-        }
-        if (this.customProjectionYDownInput != null) {
-            bookmark.customYDownText = this.customProjectionYDownInput.getValue();
-            bookmark.customYDownRegex =
-                this.customProjectionYDownInput.getInRegexMode();
-        }
-        this.enablePolymerChangesTriggerReprojection();
-    };
-    // This method is marked as public as it is used as the view method that
-    // abstracts DOM manipulation so we can stub it in a test.
-    // TODO(nsthorat): Move this to its own class as the glue between this class
-    // and the DOM.
-    ProjectionsPanel.prototype.setZDropdownEnabled = function (enabled) {
-        if (this.zDropdown) {
-            this.zDropdown.attr('disabled', enabled ? null : true);
-        }
-    };
-    ProjectionsPanel.prototype.dataSetUpdated = function (dataSet, originalDataSet, dim) {
-        this.dataSet = dataSet;
-        this.originalDataSet = originalDataSet;
-        this.dim = dim;
-        var pointCount = (dataSet == null) ? 0 : dataSet.points.length;
-        var perplexity = Math.max(5, Math.ceil(Math.sqrt(pointCount) / 4));
-        this.perplexitySlider.value = perplexity.toString();
-        this.updateTSNEPerplexityFromSliderChange();
-        this.clearCentroids();
-        this.dom.select('#tsne-sampling')
-            .style('display', pointCount > data.TSNE_SAMPLE_SIZE ? null : 'none');
-        var wasSampled = (dataSet == null) ? false : (dataSet.dim[0] > data.PCA_SAMPLE_DIM ||
-            dataSet.dim[1] > data.PCA_SAMPLE_DIM);
-        this.dom.select('#pca-sampling')
-            .style('display', wasSampled ? null : 'none');
-        this.showTab('pca');
-    };
-    ProjectionsPanel.prototype._pcaDimensionToggleObserver = function () {
-        this.setZDropdownEnabled(this.pcaIs3d);
-        this.beginProjection(this.currentProjection);
-    };
-    ProjectionsPanel.prototype._tsneDimensionToggleObserver = function () {
-        this.beginProjection(this.currentProjection);
-    };
-    ProjectionsPanel.prototype.metadataChanged = function (spriteAndMetadata) {
-        // Project by options for custom projections.
-        var searchByMetadataIndex = -1;
-        this.searchByMetadataOptions = spriteAndMetadata.stats.map(function (stats, i) {
-            // Make the default label by the first non-numeric column.
-            if (!stats.isNumeric && searchByMetadataIndex === -1) {
-                searchByMetadataIndex = i;
-            }
-            return stats.name;
-        });
-        this.customSelectedSearchByMetadataOption =
-            this.searchByMetadataOptions[Math.max(0, searchByMetadataIndex)];
-    };
-    ProjectionsPanel.prototype.showTab = function (id) {
-        var _this = this;
-        this.currentProjection = id;
-        var tab = this.dom.select('.ink-tab[data-tab="' + id + '"]');
-        this.dom.selectAll('.ink-tab').classed('active', false);
-        tab.classed('active', true);
-        this.dom.selectAll('.ink-panel-content').classed('active', false);
-        this.dom.select('.ink-panel-content[data-panel="' + id + '"]')
-            .classed('active', true);
-        // guard for unit tests, where polymer isn't attached and $ doesn't exist.
-        if (this.$ != null) {
-            var main_1 = this.$['main'];
-            // In order for the projections panel to animate its height, we need to
-            // set it explicitly.
-            requestAnimationFrame(function () {
-                _this.style.height = main_1.clientHeight + 'px';
-            });
-        }
-        this.beginProjection(id);
-    };
-    ProjectionsPanel.prototype.beginProjection = function (projection) {
-        if (this.polymerChangesTriggerReprojection === false) {
-            return;
-        }
-        if (projection === 'pca') {
-            if (this.dataSet != null) {
-                this.dataSet.stopTSNE();
-            }
-            this.showPCA();
-        }
-        else if (projection === 'tsne') {
-            this.showTSNE();
-        }
-        else if (projection === 'custom') {
-            if (this.dataSet != null) {
-                this.dataSet.stopTSNE();
-            }
-            this.computeAllCentroids();
-            this.reprojectCustom();
-        }
-    };
-    ProjectionsPanel.prototype.showTSNE = function () {
-        var dataSet = this.dataSet;
-        if (dataSet == null) {
-            return;
-        }
-        var accessors = data.getProjectionComponents('tsne', [0, 1, this.tSNEis3d ? 2 : null]);
-        var dimensionality = this.tSNEis3d ? 3 : 2;
-        var projection = new data_1.Projection('tsne', accessors, dimensionality, dataSet);
-        this.projector.setProjection(projection);
-        if (!this.dataSet.hasTSNERun) {
-            this.runTSNE();
-        }
-        else {
-            this.projector.notifyProjectionPositionsUpdated();
-        }
-    };
-    ProjectionsPanel.prototype.runTSNE = function () {
-        var _this = this;
-        this.runTsneButton.attr('disabled', true);
-        this.stopTsneButton.attr('disabled', null);
-        this.dataSet.projectTSNE(this.perplexity, this.learningRate, this.tSNEis3d ? 3 : 2, function (iteration) {
-            if (iteration != null) {
-                _this.iterationLabel.text(iteration);
-                _this.projector.notifyProjectionPositionsUpdated();
-            }
-            else {
-                _this.runTsneButton.attr('disabled', null);
-                _this.stopTsneButton.attr('disabled', true);
-            }
-        });
-    };
-    // tslint:disable-next-line:no-unused-variable
-    ProjectionsPanel.prototype.showPCAIfEnabled = function () {
-        if (this.polymerChangesTriggerReprojection) {
-            this.showPCA();
-        }
-    };
-    ProjectionsPanel.prototype.updateTotalVarianceMessage = function () {
-        var variances = this.dataSet.fracVariancesExplained;
-        var totalVariance = variances[this.pcaX] + variances[this.pcaY];
-        var msg = 'Total variance described: ';
-        if (this.pcaIs3d) {
-            totalVariance += variances[this.pcaZ];
-        }
-        msg += (totalVariance * 100).toFixed(1) + '%.';
-        this.dom.select('#total-variance').html(msg);
-    };
-    ProjectionsPanel.prototype.showPCA = function () {
-        var _this = this;
-        if (this.dataSet == null) {
-            return;
-        }
-        this.dataSet.projectPCA().then(function () {
-            // Polymer properties are 1-based.
-            var accessors = data.getProjectionComponents('pca', [_this.pcaX, _this.pcaY, _this.pcaZ]);
-            var dimensionality = _this.pcaIs3d ? 3 : 2;
-            var projection = new data_1.Projection('pca', accessors, dimensionality, _this.dataSet);
-            _this.projector.setProjection(projection);
-            var numComponents = Math.min(NUM_PCA_COMPONENTS, _this.dataSet.dim[1]);
-            _this.updateTotalVarianceMessage();
-            _this.pcaComponents = d3.range(0, numComponents).map(function (i) {
-                var fracVariance = _this.dataSet.fracVariancesExplained[i];
-                return {
-                    id: i,
-                    componentNumber: i + 1,
-                    percVariance: (fracVariance * 100).toFixed(1)
-                };
-            });
-        });
-    };
-    ProjectionsPanel.prototype.reprojectCustom = function () {
-        if (this.centroids == null || this.centroids.xLeft == null ||
-            this.centroids.xRight == null || this.centroids.yUp == null ||
-            this.centroids.yDown == null) {
-            return;
-        }
-        var xDir = vector.sub(this.centroids.xRight, this.centroids.xLeft);
-        this.dataSet.projectLinear(xDir, 'linear-x');
-        var yDir = vector.sub(this.centroids.yUp, this.centroids.yDown);
-        this.dataSet.projectLinear(yDir, 'linear-y');
-        var accessors = data.getProjectionComponents('custom', ['x', 'y']);
-        var projection = new data_1.Projection('custom', accessors, 2, this.dataSet);
-        this.projector.setProjection(projection);
-    };
-    ProjectionsPanel.prototype.clearCentroids = function () {
-        this.centroids = { xLeft: null, xRight: null, yUp: null, yDown: null };
-        this.allCentroid = null;
-    };
-    ProjectionsPanel.prototype._customSelectedSearchByMetadataOptionChanged = function (newVal, oldVal) {
-        if (this.polymerChangesTriggerReprojection === false) {
-            return;
-        }
-        if (this.currentProjection === 'custom') {
-            this.computeAllCentroids();
-            this.reprojectCustom();
-        }
-    };
-    ProjectionsPanel.prototype.setupCustomProjectionInputFields = function () {
-        this.customProjectionXLeftInput =
-            this.setupCustomProjectionInputField('xLeft');
-        this.customProjectionXRightInput =
-            this.setupCustomProjectionInputField('xRight');
-        this.customProjectionYUpInput = this.setupCustomProjectionInputField('yUp');
-        this.customProjectionYDownInput =
-            this.setupCustomProjectionInputField('yDown');
-    };
-    ProjectionsPanel.prototype.computeAllCentroids = function () {
-        this.computeCentroid('xLeft');
-        this.computeCentroid('xRight');
-        this.computeCentroid('yUp');
-        this.computeCentroid('yDown');
-    };
-    ProjectionsPanel.prototype.computeCentroid = function (name) {
-        var input = this.querySelector('#' + name);
-        if (input == null) {
-            return;
-        }
-        var value = input.getValue();
-        if (value == null) {
-            return;
-        }
-        var inRegexMode = input.getInRegexMode();
-        var result = this.getCentroid(value, inRegexMode);
-        if (result.numMatches === 0) {
-            input.message = '0 matches. Using a random vector.';
-            result.centroid = vector.rn(this.dim);
-        }
-        else {
-            input.message = result.numMatches + " matches.";
-        }
-        this.centroids[name] = result.centroid;
-        this.centroidValues[name] = value;
-    };
-    ProjectionsPanel.prototype.setupCustomProjectionInputField = function (name) {
-        var _this = this;
-        var input = this.querySelector('#' + name);
-        input.registerInputChangedListener(function (input, inRegexMode) {
-            if (_this.polymerChangesTriggerReprojection) {
-                _this.computeCentroid(name);
-                _this.reprojectCustom();
-            }
-        });
-        return input;
-    };
-    ProjectionsPanel.prototype.getCentroid = function (pattern, inRegexMode) {
-        var _this = this;
-        if (pattern == null || pattern === '') {
-            return { numMatches: 0 };
-        }
-        // Search by the original dataset since we often want to filter and project
-        // only the nearest neighbors of A onto B-C where B and C are not nearest
-        // neighbors of A.
-        var accessor = function (i) { return _this.originalDataSet.points[i].vector; };
-        var r = this.originalDataSet.query(pattern, inRegexMode, this.customSelectedSearchByMetadataOption);
-        return { centroid: vector.centroid(r, accessor), numMatches: r.length };
-    };
-    ProjectionsPanel.prototype.getPcaSampledDimText = function () {
-        return data.PCA_SAMPLE_DIM.toLocaleString();
-    };
-    ProjectionsPanel.prototype.getPcaSampleSizeText = function () {
-        return data.PCA_SAMPLE_SIZE.toLocaleString();
-    };
-    ProjectionsPanel.prototype.getTsneSampleSizeText = function () {
-        return data.TSNE_SAMPLE_SIZE.toLocaleString();
-    };
-    return ProjectionsPanel;
-}(exports.ProjectionsPanelPolymer));
-exports.ProjectionsPanel = ProjectionsPanel;
-document.registerElement(ProjectionsPanel.prototype.is, ProjectionsPanel);
-
-},{"./data":7,"./vector":25,"./vz-projector-util":33}],33:[function(require,module,exports){
-/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-"use strict";
-function PolymerElement(spec) {
-    return Polymer.Class(spec);
-}
-exports.PolymerElement = PolymerElement;
-
-},{}],34:[function(require,module,exports){
-/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-"use strict";
-var __extends = (this && this.__extends) || function (d, b) {
-    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
-    function __() { this.constructor = d; }
-    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
-};
-var analyticsLogger_1 = require('./analyticsLogger');
-var data = require('./data');
-var data_1 = require('./data');
-var data_provider_demo_1 = require('./data-provider-demo');
-var data_provider_proto_1 = require('./data-provider-proto');
-var data_provider_server_1 = require('./data-provider-server');
-var logging = require('./logging');
-var projectorScatterPlotAdapter_1 = require('./projectorScatterPlotAdapter');
-var scatterPlot_1 = require('./scatterPlot');
-var util = require('./util');
-// tslint:disable-next-line:no-unused-variable
-var vz_projector_util_1 = require('./vz-projector-util');
-/**
- * The minimum number of dimensions the data should have to automatically
- * decide to normalize the data.
- */
-var THRESHOLD_DIM_NORMALIZE = 50;
-var POINT_COLOR_MISSING = 'black';
-exports.ProjectorPolymer = vz_projector_util_1.PolymerElement({
-    is: 'vz-projector',
-    properties: {
-        routePrefix: String,
-        dataProto: { type: String, observer: '_dataProtoChanged' },
-        servingMode: String,
-        projectorConfigJsonPath: String,
-        pageViewLogging: Boolean,
-        eventLogging: Boolean
-    }
-});
-var INDEX_METADATA_FIELD = '__index__';
-var Projector = (function (_super) {
-    __extends(Projector, _super);
-    function Projector() {
-        _super.apply(this, arguments);
-    }
-    Projector.prototype.ready = function () {
-        this.dom = d3.select(this);
-        logging.setDomContainer(this);
-        this.analyticsLogger =
-            new analyticsLogger_1.AnalyticsLogger(this.pageViewLogging, this.eventLogging);
-        this.analyticsLogger.logPageView('embeddings');
-        if (!util.hasWebGLSupport()) {
-            this.analyticsLogger.logWebGLDisabled();
-            logging.setErrorMessage('Your browser or device does not have WebGL enabled. Please enable ' +
-                'hardware acceleration, or use a browser that supports WebGL.');
-            return;
-        }
-        this.selectionChangedListeners = [];
-        this.hoverListeners = [];
-        this.projectionChangedListeners = [];
-        this.distanceMetricChangedListeners = [];
-        this.selectedPointIndices = [];
-        this.neighborsOfFirstPoint = [];
-        this.dataPanel = this.$['data-panel'];
-        this.inspectorPanel = this.$['inspector-panel'];
-        this.inspectorPanel.initialize(this, this);
-        this.projectionsPanel = this.$['projections-panel'];
-        this.projectionsPanel.initialize(this);
-        this.bookmarkPanel = this.$['bookmark-panel'];
-        this.bookmarkPanel.initialize(this, this);
-        this.metadataCard = this.$['metadata-card'];
-        this.statusBar = this.dom.select('#status-bar');
-        this.scopeSubtree(this.$$('#notification-dialog'), true);
-        this.setupUIControls();
-        this.initializeDataProvider();
-    };
-    Projector.prototype.setSelectedLabelOption = function (labelOption) {
-        this.selectedLabelOption = labelOption;
-        this.metadataCard.setLabelOption(this.selectedLabelOption);
-        this.projectorScatterPlotAdapter.setLabelPointAccessor(labelOption);
-        this.projectorScatterPlotAdapter.updateScatterPlotAttributes();
-        this.projectorScatterPlotAdapter.render();
-    };
-    Projector.prototype.setSelectedColorOption = function (colorOption) {
-        this.selectedColorOption = colorOption;
-        this.projectorScatterPlotAdapter.setLegendPointColorer(this.getLegendPointColorer(colorOption));
-        this.projectorScatterPlotAdapter.updateScatterPlotAttributes();
-        this.projectorScatterPlotAdapter.render();
-    };
-    Projector.prototype.setNormalizeData = function (normalizeData) {
-        this.normalizeData = normalizeData;
-        this.setCurrentDataSet(this.originalDataSet.getSubset());
-    };
-    Projector.prototype.updateDataSet = function (ds, spriteAndMetadata, metadataFile) {
-        this.dataSetFilterIndices = null;
-        this.originalDataSet = ds;
-        if (ds != null) {
-            this.normalizeData =
-                this.originalDataSet.dim[1] >= THRESHOLD_DIM_NORMALIZE;
-            spriteAndMetadata = spriteAndMetadata || {};
-            if (spriteAndMetadata.pointsInfo == null) {
-                var _a = this.makeDefaultPointsInfoAndStats(ds.points), pointsInfo = _a[0], stats = _a[1];
-                spriteAndMetadata.pointsInfo = pointsInfo;
-                spriteAndMetadata.stats = stats;
-            }
-            var metadataMergeSucceeded = ds.mergeMetadata(spriteAndMetadata);
-            if (!metadataMergeSucceeded) {
-                return;
-            }
-        }
-        if (this.projectorScatterPlotAdapter != null) {
-            if (ds == null) {
-                this.projectorScatterPlotAdapter.setLabelPointAccessor(null);
-                this.setProjection(null);
-            }
-            else {
-                this.projectorScatterPlotAdapter.updateScatterPlotPositions();
-                this.projectorScatterPlotAdapter.updateScatterPlotAttributes();
-                this.projectorScatterPlotAdapter.resize();
-                this.projectorScatterPlotAdapter.render();
-            }
-        }
-        if (ds != null) {
-            this.dataPanel.setNormalizeData(this.normalizeData);
-            this.setCurrentDataSet(ds.getSubset());
-            this.projectorScatterPlotAdapter.setLabelPointAccessor(this.selectedLabelOption);
-            this.inspectorPanel.datasetChanged();
-            this.inspectorPanel.metadataChanged(spriteAndMetadata);
-            this.projectionsPanel.metadataChanged(spriteAndMetadata);
-            this.dataPanel.metadataChanged(spriteAndMetadata, metadataFile);
-            // Set the container to a fixed height, otherwise in Colab the
-            // height can grow indefinitely.
-            var container = this.dom.select('#container');
-            container.style('height', container.property('clientHeight') + 'px');
-        }
-        else {
-            this.setCurrentDataSet(null);
-        }
-    };
-    Projector.prototype.setSelectedTensor = function (run, tensorInfo) {
-        this.bookmarkPanel.setSelectedTensor(run, tensorInfo, this.dataProvider);
-    };
-    /**
-     * Registers a listener to be called any time the selected point set changes.
-     */
-    Projector.prototype.registerSelectionChangedListener = function (listener) {
-        this.selectionChangedListeners.push(listener);
-    };
-    Projector.prototype.filterDataset = function (pointIndices) {
-        var selectionSize = this.selectedPointIndices.length;
-        if (this.dataSetBeforeFilter == null) {
-            this.dataSetBeforeFilter = this.dataSet;
-        }
-        this.setCurrentDataSet(this.dataSet.getSubset(pointIndices));
-        this.dataSetFilterIndices = pointIndices;
-        this.projectorScatterPlotAdapter.updateScatterPlotPositions();
-        this.projectorScatterPlotAdapter.updateScatterPlotAttributes();
-        this.adjustSelectionAndHover(d3.range(selectionSize));
-    };
-    Projector.prototype.resetFilterDataset = function () {
-        var _this = this;
-        var originalPointIndices = this.selectedPointIndices.map(function (filteredIndex) { return _this.dataSet.points[filteredIndex].index; });
-        this.setCurrentDataSet(this.dataSetBeforeFilter);
-        if (this.projection != null) {
-            this.projection.dataSet = this.dataSetBeforeFilter;
-        }
-        this.dataSetBeforeFilter = null;
-        this.projectorScatterPlotAdapter.updateScatterPlotPositions();
-        this.projectorScatterPlotAdapter.updateScatterPlotAttributes();
-        this.dataSetFilterIndices = [];
-        this.adjustSelectionAndHover(originalPointIndices);
-    };
-    /**
-     * Used by clients to indicate that a selection has occurred.
-     */
-    Projector.prototype.notifySelectionChanged = function (newSelectedPointIndices) {
-        var _this = this;
-        this.selectedPointIndices = newSelectedPointIndices;
-        var neighbors = [];
-        if (newSelectedPointIndices.length === 1) {
-            neighbors = this.dataSet.findNeighbors(newSelectedPointIndices[0], this.inspectorPanel.distFunc, this.inspectorPanel.numNN);
-            this.metadataCard.updateMetadata(this.dataSet.points[newSelectedPointIndices[0]].metadata);
-        }
-        else {
-            this.metadataCard.updateMetadata(null);
-        }
-        this.selectionChangedListeners.forEach(function (l) { return l(_this.selectedPointIndices, neighbors); });
-    };
-    /**
-     * Registers a listener to be called any time the mouse hovers over a point.
-     */
-    Projector.prototype.registerHoverListener = function (listener) {
-        this.hoverListeners.push(listener);
-    };
-    /**
-     * Used by clients to indicate that a hover is occurring.
-     */
-    Projector.prototype.notifyHoverOverPoint = function (pointIndex) {
-        this.hoverListeners.forEach(function (l) { return l(pointIndex); });
-    };
-    Projector.prototype.registerProjectionChangedListener = function (listener) {
-        this.projectionChangedListeners.push(listener);
-    };
-    Projector.prototype.notifyProjectionChanged = function (projection) {
-        this.projectionChangedListeners.forEach(function (l) { return l(projection); });
-    };
-    Projector.prototype.registerDistanceMetricChangedListener = function (l) {
-        this.distanceMetricChangedListeners.push(l);
-    };
-    Projector.prototype.notifyDistanceMetricChanged = function (distMetric) {
-        this.distanceMetricChangedListeners.forEach(function (l) { return l(distMetric); });
-    };
-    Projector.prototype._dataProtoChanged = function (dataProtoString) {
-        var dataProto = dataProtoString ? JSON.parse(dataProtoString) : null;
-        this.initializeDataProvider(dataProto);
-    };
-    Projector.prototype.makeDefaultPointsInfoAndStats = function (points) {
-        var pointsInfo = [];
-        points.forEach(function (p) {
-            var pointInfo = {};
-            pointInfo[INDEX_METADATA_FIELD] = p.index;
-            pointsInfo.push(pointInfo);
-        });
-        var stats = [{
-                name: INDEX_METADATA_FIELD,
-                isNumeric: false,
-                tooManyUniqueValues: true,
-                min: 0,
-                max: pointsInfo.length - 1
-            }];
-        return [pointsInfo, stats];
-    };
-    Projector.prototype.initializeDataProvider = function (dataProto) {
-        if (this.servingMode === 'demo') {
-            var projectorConfigUrl = void 0;
-            // Only in demo mode do we allow the config being passed via URL.
-            var urlParams = util.getURLParams(window.location.search);
-            if ('config' in urlParams) {
-                projectorConfigUrl = urlParams['config'];
-            }
-            else {
-                projectorConfigUrl = this.projectorConfigJsonPath;
-            }
-            this.dataProvider = new data_provider_demo_1.DemoDataProvider(projectorConfigUrl);
-        }
-        else if (this.servingMode === 'server') {
-            if (!this.routePrefix) {
-                throw 'route-prefix is a required parameter';
-            }
-            this.dataProvider = new data_provider_server_1.ServerDataProvider(this.routePrefix);
-        }
-        else if (this.servingMode === 'proto' && dataProto != null) {
-            this.dataProvider = new data_provider_proto_1.ProtoDataProvider(dataProto);
-        }
-        this.dataPanel.initialize(this, this.dataProvider);
-    };
-    Projector.prototype.getLegendPointColorer = function (colorOption) {
-        var _this = this;
-        if ((colorOption == null) || (colorOption.map == null)) {
-            return null;
-        }
-        var colorer = function (ds, i) {
-            var value = ds.points[i].metadata[_this.selectedColorOption.name];
-            if (value == null) {
-                return POINT_COLOR_MISSING;
-            }
-            return colorOption.map(value);
-        };
-        return colorer;
-    };
-    Projector.prototype.get3DLabelModeButton = function () {
-        return this.querySelector('#labels3DMode');
-    };
-    Projector.prototype.get3DLabelMode = function () {
-        var label3DModeButton = this.get3DLabelModeButton();
-        return label3DModeButton.active;
-    };
-    Projector.prototype.adjustSelectionAndHover = function (selectedPointIndices, hoverIndex) {
-        this.notifySelectionChanged(selectedPointIndices);
-        this.notifyHoverOverPoint(hoverIndex);
-        this.setMouseMode(scatterPlot_1.MouseMode.CAMERA_AND_CLICK_SELECT);
-    };
-    Projector.prototype.setMouseMode = function (mouseMode) {
-        var selectModeButton = this.querySelector('#selectMode');
-        selectModeButton.active = (mouseMode === scatterPlot_1.MouseMode.AREA_SELECT);
-        this.projectorScatterPlotAdapter.scatterPlot.setMouseMode(mouseMode);
-    };
-    Projector.prototype.setCurrentDataSet = function (ds) {
-        this.adjustSelectionAndHover([]);
-        if (this.dataSet != null) {
-            this.dataSet.stopTSNE();
-        }
-        if ((ds != null) && this.normalizeData) {
-            ds.normalize();
-        }
-        this.dim = (ds == null) ? 0 : ds.dim[1];
-        this.dom.select('span.numDataPoints').text((ds == null) ? '0' : ds.dim[0]);
-        this.dom.select('span.dim').text((ds == null) ? '0' : ds.dim[1]);
-        this.dataSet = ds;
-        this.projectionsPanel.dataSetUpdated(this.dataSet, this.originalDataSet, this.dim);
-        this.projectorScatterPlotAdapter.setDataSet(this.dataSet);
-        this.projectorScatterPlotAdapter.scatterPlot
-            .setCameraParametersForNextCameraCreation(null, true);
-    };
-    Projector.prototype.setupUIControls = function () {
-        var _this = this;
-        // View controls
-        this.querySelector('#reset-zoom').addEventListener('click', function () {
-            _this.projectorScatterPlotAdapter.scatterPlot.resetZoom();
-            _this.projectorScatterPlotAdapter.scatterPlot.startOrbitAnimation();
-        });
-        var selectModeButton = this.querySelector('#selectMode');
-        selectModeButton.addEventListener('click', function (event) {
-            _this.setMouseMode(selectModeButton.active ? scatterPlot_1.MouseMode.AREA_SELECT :
-                scatterPlot_1.MouseMode.CAMERA_AND_CLICK_SELECT);
-        });
-        var nightModeButton = this.querySelector('#nightDayMode');
-        nightModeButton.addEventListener('click', function () {
-            _this.projectorScatterPlotAdapter.scatterPlot.setDayNightMode(nightModeButton.active);
-        });
-        var labels3DModeButton = this.get3DLabelModeButton();
-        labels3DModeButton.addEventListener('click', function () {
-            _this.projectorScatterPlotAdapter.set3DLabelMode(_this.get3DLabelMode());
-        });
-        window.addEventListener('resize', function () {
-            var container = _this.dom.select('#container');
-            var parentHeight = container.node().parentNode.clientHeight;
-            container.style('height', parentHeight + 'px');
-            _this.projectorScatterPlotAdapter.resize();
-        });
-        {
-            this.projectorScatterPlotAdapter = new projectorScatterPlotAdapter_1.ProjectorScatterPlotAdapter(this.getScatterContainer(), this);
-            this.projectorScatterPlotAdapter.setLabelPointAccessor(this.selectedLabelOption);
-        }
-        this.projectorScatterPlotAdapter.scatterPlot.onCameraMove(function (cameraPosition, cameraTarget) {
-            return _this.bookmarkPanel.clearStateSelection();
-        });
-        this.registerHoverListener(function (hoverIndex) { return _this.onHover(hoverIndex); });
-        this.registerSelectionChangedListener(function (selectedPointIndices, neighborsOfFirstPoint) {
-            return _this.onSelectionChanged(selectedPointIndices, neighborsOfFirstPoint);
-        });
-    };
-    Projector.prototype.onHover = function (hoverIndex) {
-        this.hoverPointIndex = hoverIndex;
-        var hoverText = null;
-        if (hoverIndex != null) {
-            var point = this.dataSet.points[hoverIndex];
-            if (point.metadata[this.selectedLabelOption]) {
-                hoverText = point.metadata[this.selectedLabelOption].toString();
-            }
-        }
-        if (this.selectedPointIndices.length === 0) {
-            this.statusBar.style('display', hoverText ? null : 'none');
-            this.statusBar.text(hoverText);
-        }
-    };
-    Projector.prototype.getScatterContainer = function () {
-        return this.dom.select('#scatter');
-    };
-    Projector.prototype.onSelectionChanged = function (selectedPointIndices, neighborsOfFirstPoint) {
-        this.selectedPointIndices = selectedPointIndices;
-        this.neighborsOfFirstPoint = neighborsOfFirstPoint;
-        var totalNumPoints = this.selectedPointIndices.length + neighborsOfFirstPoint.length;
-        this.statusBar.text("Selected " + totalNumPoints + " points")
-            .style('display', totalNumPoints > 0 ? null : 'none');
-    };
-    Projector.prototype.setProjection = function (projection) {
-        this.projection = projection;
-        if (projection != null) {
-            this.analyticsLogger.logProjectionChanged(projection.projectionType);
-        }
-        this.notifyProjectionChanged(projection);
-    };
-    Projector.prototype.notifyProjectionPositionsUpdated = function () {
-        this.projectorScatterPlotAdapter.notifyProjectionPositionsUpdated();
-    };
-    /**
-     * Gets the current view of the embedding and saves it as a State object.
-     */
-    Projector.prototype.getCurrentState = function () {
-        var state = new data_1.State();
-        // Save the individual datapoint projections.
-        state.projections = [];
-        for (var i = 0; i < this.dataSet.points.length; i++) {
-            var point = this.dataSet.points[i];
-            var projections = {};
-            var keys = Object.keys(point.projections);
-            for (var j = 0; j < keys.length; ++j) {
-                projections[keys[j]] = point.projections[keys[j]];
-            }
-            state.projections.push(projections);
-        }
-        state.selectedProjection = this.projection.projectionType;
-        state.dataSetDimensions = this.dataSet.dim;
-        state.tSNEIteration = this.dataSet.tSNEIteration;
-        state.selectedPoints = this.selectedPointIndices;
-        state.filteredPoints = this.dataSetFilterIndices;
-        this.projectorScatterPlotAdapter.populateBookmarkFromUI(state);
-        state.selectedColorOptionName = this.dataPanel.selectedColorOptionName;
-        state.forceCategoricalColoring = this.dataPanel.forceCategoricalColoring;
-        state.selectedLabelOption = this.selectedLabelOption;
-        this.projectionsPanel.populateBookmarkFromUI(state);
-        return state;
-    };
-    /** Loads a State object into the world. */
-    Projector.prototype.loadState = function (state) {
-        this.setProjection(null);
-        {
-            this.projectionsPanel.disablePolymerChangesTriggerReprojection();
-            if (this.dataSetBeforeFilter != null) {
-                this.resetFilterDataset();
-            }
-            if (state.filteredPoints != null) {
-                this.filterDataset(state.filteredPoints);
-            }
-            this.projectionsPanel.enablePolymerChangesTriggerReprojection();
-        }
-        for (var i = 0; i < state.projections.length; i++) {
-            var point = this.dataSet.points[i];
-            var projection = state.projections[i];
-            var keys = Object.keys(projection);
-            for (var j = 0; j < keys.length; ++j) {
-                point.projections[keys[j]] = projection[keys[j]];
-            }
-        }
-        this.dataSet.hasTSNERun = (state.selectedProjection === 'tsne');
-        this.dataSet.tSNEIteration = state.tSNEIteration;
-        this.projectionsPanel.restoreUIFromBookmark(state);
-        this.inspectorPanel.restoreUIFromBookmark(state);
-        this.dataPanel.selectedColorOptionName = state.selectedColorOptionName;
-        this.dataPanel.setForceCategoricalColoring(!!state.forceCategoricalColoring);
-        this.selectedLabelOption = state.selectedLabelOption;
-        this.projectorScatterPlotAdapter.restoreUIFromBookmark(state);
-        {
-            var dimensions = data_1.stateGetAccessorDimensions(state);
-            var components = data.getProjectionComponents(state.selectedProjection, dimensions);
-            var projection = new data_1.Projection(state.selectedProjection, components, dimensions.length, this.dataSet);
-            this.setProjection(projection);
-        }
-        this.notifySelectionChanged(state.selectedPoints);
-    };
-    return Projector;
-}(exports.ProjectorPolymer));
-exports.Projector = Projector;
-document.registerElement(Projector.prototype.is, Projector);
-
-},{"./analyticsLogger":1,"./data":7,"./data-provider-demo":3,"./data-provider-proto":4,"./data-provider-server":5,"./logging":12,"./projectorScatterPlotAdapter":14,"./scatterPlot":16,"./util":24,"./vz-projector-util":33}],35:[function(require,module,exports){
-arguments[4][8][0].apply(exports,arguments)
-},{"dup":8}]},{},[35,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34]);
-</script>
-</dom-module>
-</body></html>
diff --git a/tensorflow/tensorboard/gulp_tasks/bower.js b/tensorflow/tensorboard/gulp_tasks/bower.js
deleted file mode 100644
index 8f4666a..0000000
--- a/tensorflow/tensorboard/gulp_tasks/bower.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-
-const gulp = require('gulp');
-const bower = require('gulp-bower');
-
-module.exports = function() {
-  return function() {
-    return bower();
-  }
-}
diff --git a/tensorflow/tensorboard/gulp_tasks/compile.js b/tensorflow/tensorboard/gulp_tasks/compile.js
deleted file mode 100644
index 01af60e..0000000
--- a/tensorflow/tensorboard/gulp_tasks/compile.js
+++ /dev/null
@@ -1,95 +0,0 @@
-/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-
-const gulp = require('gulp');
-const ts = require('gulp-typescript');
-const typescript = require('typescript');
-const gutil = require('gulp-util');
-const filter = require('gulp-filter');
-const merge = require('merge2');
-const browserify = require('browserify');
-const tsify = require('tsify');
-const source = require('vinyl-source-stream');
-const glob = require('glob').sync;
-const concat = require('gulp-concat');
-
-const tsProject = ts.createProject('./tsconfig.json', {
-  typescript: typescript,
-  noExternalResolve: true,  // opt-in for faster compilation!
-});
-
-/** List of components (and their external deps) that are using es6 modules. */
-const ES6_COMPONENTS = [{
-  name: 'vz_projector',
-  deps: [
-    'd3/d3.min.js', 'weblas/dist/weblas.js', 'three.js/build/three.min.js',
-    'three.js/examples/js/controls/OrbitControls.js',
-    'numericjs/lib/numeric-1.2.6.js'
-  ]
-}];
-
-module.exports = function(includeDeps) {
-  return function() {
-    // Compile all components that are using ES6 modules into a bundle.js
-    // using browserify.
-    const entries = ['typings/index.d.ts'];
-    const deps = {};
-    ES6_COMPONENTS.forEach(function(component) {
-      // Collect all the typescript files across the components.
-      entries = entries.concat(glob(
-          'components/' + component.name + '/**/*.ts',
-          // Do not include tests or IDE-purposed files.
-          {ignore: ['**/*_test.ts', '**/deps.d.ts']}));
-      // Collect the unique external deps across all components using es6
-      // modules.
-      component.deps.forEach(function(dep) {
-        deps['components/' + dep] = true;
-      });
-    });
-    deps = Object.keys(deps);
-
-    // Compile, bundle all the typescript files and prepend their deps.
-    browserify(entries)
-        .plugin(tsify)
-        .bundle()
-        .on('error', function(error) { console.error(error.toString()); })
-        .pipe(source('bundle.js'))
-        .pipe(gulp.dest('components'))
-        .on('end', function() {
-          // Typescript was compiled and bundled. Now we need to prepend
-          // the external dependencies.
-          if (includeDeps) {
-            gulp.src(deps.concat(['components/bundle.js']))
-                .pipe(concat('bundle.js'))
-                .pipe(gulp.dest('components'));
-          }
-        });
-
-    // Compile components that are using global namespaces producing 1 js file
-    // for each ts file.
-    const isComponent = filter([
-      'components/tf_*/**/*.ts', 'components/vz_*/**/*.ts', 'typings/**/*.ts',
-      'components/plottable/plottable.d.ts'
-      // Ignore components that use es6 modules.
-    ].concat(ES6_COMPONENTS.map(function(component) {
-      return '!components/' + component.name + '/**/*.ts';
-    })));
-
-    return tsProject.src()
-        .pipe(isComponent)
-        .pipe(ts(tsProject))
-        .js.pipe(gulp.dest('.'));
-  };
-};
diff --git a/tensorflow/tensorboard/gulp_tasks/test.js b/tensorflow/tensorboard/gulp_tasks/test.js
deleted file mode 100644
index 0c8b14a..0000000
--- a/tensorflow/tensorboard/gulp_tasks/test.js
+++ /dev/null
@@ -1,28 +0,0 @@
-/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-
-const gulp = require('gulp');
-const tester = require('web-component-tester').test;
-
-module.exports = function(done) {
-  tester({}, function(error) {
-    if (error) {
-      // Pretty error for gulp.
-      error = new Error(error.message || error);
-      error.showStack = false;
-    }
-    done(error);
-  });
-}
diff --git a/tensorflow/tensorboard/gulp_tasks/util.js b/tensorflow/tensorboard/gulp_tasks/util.js
deleted file mode 100644
index 0d73f69..0000000
--- a/tensorflow/tensorboard/gulp_tasks/util.js
+++ /dev/null
@@ -1,39 +0,0 @@
-/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-
-const fs = require('fs');
-const path = require('path');
-
-/**
- * Returns a list of web components inside the components directory for which
- * the name predicate is true.
- */
-exports.getComponents = function(namePredicate) {
-  return fs.readdirSync('components')
-      .filter(function(file) {
-        return fs.statSync(path.join('components', file)).isDirectory() &&
-            namePredicate(file);
-      })
-      .map(function(dir) { return '/' + dir + '/'; });
-};
-
-/**
- * Returns a list of tensorboard web components that are inside the components
- * directory.
- */
-exports.tbComponents = exports.getComponents(function(name) {
-  const prefix = name.slice(0, 3);
-  return prefix == 'tf_' || prefix == 'vz_';
-});
diff --git a/tensorflow/tensorboard/gulp_tasks/vulcanize.js b/tensorflow/tensorboard/gulp_tasks/vulcanize.js
deleted file mode 100644
index d2286f1..0000000
--- a/tensorflow/tensorboard/gulp_tasks/vulcanize.js
+++ /dev/null
@@ -1,83 +0,0 @@
-/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-
-const gulp = require('gulp');
-const path = require('path');
-const util = require('./util');
-const vulcanize = require('gulp-vulcanize');
-const replace = require('gulp-replace');
-const rename = require('gulp-rename');
-const header = require('gulp-header');
-
-const HEADER_STR =
-    '<!-- Copyright 2015 The TensorFlow Authors. All Rights Reserved.\n\
-\n\
-Licensed under the Apache License, Version 2.0 (the "License");\n\
-you may not use this file except in compliance with the License.\n\
-You may obtain a copy of the License at\n\
-\n\
-   http://www.apache.org/licenses/LICENSE-2.0\n\
-\n\
-Unless required by applicable law or agreed to in writing, software\n\
-distributed under the License is distributed on an "AS IS" BASIS,\n\
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n\
-See the License for the specific language governing permissions and\n\
-limitations under the License.\n\
-============================================================================\n\
-\n\
-This file is generated by `gulp` & `vulcanize`. Do not directly change it.\n\
-Instead, use `gulp regenerate` to create a new version with your changes.\n\
--->\n\n'
-
-const base = path.join(__dirname, '../components');
-// List of redirects of the form path1|path2 for every tensorboard component
-// in order to replace dashes with underscores.
-// E.g. .../tf-tensorboard|.../tf_tensorboard
-const redirects = util.tbComponents.map(function(dir) {
-  return path.join(base, dir.replace(/_/g, '-')) + '|' + path.join(base, dir);
-});
-
-const nonTBComponents = util.getComponents(function(name) {
-  const prefix = name.slice(0, 3);
-  return prefix !== 'tf_'  && prefix !== 'vz_';
-});
-
-// These manual additions are necessary. The task should not inline these
-// third-party javascript files. However, vulcanization still needs the HTML
-// files found within those directories. Upon adding new third-party javascript,
-// consider updating this list.
-nonTBComponents.push('/tf-imports/d3.js');
-nonTBComponents.push('/tf-imports/dagre.js');
-nonTBComponents.push('/tf-imports/graphlib.js');
-nonTBComponents.push('/tf-imports/lodash.js');
-nonTBComponents.push('/tf-imports/plottable.js');
-
-module.exports = function(overwrite) {
-  return function() {
-    const suffix = overwrite ? '' : '.OPENSOURCE';
-    // Vulcanize TensorBoard without external libraries.
-    gulp.src('components/tf_tensorboard/tf-tensorboard.html')
-        .pipe(vulcanize({
-          inlineScripts: true,
-          inlineCss: true,
-          stripComments: true,
-          excludes: nonTBComponents,
-          redirects: redirects
-        }))
-        .pipe(header(HEADER_STR))
-        .pipe(rename('tf-tensorboard.html' + suffix))
-        .pipe(gulp.dest('./dist'));
-  }
-}
diff --git a/tensorflow/tensorboard/gulpfile.js b/tensorflow/tensorboard/gulpfile.js
deleted file mode 100644
index c03c4fa..0000000
--- a/tensorflow/tensorboard/gulpfile.js
+++ /dev/null
@@ -1,91 +0,0 @@
-/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-
-const gulp = require('gulp');
-const server = require('gulp-server-livereload');
-const minimist = require('minimist');
-const util = require('./gulp_tasks/util');
-
-const options = minimist(process.argv.slice(2), {
-  default: {
-    p: 8000,       // port for gulp server
-    h: '0.0.0.0',  // host to serve on
-  }
-});
-
-function getTask(task) {
-    return require('./gulp_tasks/' + task);
-}
-
-
-gulp.task('compile', getTask('compile')(true));
-gulp.task('first-compile', getTask('compile')(true));
-gulp.task('compile-without-deps', getTask('compile')(false));
-gulp.task('test.onlytest', getTask('test'));
-gulp.task('test', ['compile'], getTask('test'));
-
-gulp.task('watch', [], function() {
-  // Avoid watching generated .d.ts in the build (aka output) directory.
-  return gulp.watch(
-      ['components/tf_*/**/*.ts', 'components/vz_*/**/*.ts'],
-      {ignoreInitial: true}, ['compile']);
-});
-
-const httpPrefix = 'http://' + options.h + ':' + options.p + '/components';
-const proxies = util.tbComponents.map(function(component) {
-  return {
-    source: '/components' + component.replace(/_/g, '-'),
-    target: httpPrefix + component
-  };
-});
-
-// Do first-compile before turning on server, to avoid spamming
-// livereload info
-// TODO(danmane): Disconnect this once we can get livereload to
-// no longer spam.
-gulp.task('server', ['first-compile'], function() {
-  gulp.src('.').pipe(server({
-    host: options.h,
-    port: options.p,
-    livereload: {
-      enable: true,
-      // Don't livereload on .ts changes, since they aren't loaded by browser.
-      filter: function(filePath, cb) { cb(!(/\.ts$/.test(filePath))); },
-      port: 27729 + options.p
-    },
-    proxies: proxies,
-    directoryListing: true,
-  }));
-});
-
-// TODO(danmane): When testing is nicer, integrate into vulcanize task
-// gulp vulcanize: Regenerate the tf-tensorboard.html.OPENSOURCE file for pre-release
-gulp.task(
-    'vulcanize', ['compile-without-deps'],
-    getTask('vulcanize')(false));
-// gulp regenerate: Regenerate the tf-tensorboard.html for interactive bazel development
-gulp.task(
-    'regenerate', ['compile-without-deps'],
-    getTask('vulcanize')(true));
-
-// TODO(danmane): consider making bower install part of default task
-gulp.task('default', ['watch', 'server']);
-
-// Clean all compiled JS files.
-const cleanCompiledTypeScript = require('gulp-clean-compiled-typescript');
-gulp.task('clean', function () {
-  return gulp.src(['./components/**/*.ts', '!./components/**/deps.d.ts'])
-      .pipe(cleanCompiledTypeScript());
-});
diff --git a/tensorflow/tensorboard/java/org/tensorflow/tensorboard/vulcanize/BUILD b/tensorflow/tensorboard/java/org/tensorflow/tensorboard/vulcanize/BUILD
index 07fc3a7..de57d7c 100644
--- a/tensorflow/tensorboard/java/org/tensorflow/tensorboard/vulcanize/BUILD
+++ b/tensorflow/tensorboard/java/org/tensorflow/tensorboard/vulcanize/BUILD
@@ -5,6 +5,7 @@
 java_binary(
     name = "Vulcanize",
     srcs = ["Vulcanize.java"],
+    visibility = ["//visibility:public"],
     deps = [
         "@com_google_guava",
         "@com_google_protobuf_java",
diff --git a/tensorflow/tensorboard/lib/BUILD b/tensorflow/tensorboard/lib/BUILD
deleted file mode 100644
index 9c49739..0000000
--- a/tensorflow/tensorboard/lib/BUILD
+++ /dev/null
@@ -1,22 +0,0 @@
-# Description:
-# BUILD rules for the static resources in TensorBoard.
-
-package(default_visibility = [
-    "//tensorflow:internal",
-])
-
-licenses(["notice"])  # Apache 2.0
-
-exports_files([
-    "LICENSE",
-])
-
-filegroup(
-    name = "all_files",
-    srcs = glob(
-        [
-            "**/*",
-        ],
-    ),
-    visibility = ["//tensorflow:__subpackages__"],
-)
diff --git a/tensorflow/tensorboard/lib/css/global.css b/tensorflow/tensorboard/lib/css/global.css
deleted file mode 100644
index cb6e966..0000000
--- a/tensorflow/tensorboard/lib/css/global.css
+++ /dev/null
@@ -1,21 +0,0 @@
-/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
-
-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.
-==============================================================================*/
-
-html, body {
-  margin: 0;
-  padding: 0;
-  height: 100%;
-  font-family: "RobotoDraft","Roboto",sans-serif;
-}
diff --git a/tensorflow/tensorboard/package.json b/tensorflow/tensorboard/package.json
deleted file mode 100644
index d424f10..0000000
--- a/tensorflow/tensorboard/package.json
+++ /dev/null
@@ -1,39 +0,0 @@
-{
-  "name": "tensorflow-vis",
-  "version": "0.0.0",
-  "description": "Visualizers for TensorFlow",
-  "scripts": {
-    "test": "gulp test",
-    "prep": "npm install && bower install && typings install",
-    "compile": "gulp compile"
-  },
-  "keywords": [
-    "tensorflow"
-  ],
-  "author": "Google",
-  "license": "Apache-2.0",
-  "devDependencies": {
-    "browserify": "^13.1.0",
-    "gulp": "~3.9.0",
-    "gulp-bower": "0.0.13",
-    "gulp-clean-compiled-typescript": "~1.0.1",
-    "gulp-cli": "^1.1.0",
-    "gulp-concat": "^2.6.0",
-    "gulp-filter": "~3.0.1",
-    "gulp-header": "~1.7.1",
-    "gulp-rename": "~1.2.2",
-    "gulp-replace": "~0.5.4",
-    "gulp-server-livereload": "1.9.2",
-    "gulp-typescript": "~2.10.0",
-    "gulp-util": "~3.0.7",
-    "gulp-vulcanize": "~6.1.0",
-    "merge2": "~0.3.6",
-    "minimist": "~1.2.0",
-    "tsify": "^0.14.8",
-    "typescript": "2.3.1",
-    "typings": "1.4.0",
-    "vinyl-source-stream": "^1.1.0",
-    "vulcanize": "^1.14.0",
-    "web-component-tester": "4.2.2"
-  }
-}
diff --git a/tensorflow/tensorboard/tsconfig.json b/tensorflow/tensorboard/tsconfig.json
deleted file mode 100644
index ac69c30..0000000
--- a/tensorflow/tensorboard/tsconfig.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
-  "compilerOptions": {
-    "noImplicitAny": false,
-    "noEmitOnError": true,
-    "target": "ES5",
-    "module": "commonjs"
-  },
-  "compileOnSave": false,
-  "exclude": [
-    "node_modules",
-    "typings/main.d.ts",
-    "typings/main",
-    "lib",
-    "components/**/deps.d.ts"
-  ]
-}
diff --git a/tensorflow/tensorboard/wct.conf.json b/tensorflow/tensorboard/wct.conf.json
deleted file mode 100644
index 519218c..0000000
--- a/tensorflow/tensorboard/wct.conf.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "suites": [
-    "components/tf_*/test",
-    "components/vz_*/test"
-  ],
-  "plugins": ["local"]
-}
diff --git a/tensorflow/tools/pip_package/BUILD b/tensorflow/tools/pip_package/BUILD
index 377a687..c44b966 100644
--- a/tensorflow/tools/pip_package/BUILD
+++ b/tensorflow/tools/pip_package/BUILD
@@ -96,6 +96,7 @@
         "//third_party/hadoop:LICENSE.txt",
         "@boringssl//:LICENSE",
         "@com_googlesource_code_re2//:LICENSE",
+        "@com_microsoft_typescript//:LICENSE.txt",
         "@curl//:COPYING",
         "@eigen_archive//:COPYING.MPL2",
         "@farmhash_archive//:COPYING",
@@ -109,13 +110,14 @@
         "@libxsmm_archive//:LICENSE",
         "@local_config_sycl//sycl:LICENSE.text",
         "@nanopb_git//:LICENSE.txt",
+        "@org_html5lib//:LICENSE",
+        "@org_mozilla_bleach//:LICENSE",
+        "@org_nodejs//:LICENSE",
+        "@org_pocoo_werkzeug//:LICENSE",
+        "@org_pythonhosted_markdown//:LICENSE.md",
         "@png_archive//:LICENSE",
         "@protobuf//:LICENSE",
         "@six_archive//:LICENSE",
-        "@org_html5lib//:LICENSE",
-        "@org_mozilla_bleach//:LICENSE",
-        "@org_pocoo_werkzeug//:LICENSE",
-        "@org_pythonhosted_markdown//:LICENSE.md",
         "@snappy//:COPYING",
         "@zlib_archive//:zlib.h",
     ] + if_not_windows([
diff --git a/tensorflow/tools/pip_package/setup.py b/tensorflow/tools/pip_package/setup.py
index f898207..ae6516d 100644
--- a/tensorflow/tools/pip_package/setup.py
+++ b/tensorflow/tools/pip_package/setup.py
@@ -190,10 +190,7 @@
     package_data={
         'tensorflow': [
             EXTENSION_NAME,
-            'tensorboard/dist/bazel-html-imports.html',
-            'tensorboard/dist/index.html',
-            'tensorboard/dist/tf-tensorboard.html',
-            'tensorboard/lib/css/global.css',
+            'tensorboard/components/index.html',
             'tensorboard/TAG',
         ] + matches,
     },
diff --git a/tensorflow/workspace.bzl b/tensorflow/workspace.bzl
index 2a206b0..f41dffe 100644
--- a/tensorflow/workspace.bzl
+++ b/tensorflow/workspace.bzl
@@ -669,6 +669,10 @@
           ],
       },
       sha256_urls_windows = {
+          "3d4cfca9dcec556a077a2324bf5bd165ea3e6e64a2bfd7fc6e7a1f0dc4eb552b": [
+              "http://mirror.bazel.build/raw.githubusercontent.com/nodejs/node/v4.3.2/LICENSE",
+              "https://raw.githubusercontent.com/nodejs/node/v4.3.2/LICENSE",
+          ],
           "606c44c42d17866c017c50c0afadad411d9492ac4281d2431b937f881911614e": [
               "http://mirror.bazel.build/nodejs.org/dist/v4.3.2/win-x64/node.exe",
               "http://nodejs.org/dist/v4.3.2/win-x64/node.exe",
@@ -698,6 +702,10 @@
       name = "com_microsoft_typescript",
       licenses = ["notice"],  # Apache 2.0
       sha256_urls = {
+          "a7d00bfd54525bc694b6e32f64c7ebcf5e6b7ae3657be5cc12767bce74654a47": [
+              "http://mirror.bazel.build/raw.githubusercontent.com/Microsoft/TypeScript/v2.3.1/LICENSE.txt",
+              "https://raw.githubusercontent.com/Microsoft/TypeScript/v2.3.1/LICENSE.txt",
+          ],
           "8465342c318f9c4cf0a29b109fa63ee3742dd4dc7080d05d9fd8f604814d04cf": [
               "http://mirror.bazel.build/raw.githubusercontent.com/Microsoft/TypeScript/v2.3.1/lib/tsc.js",
               "https://raw.githubusercontent.com/Microsoft/TypeScript/v2.3.1/lib/tsc.js",
@@ -754,6 +762,10 @@
       # no @license header
       licenses = ["notice"],  # MIT
       sha256_urls = {
+          "0e94aada97f12dee6118064add9170484c55022f5d53206ee4407143cd36ddcd": [
+              "http://mirror.bazel.build/raw.githubusercontent.com/sloisel/numeric/v1.2.6/license.txt",
+              "https://raw.githubusercontent.com/sloisel/numeric/v1.2.6/license.txt",
+          ],
           "dfaca3b8485bee735788cc6eebca82ea25719adc1fb8911c7799c6bd5a95df3b": [
               "http://mirror.bazel.build/raw.githubusercontent.com/sloisel/numeric/v1.2.6/src/numeric.js",
               "https://raw.githubusercontent.com/sloisel/numeric/v1.2.6/src/numeric.js",
@@ -802,6 +814,10 @@
       # no @license header
       licenses = ["notice"],  # MIT
       sha256_urls = {
+          "6a349742a6cb219d5a2fc8d0844f6d89a6efc62e20c664450d884fc7ff2d6015": [
+              "http://mirror.bazel.build/raw.githubusercontent.com/cpettitt/dagre/v0.7.4/LICENSE",
+              "https://raw.githubusercontent.com/cpettitt/dagre/v0.7.4/LICENSE",
+          ],
           "7323829ddd77924a69e2b1235ded3eac30acd990da0f037e0fbd3c8e9035b50d": [
               "http://mirror.bazel.build/raw.githubusercontent.com/cpettitt/dagre/v0.7.4/dist/dagre.core.js",
               "https://raw.githubusercontent.com/cpettitt/dagre/v0.7.4/dist/dagre.core.js",
@@ -813,6 +829,10 @@
       name = "io_github_cpettitt_graphlib",
       licenses = ["notice"],  # MIT
       sha256_urls = {
+          "6a349742a6cb219d5a2fc8d0844f6d89a6efc62e20c664450d884fc7ff2d6015": [
+              "http://mirror.bazel.build/raw.githubusercontent.com/cpettitt/graphlib/v1.0.7/LICENSE",
+              "https://raw.githubusercontent.com/cpettitt/graphlib/v1.0.7/LICENSE",
+          ],
           "772045d412b1513b549be991c2e1846c38019429d43974efcae943fbe83489bf": [
               "http://mirror.bazel.build/raw.githubusercontent.com/cpettitt/graphlib/v1.0.7/dist/graphlib.core.js",
               "https://raw.githubusercontent.com/cpettitt/graphlib/v1.0.7/dist/graphlib.core.js",
@@ -825,6 +845,10 @@
       # no @license header
       licenses = ["notice"],  # MIT
       sha256_urls = {
+          "633f2861a9a862b9cd7967e841e14dd3527912f209d6563595774fa31e3d84cb": [
+              "http://mirror.bazel.build/raw.githubusercontent.com/waylonflinn/weblas/v0.9.0/LICENSES",
+              "https://raw.githubusercontent.com/waylonflinn/weblas/v0.9.0/LICENSE",
+          ],
           "f138fce57f673ca8a633f4aee5ae5b6fcb6ad0de59069a42a74e996fd04d8fcc": [
               "http://mirror.bazel.build/raw.githubusercontent.com/waylonflinn/weblas/v0.9.0/dist/weblas.js",
               "https://raw.githubusercontent.com/waylonflinn/weblas/v0.9.0/dist/weblas.js",