diff options
author | Justine Tunney <jart@google.com> | 2017-05-17 16:13:33 -0700 |
---|---|---|
committer | TensorFlower Gardener <gardener@tensorflow.org> | 2017-05-17 16:17:17 -0700 |
commit | 52b4d471ebde27b5065a4f6195049a679d12d97e (patch) | |
tree | 1c8eb2723802e520ceb880db63a00f0de8f2e7a3 | |
parent | 9d9aa78c105a2414deed6ced31fb7699f08e55bb (diff) |
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
70 files changed, 858 insertions, 29688 deletions
@@ -20,7 +20,7 @@ load("//tensorflow:workspace.bzl", "tf_workspace") #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 @@ load("//tensorflow:workspace.bzl", "tf_workspace") #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 @@ load("//tensorflow:workspace.bzl", "tf_workspace") tf_workspace() new_http_archive( - name = "inception5h", - build_file = "models.BUILD", - url = "https://storage.googleapis.com/download.tensorflow.org/models/inception5h.zip", - sha256 = "d13569f6a98159de37e92e9c8ec4dae8f674fbf475f69fe6199b514f756d4364" -) - -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" -) - -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", + 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 = "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", + 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 = "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 eabd1d6450..0000000000 --- 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 8f67ae25df..acd5c108f9 100644 --- a/tensorflow/BUILD +++ b/tensorflow/BUILD @@ -321,7 +321,6 @@ filegroup( "//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 @@ filegroup( "//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 @@ filegroup( "//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 83b405fd4e..62afa9481b 100644 --- a/tensorflow/contrib/cmake/CMakeLists.txt +++ b/tensorflow/contrib/cmake/CMakeLists.txt @@ -261,7 +261,6 @@ if(tensorflow_BUILD_CC_EXAMPLE) 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 457868c72b..0000000000 --- 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 b660508b8b..793f7b22f1 100755 --- a/tensorflow/contrib/cmake/tf_python.cmake +++ b/tensorflow/contrib/cmake/tf_python.cmake @@ -514,13 +514,6 @@ add_python_module("tensorflow/contrib/training/python/training") 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 @@ endif(WIN32) 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 @@ add_custom_command(TARGET tf_python_build_pip_package POST_BUILD # 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 333544ec7a..0000000000 --- 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 98b9642545..0000000000 --- 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 9772538524..11715319a0 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 @@ py_binary( ) 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 3ff2c87dab..8e86bf04db 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 - -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`. - -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 +## Launching TensorBoard Proper -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: +Running TensorBoard automatically asks Bazel to create a vulcanized HTML binary: - 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 9afcd23e9e..0000000000 --- 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 497c02ced8..0000000000 --- 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 d27a22a82b..adbdea5f45 100644 --- a/tensorflow/tensorboard/backend/BUILD +++ b/tensorflow/tensorboard/backend/BUILD @@ -57,7 +57,7 @@ py_test( 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 3c8963e302..c38ad92be6 100644 --- a/tensorflow/tensorboard/backend/application.py +++ b/tensorflow/tensorboard/backend/application.py @@ -24,7 +24,6 @@ from __future__ import print_function import csv import imghdr -import mimetypes import os import re import threading @@ -159,8 +158,6 @@ class TensorBoardWSGIApp(object): 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 @@ class TensorBoardWSGIApp(object): @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 @@ class TensorBoardWSGIApp(object): 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 4ea627def7..e8556c0c61 100644 --- a/tensorflow/tensorboard/backend/application_test.py +++ b/tensorflow/tensorboard/backend/application_test.py @@ -154,11 +154,6 @@ class TensorboardServerTest(test.TestCase): 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 0a0fac45fd..0000000000 --- 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 2c2921d988..0000000000 --- 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 301425d96d..2d2c2d2d7b 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 index 56b37ebe4b..d319f576fc 100644 --- a/tensorflow/tensorboard/components/tf_imports_google/graphlib.html +++ b/tensorflow/tensorboard/components/analytics.html @@ -15,4 +15,4 @@ See the License for the specific language governing permissions and 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 c790a76f75..0000000000 --- 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 index 89bcfb733e..0652902cfa 100644 --- a/tensorflow/tensorboard/components/tf_tensorboard_d3v4/dist.html +++ b/tensorflow/tensorboard/components/tensorboard.html @@ -16,17 +16,12 @@ See the License for the specific language governing permissions and 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 9172ebb22a..010643b856 100644 --- a/tensorflow/tensorboard/components/tf_audio_dashboard/BUILD +++ b/tensorflow/tensorboard/components/tf_audio_dashboard/BUILD @@ -44,9 +44,9 @@ tensorboard_webcomponent_library( ], 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 @@ tensorboard_ts_library( 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 d9f3a035d8..14781f327c 100644 --- a/tensorflow/tensorboard/components/tf_backend/BUILD +++ b/tensorflow/tensorboard/components/tf_backend/BUILD @@ -58,7 +58,7 @@ tensorboard_webcomponent_library( 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 @@ tensorboard_ts_library( 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 019283816f..179ca27cd2 100644 --- a/tensorflow/tensorboard/components/tf_backend_d3v4/BUILD +++ b/tensorflow/tensorboard/components/tf_backend_d3v4/BUILD @@ -13,6 +13,7 @@ web_library( "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 3e64fccd2e..f6f1be099a 100644 --- a/tensorflow/tensorboard/components/tf_color_scale/BUILD +++ b/tensorflow/tensorboard/components/tf_color_scale/BUILD @@ -48,7 +48,7 @@ tensorboard_webcomponent_library( ], 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 @@ tensorboard_ts_library( ], 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 321e4a821e..5a9e941e46 100644 --- a/tensorflow/tensorboard/components/tf_dashboard_common/BUILD +++ b/tensorflow/tensorboard/components/tf_dashboard_common/BUILD @@ -65,7 +65,7 @@ tensorboard_webcomponent_library( 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 @@ tensorboard_ts_library( 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 38c3eb6c11..65341e7647 100644 --- a/tensorflow/tensorboard/components/tf_distribution_dashboard/BUILD +++ b/tensorflow/tensorboard/components/tf_distribution_dashboard/BUILD @@ -42,9 +42,9 @@ tensorboard_webcomponent_library( ], 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 @@ tensorboard_ts_library( 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 50f7a309ed..7c0a4a44a2 100644 --- a/tensorflow/tensorboard/components/tf_graph_common/BUILD +++ b/tensorflow/tensorboard/components/tf_graph_common/BUILD @@ -51,7 +51,7 @@ tensorboard_webcomponent_library( ], 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 @@ tensorboard_ts_library( 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 3f17bda34c..d8e2f672f8 100644 --- a/tensorflow/tensorboard/components/tf_histogram_dashboard/BUILD +++ b/tensorflow/tensorboard/components/tf_histogram_dashboard/BUILD @@ -42,9 +42,9 @@ tensorboard_webcomponent_library( ], 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 ac4c41d00f..81d1234d66 100644 --- a/tensorflow/tensorboard/components/tf_imports/BUILD +++ b/tensorflow/tensorboard/components/tf_imports/BUILD @@ -24,7 +24,7 @@ web_library( 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 @@ web_library( 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 edf759052f..abebe34368 100644 --- a/tensorflow/tensorboard/components/tf_imports_d3v4/BUILD +++ b/tensorflow/tensorboard/components/tf_imports_d3v4/BUILD @@ -26,7 +26,7 @@ web_library( name = "numericjs", srcs = [ "numericjs.html", - "@com_numericjs", + "@com_numericjs//:numeric.js", ], path = "/tf-imports", ) @@ -35,7 +35,7 @@ web_library( name = "weblas", srcs = [ "weblas.html", - "@io_github_waylonflinn_weblas", + "@io_github_waylonflinn_weblas//:weblas.js", ], path = "/tf-imports", ) @@ -44,7 +44,7 @@ web_library( 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 @@ web_library( name = "dagre", srcs = [ "dagre.html", - "@io_github_cpettitt_dagre", + "@io_github_cpettitt_dagre//:dagre.core.js", ], path = "/tf-imports", deps = [ @@ -67,7 +67,7 @@ web_library( 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 60d9cce777..0000000000 --- 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 dbfd11aa87..0000000000 --- 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 5b8b981741..0000000000 --- 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/plottable.html b/tensorflow/tensorboard/components/tf_imports_google/plottable.html deleted file mode 100644 index 6f9678f9cb..0000000000 --- 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 c1fbf80c4b..46c91c484f 100644 --- a/tensorflow/tensorboard/components/tf_scalar_dashboard/BUILD +++ b/tensorflow/tensorboard/components/tf_scalar_dashboard/BUILD @@ -49,10 +49,10 @@ tensorboard_webcomponent_library( ], 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 @@ tensorboard_ts_library( 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 8b2e006367..940d09681d 100644 --- a/tensorflow/tensorboard/components/tf_storage/BUILD +++ b/tensorflow/tensorboard/components/tf_storage/BUILD @@ -52,8 +52,8 @@ tensorboard_webcomponent_library( 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 @@ tensorboard_ts_library( 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 683302bb45..e17a083cb1 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,32 +8,35 @@ licenses(["notice"]) # Apache 2.0 web_library( name = "tf_tensorboard_d3v4", srcs = [ + "style.html", "tf-tensorboard.html", ":ts", ], path = "/tf-tensorboard", + visibility = ["//visibility:public"], deps = [ - "@org_polymer_iron_icons", - "@org_polymer_paper_button", - "@org_polymer_paper_checkbox", - "@org_polymer_paper_dialog", - "@org_polymer_paper_header_panel", - "@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_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", + "@org_polymer_paper_dialog", + "@org_polymer_paper_header_panel", + "@org_polymer_paper_icon_button", + "@org_polymer_paper_tabs", + "@org_polymer_paper_toolbar", ], ) @@ -49,25 +51,6 @@ web_library( ], ) -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 95f5b718a5..c8a9238aef 100644 --- a/tensorflow/tensorboard/components/tf_tensorboard_d3v4/demo.html +++ b/tensorflow/tensorboard/components/tf_tensorboard_d3v4/demo.html @@ -16,17 +16,10 @@ See the License for the specific language governing permissions and 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/lodash.html b/tensorflow/tensorboard/components/tf_tensorboard_d3v4/style.html index eb8fef2883..575e89e398 100644 --- a/tensorflow/tensorboard/components/tf_imports_google/lodash.html +++ b/tensorflow/tensorboard/components/tf_tensorboard_d3v4/style.html @@ -15,4 +15,14 @@ See the License for the specific language governing permissions and limitations under the License. --> -<link rel="import" href="../lodash-library/lodash-library.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 f3f5d590f9..3a56535410 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 @@ limitations under the License. <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 3346805fb0..3b46082bec 100644 --- a/tensorflow/tensorboard/components/vz_distribution_chart/BUILD +++ b/tensorflow/tensorboard/components/vz_distribution_chart/BUILD @@ -51,7 +51,7 @@ tensorboard_webcomponent_library( 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 @@ tensorboard_ts_library( 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 0000000000..eccc411f1b --- /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 63dd04485f..0ef092fccc 100644 --- a/tensorflow/tensorboard/components/vz_heatmap/demo/index.html +++ b/tensorflow/tensorboard/components/vz_heatmap/demo/index.html @@ -22,6 +22,7 @@ limitations under the License. <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 @@ limitations under the License. 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 7acba47d5f..4a00542276 100644 --- a/tensorflow/tensorboard/components/vz_heatmap/vz-heatmap.html +++ b/tensorflow/tensorboard/components/vz_heatmap/vz-heatmap.html @@ -89,7 +89,7 @@ fits itself to the width of the container it is placed in. _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 @@ fits itself to the width of the container it is placed in. */ _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 @@ fits itself to the width of the container it is placed in. } }, _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 0000000000..b62629fc55 --- /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 0000000000..0ef092fccc --- /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 index 2268e6d7d4..656306499e 100644 --- a/tensorflow/tensorboard/dist/bazel-html-imports.html +++ b/tensorflow/tensorboard/components/vz_heatmap_d3v4/index.html @@ -16,8 +16,16 @@ See the License for the specific language governing permissions and 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 0000000000..8a3503468f --- /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 d36cf2b331..3ca3936f32 100644 --- a/tensorflow/tensorboard/components/vz_histogram_timeseries/BUILD +++ b/tensorflow/tensorboard/components/vz_histogram_timeseries/BUILD @@ -37,7 +37,7 @@ tensorboard_webcomponent_library( 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 130d58dec7..6b988e5279 100644 --- a/tensorflow/tensorboard/components/vz_line_chart/BUILD +++ b/tensorflow/tensorboard/components/vz_line_chart/BUILD @@ -54,7 +54,7 @@ tensorboard_webcomponent_library( 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 @@ tensorboard_ts_library( ], 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 ab5d7c3989..795fc5cee4 100644 --- a/tensorflow/tensorboard/components/vz_sorting/BUILD +++ b/tensorflow/tensorboard/components/vz_sorting/BUILD @@ -46,5 +46,4 @@ tensorboard_ts_library( 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 d1cf5a596a..649bfc5c6e 100644 --- a/tensorflow/tensorboard/components/vz_sorting/test/BUILD +++ b/tensorflow/tensorboard/components/vz_sorting/test/BUILD @@ -34,7 +34,15 @@ tensorboard_ts_library( 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 5f09faf31d..0000000000 --- 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 9f13cdfb47..0000000000 --- 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("#gradient");"></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 8f4666a8c1..0000000000 --- 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 01af60eba7..0000000000 --- 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 0c8b14a4cd..0000000000 --- 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 0d73f69c73..0000000000 --- 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 d2286f1d6c..0000000000 --- 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 c03c4faebc..0000000000 --- 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 07fc3a70a7..de57d7ce2e 100644 --- a/tensorflow/tensorboard/java/org/tensorflow/tensorboard/vulcanize/BUILD +++ b/tensorflow/tensorboard/java/org/tensorflow/tensorboard/vulcanize/BUILD @@ -5,6 +5,7 @@ licenses(["notice"]) # Apache 2.0 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 9c497396c6..0000000000 --- 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 cb6e966fdd..0000000000 --- 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 d424f103dd..0000000000 --- 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 ac69c30533..0000000000 --- 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 519218ce41..0000000000 --- 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 377a687c34..c44b966451 100644 --- a/tensorflow/tools/pip_package/BUILD +++ b/tensorflow/tools/pip_package/BUILD @@ -96,6 +96,7 @@ filegroup( "//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 @@ filegroup( "@libxsmm_archive//:LICENSE", "@local_config_sycl//sycl:LICENSE.text", "@nanopb_git//:LICENSE.txt", - "@png_archive//:LICENSE", - "@protobuf//:LICENSE", - "@six_archive//:LICENSE", "@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", "@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 f8982073c3..ae6516db89 100644 --- a/tensorflow/tools/pip_package/setup.py +++ b/tensorflow/tools/pip_package/setup.py @@ -190,10 +190,7 @@ setup( 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 2a206b0acd..f41dffefec 100644 --- a/tensorflow/workspace.bzl +++ b/tensorflow/workspace.bzl @@ -669,6 +669,10 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], }, 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 @@ def tf_workspace(path_prefix="", tf_repo_name=""): 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 @@ def tf_workspace(path_prefix="", tf_repo_name=""): # 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 @@ def tf_workspace(path_prefix="", tf_repo_name=""): # 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 @@ def tf_workspace(path_prefix="", tf_repo_name=""): 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 @@ def tf_workspace(path_prefix="", tf_repo_name=""): # 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", |