aboutsummaryrefslogtreecommitdiffhomepage
path: root/tensorflow/tensorboard/components
diff options
context:
space:
mode:
Diffstat (limited to 'tensorflow/tensorboard/components')
-rw-r--r--tensorflow/tensorboard/components/hydrogen-join/demo/index.html118
-rw-r--r--tensorflow/tensorboard/components/hydrogen-join/hydrogen-join.html118
-rw-r--r--tensorflow/tensorboard/components/hydrogen-set/demo/index.html106
-rw-r--r--tensorflow/tensorboard/components/hydrogen-set/hydrogen-set.html174
-rw-r--r--tensorflow/tensorboard/components/imports/d3.html1
-rw-r--r--tensorflow/tensorboard/components/imports/lodash.html1
-rw-r--r--tensorflow/tensorboard/components/imports/plottable.html3
-rw-r--r--tensorflow/tensorboard/components/tf-categorizer/categorizer.ts133
-rw-r--r--tensorflow/tensorboard/components/tf-categorizer/demo/index.html97
-rw-r--r--tensorflow/tensorboard/components/tf-categorizer/index.html18
-rw-r--r--tensorflow/tensorboard/components/tf-categorizer/test/categorizerTest.ts139
-rw-r--r--tensorflow/tensorboard/components/tf-categorizer/tf-categorizer.html103
-rw-r--r--tensorflow/tensorboard/components/tf-collapsable-pane/demo/index.html17
-rw-r--r--tensorflow/tensorboard/components/tf-collapsable-pane/index.html18
-rw-r--r--tensorflow/tensorboard/components/tf-collapsable-pane/tf-collapsable-pane.html90
-rw-r--r--tensorflow/tensorboard/components/tf-dashboard-common/dashboard-style.html97
-rw-r--r--tensorflow/tensorboard/components/tf-dashboard-common/run-color-style.html62
-rw-r--r--tensorflow/tensorboard/components/tf-dashboard-common/scrollbar-style.html28
-rw-r--r--tensorflow/tensorboard/components/tf-dashboard-common/tensorboard-color.html11
-rw-r--r--tensorflow/tensorboard/components/tf-dashboard-common/tf-dashboard-layout.html50
-rw-r--r--tensorflow/tensorboard/components/tf-dashboard-common/tf-downloader.html85
-rw-r--r--tensorflow/tensorboard/components/tf-dashboard-common/tf-run-generator.html97
-rw-r--r--tensorflow/tensorboard/components/tf-dashboard-common/tf-url-generator.html50
-rw-r--r--tensorflow/tensorboard/components/tf-dashboard-common/urlGenerator.ts33
-rw-r--r--tensorflow/tensorboard/components/tf-dashboard-common/warning-style.html10
-rw-r--r--tensorflow/tensorboard/components/tf-event-dashboard/dataCoordinator.ts57
-rw-r--r--tensorflow/tensorboard/components/tf-event-dashboard/dataset.ts41
-rw-r--r--tensorflow/tensorboard/components/tf-event-dashboard/demo/data/alpha/d1.json1
-rw-r--r--tensorflow/tensorboard/components/tf-event-dashboard/demo/data/alpha/d2.json1
-rw-r--r--tensorflow/tensorboard/components/tf-event-dashboard/demo/data/alpha/d3.json1
-rw-r--r--tensorflow/tensorboard/components/tf-event-dashboard/demo/data/alpha/d4.json1
-rw-r--r--tensorflow/tensorboard/components/tf-event-dashboard/demo/data/beta/d1.json1
-rw-r--r--tensorflow/tensorboard/components/tf-event-dashboard/demo/data/beta/d2.json1
-rw-r--r--tensorflow/tensorboard/components/tf-event-dashboard/demo/data/beta/d3.json1
-rw-r--r--tensorflow/tensorboard/components/tf-event-dashboard/demo/data/beta/d4.json1
-rw-r--r--tensorflow/tensorboard/components/tf-event-dashboard/demo/data/runs.json22
-rw-r--r--tensorflow/tensorboard/components/tf-event-dashboard/demo/index.html17
-rw-r--r--tensorflow/tensorboard/components/tf-event-dashboard/dragZoomInteraction.ts150
-rw-r--r--tensorflow/tensorboard/components/tf-event-dashboard/tf-chart.html101
-rw-r--r--tensorflow/tensorboard/components/tf-event-dashboard/tf-chart.ts327
-rw-r--r--tensorflow/tensorboard/components/tf-event-dashboard/tf-color-scale.html69
-rw-r--r--tensorflow/tensorboard/components/tf-event-dashboard/tf-data-coordinator.html29
-rw-r--r--tensorflow/tensorboard/components/tf-event-dashboard/tf-event-dashboard.html208
-rw-r--r--tensorflow/tensorboard/components/tf-event-dashboard/tf-run-selector.html104
-rw-r--r--tensorflow/tensorboard/components/tf-event-dashboard/tf-tooltip-coordinator.html48
-rw-r--r--tensorflow/tensorboard/components/tf-event-dashboard/tf-x-type-selector.html75
-rw-r--r--tensorflow/tensorboard/components/tf-graph-board/tf-graph-board.html152
-rw-r--r--tensorflow/tensorboard/components/tf-graph-common/lib/colors.ts133
-rw-r--r--tensorflow/tensorboard/components/tf-graph-common/lib/common.ts236
-rw-r--r--tensorflow/tensorboard/components/tf-graph-common/lib/graph.ts889
-rw-r--r--tensorflow/tensorboard/components/tf-graph-common/lib/hierarchy.ts715
-rw-r--r--tensorflow/tensorboard/components/tf-graph-common/lib/layout.ts628
-rw-r--r--tensorflow/tensorboard/components/tf-graph-common/lib/parser.ts189
-rw-r--r--tensorflow/tensorboard/components/tf-graph-common/lib/render.ts1360
-rw-r--r--tensorflow/tensorboard/components/tf-graph-common/lib/scene/annotation.ts223
-rw-r--r--tensorflow/tensorboard/components/tf-graph-common/lib/scene/edge.ts177
-rw-r--r--tensorflow/tensorboard/components/tf-graph-common/lib/scene/minimap.ts269
-rw-r--r--tensorflow/tensorboard/components/tf-graph-common/lib/scene/node.ts525
-rw-r--r--tensorflow/tensorboard/components/tf-graph-common/lib/scene/scene.ts409
-rw-r--r--tensorflow/tensorboard/components/tf-graph-common/lib/template.ts282
-rw-r--r--tensorflow/tensorboard/components/tf-graph-common/tf-graph-common.html16
-rw-r--r--tensorflow/tensorboard/components/tf-graph-dashboard/tf-graph-dashboard.html118
-rw-r--r--tensorflow/tensorboard/components/tf-graph-info/tf-graph-info.html65
-rw-r--r--tensorflow/tensorboard/components/tf-graph-info/tf-node-info.html345
-rw-r--r--tensorflow/tensorboard/components/tf-graph-info/tf-node-list-item.html91
-rw-r--r--tensorflow/tensorboard/components/tf-graph-loader/tf-graph-loader.html172
-rw-r--r--tensorflow/tensorboard/components/tf-graph/demo/tf-graph-demo.html185
-rw-r--r--tensorflow/tensorboard/components/tf-graph/tf-graph-controls.html487
-rw-r--r--tensorflow/tensorboard/components/tf-graph/tf-graph-icon.html164
-rw-r--r--tensorflow/tensorboard/components/tf-graph/tf-graph-minimap.html69
-rw-r--r--tensorflow/tensorboard/components/tf-graph/tf-graph-params.html113
-rw-r--r--tensorflow/tensorboard/components/tf-graph/tf-graph-scene.html475
-rw-r--r--tensorflow/tensorboard/components/tf-graph/tf-graph-style.html339
-rw-r--r--tensorflow/tensorboard/components/tf-graph/tf-graph.html221
-rw-r--r--tensorflow/tensorboard/components/tf-histogram-dashboard/tf-histogram-dashboard.html210
-rw-r--r--tensorflow/tensorboard/components/tf-image-dashboard/demo/image-loader-demo.html73
-rw-r--r--tensorflow/tensorboard/components/tf-image-dashboard/demo/index.html39
-rw-r--r--tensorflow/tensorboard/components/tf-image-dashboard/tf-image-dashboard.html90
-rw-r--r--tensorflow/tensorboard/components/tf-image-dashboard/tf-image-grid.html166
-rw-r--r--tensorflow/tensorboard/components/tf-image-dashboard/tf-image-loader.html64
-rw-r--r--tensorflow/tensorboard/components/tf-multi-checkbox/demo/index.html162
-rw-r--r--tensorflow/tensorboard/components/tf-multi-checkbox/tf-multi-checkbox.html228
-rw-r--r--tensorflow/tensorboard/components/tf-regex-group/demo/index.html32
-rw-r--r--tensorflow/tensorboard/components/tf-regex-group/index.html18
-rw-r--r--tensorflow/tensorboard/components/tf-regex-group/tf-regex-group.html151
85 files changed, 13195 insertions, 0 deletions
diff --git a/tensorflow/tensorboard/components/hydrogen-join/demo/index.html b/tensorflow/tensorboard/components/hydrogen-join/demo/index.html
new file mode 100644
index 0000000000..238cff294d
--- /dev/null
+++ b/tensorflow/tensorboard/components/hydrogen-join/demo/index.html
@@ -0,0 +1,118 @@
+<!doctype html>
+<!--
+@license
+Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
+This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
+The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
+The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
+Code distributed by Google as part of the polymer project is also
+subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
+-->
+<html>
+ <head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+ <title>tf-graph Demo</title>
+ <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+ <link rel="import" href="../hydrogen-join.html">
+ <link rel="import" href="../../../bower_components/paper-button/paper-button.html">
+ <link rel="import" href="../../../bower_components/paper-slider/paper-slider.html">
+ </head>
+
+ <style>
+ body {
+ font-family: "RobotoDraft","Roboto",sans-serif;
+ font-size: 14;
+ }
+ </style>
+
+ <body unresolved>
+ <dom-module id="button-press-counter">
+ <style>
+ paper-button {
+ background-color: #4DB6AC;
+ color: white;
+ }
+ </style>
+ <template>
+ <paper-button raised on-click="increment"><span>[[count]]<span></paper-button>
+ </template>
+ <script>
+ Polymer({
+ is: "button-press-counter",
+ properties: {count: {notify: true, value: 0}},
+ increment: function() {this.count++;}
+ });
+ </script>
+ </dom-module>
+
+ <dom-module id="args-demo">
+ <template>
+ <h1>args-demo</h1>
+ <button-press-counter count="{{in1}}"></button-press-counter>
+ <button-press-counter count="{{in2}}"></button-press-counter>
+ <button-press-counter count="{{in3}}"></button-press-counter>
+ <hydrogen-join
+ in1="[[in1]]"
+ in2="[[in2]]"
+ in3="[[in3]]"
+ out="{{out}}"
+ id="argsjoin"
+ ></hydrogen-join>
+ <p>Output from the hydrogen-join: <span>[[out]]</span></p>
+ </template>
+ <script>
+ Polymer({
+ is: "args-demo",
+ properties: {
+ in1: Number,
+ in2: Number,
+ in3: Number,
+ out: Array,
+ },
+ });
+ </script>
+ </dom-module>
+
+ <dom-module id="repeat-demo">
+ <style>
+ .button {
+ padding: 3px;
+ margin-bottom: 4px;
+ display: inline-block;
+ }
+ </style>
+ <template>
+ <h1>repeat-demo</h1>
+ <hydrogen-join id="repeatjoin" in-join-property="count" out="{{out}}">
+ <template is="dom-repeat" items="[[counters]]">
+ <button-press-counter class="button" count="[[item]]"></button-press-counter>
+ </template>
+ </hydrogen-join>
+ <br>
+ <p> Move this slider to add/remove buttons. It stays synced! What magic! </p>
+ <paper-slider min="0" max="20" value="{{nCounters}}"></paper-slider>
+ <p>Output from the hydrogen-join: <span>[[out]]</span></p>
+ </template>
+ <script>
+ Polymer({
+ is: "repeat-demo",
+ properties: {
+ nCounters: {type: Number, value: 10},
+ counters: {type: Array, computed: "_makeCounters(nCounters)"},
+ },
+ _makeCounters: function(nCounters) {
+ var c = [];
+ for (var i=0; i<nCounters; i++) {
+ c.push(i);
+ }
+ return c;
+ }
+ });
+ </script>
+ </dom-module>
+
+ <args-demo id="argsdemo"></args-demo>
+ <repeat-demo id="repeatdemo"></repeat-demo>
+ </body>
+</html>
diff --git a/tensorflow/tensorboard/components/hydrogen-join/hydrogen-join.html b/tensorflow/tensorboard/components/hydrogen-join/hydrogen-join.html
new file mode 100644
index 0000000000..9f8dcfe2af
--- /dev/null
+++ b/tensorflow/tensorboard/components/hydrogen-join/hydrogen-join.html
@@ -0,0 +1,118 @@
+<link rel="import" href="../../bower_components/polymer/polymer.html">
+
+<!--
+A plumber component which joins a number of input bindings. It outputs a list
+consisting of all of the inputs that are not null or undefined.
+
+It can take explicit arguments [in0...in98] or it can pull its arguments from
+a dom-repeat template, provided that it is given the property name it is looking
+for on the repeated children, and that property has {notify: true}. The repeat
+binding involves some magic and may not be totally reliable; ping danmane@ if
+something goes wrong.
+
+Example:
+ <hydrogen-join
+ in1="[[foo1]]"
+ in2="[[foo2]]"
+ in3="[[foo3]]"
+ out="{{foos}}" // equivalent to [foo1].push(foo2).push(foo3)
+ ></hydrogen-join>
+
+ <hydrogen-join out="{{foo}}" in-join-property="out">
+ <template is="dom-repeat" items="[[foos]]">
+ <foo item="[[item]]"></foo> //foo has a property out: {type: Array, notify: true}
+ </template>
+ </hydrogen-join>
+
+Swapping the inJoinProperty is not currently supported, it will warn if you try.
+
+There's a possible bug in repeat mode if an element is removed from the dom-repeat,
+but continues to exist somewhere else, and continues to fire property-changed events.
+Then the hydrogen-join will inappropriately record its value, even though it is not
+still connected in hydrogen-join's DOM.
+
+@demo
+-->
+<dom-module id="hydrogen-join">
+ <script>
+ var declaration = {
+ is: 'hydrogen-join',
+ properties: {
+ out: {type: Array, readOnly: true, notify: true},
+ _items: {type: Array, value: function() {return [];}},
+ /* Property to pull from dom-repeated child nodes and then pull and join */
+ inJoinProperty: {type: String, observer: "_modifyJoinProperty"},
+ },
+ listeners: {
+ "dom-change": "_syncListenersAndState",
+ },
+ /* If we are in repeat-mode, ensure all event listeners are setup, and pull
+ * items out of whatever children currently exist
+ */
+ _syncListenersAndState: function() {
+ if (this.inJoinProperty == null) {
+ // this codepath is for pulling properties out of children in a repeat
+ return;
+ }
+ function repeatUpdateFunction(i) {
+ return function(e) {
+ this._items[i] = e.detail.value;
+ // Debounce just in case something else thrashes
+ this.debounce("updateOut", this._updateOutput)
+ }
+ }
+ // create a new items array to replace the old one, otherwise old items
+ // might never get removed when their corresponding element leaves
+ this._items = [];
+ // Sadly, we need to bind an update function onto every child node,
+ // because the property-changed event does not bubble.
+ for (var i=0; i<this.childNodes.length; i++) {
+ var child = this.childNodes[i];
+ if (child.properties != null && child.properties[this.inJoinProperty] != null) {
+ child.addEventListener(this.inJoinProperty + "-changed", repeatUpdateFunction(i).bind(this));
+ this._items[i] = child[this.inJoinProperty];
+ }
+ }
+ this._updateOutput();
+ },
+ _modifyJoinProperty: function(newJoinProperty, oldJoinProperty) {
+ if (oldJoinProperty != null) {
+ console.warning("Changing the join property may be unsafe. Have fun!");
+ }
+ this._syncListenersAndState();
+ },
+ _updateOutput: function() {
+ var out = [];
+ for (var i=0; i<99; i++) {
+ if (this._items[i] != null) {
+ out.push(this._items[i]);
+ }
+ }
+ this._setOut(out);
+ }
+ };
+
+ /* Programatically add properties in0-in98, with associated observers */
+ function argsUpdateFunction(i) {
+ return function(newval) {
+ this._items[i] = newval;
+ if (i === 98) {
+ console.warn("You're updating the last arg (98). Possibly some values are being lost");
+ }
+ if (this.inJoinProperty != null) {
+ console.warn("It looks like you're providing a join property and also arguments. This is not supported.")
+ }
+ this.debounce("updateOut", this._updateOutput)
+ }
+ }
+ // I got 99 arguments and ain't off by 1!
+ for (var i = 0; i < 99; i++) {
+ var propName = "in" + i;
+ var updateName = "_update" + i;
+ var property = {type: Object, observer: updateName};
+ declaration.properties[propName] = property;
+ declaration[updateName] = argsUpdateFunction(i);
+ }
+ Polymer(declaration);
+ </script>
+</dom-module>
diff --git a/tensorflow/tensorboard/components/hydrogen-set/demo/index.html b/tensorflow/tensorboard/components/hydrogen-set/demo/index.html
new file mode 100644
index 0000000000..6a1735a699
--- /dev/null
+++ b/tensorflow/tensorboard/components/hydrogen-set/demo/index.html
@@ -0,0 +1,106 @@
+<!doctype html>
+<!--
+@license
+Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
+This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
+The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
+The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
+Code distributed by Google as part of the polymer project is also
+subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
+-->
+<html>
+ <head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+ <title>hydrogen-set demo</title>
+ <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+ <link rel="import" href="../hydrogen-set.html">
+ <link rel="import" href="../../../bower_components/paper-checkbox/paper-checkbox.html">
+ </head>
+
+ <style>
+ body {
+ margin: 0;
+ font-family: "RobotoDraft","Roboto",sans-serif;
+ font-size: 14;
+ }
+ </style>
+
+ <body unresolved>
+ <dom-module id="x-a">
+ <template>
+ <template is="dom-repeat" items="[[items]]">
+ <span>[[item]]</span>
+ <paper-checkbox
+ name="[[item]]"
+ on-iron-change="_onChange"
+ checked="[[_isSelected(item, selected.*)]]"
+ ></paper-checkbox>
+ </template>
+ </template>
+ </dom-module>
+ <script>
+ Polymer({
+ is: 'x-a',
+ properties: {
+ selected: {
+ type: Array
+ },
+ items: {
+ type: Array,
+ value: function() {
+ return ["a","b","c","d","e"];
+ }
+ }
+ },
+ _onChange: function(e) {
+ var name = e.srcElement.name;
+ if (name) {
+ if (e.srcElement.checked) {
+ this.fire('select', name);
+ } else {
+ this.fire('unselect', name);
+ }
+ }
+ },
+ _isSelected: function(item, selected) {
+ return selected.base.indexOf(item) >= 0;
+ }
+ });
+ </script>
+ <template is="dom-bind">
+ <hydrogen-set
+ id="set"
+ event-add="{{add}}"
+ event-delete="{{del}}"
+ out-value="{{selected}}"
+ ></hydrogen-set>
+ <div>
+ Mutate the two sets below.
+ </div>
+ <br/>
+ <div>First Set</div>
+ <x-a
+ id="a"
+ on-select="add"
+ on-unselect="del"
+ selected="[[selected]]"
+ ></x-a>
+ <br/>
+ <br/>
+ <div>Second Set</div>
+ <x-a
+ id="b"
+ on-select="add"
+ on-unselect="del"
+ selected="[[selected]]"
+ ></x-a>
+ <br/>
+ <br/>
+ <div>List Selected:</div>
+ <template is="dom-repeat" items="[[selected]]">
+ <div>[[item]]</div>
+ </template>
+ </template>
+ </body>
+</html>
diff --git a/tensorflow/tensorboard/components/hydrogen-set/hydrogen-set.html b/tensorflow/tensorboard/components/hydrogen-set/hydrogen-set.html
new file mode 100644
index 0000000000..28fb8bd2a1
--- /dev/null
+++ b/tensorflow/tensorboard/components/hydrogen-set/hydrogen-set.html
@@ -0,0 +1,174 @@
+<link rel="import" href="../../bower_components/polymer/polymer.html">
+
+<!--
+hydrogen-set is a plumber component that is driven by events
+and produces data in the form of an upward-bindable set.
+It provides event handler functions like event-add and
+event-del, which may be attached elsewhere as event listeners.
+The hydrogen-set then captures those events and adds or
+removes their details to an internal set, which it
+publishes as out-value.
+
+As an example, you may have a list of input widgets that
+generate strings by firing an event. If you attach
+the hydrogen-set `event-add` function as the handler
+for the input widgets' input event, then the hydrogen-set
+will collect and deduplicate all of the strings the
+user generated.
+
+Thus, hydrogen-set is useful for capturing semantic
+events from across an application, and organizing
+the data generated into a single store.
+
+Example:
+
+ <hydrogen-set
+ event-add="{{add}}
+ event-delete="{{del}}
+ out-value="{{selected}}"
+ ></hydrogen-set>
+ <element-one
+ selected="{{selected}}"
+ on-select="add"
+ on-deselect="del"
+ ></element-one>
+ <element-two
+ selected="{{selected}}"
+ on-select="add"
+ on-deselect="del"
+ ></element-two>
+
+@demo demo/index.html
+-->
+<script>
+Polymer({
+ is: 'hydrogen-set',
+ properties: {
+ /**
+ * A function to bind to event callback where
+ * the detail value is the item to add.
+ *
+ * @property eventAdd
+ * @type Function
+ */
+ eventAdd: {
+ readOnly: true,
+ notify: true,
+ type: Function,
+ value: function() {
+ return function(e) {
+ this.add(e.detail);
+ }.bind(this);
+ }
+ },
+ /**
+ * A function to bind to event callback where
+ * the detail value is the item to remove.
+ *
+ * @property eventDelete
+ * @type Function
+ */
+ eventDelete: {
+ readOnly: true,
+ notify: true,
+ type: Function,
+ value: function() {
+ return function(e) {
+ this.delete(e.detail);
+ }.bind(this);
+ }
+ },
+ /**
+ * A function to bind to event callback where
+ * the detail value is the list of items that should
+ * replace the current set.
+ *
+ * @property eventUpdate
+ * @type Function
+ */
+ eventUpdate: {
+ readOnly: true,
+ notify: true,
+ type: Function,
+ value: function() {
+ return function(e) {
+ this.update(e.detail);
+ }.bind(this);
+ }
+ },
+ /**
+ * A function to bind to event callback
+ * which when called reset the set to the
+ * empty set ([]).
+ *
+ * @property eventClear
+ * @type Function
+ */
+ eventClear: {
+ readOnly: true,
+ notify: true,
+ type: Function,
+ value: function() {
+ return function(e) {
+ this.clear(e.detail);
+ }.bind(this);
+ }
+ },
+ /**
+ * The read-only array representing the set of
+ * items in this set.
+ *
+ * @property outValue
+ * @type Array
+ * @default []
+ */
+ outValue: {
+ type: Array,
+ readOnly: true,
+ notify: true,
+ value: function() {
+ return [];
+ }
+ }
+ },
+ /**
+ * Adds an item to the set.
+ */
+ add: function(value) {
+ if (this.outValue.indexOf(value) >= 0) { return; }
+ this.push('outValue', value);
+ },
+ /**
+ * Removes an item from the set.
+ */
+ delete: function(value) {
+ var i = this.outValue.indexOf(value);
+ if (i < 0) { return; }
+ this.splice('outValue', i, 1);
+ },
+ /**
+ * Sets the set to a specific array of items.
+ */
+ update: function(value) {
+ if (value.constructor === Array) {
+ var uniq = {};
+ var list = [];
+ for (var i=0, l = value.length; i < l; ++i) {
+ var item = value[i];
+ if (uniq.hasOwnProperty(item)) {
+ continue;
+ }
+ list.push(item);
+ uniq[item] = true;
+ }
+ this._setOutValue(list)
+ }
+ },
+ /**
+ * Resets the set to the empty set.
+ */
+ clear: function() {
+ this.update([]);
+ }
+});
+</script>
diff --git a/tensorflow/tensorboard/components/imports/d3.html b/tensorflow/tensorboard/components/imports/d3.html
new file mode 100644
index 0000000000..d63c480fd9
--- /dev/null
+++ b/tensorflow/tensorboard/components/imports/d3.html
@@ -0,0 +1 @@
+<script src="../../bower_components/d3/d3.min.js"></script>
diff --git a/tensorflow/tensorboard/components/imports/lodash.html b/tensorflow/tensorboard/components/imports/lodash.html
new file mode 100644
index 0000000000..1e94d2c1c4
--- /dev/null
+++ b/tensorflow/tensorboard/components/imports/lodash.html
@@ -0,0 +1 @@
+<script src="../../bower_components/lodash/lodash.min.js"></script>
diff --git a/tensorflow/tensorboard/components/imports/plottable.html b/tensorflow/tensorboard/components/imports/plottable.html
new file mode 100644
index 0000000000..08e636886a
--- /dev/null
+++ b/tensorflow/tensorboard/components/imports/plottable.html
@@ -0,0 +1,3 @@
+<link rel="import" href="d3.html">
+<script src="../../bower_components/plottable/plottable.min.js"></script>
+<link rel="stylesheet" type="text/css" href="../../bower_components/plottable/plottable.css">
diff --git a/tensorflow/tensorboard/components/tf-categorizer/categorizer.ts b/tensorflow/tensorboard/components/tf-categorizer/categorizer.ts
new file mode 100644
index 0000000000..e05078279e
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-categorizer/categorizer.ts
@@ -0,0 +1,133 @@
+/// <reference path="../../typings/tsd.d.ts" />
+
+module 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.
+ */
+
+ export interface Category {
+ // Categories that data is sorted into
+ name: string;
+ tags: string[];
+ }
+
+ export interface CustomCategorization {
+ // Defines a categorization strategy
+ categoryDefinitions: string[];
+ fallbackCategorizer: string;
+ /* {"TopLevelNamespaceCategorizer",
+ "LegacyUnderscoreCategorizer"} */
+ }
+
+ export interface Categorizer {
+ // Function that generates categories
+ (tags: string[]): Category[];
+ }
+
+ /* Canonical TensorFlow ops are namespaced using forward slashes.
+ * This fallback categorizer categorizes by the top-level namespace.
+ */
+ export var topLevelNamespaceCategorizer: Categorizer = splitCategorizer(/\//);
+
+ // Try to produce good categorizations on legacy graphs, which often
+ // are namespaced like l1_foo/bar or l2_baz/bam.
+ // If there is no leading underscore before the first forward slash,
+ // then it behaves the same as topLevelNamespaceCategorizer
+ export var legacyUnderscoreCategorizer: Categorizer = splitCategorizer(/[\/_]/);
+
+ export function fallbackCategorizer(s: string): Categorizer {
+ switch (s) {
+ case "TopLevelNamespaceCategorizer":
+ return topLevelNamespaceCategorizer;
+ case "LegacyUnderscoreCategorizer":
+ return legacyUnderscoreCategorizer;
+ default:
+ throw new Error("Unrecognized categorization strategy: " + s);
+ }
+ }
+
+ /* 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: (s: string) => string): Categorizer {
+ return (tags: string[]): Category[] => {
+ if (tags.length === 0) {
+ return [];
+ }
+ var sortedTags = tags.slice().sort();
+ var categories: Category[] = [];
+ var currentCategory = {
+ name: extractor(sortedTags[0]),
+ tags: [],
+ };
+ sortedTags.forEach((t: string) => {
+ 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: RegExp): Categorizer {
+ var extractor = (t: string) => {
+ return t.split(r)[0];
+ };
+ return extractorToCategorizer(extractor);
+ }
+
+ export interface CategoryDefinition {
+ name: string;
+ matches: (t: string) => boolean;
+ }
+
+ export function defineCategory(ruledef: string): CategoryDefinition {
+ var r = new RegExp(ruledef);
+ var f = function(tag: string): boolean {
+ return r.test(tag);
+ };
+ return { name: ruledef, matches: f };
+ }
+
+ export function _categorizer(rules: CategoryDefinition[], fallback: Categorizer) {
+ return function(tags: string[]): Category[] {
+ var remaining: d3.Set = d3.set(tags);
+ var userSpecified = rules.map((def: CategoryDefinition) => {
+ var tags: string[] = [];
+ remaining.forEach((t: string) => {
+ if (def.matches(t)) {
+ tags.push(t);
+ }
+ });
+ var cat = { name: def.name, tags: tags.sort() };
+ return cat;
+ });
+ var defaultCategories = fallback(remaining.values());
+ return userSpecified.concat(defaultCategories);
+ };
+ }
+
+ export function categorizer(s: CustomCategorization): Categorizer {
+ var rules = s.categoryDefinitions.map(defineCategory);
+ var fallback = fallbackCategorizer(s.fallbackCategorizer);
+ return _categorizer(rules, fallback);
+ };
+}
diff --git a/tensorflow/tensorboard/components/tf-categorizer/demo/index.html b/tensorflow/tensorboard/components/tf-categorizer/demo/index.html
new file mode 100644
index 0000000000..ea3f162aa5
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-categorizer/demo/index.html
@@ -0,0 +1,97 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+ <script src="../../../bower_components/d3/d3.js"></script>
+ <link rel="import" href="../tf-categorizer.html">
+ <link rel="import" href="../../../bower_components/iron-flex-layout/classes/iron-flex-layout.html">
+
+ </head>
+ <body>
+ <style>
+ </style>
+ <dom-module id="x-demo">
+ <style>
+ .container {
+ width: 255px;
+ padding: 10px;
+ border: 1px solid var(--paper-indigo-900);
+ border-radius: 5px;
+ position: fixed;
+ }
+ :host {
+ margin: 0px;
+ }
+
+ .categories {
+ font-family: "RobotoDraft",Helvetica;
+ margin-left: 300px;
+ width: 500px;
+ border: 1px solid var(--paper-indigo-500);
+ border-radius: 5px;
+ }
+
+ .category {
+ background-color: var(--paper-indigo-50);
+ margin: 20px;
+ padding: 20px;
+ border-radius: 5px;
+ }
+
+ .cat-name {
+ font-size: 20px;
+ }
+
+ .tag {
+ border-radius: 5px;
+ padding: 5px;
+ margin: 5px;
+ background-color: var(--paper-indigo-900);
+ color: white;
+ }
+ </style>
+ <template>
+ <div class="container">
+ <tf-categorizer categories="{{categories}}" tags="[[tags]]" id="demo"></tf-categorizer>
+ </div>
+ <div class="categories">
+ <template is="dom-repeat" items="[[categories]]">
+ <div class="category">
+ <p class="cat-name">Category: <span>[[item.name]]</span></p>
+ <div class="tags-container layout horizontal wrap">
+ <template is="dom-repeat" items="[[item.tags]]">
+ <span class="tag layout vertical center-center">[[item]]</span>
+ </template>
+ </div>
+ </template>
+ </div>
+ </template>
+ <script>
+
+ function tagsGenerator() {
+ var tags = ["special1", "special2", "special3", "special4", "special5"];
+ ["l1", "l2", "l3", "l4", "l5"].forEach(function(l) {
+ ["foo", "bar", "baz", "boink", "zod", "specialx"].forEach(function(x) {
+ tags.push(l + "/" + x);
+ });
+ });
+ return tags;
+ }
+
+ Polymer({
+ is: "x-demo",
+ properties: {
+ tags: { type: Array, value: tagsGenerator },
+ },
+ });
+ </script>
+ </dom-module>
+
+ <x-demo id="demo"></x-demo>
+ </body>
+ <script>
+ HTMLImports.whenReady(function() {
+ window.demo = document.getElementById("demo");
+ })
+ </script>
+</html>
diff --git a/tensorflow/tensorboard/components/tf-categorizer/index.html b/tensorflow/tensorboard/components/tf-categorizer/index.html
new file mode 100644
index 0000000000..f08a125f7c
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-categorizer/index.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<html>
+<head>
+
+ <title>tf-categorizer</title>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+
+ <script src="../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+ <link rel="import" href="../../bower_components/iron-component-page/iron-component-page.html">
+
+</head>
+<body>
+
+ <iron-component-page></iron-component-page>
+
+</body>
+</html>
diff --git a/tensorflow/tensorboard/components/tf-categorizer/test/categorizerTest.ts b/tensorflow/tensorboard/components/tf-categorizer/test/categorizerTest.ts
new file mode 100644
index 0000000000..be09c56c41
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-categorizer/test/categorizerTest.ts
@@ -0,0 +1,139 @@
+/// <reference path="../../../typings/tsd.d.ts" />
+/// <reference path="../categorizer.ts" />
+var assert = chai.assert;
+
+module Categorizer {
+ describe("categorizer", () => {
+ describe("topLevelNamespaceCategorizer", () => {
+ it("returns empty array on empty tags", () => {
+ assert.lengthOf(topLevelNamespaceCategorizer([]), 0);
+ });
+
+ it("handles a simple case", () => {
+ var simple = ["foo1/bar", "foo1/zod", "foo2/bar", "foo2/zod",
+ "gosh/lod/mar", "gosh/lod/ned"];
+ var expected = [
+ { name: "foo1", tags: ["foo1/bar", "foo1/zod"] },
+ { name: "foo2", tags: ["foo2/bar", "foo2/zod"] },
+ { name: "gosh", tags: ["gosh/lod/mar", "gosh/lod/ned"] },
+ ];
+ assert.deepEqual(topLevelNamespaceCategorizer(simple), expected);
+ });
+
+ it("orders the categories", () => {
+ var test = ["e", "f", "g", "a", "b", "c"];
+ var expected = [
+ { name: "a", tags: ["a"] },
+ { name: "b", tags: ["b"] },
+ { name: "c", tags: ["c"] },
+ { name: "e", tags: ["e"] },
+ { name: "f", tags: ["f"] },
+ { name: "g", tags: ["g"] },
+ ];
+ assert.deepEqual(topLevelNamespaceCategorizer(test), expected);
+ });
+
+ it("handles cases where category names overlap node names", () => {
+ var test = ["a", "a/a", "a/b", "a/c", "b", "b/a"];
+ var actual = topLevelNamespaceCategorizer(test);
+ var expected = [
+ { name: "a", tags: ["a", "a/a", "a/b", "a/c"] },
+ { name: "b", tags: ["b", "b/a"] },
+ ];
+ assert.deepEqual(actual, expected);
+ });
+
+ it("handles singleton case", () => {
+ assert.deepEqual(topLevelNamespaceCategorizer(["a"]), [{ name: "a", tags: ["a"] }]);
+ });
+ });
+
+ describe("legacyUnderscoreCategorizer", () => {
+ it("splits by shorter of first _ or /", () => {
+ var tags = ["l0_bar/foo", "l0_bar/baz", "l0_foo/wob", "l1_zoink/bla",
+ "l1_wibble/woz", "l1/foo_woink", "l2/wozzle_wizzle"];
+ var actual = legacyUnderscoreCategorizer(tags);
+ var expected = [
+ { name: "l0", tags: ["l0_bar/baz", "l0_bar/foo", "l0_foo/wob"] },
+ { name: "l1", tags: ["l1/foo_woink", "l1_wibble/woz", "l1_zoink/bla"] },
+ { name: "l2", tags: ["l2/wozzle_wizzle"] },
+ ];
+ assert.deepEqual(actual, expected);
+ });
+ });
+
+ describe("customCategorizer", () => {
+ function noFallbackCategorizer(tags: string[]): Category[] {
+ return [];
+ }
+
+ function testCategorizer(defs: string[],
+ fallback: Categorizer, tags: string[]): Category[] {
+ var catDefs = defs.map(defineCategory);
+ return _categorizer(catDefs, fallback)(tags);
+ }
+
+ it("categorizes by regular expression", () => {
+ var defs = ["foo..", "bar.."];
+ var tags = ["fooab", "fooxa", "barts", "barms"];
+ var actual = testCategorizer(defs, noFallbackCategorizer, tags);
+ var expected = [
+ { name: "foo..", tags: ["fooab", "fooxa"] },
+ { name: "bar..", tags: ["barms", "barts"] },
+ ];
+ assert.deepEqual(actual, expected);
+ });
+
+ it("matches non-exclusively", () => {
+ var tags = ["abc", "bar", "zod"];
+ var actual = testCategorizer(["...", "bar"], noFallbackCategorizer, tags);
+ var expected = [
+ { name: "...", tags: ["abc", "bar", "zod"] },
+ { name: "bar", tags: ["bar"] },
+ ];
+ assert.deepEqual(actual, expected);
+ });
+
+ it("creates categories for unmatched rules", () => {
+ var actual = testCategorizer(["a", "b", "c"], noFallbackCategorizer, []);
+ var expected = [
+ { name: "a", tags: [] },
+ { name: "b", tags: [] },
+ { name: "c", tags: [] },
+ ];
+ assert.deepEqual(actual, expected);
+ });
+
+ it("category regexs work with special characters", () => {
+ var defs = ["^\\w+$", "^\\d+$", "^\\/..$"];
+ var tags = ["foo", "3243", "/xa"];
+ var actual = testCategorizer(defs, noFallbackCategorizer, tags);
+ var expected = [
+ { name: "^\\w+$", tags: ["3243", "foo"] },
+ { name: "^\\d+$", tags: ["3243"] },
+ { name: "^\\/..$", tags: ["/xa"] },
+ ];
+ assert.deepEqual(actual, expected);
+ });
+
+ it("category tags are sorted", () => {
+ var tags = ["a", "z", "c", "d", "e", "x", "f", "y", "g"];
+ var sorted = tags.slice().sort();
+ var expected = [{ name: ".*", tags: sorted}];
+ var actual = testCategorizer([".*"], noFallbackCategorizer, tags);
+ assert.deepEqual(actual, expected);
+ });
+
+ it("if nonexclusive: all tags passed to fallback", () => {
+ var passedToDefault = null;
+ function defaultCategorizer(tags: string[]): Category[] {
+ passedToDefault = tags;
+ return [];
+ }
+ var tags = ["foo", "bar", "foo123"];
+ testCategorizer(["foo"], defaultCategorizer, tags);
+ assert.deepEqual(passedToDefault, tags);
+ });
+ });
+ });
+}
diff --git a/tensorflow/tensorboard/components/tf-categorizer/tf-categorizer.html b/tensorflow/tensorboard/components/tf-categorizer/tf-categorizer.html
new file mode 100644
index 0000000000..3672db38a2
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-categorizer/tf-categorizer.html
@@ -0,0 +1,103 @@
+<link rel="import" href="../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../bower_components/paper-toggle-button/paper-toggle-button.html">
+
+<link rel="import" href="../tf-regex-group/tf-regex-group.html">
+<link rel="import" href="../tf-dashboard-common/tensorboard-color.html">
+
+<!--
+`tf-categorizer` turns an array of tags into an array of categories
+
+The transformation from tags to categories is controlled by the user, through
+interacting with the categorizer widget.
+
+(See type signatures in categorizer.ts)
+
+Example:
+ <tf-categorizer tags="[[tags]]" categories="{{categories}}"></tf-categorizer>
+
+Public Properties:
+`tags` - Array of strings that are the tags to categorize. Should be one-way bound downward.
+`categories` - Array of Categorizer.Category objects that are generated by the Categorizer.
+ Are readOnly and notify: True. Expected to be one-way bound upward.
+
+The categorizer provides inputs for adding regular expression rules and toggling whether
+categories are exclusive.
+-->
+<dom-module id="tf-categorizer">
+ <template>
+ <div class="inputs">
+ <tf-regex-group id="regex-group" regexes="{{regexes}}"></tf-regex-group>
+ </div>
+ <div id="underscore-categorization">
+ <span>Split On Underscores:</span>
+ <paper-toggle-button checked="{{splitOnUnderscore}}"></paper-toggle-button>
+ </div>
+ <style>
+ :host {
+ display: block;
+ padding-bottom: 5px;
+ padding-top: 5px;
+ }
+
+ .inputs {
+ padding-left: 5px;
+ }
+
+ paper-toggle-button {
+ --paper-toggle-button-checked-button-color: var(--tb-orange-strong);
+ --paper-toggle-button-checked-bar-color: var(--tb-orange-weak);
+ }
+ #underscore-categorization {
+ padding-left: 94px;
+ color: var(--paper-grey-700);
+ font-size: 14px;
+ }
+ </style>
+ </template>
+ <script src="categorizer.js"></script>
+ <script>
+ Polymer({
+ is: "tf-categorizer",
+ properties: {
+ regexes: {type: Array},
+ tags: {type: Array},
+ categoriesAreExclusive: {type: Boolean, value: true},
+ fallbackCategorizer: {
+ type: String,
+ computed: "chooseFallbackCategorizer(splitOnUnderscore)"
+ },
+ splitOnUnderscore: {
+ type: Boolean,
+ value: false,
+ },
+ 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);
+ })
+ },
+ chooseFallbackCategorizer: function(splitOnUnderscore) {
+ if (splitOnUnderscore) {
+ return "LegacyUnderscoreCategorizer";
+ } else {
+ return "TopLevelNamespaceCategorizer";
+ }
+ },
+ });
+ </script>
+</dom-module>
diff --git a/tensorflow/tensorboard/components/tf-collapsable-pane/demo/index.html b/tensorflow/tensorboard/components/tf-collapsable-pane/demo/index.html
new file mode 100644
index 0000000000..8906b0f3da
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-collapsable-pane/demo/index.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+ <link rel="import" href="../tf-collapsable-pane.html">
+ </head>
+ <body>
+ <style>
+ </style>
+ <tf-collapsable-pane name="foo">
+ <h1>This is content inside the pane.</h1>
+ </tf-collapsable-pane>
+ </body>
+ <script>
+
+ </script>
+</html>
diff --git a/tensorflow/tensorboard/components/tf-collapsable-pane/index.html b/tensorflow/tensorboard/components/tf-collapsable-pane/index.html
new file mode 100644
index 0000000000..032e5be8c8
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-collapsable-pane/index.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<html>
+<head>
+
+ <title>tf-collapsable-pane</title>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+
+ <script src="../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+ <link rel="import" href="../../bower_components/iron-component-page/iron-component-page.html">
+
+</head>
+<body>
+
+ <iron-component-page></iron-component-page>
+
+</body>
+</html>
diff --git a/tensorflow/tensorboard/components/tf-collapsable-pane/tf-collapsable-pane.html b/tensorflow/tensorboard/components/tf-collapsable-pane/tf-collapsable-pane.html
new file mode 100644
index 0000000000..c06d40a2ec
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-collapsable-pane/tf-collapsable-pane.html
@@ -0,0 +1,90 @@
+<link rel="import" href="../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../bower_components/iron-collapse/iron-collapse.html">
+
+<dom-module id="tf-collapsable-pane">
+ <template>
+ <button
+ class="heading"
+ on-tap="togglePane"
+ open-button$="[[opened]]"
+ >
+ <span class="name">[[name]]</span>
+ <span class="hackpadding"></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>
+ .heading {
+ margin-top: 10px;
+ padding-left: 15px;
+ background-color: #f3f3f3;
+ border: 1px solid #dedede;
+ border-radius: 5px;
+ font-size: 18px;
+ cursor: pointer;
+ -webkit-tap-highlight-color: rgba(0,0,0,0);
+ width: 100%;
+ height: 30px;
+ box-sizing: border-box;
+ font-size: 16px;
+ display: inline-flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: space-between;
+ line-height: 1;
+ padding-top: 2px;
+ padding-bottom: 2px;
+ }
+
+ .content {
+ padding: 15px;
+ border: 1px solid #dedede;
+ border-top: none;
+ border-bottom-left-radius: 5px;
+ border-bottom-right-radius: 5px;
+ }
+ [open-button] {
+ border-bottom-left-radius: 0px !important;
+ border-bottom-right-radius: 0px !important;
+ }
+ .name {
+ flex-grow: 0;
+ }
+ .count {
+ flex-grow: 0;
+ float: right;
+ font-size: 12px;
+ }
+ .hackpadding {
+ /* An obnoxious hack, but I can't get justify-content: space-between to work */
+ flex-grow: 1;
+ }
+ </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>
diff --git a/tensorflow/tensorboard/components/tf-dashboard-common/dashboard-style.html b/tensorflow/tensorboard/components/tf-dashboard-common/dashboard-style.html
new file mode 100644
index 0000000000..795cbbcac3
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-dashboard-common/dashboard-style.html
@@ -0,0 +1,97 @@
+<link rel="import" href="../../bower_components/paper-styles/paper-styles.html">
+<link rel="import" href="../tf-dashboard-common/tensorboard-color.html">
+
+<dom-module id="dashboard-style">
+ <template>
+ <style>
+ .card {
+ height: 200px;
+ width: 300px;
+ display: flex;
+ flex-direction: column;
+ margin: 5px 5px;
+ padding: 5px;
+ border: 1px solid var(--paper-grey-500);
+ border-radius: 3px;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ position: relative;
+ }
+
+ .card .card-title {
+ flex-grow: 0;
+ flex-shrink: 0;
+ margin-bottom: 2px;
+ font-size: 14px;
+ font-weight: bold;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ }
+
+ .card .card-content {
+ flex-grow: 1;
+ flex-shrink: 1;
+ display: flex;
+ }
+ .card .card-bottom-row {
+ flex-grow: 0;
+ flex-shrink: 0;
+ padding-left: 10px;
+ padding-right: 10px;
+ }
+
+ .card.selected {
+ height: 400px;
+ width: 100%;
+ }
+
+ [shift] {
+ bottom: 20px !important;
+ }
+
+ .expand-button {
+ position: absolute;
+ left: 0px;
+ bottom: 0px;
+ color: #2196F3;
+ display: block;
+ }
+
+ #content-container{
+ display: block;
+ }
+
+ .sidebar {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ }
+
+ #categorizer {
+ flex-shrink: 0;
+ }
+
+ #xTypeSelector {
+ flex-shrink: 0;
+ margin: 20px 0;
+ }
+
+ #runSelector {
+ flex-shrink: 1;
+ flex-grow: 1;
+ }
+
+ #download-option {
+ padding-left: 55px;
+ color: var(--paper-grey-700);
+ font-size: 14px;
+ }
+
+ #download-option paper-toggle-button {
+ --paper-toggle-button-checked-button-color: var(--tb-orange-strong);
+ --paper-toggle-button-checked-bar-color: var(--tb-orange-weak);
+
+ }
+ </style>
+ </template>
+</dom-module>
diff --git a/tensorflow/tensorboard/components/tf-dashboard-common/run-color-style.html b/tensorflow/tensorboard/components/tf-dashboard-common/run-color-style.html
new file mode 100644
index 0000000000..d9b12f366a
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-dashboard-common/run-color-style.html
@@ -0,0 +1,62 @@
+<link rel="import" href="../../bower_components/paper-styles/paper-styles.html">
+
+<dom-module id="run-color-style">
+ <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>
diff --git a/tensorflow/tensorboard/components/tf-dashboard-common/scrollbar-style.html b/tensorflow/tensorboard/components/tf-dashboard-common/scrollbar-style.html
new file mode 100644
index 0000000000..90fc184e8d
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-dashboard-common/scrollbar-style.html
@@ -0,0 +1,28 @@
+<link rel="import" href="../../bower_components/paper-styles/paper-styles.html">
+
+<dom-module id="scrollbar-style">
+ <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>
diff --git a/tensorflow/tensorboard/components/tf-dashboard-common/tensorboard-color.html b/tensorflow/tensorboard/components/tf-dashboard-common/tensorboard-color.html
new file mode 100644
index 0000000000..c3a59b7a31
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-dashboard-common/tensorboard-color.html
@@ -0,0 +1,11 @@
+<link rel="import" href="../../bower_components/polymer/polymer.html">
+<style is="custom-style">
+
+ :root {
+ --tb-orange-weak: #fcb938;
+ --tb-orange-strong: #f3913e;
+ --tb-grey-darker: #e2e2e2;
+ --tb-grey-lighter: #f3f3f3;
+ }
+
+</style>
diff --git a/tensorflow/tensorboard/components/tf-dashboard-common/tf-dashboard-layout.html b/tensorflow/tensorboard/components/tf-dashboard-common/tf-dashboard-layout.html
new file mode 100644
index 0000000000..89c51342fe
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-dashboard-common/tf-dashboard-layout.html
@@ -0,0 +1,50 @@
+<link rel="import" href="../../bower_components/polymer/polymer.html">
+<link rel="import" href="scrollbar-style.html">
+<link rel="import" href="tensorboard-color.html">
+<!--
+Generic layout for a dashboard.
+-->
+<dom-module id="tf-dashboard-layout">
+ <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%;
+ background-color: var(--tb-grey-darker);
+ background-image: linear-gradient(to right, var(--tb-grey-lighter), var(--tb-grey-lighter));
+ overflow: ellipsis;
+ padding-left: 10px;
+ padding-right: 10px;
+ flex-grow: 0;
+ flex-shrink: 0;
+ }
+
+ #center {
+ margin: 0 10px;
+ height: 100%;
+ overflow-y: scroll;
+ padding-right: 12px;
+ flex-grow: 1;
+ flex-shrink: 1;
+ }
+ :host {
+ display: flex;
+ flex-direction: row;
+ height: 100%;
+ }
+ </style>
+ </template>
+ <script>
+ Polymer({
+ is: "tf-dashboard-layout",
+ });
+ </script>
+</dom-module>
diff --git a/tensorflow/tensorboard/components/tf-dashboard-common/tf-downloader.html b/tensorflow/tensorboard/components/tf-dashboard-common/tf-downloader.html
new file mode 100644
index 0000000000..c7251ec578
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-dashboard-common/tf-downloader.html
@@ -0,0 +1,85 @@
+<link rel="import" href="../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../bower_components/paper-dropdown-menu/paper-dropdown-menu.html">
+<link rel="import" href="../../bower_components/paper-menu/paper-menu.html">
+<link rel="import" href="../../bower_components/paper-item/paper-item.html">
+
+<dom-module id="tf-downloader">
+ <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>
+ <a
+ download="[[_csvName(_run)]]"
+ href="[[_csvUrl(_run, urlFn)]]"
+ >CSV</a>
+ <a
+ download="[[_jsonName(_run)]]"
+ href="[[_jsonUrl(_run, urlFn)]]"
+ >JSON</a>
+ <style>
+ :host {
+ display: block;
+ }
+ paper-dropdown-menu {
+ width: 220px;
+ --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: {
+ type: Array,
+ computed: "_computeRuns(runToTag.*, selectedRuns.*)",
+ },
+ selectedRuns: Array,
+ runToTag: Object,
+ tag: String,
+ urlFn: Function,
+ },
+ _computeRuns: function(runToTagChange, selectedRunsChange) {
+ var runToTag = this.runToTag;
+ var tag = this.tag;
+ return this.selectedRuns.filter(function(x) {
+ return runToTag[x].indexOf(tag) !== -1;
+ })
+ },
+ _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>
diff --git a/tensorflow/tensorboard/components/tf-dashboard-common/tf-run-generator.html b/tensorflow/tensorboard/components/tf-dashboard-common/tf-run-generator.html
new file mode 100644
index 0000000000..4d72552049
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-dashboard-common/tf-run-generator.html
@@ -0,0 +1,97 @@
+<link rel="import" href="../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../bower_components/iron-ajax/iron-ajax.html">
+<link rel="import" href="../imports/lodash.html">
+
+<!--
+tf-run-generator is a plumbing component that takes in a url to load runs from, and
+ produces the following upward-bindable properties:
+
+ outRunToScalars: Maps from run name (string) to an array of scalar tags (strings).
+ outRunToHistograms: Maps from run name (string) to an array of histogram tags (strings).
+ outRunToImages: Maps from run name (string) to an array of image tags (strings).
+-->
+<dom-module id="tf-run-generator">
+ <template>
+ <iron-ajax
+ id="ajax"
+ auto
+ url="[[url]]"
+ handle-as="json"
+ debounce="300"
+ on-response="_setResponse"
+ verbose=true
+ >
+ </iron-ajax>
+ </template>
+ <script>
+ Polymer({
+ is: "tf-run-generator",
+ properties: {
+ url: String,
+ _runToTag: {
+ type: Object,
+ readOnly: true,
+ },
+ outRunToScalars: {
+ // {[runName: string]: string[]}
+ // the names of scalar tags.
+ type: Object,
+ computed: "_scalars(_runToTag.*)",
+ notify: true,
+ },
+ outRunToHistograms: {
+ // {[runName: string]: string[]}
+ // the names of histogram tags.
+ type: Object,
+ computed: "_histograms(_runToTag.*)",
+ notify: true,
+ },
+ outRunToCompressedHistograms: {
+ // {[runName: string]: string[]}
+ // the names of histogram tags.
+ type: Object,
+ computed: "_compressedHistograms(_runToTag.*)",
+ notify: true,
+ },
+ outRunToImages: {
+ // {[runName: string]: string[]}
+ // the names of image tags.
+ type: Object,
+ computed: "_images(_runToTag.*)",
+ notify: true,
+ },
+ outRunsWithGraph: {
+ // ["run1", "run2", ...]
+ // array of run names that have an associated graph definition.
+ type: Array,
+ computed: "_graphs(_runToTag.*)",
+ notify: true
+ }
+ },
+ _scalars: function(_runToTag) {
+ return _.mapValues(_runToTag.base, "scalars");
+ },
+ _histograms: function(_runToTag) {
+ return _.mapValues(_runToTag.base, "histograms");
+ },
+ _compressedHistograms: function(_runToTag) {
+ return _.mapValues(_runToTag.base, "compressedHistograms");
+ },
+ _images: function(_runToTag) {
+ return _.mapValues(_runToTag.base, "images");
+ },
+ _graphs: function(_runToTag) {
+ var runsWithGraph = [];
+ _.each(_runToTag.base, function(runInfo, runName) {
+ if (runInfo.graph === true) {
+ runsWithGraph.push(runName);
+ }
+ });
+ return runsWithGraph;
+ },
+ _setResponse: function(event) {
+ this._set_runToTag(event.detail.response);
+ }
+ });
+ </script>
+</dom-module>
diff --git a/tensorflow/tensorboard/components/tf-dashboard-common/tf-url-generator.html b/tensorflow/tensorboard/components/tf-dashboard-common/tf-url-generator.html
new file mode 100644
index 0000000000..803998daeb
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-dashboard-common/tf-url-generator.html
@@ -0,0 +1,50 @@
+<link rel="import" href="../../bower_components/polymer/polymer.html">
+
+<!-- tf-url-generator is a plumbing component that provides two upward bindable properties:
+ outRunsUrl and outValuesUrlGenerator. These may be used by the rest of the application to communicate
+ with the backend, and by overriding the TF.Urls code that backs it, can be modified to load data from
+ a demo data source instead.
+ -->
+<dom-module id="tf-url-generator">
+ <script src="urlGenerator.js"></script>
+ <script>
+ var polymerObject = {
+ is: "tf-url-generator",
+ properties: {
+ outRunsUrl: {
+ type: String,
+ value: function() {
+ return TF.Urls.runsUrl();
+ },
+ readOnly: true,
+ notify: true,
+ },
+ },
+ };
+ TF.Urls.routes.forEach(function(route) {
+ /* for each route (other than runs, handled seperately):
+ * out`RouteName`: {
+ * type: Function,
+ * readOnly: true,
+ * notify: true,
+ * value: function() {
+ * return TF.Urls.`routeName`Url;
+ * }
+ */
+ if (route === "runs") {
+ return;
+ }
+ var urlName = route + "Url";
+ var propertyName = Polymer.CaseMap.dashToCamelCase("out-" + urlName + "Generator");
+ polymerObject.properties[propertyName] = {
+ type: Function,
+ value: function() {
+ return TF.Urls[urlName];
+ },
+ notify: true,
+ readOnly: true,
+ }
+ });
+ Polymer(polymerObject);
+ </script>
+</dom-module>
diff --git a/tensorflow/tensorboard/components/tf-dashboard-common/urlGenerator.ts b/tensorflow/tensorboard/components/tf-dashboard-common/urlGenerator.ts
new file mode 100644
index 0000000000..c7bbcbf434
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-dashboard-common/urlGenerator.ts
@@ -0,0 +1,33 @@
+/// <reference path="../../typings/tsd.d.ts" />
+/// <reference path="../../bower_components/plottable/plottable.d.ts" />
+
+module TF {
+ export module Urls {
+
+ export var routes = ["runs", "scalars", "histograms",
+ "compressedHistograms", "images",
+ "individualImage", "graph"];
+
+ function router(route: string): ((tag: string, run: string) => string) {
+ return function(tag: string, run: string): string {
+ return "/" + route + "?tag=" + encodeURIComponent(tag)
+ + "&run=" + encodeURIComponent(run);
+ };
+ }
+
+ export function runsUrl() {
+ return "/runs";
+ }
+ export var scalarsUrl = router("scalars");
+ export var histogramsUrl = router("histograms");
+ export var compressedHistogramsUrl = router("compressedHistograms");
+ export var imagesUrl = router("images");
+ export function individualImageUrl(query: string) {
+ return "/individualImage?" + query;
+ }
+ export function graphUrl(run: string) {
+ return "/graph?run=" + encodeURIComponent(run);
+ }
+
+ }
+}
diff --git a/tensorflow/tensorboard/components/tf-dashboard-common/warning-style.html b/tensorflow/tensorboard/components/tf-dashboard-common/warning-style.html
new file mode 100644
index 0000000000..c4103a7248
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-dashboard-common/warning-style.html
@@ -0,0 +1,10 @@
+<dom-module id="warning-style">
+ <template>
+ <style>
+ .warning {
+ max-width: 540px;
+ margin: 80px auto 0 auto;
+ }
+ </style>
+ </template>
+</dom-module>
diff --git a/tensorflow/tensorboard/components/tf-event-dashboard/dataCoordinator.ts b/tensorflow/tensorboard/components/tf-event-dashboard/dataCoordinator.ts
new file mode 100644
index 0000000000..c489eca17c
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-event-dashboard/dataCoordinator.ts
@@ -0,0 +1,57 @@
+/// <reference path="../../typings/tsd.d.ts" />
+/// <reference path="../../bower_components/plottable/plottable.d.ts" />
+
+module TF {
+
+ /* The DataCoordinator generates TF.Datasets for each run/tag combination,
+ * and is responsible for communicating with the backend to load data into them.
+ * A key fact about this design is that when Datasets modify their data, they
+ * automatically notify all dependent Plottable charts.
+ */
+ export class DataCoordinator {
+ private urlGenerator: (tag: string, run: string) => string;
+ private datasets: {[key: string]: TF.Dataset};
+ private runToTag: {[run: string]: string[]};
+
+ constructor(urlGenerator: (tag: string, run: string) => string,
+ runToTag: {[run: string]: string[]}) {
+ this.datasets = {};
+ this.urlGenerator = urlGenerator;
+ this.runToTag = runToTag;
+ }
+
+ /* Create or return an array of Datasets for the given
+ * tag and runs. It filters which runs it uses by checking
+ * that data exists for each tag-run combination.
+ * Calling this triggers a load on the dataset.
+ */
+ public getDatasets(tag: string, runs: string[]) {
+ var usableRuns = runs.filter((r) => {
+ var tags = this.runToTag[r];
+ return tags.indexOf(tag) !== -1;
+ });
+ return usableRuns.map((r) => this.getDataset(tag, r));
+ }
+
+ /* Create or return a Dataset for given tag and run.
+ * Calling this triggers a load on the dataset.
+ */
+ public getDataset(tag: string, run: string): TF.Dataset {
+ var dataset = this._getDataset(tag, run);
+ dataset.load();
+ return dataset;
+ }
+
+ private _getDataset(tag: string, run: string): TF.Dataset {
+ var key = [tag, run].toString();
+ var dataset: TF.Dataset;
+ if (this.datasets[key] != null) {
+ dataset = this.datasets[key];
+ } else {
+ dataset = new TF.Dataset(tag, run, this.urlGenerator);
+ this.datasets[key] = dataset;
+ }
+ return dataset;
+ }
+ }
+}
diff --git a/tensorflow/tensorboard/components/tf-event-dashboard/dataset.ts b/tensorflow/tensorboard/components/tf-event-dashboard/dataset.ts
new file mode 100644
index 0000000000..de814583d3
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-event-dashboard/dataset.ts
@@ -0,0 +1,41 @@
+/// <reference path="../../typings/tsd.d.ts" />
+/// <reference path="../../bower_components/plottable/plottable.d.ts" />
+
+module TF {
+ /* An extension of Plottable.Dataset that knows how to load data from a backend.
+ */
+ export class Dataset extends Plottable.Dataset {
+ public tag: string;
+ public run: string;
+ private lastLoadTime: number;
+ private lastRequest;
+ private urlGenerator: Function;
+
+ constructor(tag: string, run: string, urlGenerator: (run: string, tag: string) => string) {
+ super([], {tag: tag, run: run});
+ this.tag = tag;
+ this.run = run;
+ this.urlGenerator = urlGenerator;
+ }
+
+ public load = _.debounce(this._load, 10);
+
+ private _load() {
+ var url = this.urlGenerator(this.tag, this.run);
+ if (this.lastRequest != null) {
+ this.lastRequest.abort();
+ }
+ this.lastRequest = d3.json(url, (error, json) => {
+ this.lastRequest = null;
+ if (error) {
+ /* tslint:disable */
+ console.log(error);
+ /* tslint:enable */
+ throw new Error("Failure loading JSON at url: \"" + url + "\"");
+ } else {
+ this.data(json);
+ }
+ });
+ }
+ }
+}
diff --git a/tensorflow/tensorboard/components/tf-event-dashboard/demo/data/alpha/d1.json b/tensorflow/tensorboard/components/tf-event-dashboard/demo/data/alpha/d1.json
new file mode 100644
index 0000000000..af17f5c328
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-event-dashboard/demo/data/alpha/d1.json
@@ -0,0 +1 @@
+[[1436926051.074826, 84, 0.6990088224411011], [1436926530.99861, 2289, 6.9384379386901855], [1436927011.134076, 7611, 13.698328971862793], [1436927490.984256, 16147, 20.168190002441406], [1436927970.957234, 26087, 20.877344131469727], [1436928450.977514, 36241, 21.269058227539062], [1436928930.989548, 46432, 21.329505920410156], [1436929410.976308, 56629, 21.220420837402344], [1436929890.966395, 66791, 21.190065383911133], [1436930370.958199, 76936, 21.108604431152344], [1436930850.985301, 87083, 21.157001495361328], [1436931331.009261, 97161, 21.02127456665039], [1436931810.966042, 107210, 20.891658782958984], [1436932290.955417, 117262, 20.930112838745117], [1436932770.964496, 127333, 20.986324310302734], [1436933250.962592, 137430, 20.981359481811523], [1436933730.992022, 147528, 21.083036422729492], [1436934210.959831, 157635, 21.092649459838867], [1436934690.97072, 167749, 21.11568832397461], [1436935170.957944, 177869, 21.145965576171875], [1436935650.959987, 188025, 21.215585708618164], [1436936130.997541, 198206, 21.227184295654297], [1436936610.965526, 208395, 21.226459503173828], [1436937090.965581, 218592, 21.264968872070312], [1436937570.964874, 228818, 21.335866928100586], [1436938050.965706, 239021, 21.286521911621094], [1436938531.013159, 249210, 21.20963478088379], [1436939010.957926, 259415, 21.28431510925293], [1436939490.96341, 269637, 21.326831817626953], [1436939970.959372, 279876, 21.38308334350586], [1436940450.963802, 290127, 21.355499267578125], [1436940931.004537, 300349, 21.31337547302246], [1436941410.979614, 310601, 21.405778884887695], [1436941890.979674, 320872, 21.368688583374023], [1436942370.975153, 331131, 21.39077377319336], [1436942850.980459, 341399, 21.41745948791504], [1436943331.000808, 351651, 21.384023666381836], [1436943810.968736, 361904, 21.326438903808594], [1436944290.95947, 372158, 21.367351531982422], [1436944770.955783, 382430, 21.476247787475586], [1436945250.966321, 392684, 21.36678695678711], [1436945731.008667, 402950, 21.349145889282227], [1436946210.977922, 413210, 21.373897552490234], [1436946690.975303, 423463, 21.322399139404297], [1436947170.964596, 433723, 21.341150283813477], [1436947650.955017, 443991, 21.366348266601562], [1436948130.992501, 454271, 21.43844223022461], [1436948610.960555, 464519, 21.36829948425293], [1436949090.961079, 474758, 21.266357421875], [1436949570.971528, 484987, 21.316511154174805], [1436950050.977787, 495228, 21.356050491333008], [1436950531.020035, 505458, 21.31462860107422], [1436951010.959775, 515682, 21.277490615844727], [1436951490.967418, 525910, 21.289737701416016], [1436951970.969778, 536112, 21.2515811920166], [1436952450.956291, 546320, 21.254491806030273], [1436952931.005547, 556541, 21.297870635986328], [1436953410.955758, 566755, 21.320045471191406], [1436953890.959151, 576957, 21.23529624938965], [1436954370.959553, 587165, 21.25132179260254], [1436954850.960546, 597371, 21.23470115661621], [1436955330.989932, 607582, 21.19434356689453], [1436955810.957128, 617790, 21.258535385131836], [1436956290.9763, 627991, 21.221921920776367], [1436956770.957785, 638208, 21.309843063354492], [1436957250.974143, 648404, 21.252185821533203], [1436957731.012441, 658613, 21.265626907348633], [1436958210.980787, 668824, 21.239660263061523], [1436958690.973474, 679034, 21.2642765045166], [1436959170.95825, 689249, 21.303138732910156], [1436959650.959345, 699454, 21.24073600769043], [1436960131.008682, 709664, 21.217615127563477], [1436960610.958074, 719876, 21.251184463500977], [1436961090.963638, 730100, 21.290971755981445], [1436961570.979029, 740316, 21.305265426635742], [1436962050.974645, 750534, 21.27857208251953], [1436962531.055479, 760757, 21.329837799072266], [1436963010.975299, 770964, 21.248849868774414], [1436963490.963107, 781164, 21.19978904724121], [1436963970.965936, 791382, 21.30535888671875], [1436964450.959947, 801590, 21.226255416870117], [1436964931.00587, 811785, 21.242237091064453], [1436965410.977997, 821977, 21.226497650146484], [1436965890.988465, 832189, 21.31219482421875], [1436966370.965612, 842399, 21.283390045166016], [1436966850.965794, 852612, 21.273908615112305], [1436967331.009476, 862825, 21.260452270507812], [1436967810.96767, 873037, 21.315444946289062], [1436968290.959107, 883248, 21.28677749633789], [1436968770.9681, 893452, 21.265335083007812], [1436969250.959332, 903655, 21.252891540527344], [1436969731.055609, 913856, 21.233684539794922], [1436970210.961426, 924047, 21.191429138183594], [1436970690.962999, 934250, 21.23288345336914], [1436971170.989107, 944430, 21.17190170288086], [1436971650.956015, 954634, 21.275972366333008], [1436972131.006841, 964844, 21.278474807739258], [1436972610.981754, 975045, 21.25553321838379], [1436973090.961548, 985239, 21.21686553955078], [1436973570.960013, 995439, 21.26004981994629], [1436974050.975653, 1005642, 21.25356101989746], [1436974530.988571, 1015842, 21.23944664001465], [1436975010.95851, 1026048, 21.293363571166992], [1436975490.97355, 1036253, 21.277101516723633], [1436975970.960916, 1046451, 21.242155075073242], [1436976450.990263, 1056636, 21.182037353515625], [1436976930.999578, 1066834, 21.21113395690918], [1436977410.962637, 1077031, 21.230762481689453], [1436977890.970389, 1087222, 21.232444763183594], [1436978370.959059, 1097405, 21.202342987060547], [1436978850.956562, 1107601, 21.23992156982422], [1436979331.021134, 1117786, 21.197628021240234], [1436979810.958593, 1127973, 21.2270565032959], [1436980290.958763, 1138163, 21.250303268432617], [1436980770.967171, 1148348, 21.215538024902344], [1436981250.960473, 1158540, 21.277185440063477], [1436981731.009465, 1168733, 21.268449783325195], [1436982210.960797, 1178930, 21.268077850341797], [1436982690.959709, 1189129, 21.243141174316406], [1436983170.961963, 1199327, 21.21793556213379], [1436983650.958504, 1209524, 21.2817440032959], [1436984130.998057, 1219726, 21.261478424072266], [1436984610.958945, 1229936, 21.300107955932617], [1436985090.978825, 1240145, 21.326183319091797], [1436985570.993741, 1250311, 21.115875244140625], [1436986050.965608, 1260436, 21.19010353088379], [1436986531.026713, 1270611, 21.183719635009766], [1436987010.969056, 1280784, 21.273176193237305], [1436987490.975071, 1290959, 21.182931900024414], [1436987970.96007, 1301147, 21.260244369506836], [1436988450.966092, 1311328, 21.225025177001953], [1436988931.004917, 1321514, 21.242164611816406], [1436989410.980351, 1331709, 21.19801139831543], [1436989890.975192, 1341910, 21.273555755615234], [1436990370.964941, 1352090, 21.175983428955078], [1436990850.973647, 1362240, 21.13412094116211], [1436991330.999346, 1372396, 21.153064727783203], [1436991811.003573, 1382550, 21.155475616455078], [1436992290.962706, 1392710, 21.17011833190918], [1436992770.999149, 1402862, 21.128713607788086], [1436993250.965124, 1413020, 21.1361026763916], [1436993731.020464, 1423164, 21.157777786254883], [1436994210.966935, 1433312, 21.119478225708008], [1436994690.962803, 1443468, 21.161104202270508], [1436995170.972952, 1453657, 21.11492919921875], [1436995650.976233, 1463820, 21.194231033325195], [1436996130.990524, 1473980, 21.169816970825195], [1436996610.97302, 1484152, 21.18223762512207], [1436997090.958457, 1494308, 21.1954402923584], [1436997570.980333, 1504463, 21.140769958496094], [1436998050.969869, 1514618, 21.162744522094727], [1436998530.99688, 1524770, 21.139591217041016], [1436999010.970375, 1534905, 21.107114791870117], [1436999490.960775, 1545070, 21.233396530151367], [1436999970.965087, 1555223, 21.201074600219727], [1437000450.969008, 1565370, 21.147083282470703], [1437000931.007425, 1575517, 21.108510971069336], [1437001410.962798, 1585666, 21.11674690246582], [1437001890.966192, 1595826, 21.17819595336914], [1437002370.961814, 1605980, 21.157669067382812], [1437002850.962206, 1616145, 21.212690353393555], [1437003330.994816, 1626291, 21.177446365356445], [1437003810.966017, 1636448, 21.17884063720703], [1437004290.959479, 1646599, 21.150310516357422], [1437004770.965083, 1656754, 21.21011734008789], [1437005250.958234, 1666902, 21.14912986755371], [1437005731.003528, 1677043, 21.125459671020508], [1437006210.961371, 1687192, 21.124374389648438], [1437006690.962663, 1697338, 21.150362014770508], [1437007170.961639, 1707484, 21.16637420654297], [1437007650.972242, 1717625, 21.163259506225586], [1437008131.003191, 1727767, 21.167280197143555], [1437008610.962644, 1737913, 21.174945831298828], [1437009090.964129, 1748068, 21.17894172668457], [1437009570.962582, 1758219, 21.116622924804688], [1437010050.984863, 1768384, 21.23469352722168], [1437010531.002295, 1778534, 21.143510818481445], [1437011010.961803, 1788677, 21.159791946411133], [1437011490.974074, 1798822, 21.119792938232422], [1437011970.959982, 1808958, 21.10943603515625], [1437012450.95932, 1819091, 21.123899459838867], [1437012931.004909, 1829227, 21.094532012939453], [1437013410.957751, 1839374, 21.200057983398438], [1437013890.960506, 1849509, 21.10895538330078], [1437014370.96113, 1859653, 21.108680725097656], [1437014850.962876, 1869791, 21.141136169433594], [1437015331.009875, 1879944, 21.160165786743164], [1437015810.960671, 1890090, 21.158742904663086], [1437016290.970743, 1900242, 21.16562271118164], [1437016770.961673, 1910391, 21.141860961914062], [1437017250.96735, 1920551, 21.19420051574707], [1437017731.000324, 1930702, 21.16814422607422], [1437018210.967878, 1940856, 21.125978469848633], [1437018690.962742, 1951005, 21.15043067932129], [1437019170.975774, 1961158, 21.157419204711914], [1437019650.964573, 1971309, 21.150177001953125], [1437020130.999343, 1981461, 21.124492645263672], [1437020610.960696, 1991611, 21.109933853149414], [1437021090.958597, 2001766, 21.169754028320312], [1437021570.964477, 2011919, 21.13479995727539], [1437022050.966522, 2022063, 21.131561279296875], [1437022531.005607, 2032219, 21.135629653930664], [1437023010.970667, 2042380, 21.207313537597656], [1437023490.964885, 2052534, 21.108623504638672], [1437023970.965596, 2062691, 21.14097023010254], [1437024450.962296, 2072837, 21.129037857055664], [1437024931.00395, 2082982, 21.077030181884766], [1437025410.96602, 2093128, 21.13152503967285], [1437025890.961753, 2103274, 21.117740631103516], [1437026370.962022, 2113424, 21.141584396362305], [1437026850.975475, 2123570, 21.143577575683594], [1437027331.009277, 2133721, 21.175586700439453], [1437027810.97206, 2143857, 21.099014282226562], [1437028290.961523, 2154015, 21.141523361206055], [1437028770.964366, 2164168, 21.141345977783203], [1437029250.962109, 2174320, 21.14827537536621], [1437029731.003068, 2184453, 21.086946487426758], [1437030210.960946, 2194602, 21.1590576171875], [1437030690.966681, 2204754, 21.17353057861328], [1437031170.961207, 2214899, 21.133989334106445], [1437031650.962809, 2225062, 21.14800453186035], [1437032130.997644, 2235215, 21.15397834777832], [1437032610.962999, 2245366, 21.15763282775879], [1437033090.962192, 2255521, 21.133577346801758], [1437033570.963341, 2265657, 21.058490753173828], [1437034050.979501, 2275787, 21.079614639282227], [1437034531.003514, 2285923, 21.12677574157715], [1437035010.960984, 2296058, 21.100793838500977], [1437035490.97325, 2306176, 21.10753059387207], [1437035970.969759, 2316297, 21.100393295288086], [1437036450.962305, 2326428, 21.041208267211914], [1437036931.001785, 2336571, 21.15167999267578], [1437037410.967681, 2346709, 21.09291648864746], [1437037890.963194, 2356854, 21.18524932861328], [1437038370.96445, 2366985, 21.116247177124023], [1437038850.960718, 2377124, 21.125469207763672], [1437039331.003148, 2387259, 21.132274627685547], [1437039810.974007, 2397400, 21.119945526123047], [1437040290.983415, 2407539, 21.154672622680664], [1437040770.961836, 2417667, 21.066741943359375], [1437041250.964281, 2427791, 21.126564025878906], [1437041731.0196, 2437923, 21.1062068939209], [1437042210.962927, 2448056, 21.124549865722656], [1437042690.964392, 2458193, 21.13232421875], [1437043170.972024, 2468318, 21.066423416137695], [1437043650.966111, 2478449, 21.123788833618164], [1437044131.030028, 2488576, 21.138349533081055], [1437044610.962532, 2498717, 21.11895179748535], [1437045090.965094, 2508839, 21.019609451293945], [1437045570.963352, 2518972, 21.079254150390625], [1437046050.96194, 2529106, 21.15033531188965], [1437046530.995016, 2539243, 21.11912727355957], [1437047010.963313, 2549369, 21.08464813232422], [1437047490.963943, 2559509, 21.133895874023438], [1437047970.958612, 2569646, 21.108659744262695], [1437048450.962392, 2579776, 21.084848403930664], [1437048931.005408, 2589906, 21.092708587646484], [1437049410.984115, 2600033, 21.130634307861328], [1437049890.964103, 2610162, 21.074010848999023], [1437050370.960886, 2620282, 21.086149215698242], [1437050850.959795, 2630402, 21.08969497680664], [1437051331.008292, 2640533, 21.134498596191406], [1437051810.96622, 2650643, 21.065444946289062], [1437052290.98584, 2660774, 21.120830535888672], [1437052770.967707, 2670900, 21.085134506225586], [1437053250.978851, 2681021, 21.037155151367188], [1437053731.021686, 2691151, 21.09203338623047], [1437054210.971744, 2701273, 21.048450469970703], [1437054690.966686, 2711425, 21.048809051513672], [1437055170.964463, 2721564, 21.13330078125], [1437055650.97301, 2731694, 21.097095489501953], [1437056130.997053, 2741810, 21.031536102294922], [1437056610.968681, 2751927, 21.04400634765625], [1437057090.976676, 2762049, 21.114444732666016], [1437057570.962334, 2772169, 21.06243896484375], [1437058050.969524, 2782292, 21.12563133239746], [1437058531.012918, 2792420, 21.12433433532715], [1437059010.972868, 2802545, 21.067407608032227], [1437059490.96188, 2812684, 21.099285125732422], [1437059970.965083, 2822806, 21.08357810974121], [1437060450.964845, 2832940, 21.142192840576172], [1437060931.011947, 2843080, 21.109895706176758], [1437061410.963414, 2853223, 21.13360023498535], [1437061890.969303, 2863361, 21.152849197387695], [1437062370.963703, 2873490, 21.08356285095215], [1437062850.964392, 2883627, 21.115087509155273], [1437063331.025516, 2893758, 21.13198471069336], [1437063810.962087, 2903877, 21.084623336791992], [1437064290.973818, 2914013, 21.14010238647461], [1437064770.967792, 2924145, 21.108346939086914], [1437065250.95886, 2934291, 21.1142635345459], [1437065731.01002, 2944434, 21.17418670654297], [1437066210.959306, 2954576, 21.084075927734375], [1437066690.960644, 2964724, 21.125164031982422], [1437067170.969539, 2974890, 21.200775146484375], [1437067650.960018, 2985036, 21.14740562438965], [1437068130.990731, 2995179, 21.11964225769043], [1437068610.960429, 3005322, 21.141313552856445], [1437069090.95752, 3015461, 21.082963943481445], [1437069570.974879, 3025595, 21.12288475036621], [1437070050.95761, 3035734, 21.107513427734375], [1437070531.0013, 3045868, 21.171630859375], [1437071010.961705, 3056004, 21.066505432128906], [1437071490.961495, 3066137, 21.10834312438965], [1437071970.978122, 3076267, 21.08027458190918], [1437072450.963299, 3086399, 21.089733123779297], [1437072931.018382, 3096524, 21.133176803588867], [1437073050.962102, 3099048, 21.041847229003906], [1437073170.96983, 3101584, 21.131967544555664], [1437073290.957895, 3104118, 21.118793487548828]] \ No newline at end of file
diff --git a/tensorflow/tensorboard/components/tf-event-dashboard/demo/data/alpha/d2.json b/tensorflow/tensorboard/components/tf-event-dashboard/demo/data/alpha/d2.json
new file mode 100644
index 0000000000..92bb414348
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-event-dashboard/demo/data/alpha/d2.json
@@ -0,0 +1 @@
+[[1436925978.257845, 7, 0.04500000178813934], [1436926413.945391, 1476, 0.04500000178813934], [1436926893.945037, 6006, 0.04500000178813934], [1436927373.995472, 13786, 0.04500000178813934], [1436927853.989794, 23650, 0.04500000178813934], [1436928334.132361, 33755, 0.04500000178813934], [1436928813.973288, 43941, 0.04500000178813934], [1436929293.975949, 54146, 0.04500000178813934], [1436929773.992781, 64316, 0.04500000178813934], [1436930253.997415, 74465, 0.04500000178813934], [1436930734.203004, 84611, 0.04230000078678131], [1436931214.03644, 94700, 0.04230000078678131], [1436931694.094564, 104766, 0.04230000078678131], [1436932174.114955, 114817, 0.04230000078678131], [1436932654.161382, 124880, 0.04230000078678131], [1436933133.960214, 134977, 0.04230000078678131], [1436933614.044337, 145062, 0.04230000078678131], [1436934094.166206, 155169, 0.04230000078678131], [1436934574.106036, 165284, 0.03976200148463249], [1436935054.150647, 175402, 0.03976200148463249], [1436935533.819562, 185538, 0.03976200148463249], [1436936013.710422, 195712, 0.03976200148463249], [1436936493.609025, 205906, 0.03976200148463249], [1436936973.683892, 216099, 0.03976200148463249], [1436937454.138383, 226331, 0.03976200148463249], [1436937933.838475, 236532, 0.03976200148463249], [1436938413.89688, 246724, 0.0373762808740139], [1436938894.018652, 256925, 0.0373762808740139], [1436939373.69067, 267137, 0.0373762808740139], [1436939853.673692, 277369, 0.0373762808740139], [1436940333.651346, 287620, 0.0373762808740139], [1436940813.599579, 297848, 0.0373762808740139], [1436941293.596313, 308088, 0.0373762808740139], [1436941773.659172, 318362, 0.0373762808740139], [1436942253.648479, 328621, 0.03513370454311371], [1436942733.752284, 338892, 0.03513370454311371], [1436943213.621881, 349144, 0.03513370454311371], [1436943693.698743, 359399, 0.03513370454311371], [1436944173.578463, 369649, 0.03513370454311371], [1436944653.692217, 379912, 0.03513370454311371], [1436945133.677298, 390180, 0.03513370454311371], [1436945613.572411, 400445, 0.03302568197250366], [1436946093.56123, 410703, 0.03302568197250366], [1436946573.542364, 420958, 0.03302568197250366], [1436947053.616578, 431216, 0.03302568197250366], [1436947533.636973, 441483, 0.03302568197250366], [1436948013.541574, 451751, 0.03302568197250366], [1436948493.560223, 462015, 0.03302568197250366], [1436948973.512541, 472260, 0.03302568197250366], [1436949453.550055, 482483, 0.031044140458106995], [1436949933.828011, 492731, 0.031044140458106995], [1436950413.603177, 502957, 0.031044140458106995], [1436950893.563009, 513185, 0.031044140458106995], [1436951373.620887, 523410, 0.031044140458106995], [1436951853.61941, 533618, 0.031044140458106995], [1436952333.694447, 543828, 0.031044140458106995], [1436952813.621004, 554042, 0.031044140458106995], [1436953293.588156, 564251, 0.02918149158358574], [1436953773.599734, 574464, 0.02918149158358574], [1436954253.621309, 584672, 0.02918149158358574], [1436954733.738119, 594882, 0.02918149158358574], [1436955213.56617, 605091, 0.02918149158358574], [1436955693.585366, 615296, 0.02918149158358574], [1436956173.626395, 625501, 0.02918149158358574], [1436956653.601937, 635705, 0.02918149158358574], [1436957133.665878, 645915, 0.02743060328066349], [1436957613.584762, 656116, 0.02743060328066349], [1436958093.549783, 666331, 0.02743060328066349], [1436958573.646778, 676543, 0.02743060328066349], [1436959053.585655, 686750, 0.02743060328066349], [1436959533.679696, 696961, 0.02743060328066349], [1436960013.633292, 707173, 0.02743060328066349], [1436960493.578778, 717383, 0.02743060328066349], [1436960973.596715, 727598, 0.025784766301512718], [1436961453.625644, 737818, 0.025784766301512718], [1436961933.740339, 748040, 0.025784766301512718], [1436962413.573845, 758252, 0.025784766301512718], [1436962893.610678, 768470, 0.025784766301512718], [1436963373.642878, 778674, 0.025784766301512718], [1436963853.558388, 788877, 0.025784766301512718], [1436964333.658419, 799099, 0.025784766301512718], [1436964813.573319, 809289, 0.024237681180238724], [1436965293.542098, 819484, 0.024237681180238724], [1436965773.545453, 829687, 0.024237681180238724], [1436966253.586517, 839901, 0.024237681180238724], [1436966733.639348, 850120, 0.024237681180238724], [1436967213.697288, 860330, 0.024237681180238724], [1436967693.617172, 870539, 0.024237681180238724], [1436968173.593885, 880748, 0.024237681180238724], [1436968653.560836, 890955, 0.022783419117331505], [1436969133.676337, 901164, 0.022783419117331505], [1436969613.506638, 911358, 0.022783419117331505], [1436970093.595964, 921560, 0.022783419117331505], [1436970573.541227, 931756, 0.022783419117331505], [1436971053.624316, 941945, 0.022783419117331505], [1436971533.655543, 952138, 0.022783419117331505], [1436972013.604738, 962349, 0.02141641452908516], [1436972493.613199, 972551, 0.02141641452908516], [1436972973.501155, 982746, 0.02141641452908516], [1436973453.64842, 992945, 0.02141641452908516], [1436973933.689516, 1003147, 0.02141641452908516], [1436974413.577769, 1013350, 0.02141641452908516], [1436974893.542281, 1023545, 0.02141641452908516], [1436975373.638453, 1033759, 0.02141641452908516], [1436975853.524388, 1043955, 0.02013142965734005], [1436976333.625792, 1054148, 0.02013142965734005], [1436976813.610661, 1064342, 0.02013142965734005], [1436977293.601581, 1074539, 0.02013142965734005], [1436977773.575627, 1084733, 0.02013142965734005], [1436978253.564972, 1094914, 0.02013142965734005], [1436978733.673144, 1105109, 0.02013142965734005], [1436979213.540585, 1115293, 0.02013142965734005], [1436979693.699591, 1125483, 0.018923543393611908], [1436980173.613012, 1135670, 0.018923543393611908], [1436980653.575769, 1145862, 0.018923543393611908], [1436981133.719264, 1156045, 0.018923543393611908], [1436981613.563551, 1166236, 0.018923543393611908], [1436982093.553233, 1176436, 0.018923543393611908], [1436982573.577846, 1186636, 0.018923543393611908], [1436983053.605749, 1196837, 0.018923543393611908], [1436983533.684994, 1207025, 0.017788130789995193], [1436984013.561492, 1217233, 0.017788130789995193], [1436984493.629873, 1227437, 0.017788130789995193], [1436984973.606714, 1237643, 0.017788130789995193], [1436985453.690084, 1247835, 0.017788130789995193], [1436985933.711388, 1257951, 0.017788130789995193], [1436986413.598807, 1268125, 0.017788130789995193], [1436986893.631797, 1278290, 0.017788130789995193], [1436987373.596962, 1288473, 0.016720842570066452], [1436987853.555549, 1298650, 0.016720842570066452], [1436988333.722032, 1308841, 0.016720842570066452], [1436988813.55697, 1319018, 0.016720842570066452], [1436989293.756905, 1329221, 0.016720842570066452], [1436989773.665141, 1339417, 0.016720842570066452], [1436990253.768302, 1349610, 0.016720842570066452], [1436990733.708919, 1359759, 0.016720842570066452], [1436991213.663033, 1369914, 0.01571759209036827], [1436991693.730925, 1380074, 0.01571759209036827], [1436992173.751791, 1390224, 0.01571759209036827], [1436992653.758682, 1400383, 0.01571759209036827], [1436993133.835604, 1410542, 0.01571759209036827], [1436993613.674655, 1420684, 0.01571759209036827], [1436994093.747454, 1430832, 0.01571759209036827], [1436994573.768973, 1440986, 0.01571759209036827], [1436995053.666661, 1451174, 0.014774537645280361], [1436995533.83439, 1461345, 0.014774537645280361], [1436996013.556996, 1471495, 0.014774537645280361], [1436996493.635477, 1481663, 0.014774537645280361], [1436996973.668684, 1491822, 0.014774537645280361], [1436997453.59326, 1501979, 0.014774537645280361], [1436997933.774019, 1512139, 0.014774537645280361], [1436998413.575162, 1522290, 0.01388806477189064], [1436998893.640468, 1532431, 0.01388806477189064], [1436999373.551661, 1542579, 0.01388806477189064], [1436999853.57906, 1552734, 0.01388806477189064], [1437000333.680409, 1562888, 0.01388806477189064], [1437000813.602383, 1573037, 0.01388806477189064], [1437001293.610337, 1583190, 0.01388806477189064], [1437001773.618199, 1593341, 0.01388806477189064], [1437002253.572966, 1603497, 0.013054781593382359], [1437002733.67994, 1613657, 0.013054781593382359], [1437003213.583266, 1623809, 0.013054781593382359], [1437003693.639943, 1633966, 0.013054781593382359], [1437004173.568287, 1644113, 0.013054781593382359], [1437004653.610772, 1654268, 0.013054781593382359], [1437005133.663045, 1664424, 0.013054781593382359], [1437005613.580984, 1674567, 0.013054781593382359], [1437006093.601019, 1684715, 0.01227149460464716], [1437006573.625314, 1694857, 0.01227149460464716], [1437007053.584514, 1704999, 0.01227149460464716], [1437007533.719303, 1715150, 0.01227149460464716], [1437008013.604962, 1725282, 0.01227149460464716], [1437008493.655091, 1735432, 0.01227149460464716], [1437008973.640165, 1745584, 0.01227149460464716], [1437009453.715067, 1755742, 0.01227149460464716], [1437009933.765712, 1765896, 0.011535204015672207], [1437010413.632128, 1776052, 0.011535204015672207], [1437010893.66766, 1786195, 0.011535204015672207], [1437011373.636164, 1796346, 0.011535204015672207], [1437011853.631224, 1806481, 0.011535204015672207], [1437012333.706205, 1816617, 0.011535204015672207], [1437012813.61987, 1826754, 0.011535204015672207], [1437013293.479904, 1836883, 0.011535204015672207], [1437013773.604574, 1847029, 0.010843091644346714], [1437014253.618884, 1857175, 0.010843091644346714], [1437014733.756419, 1867312, 0.010843091644346714], [1437015213.638607, 1877459, 0.010843091644346714], [1437015693.625763, 1887608, 0.010843091644346714], [1437016173.63194, 1897759, 0.010843091644346714], [1437016653.609074, 1907909, 0.010843091644346714], [1437017133.717601, 1918074, 0.010843091644346714], [1437017613.716011, 1928220, 0.010192506946623325], [1437018093.626005, 1938377, 0.010192506946623325], [1437018573.626522, 1948523, 0.010192506946623325], [1437019053.648174, 1958678, 0.010192506946623325], [1437019533.803011, 1968831, 0.010192506946623325], [1437020013.667751, 1978978, 0.010192506946623325], [1437020493.659028, 1989133, 0.010192506946623325], [1437020973.657346, 1999287, 0.010192506946623325], [1437021453.650634, 2009437, 0.00958095584064722], [1437021933.848661, 2019588, 0.00958095584064722], [1437022413.674963, 2029736, 0.00958095584064722], [1437022893.69086, 2039894, 0.00958095584064722], [1437023373.68883, 2050054, 0.00958095584064722], [1437023853.686116, 2060205, 0.00958095584064722], [1437024333.763876, 2070362, 0.00958095584064722], [1437024813.707845, 2080507, 0.00958095584064722], [1437025293.483294, 2090645, 0.009006098844110966], [1437025773.695712, 2100793, 0.009006098844110966], [1437026253.672994, 2110943, 0.009006098844110966], [1437026733.780775, 2121094, 0.009006098844110966], [1437027213.617849, 2131235, 0.009006098844110966], [1437027693.694451, 2141382, 0.009006098844110966], [1437028173.68596, 2151537, 0.009006098844110966], [1437028653.584833, 2161685, 0.009006098844110966], [1437029133.792483, 2171839, 0.00846573244780302], [1437029613.661672, 2181977, 0.00846573244780302], [1437030093.641009, 2192118, 0.00846573244780302], [1437030573.656274, 2202268, 0.00846573244780302], [1437031053.643631, 2212416, 0.00846573244780302], [1437031533.777478, 2222583, 0.00846573244780302], [1437032013.704008, 2232736, 0.00846573244780302], [1437032493.638393, 2242882, 0.007957788184285164], [1437032973.684986, 2253041, 0.007957788184285164], [1437033453.699562, 2263183, 0.007957788184285164], [1437033933.918074, 2273320, 0.007957788184285164], [1437034413.596351, 2283443, 0.007957788184285164], [1437034893.640496, 2293579, 0.007957788184285164], [1437035373.637761, 2303701, 0.007957788184285164], [1437035853.669947, 2313823, 0.007957788184285164], [1437036333.78905, 2323961, 0.0074803209863603115], [1437036813.699727, 2334089, 0.0074803209863603115], [1437037293.662592, 2344235, 0.0074803209863603115], [1437037773.66716, 2354364, 0.0074803209863603115], [1437038253.603687, 2364507, 0.0074803209863603115], [1437038733.78864, 2374644, 0.0074803209863603115], [1437039213.641799, 2384782, 0.0074803209863603115], [1437039693.687078, 2394923, 0.0074803209863603115], [1437040173.635717, 2405058, 0.0070315017364919186], [1437040653.673331, 2415194, 0.0070315017364919186], [1437041133.764768, 2425322, 0.0070315017364919186], [1437041613.629279, 2435449, 0.0070315017364919186], [1437042093.703985, 2445575, 0.0070315017364919186], [1437042573.496029, 2455712, 0.0070315017364919186], [1437043053.686022, 2465844, 0.0070315017364919186], [1437043533.731929, 2475974, 0.0070315017364919186], [1437044013.636245, 2486095, 0.006609611678868532], [1437044493.69923, 2496238, 0.006609611678868532], [1437044973.652155, 2506373, 0.006609611678868532], [1437045453.691467, 2516497, 0.006609611678868532], [1437045933.935804, 2526637, 0.006609611678868532], [1437046413.635583, 2536770, 0.006609611678868532], [1437046893.626337, 2546896, 0.006609611678868532], [1437047373.67437, 2557029, 0.006609611678868532], [1437047853.652939, 2567169, 0.0062130349688231945], [1437048333.778436, 2577306, 0.0062130349688231945], [1437048813.654248, 2587433, 0.0062130349688231945], [1437049293.610609, 2597552, 0.0062130349688231945], [1437049773.646573, 2607690, 0.0062130349688231945], [1437050253.667925, 2617808, 0.0062130349688231945], [1437050733.735291, 2627933, 0.0062130349688231945], [1437051213.620222, 2638053, 0.0062130349688231945], [1437051693.601978, 2648171, 0.005840253084897995], [1437052173.634985, 2658299, 0.005840253084897995], [1437052653.687176, 2668425, 0.005840253084897995], [1437053133.762819, 2678556, 0.005840253084897995], [1437053613.643698, 2688671, 0.005840253084897995], [1437054093.673047, 2698804, 0.005840253084897995], [1437054573.667371, 2708956, 0.005840253084897995], [1437055053.650441, 2719087, 0.005840253084897995], [1437055533.778469, 2729219, 0.005489837843924761], [1437056013.694082, 2739343, 0.005489837843924761], [1437056493.674871, 2749458, 0.005489837843924761], [1437056973.700234, 2759575, 0.005489837843924761], [1437057453.666129, 2769697, 0.005489837843924761], [1437057933.848506, 2779821, 0.005489837843924761], [1437058413.643799, 2789941, 0.005489837843924761], [1437058893.715386, 2800076, 0.005489837843924761], [1437059373.62596, 2810207, 0.005160447675734758], [1437059853.650848, 2820334, 0.005160447675734758], [1437060333.792248, 2830465, 0.005160447675734758], [1437060813.682955, 2840600, 0.005160447675734758], [1437061293.681795, 2850745, 0.005160447675734758], [1437061773.691182, 2860880, 0.005160447675734758], [1437062253.662987, 2871013, 0.005160447675734758], [1437062733.760419, 2881153, 0.005160447675734758], [1437063213.651969, 2891278, 0.004850820638239384], [1437063693.723523, 2901406, 0.004850820638239384], [1437064173.68663, 2911533, 0.004850820638239384], [1437064653.547643, 2921667, 0.004850820638239384], [1437065133.62645, 2931813, 0.004850820638239384], [1437065613.566569, 2941947, 0.004850820638239384], [1437066093.537804, 2952102, 0.004850820638239384], [1437066573.529332, 2962243, 0.004850820638239384], [1437067053.520098, 2972400, 0.004559771623462439], [1437067533.605733, 2982561, 0.004559771623462439], [1437068013.535467, 2992698, 0.004559771623462439], [1437068493.559976, 3002839, 0.004559771623462439], [1437068973.558743, 3012983, 0.004559771623462439], [1437069453.562661, 3023116, 0.004559771623462439], [1437069933.627071, 3033256, 0.004559771623462439], [1437070413.574131, 3043386, 0.004286185372620821], [1437070893.658803, 3053528, 0.004286185372620821], [1437071373.638711, 3063659, 0.004286185372620821], [1437071853.621384, 3073794, 0.004286185372620821], [1437072333.665269, 3083926, 0.004286185372620821], [1437072813.584388, 3094040, 0.004286185372620821], [1437073293.569178, 3104172, 0.004286185372620821]] \ No newline at end of file
diff --git a/tensorflow/tensorboard/components/tf-event-dashboard/demo/data/alpha/d3.json b/tensorflow/tensorboard/components/tf-event-dashboard/demo/data/alpha/d3.json
new file mode 100644
index 0000000000..69191b9154
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-event-dashboard/demo/data/alpha/d3.json
@@ -0,0 +1 @@
+[[1436925978.257845, 7, 0.0], [1436927853.989794, 23650, 7360.0], [1436929773.992781, 64316, 7360.0], [1436931694.094564, 104766, 7360.0], [1436933614.044337, 145062, 7360.0], [1436935533.819562, 185538, 7360.0], [1436937454.138383, 226331, 7360.0], [1436939373.69067, 267137, 7360.0], [1436941293.596313, 308088, 7360.0], [1436943213.621881, 349144, 7360.0], [1436945133.677298, 390180, 7360.0], [1436947053.616578, 431216, 7360.0], [1436948973.512541, 472260, 7360.0], [1436950893.563009, 513185, 7360.0], [1436952813.621004, 554042, 7360.0], [1436954733.738119, 594882, 7360.0], [1436956653.601937, 635705, 7360.0], [1436958573.646778, 676543, 7360.0], [1436960493.578778, 717383, 7360.0], [1436962413.573845, 758252, 7360.0], [1436964333.658419, 799099, 7360.0], [1436966253.586517, 839901, 7360.0], [1436968173.593885, 880748, 7360.0], [1436970093.595964, 921560, 7360.0], [1436972013.604738, 962349, 7360.0], [1436973933.689516, 1003147, 7360.0], [1436975853.524388, 1043955, 7360.0], [1436977773.575627, 1084733, 7360.0], [1436979693.699591, 1125483, 7360.0], [1436981613.563551, 1166236, 7360.0], [1436983533.684994, 1207025, 7360.0], [1436985453.690084, 1247835, 7360.0], [1436987373.596962, 1288473, 7360.0], [1436989293.756905, 1329221, 7360.0], [1436991213.663033, 1369914, 7360.0], [1436993133.835604, 1410542, 7360.0], [1436995053.666661, 1451174, 7360.0], [1436996973.668684, 1491822, 7360.0], [1436998893.640468, 1532431, 7360.0], [1437000813.602383, 1573037, 7360.0], [1437002733.67994, 1613657, 7360.0], [1437004653.610772, 1654268, 7360.0], [1437006573.625314, 1694857, 7360.0], [1437008493.655091, 1735432, 7360.0], [1437010413.632128, 1776052, 7360.0], [1437012333.706205, 1816617, 7360.0], [1437014253.618884, 1857175, 7360.0], [1437016173.63194, 1897759, 7360.0], [1437018093.626005, 1938377, 7360.0], [1437020013.667751, 1978978, 7360.0], [1437021933.848661, 2019588, 7360.0], [1437023853.686116, 2060205, 7360.0], [1437025773.695712, 2100793, 7360.0], [1437027693.694451, 2141382, 7360.0], [1437029613.661672, 2181977, 7360.0], [1437031533.777478, 2222583, 7360.0], [1437033453.699562, 2263183, 7360.0], [1437035373.637761, 2303701, 7360.0], [1437037293.662592, 2344235, 7360.0], [1437039213.641799, 2384782, 7360.0], [1437041133.764768, 2425322, 7360.0], [1437043053.686022, 2465844, 7360.0], [1437044973.652155, 2506373, 7360.0], [1437046893.626337, 2546896, 7862.0], [1437048813.654248, 2587433, 7862.0], [1437050733.735291, 2627933, 7862.0], [1437052653.687176, 2668425, 7862.0], [1437054573.667371, 2708956, 7862.0], [1437056493.674871, 2749458, 7862.0], [1437058413.643799, 2789941, 7862.0], [1437060333.792248, 2830465, 7862.0], [1437062253.662987, 2871013, 7862.0], [1437064173.68663, 2911533, 7862.0], [1437066093.537804, 2952102, 7862.0], [1437068013.535467, 2992698, 7862.0], [1437069933.627071, 3033256, 7862.0], [1437071853.621384, 3073794, 7862.0], [1437072333.665269, 3083926, 7862.0], [1437072813.584388, 3094040, 7862.0], [1437073293.569178, 3104172, 7862.0]] \ No newline at end of file
diff --git a/tensorflow/tensorboard/components/tf-event-dashboard/demo/data/alpha/d4.json b/tensorflow/tensorboard/components/tf-event-dashboard/demo/data/alpha/d4.json
new file mode 100644
index 0000000000..caf1ae6e7f
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-event-dashboard/demo/data/alpha/d4.json
@@ -0,0 +1 @@
+[[1436925978.257845, 7, 2.461352825164795], [1436926413.945391, 1476, 12.772720336914062], [1436926893.945037, 6006, 12.195232391357422], [1436927373.995472, 13786, 11.528279304504395], [1436927853.989794, 23650, 10.722719192504883], [1436928334.132361, 33755, 10.215253829956055], [1436928813.973288, 43941, 9.730447769165039], [1436929293.975949, 54146, 9.399007797241211], [1436929773.992781, 64316, 9.1018648147583], [1436930253.997415, 74465, 8.961446762084961], [1436930734.203004, 84611, 8.757476806640625], [1436931214.03644, 94700, 8.4615478515625], [1436931694.094564, 104766, 8.506814956665039], [1436932174.114955, 114817, 8.246719360351562], [1436932654.161382, 124880, 8.329349517822266], [1436933133.960214, 134977, 7.90853214263916], [1436933614.044337, 145062, 8.192558288574219], [1436934094.166206, 155169, 7.865443229675293], [1436934574.106036, 165284, 7.910976886749268], [1436935054.150647, 175402, 7.925509929656982], [1436935533.819562, 185538, 7.866455078125], [1436936013.710422, 195712, 7.9123406410217285], [1436936493.609025, 205906, 7.748654842376709], [1436936973.683892, 216099, 7.849164009094238], [1436937454.138383, 226331, 7.784902572631836], [1436937933.838475, 236532, 7.749933242797852], [1436938413.89688, 246724, 7.777050971984863], [1436938894.018652, 256925, 7.663984775543213], [1436939373.69067, 267137, 7.602056980133057], [1436939853.673692, 277369, 7.539070129394531], [1436940333.651346, 287620, 7.575552463531494], [1436940813.599579, 297848, 7.47900390625], [1436941293.596313, 308088, 7.403858184814453], [1436941773.659172, 318362, 7.589539527893066], [1436942253.648479, 328621, 7.511919975280762], [1436942733.752284, 338892, 7.31054162979126], [1436943213.621881, 349144, 7.261094570159912], [1436943693.698743, 359399, 7.552957534790039], [1436944173.578463, 369649, 7.449452877044678], [1436944653.692217, 379912, 7.177209854125977], [1436945133.677298, 390180, 7.308793067932129], [1436945613.572411, 400445, 7.229344844818115], [1436946093.56123, 410703, 7.129981994628906], [1436946573.542364, 420958, 7.127549171447754], [1436947053.616578, 431216, 7.538583755493164], [1436947533.636973, 441483, 7.030594825744629], [1436948013.541574, 451751, 6.98097038269043], [1436948493.560223, 462015, 7.213271141052246], [1436948973.512541, 472260, 7.1727519035339355], [1436949453.550055, 482483, 6.985068321228027], [1436949933.828011, 492731, 7.051283836364746], [1436950413.603177, 502957, 7.082402229309082], [1436950893.563009, 513185, 7.1637864112854], [1436951373.620887, 523410, 7.193849086761475], [1436951853.61941, 533618, 7.1212921142578125], [1436952333.694447, 543828, 7.208009719848633], [1436952813.621004, 554042, 7.28671932220459], [1436953293.588156, 564251, 6.941026210784912], [1436953773.599734, 574464, 7.230144500732422], [1436954253.621309, 584672, 6.815900802612305], [1436954733.738119, 594882, 7.060589790344238], [1436955213.56617, 605091, 7.079995155334473], [1436955693.585366, 615296, 7.300849437713623], [1436956173.626395, 625501, 6.927395343780518], [1436956653.601937, 635705, 6.893837928771973], [1436957133.665878, 645915, 6.965301990509033], [1436957613.584762, 656116, 6.902514457702637], [1436958093.549783, 666331, 7.2444868087768555], [1436958573.646778, 676543, 6.784783840179443], [1436959053.585655, 686750, 6.800273418426514], [1436959533.679696, 696961, 6.743415355682373], [1436960013.633292, 707173, 7.012747764587402], [1436960493.578778, 717383, 6.548677921295166], [1436960973.596715, 727598, 6.638228416442871], [1436961453.625644, 737818, 6.884350776672363], [1436961933.740339, 748040, 6.797428607940674], [1436962413.573845, 758252, 6.815422058105469], [1436962893.610678, 768470, 6.7392377853393555], [1436963373.642878, 778674, 6.8375959396362305], [1436963853.558388, 788877, 6.7254252433776855], [1436964333.658419, 799099, 6.765130996704102], [1436964813.573319, 809289, 6.7060980796813965], [1436965293.542098, 819484, 6.63279390335083], [1436965773.545453, 829687, 6.587352752685547], [1436966253.586517, 839901, 6.4957275390625], [1436966733.639348, 850120, 6.765798091888428], [1436967213.697288, 860330, 6.681786060333252], [1436967693.617172, 870539, 6.696804523468018], [1436968173.593885, 880748, 6.571035385131836], [1436968653.560836, 890955, 6.29492712020874], [1436969133.676337, 901164, 6.679598331451416], [1436969613.506638, 911358, 6.548522472381592], [1436970093.595964, 921560, 6.585646629333496], [1436970573.541227, 931756, 6.589619159698486], [1436971053.624316, 941945, 6.333208084106445], [1436971533.655543, 952138, 6.582470417022705], [1436972013.604738, 962349, 6.289045810699463], [1436972493.613199, 972551, 6.360206127166748], [1436972973.501155, 982746, 6.567287921905518], [1436973453.64842, 992945, 6.246123313903809], [1436973933.689516, 1003147, 6.44004487991333], [1436974413.577769, 1013350, 6.315634727478027], [1436974893.542281, 1023545, 6.289544105529785], [1436975373.638453, 1033759, 6.412042140960693], [1436975853.524388, 1043955, 6.165371894836426], [1436976333.625792, 1054148, 6.403027534484863], [1436976813.610661, 1064342, 6.37597131729126], [1436977293.601581, 1074539, 6.336863994598389], [1436977773.575627, 1084733, 6.377552032470703], [1436978253.564972, 1094914, 6.28995943069458], [1436978733.673144, 1105109, 6.28420352935791], [1436979213.540585, 1115293, 6.277828216552734], [1436979693.699591, 1125483, 6.185207843780518], [1436980173.613012, 1135670, 6.186310768127441], [1436980653.575769, 1145862, 5.922095775604248], [1436981133.719264, 1156045, 6.141305923461914], [1436981613.563551, 1166236, 6.10508394241333], [1436982093.553233, 1176436, 5.967081069946289], [1436982573.577846, 1186636, 5.960882186889648], [1436983053.605749, 1196837, 6.2222185134887695], [1436983533.684994, 1207025, 6.051136493682861], [1436984013.561492, 1217233, 6.087917804718018], [1436984493.629873, 1227437, 5.95945405960083], [1436984973.606714, 1237643, 5.971570014953613], [1436985453.690084, 1247835, 5.969781398773193], [1436985933.711388, 1257951, 6.040994644165039], [1436986413.598807, 1268125, 6.142050743103027], [1436986893.631797, 1278290, 6.03120231628418], [1436987373.596962, 1288473, 5.921470642089844], [1436987853.555549, 1298650, 5.921937942504883], [1436988333.722032, 1308841, 6.050085067749023], [1436988813.55697, 1319018, 5.837893486022949], [1436989293.756905, 1329221, 5.927487850189209], [1436989773.665141, 1339417, 6.117348670959473], [1436990253.768302, 1349610, 6.052918434143066], [1436990733.708919, 1359759, 5.8977789878845215], [1436991213.663033, 1369914, 5.903198719024658], [1436991693.730925, 1380074, 5.85245418548584], [1436992173.751791, 1390224, 5.902153968811035], [1436992653.758682, 1400383, 5.822136878967285], [1436993133.835604, 1410542, 5.88037633895874], [1436993613.674655, 1420684, 5.778636932373047], [1436994093.747454, 1430832, 5.876591682434082], [1436994573.768973, 1440986, 6.196285724639893], [1436995053.666661, 1451174, 5.7718634605407715], [1436995533.83439, 1461345, 5.931266784667969], [1436996013.556996, 1471495, 5.9706597328186035], [1436996493.635477, 1481663, 5.589694023132324], [1436996973.668684, 1491822, 5.787637233734131], [1436997453.59326, 1501979, 5.634321689605713], [1436997933.774019, 1512139, 5.699962615966797], [1436998413.575162, 1522290, 5.807012557983398], [1436998893.640468, 1532431, 5.559602737426758], [1436999373.551661, 1542579, 5.918235778808594], [1436999853.57906, 1552734, 5.745569229125977], [1437000333.680409, 1562888, 5.59443473815918], [1437000813.602383, 1573037, 5.703190326690674], [1437001293.610337, 1583190, 5.468636512756348], [1437001773.618199, 1593341, 5.610755920410156], [1437002253.572966, 1603497, 5.4396867752075195], [1437002733.67994, 1613657, 5.7537946701049805], [1437003213.583266, 1623809, 5.7613725662231445], [1437003693.639943, 1633966, 5.439754009246826], [1437004173.568287, 1644113, 5.4889116287231445], [1437004653.610772, 1654268, 5.39843225479126], [1437005133.663045, 1664424, 5.576738357543945], [1437005613.580984, 1674567, 5.662004470825195], [1437006093.601019, 1684715, 5.3926777839660645], [1437006573.625314, 1694857, 5.464866638183594], [1437007053.584514, 1704999, 5.40261173248291], [1437007533.719303, 1715150, 5.23733377456665], [1437008013.604962, 1725282, 5.448479652404785], [1437008493.655091, 1735432, 5.684703826904297], [1437008973.640165, 1745584, 5.400024890899658], [1437009453.715067, 1755742, 5.378822326660156], [1437009933.765712, 1765896, 5.45297384262085], [1437010413.632128, 1776052, 5.248030185699463], [1437010893.66766, 1786195, 5.3377580642700195], [1437011373.636164, 1796346, 5.292956352233887], [1437011853.631224, 1806481, 5.438100814819336], [1437012333.706205, 1816617, 5.148743629455566], [1437012813.61987, 1826754, 5.319127559661865], [1437013293.479904, 1836883, 5.1646199226379395], [1437013773.604574, 1847029, 5.494720458984375], [1437014253.618884, 1857175, 5.17764949798584], [1437014733.756419, 1867312, 5.14331579208374], [1437015213.638607, 1877459, 5.309914588928223], [1437015693.625763, 1887608, 5.542352676391602], [1437016173.63194, 1897759, 5.075393199920654], [1437016653.609074, 1907909, 5.249225616455078], [1437017133.717601, 1918074, 5.392384052276611], [1437017613.716011, 1928220, 5.38590669631958], [1437018093.626005, 1938377, 5.229607105255127], [1437018573.626522, 1948523, 5.287610054016113], [1437019053.648174, 1958678, 5.2798333168029785], [1437019533.803011, 1968831, 5.151246070861816], [1437020013.667751, 1978978, 5.118294715881348], [1437020493.659028, 1989133, 5.327050685882568], [1437020973.657346, 1999287, 5.174264430999756], [1437021453.650634, 2009437, 5.1660661697387695], [1437021933.848661, 2019588, 5.089689254760742], [1437022413.674963, 2029736, 5.06661319732666], [1437022893.69086, 2039894, 5.031608581542969], [1437023373.68883, 2050054, 4.874476432800293], [1437023853.686116, 2060205, 5.107512474060059], [1437024333.763876, 2070362, 5.135380268096924], [1437024813.707845, 2080507, 5.087984561920166], [1437025293.483294, 2090645, 5.240448474884033], [1437025773.695712, 2100793, 4.930302619934082], [1437026253.672994, 2110943, 4.914392471313477], [1437026733.780775, 2121094, 5.182378768920898], [1437027213.617849, 2131235, 4.93843412399292], [1437027693.694451, 2141382, 4.924433708190918], [1437028173.68596, 2151537, 4.957921028137207], [1437028653.584833, 2161685, 5.040386199951172], [1437029133.792483, 2171839, 5.01956033706665], [1437029613.661672, 2181977, 4.987490177154541], [1437030093.641009, 2192118, 4.960195064544678], [1437030573.656274, 2202268, 5.0094523429870605], [1437031053.643631, 2212416, 4.83445930480957], [1437031533.777478, 2222583, 4.922268390655518], [1437032013.704008, 2232736, 5.113382339477539], [1437032493.638393, 2242882, 4.881488800048828], [1437032973.684986, 2253041, 4.953296661376953], [1437033453.699562, 2263183, 4.865671157836914], [1437033933.918074, 2273320, 4.829331874847412], [1437034413.596351, 2283443, 4.777036190032959], [1437034893.640496, 2293579, 4.864566326141357], [1437035373.637761, 2303701, 4.988693714141846], [1437035853.669947, 2313823, 5.016432285308838], [1437036333.78905, 2323961, 4.651939868927002], [1437036813.699727, 2334089, 4.767807960510254], [1437037293.662592, 2344235, 4.628738880157471], [1437037773.66716, 2354364, 4.929834842681885], [1437038253.603687, 2364507, 4.739555835723877], [1437038733.78864, 2374644, 4.821824073791504], [1437039213.641799, 2384782, 4.853730201721191], [1437039693.687078, 2394923, 4.581423759460449], [1437040173.635717, 2405058, 4.452754497528076], [1437040653.673331, 2415194, 4.837629318237305], [1437041133.764768, 2425322, 4.752482891082764], [1437041613.629279, 2435449, 4.730231761932373], [1437042093.703985, 2445575, 4.5618896484375], [1437042573.496029, 2455712, 4.673112869262695], [1437043053.686022, 2465844, 4.565918922424316], [1437043533.731929, 2475974, 4.7191481590271], [1437044013.636245, 2486095, 4.589008331298828], [1437044493.69923, 2496238, 4.599475383758545], [1437044973.652155, 2506373, 4.544175624847412], [1437045453.691467, 2516497, 4.4221673011779785], [1437045933.935804, 2526637, 4.44448709487915], [1437046413.635583, 2536770, 4.647110939025879], [1437046893.626337, 2546896, 4.768988609313965], [1437047373.67437, 2557029, 4.5318827629089355], [1437047853.652939, 2567169, 4.501277923583984], [1437048333.778436, 2577306, 4.6167216300964355], [1437048813.654248, 2587433, 4.66096305847168], [1437049293.610609, 2597552, 4.529193878173828], [1437049773.646573, 2607690, 4.455351829528809], [1437050253.667925, 2617808, 4.51211404800415], [1437050733.735291, 2627933, 4.803231716156006], [1437051213.620222, 2638053, 4.645476341247559], [1437051693.601978, 2648171, 4.419768810272217], [1437052173.634985, 2658299, 4.48175048828125], [1437052653.687176, 2668425, 4.397725582122803], [1437053133.762819, 2678556, 4.188413619995117], [1437053613.643698, 2688671, 4.291479110717773], [1437054093.673047, 2698804, 4.321218013763428], [1437054573.667371, 2708956, 4.311710834503174], [1437055053.650441, 2719087, 4.481810092926025], [1437055533.778469, 2729219, 4.452049255371094], [1437056013.694082, 2739343, 4.455989360809326], [1437056493.674871, 2749458, 4.415104866027832], [1437056973.700234, 2759575, 4.259828567504883], [1437057453.666129, 2769697, 4.510563373565674], [1437057933.848506, 2779821, 4.221935272216797], [1437058413.643799, 2789941, 4.437899112701416], [1437058893.715386, 2800076, 4.302872657775879], [1437059373.62596, 2810207, 4.228428363800049], [1437059853.650848, 2820334, 4.220061779022217], [1437060333.792248, 2830465, 4.138088703155518], [1437060813.682955, 2840600, 4.2196125984191895], [1437061293.681795, 2850745, 4.1594085693359375], [1437061773.691182, 2860880, 4.179514408111572], [1437062253.662987, 2871013, 4.202476978302002], [1437062733.760419, 2881153, 4.282044887542725], [1437063213.651969, 2891278, 4.200533866882324], [1437063693.723523, 2901406, 4.263350486755371], [1437064173.68663, 2911533, 4.378939628601074], [1437064653.547643, 2921667, 4.202810287475586], [1437065133.62645, 2931813, 4.193121910095215], [1437065613.566569, 2941947, 4.132870197296143], [1437066093.537804, 2952102, 4.35767936706543], [1437066573.529332, 2962243, 4.211732864379883], [1437067053.520098, 2972400, 4.020431041717529], [1437067533.605733, 2982561, 4.342063903808594], [1437068013.535467, 2992698, 4.197565078735352], [1437068493.559976, 3002839, 3.8806259632110596], [1437068973.558743, 3012983, 3.871702194213867], [1437069453.562661, 3023116, 4.064865589141846], [1437069933.627071, 3033256, 3.817744731903076], [1437070413.574131, 3043386, 4.106888294219971], [1437070893.658803, 3053528, 4.235474586486816], [1437071373.638711, 3063659, 4.127055644989014], [1437071853.621384, 3073794, 4.176018238067627], [1437072333.665269, 3083926, 4.048959732055664], [1437072813.584388, 3094040, 4.178991794586182], [1437073293.569178, 3104172, 3.8385396003723145]] \ No newline at end of file
diff --git a/tensorflow/tensorboard/components/tf-event-dashboard/demo/data/beta/d1.json b/tensorflow/tensorboard/components/tf-event-dashboard/demo/data/beta/d1.json
new file mode 100644
index 0000000000..27ff64e5dd
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-event-dashboard/demo/data/beta/d1.json
@@ -0,0 +1 @@
+[[1436925978.257845, 7, 1.009283951483666897], [1436926413.945391, 1476, 0.932567862421274185], [1436926893.945037, 6006, 0.02773338556289673], [1436927373.995472, 13786, 0.021291319280862808], [1436927853.989794, 23650, 0.515582754276692867], [1436928334.132361, 33755, 0.011689444072544575], [1436928813.973288, 43941, 0.009183925576508045], [1436929293.975949, 54146, 0.007850822061300278], [1436929773.992781, 64316, 0.007189035415649414], [1436930253.997415, 74465, 0.007230754010379314], [1436930734.203004, 84611, 0.007685001939535141], [1436931214.03644, 94700, 0.008264732547104359], [1436931694.094564, 104766, 0.008946491405367851], [1436932174.114955, 114817, 0.00966302677989006], [1436932654.161382, 124880, 0.010994276031851768], [1436933133.960214, 134977, 0.01196141354739666], [1436933614.044337, 145062, 0.012673594057559967], [1436934094.166206, 155169, 0.013639944605529308], [1436934574.106036, 165284, 0.014305333606898785], [1436935054.150647, 175402, 0.014946178533136845], [1436935533.819562, 185538, 0.015736915171146393], [1436936013.710422, 195712, 0.01633097417652607], [1436936493.609025, 205906, 0.01669587567448616], [1436936973.683892, 216099, 0.017459288239479065], [1436937454.138383, 226331, 0.018532060086727142], [1436937933.838475, 236532, 0.01949254982173443], [1436938413.89688, 246724, 0.01951725408434868], [1436938894.018652, 256925, 0.019763393327593803], [1436939373.69067, 267137, 0.02008610963821411], [1436939853.673692, 277369, 0.021090799942612648], [1436940333.651346, 287620, 0.021408839151263237], [1436940813.599579, 297848, 0.021988894790410995], [1436941293.596313, 308088, 0.02236073836684227], [1436941773.659172, 318362, 0.022547174245119095], [1436942253.648479, 328621, 0.02303086407482624], [1436942733.752284, 338892, 0.023787079378962517], [1436943213.621881, 349144, 0.024007514119148254], [1436943693.698743, 359399, 0.02414763905107975], [1436944173.578463, 369649, 0.024576496332883835], [1436944653.692217, 379912, 0.02469169721007347], [1436945133.677298, 390180, 0.024951916188001633], [1436945613.572411, 400445, 0.025548970326781273], [1436946093.56123, 410703, 0.025769377127289772], [1436946573.542364, 420958, 0.02602097950875759], [1436947053.616578, 431216, 0.026028109714388847], [1436947533.636973, 441483, 0.026348495855927467], [1436948013.541574, 451751, 0.02621930092573166], [1436948493.560223, 462015, 0.02671053633093834], [1436948973.512541, 472260, 0.0272178016602993], [1436949453.550055, 482483, 0.02734796144068241], [1436949933.828011, 492731, 0.027217809110879898], [1436950413.603177, 502957, 0.027318621054291725], [1436950893.563009, 513185, 0.027304155752062798], [1436951373.620887, 523410, 0.027759933844208717], [1436951853.61941, 533618, 0.028056234121322632], [1436952333.694447, 543828, 0.028620803728699684], [1436952813.621004, 554042, 0.028957637026906013], [1436953293.588156, 564251, 0.029187509790062904], [1436953773.599734, 574464, 0.028960268944501877], [1436954253.621309, 584672, 0.02891424670815468], [1436954733.738119, 594882, 0.029211293905973434], [1436955213.56617, 605091, 0.029444213956594467], [1436955693.585366, 615296, 0.02974688820540905], [1436956173.626395, 625501, 0.03026159666478634], [1436956653.601937, 635705, 0.03039497137069702], [1436957133.665878, 645915, 0.03041839227080345], [1436957613.584762, 656116, 0.030588043853640556], [1436958093.549783, 666331, 0.030284974724054337], [1436958573.646778, 676543, 0.030354496091604233], [1436959053.585655, 686750, 0.030551007017493248], [1436959533.679696, 696961, 0.03068561479449272], [1436960013.633292, 707173, 0.030921893194317818], [1436960493.578778, 717383, 0.031080031767487526], [1436960973.596715, 727598, 0.030773505568504333], [1436961453.625644, 737818, 0.03084484674036503], [1436961933.740339, 748040, 0.03110458515584469], [1436962413.573845, 758252, 0.03114113211631775], [1436962893.610678, 768470, 0.03101053647696972], [1436963373.642878, 778674, 0.03110116347670555], [1436963853.558388, 788877, 0.031342316418886185], [1436964333.658419, 799099, 0.03130127117037773], [1436964813.573319, 809289, 0.031288161873817444], [1436965293.542098, 819484, 0.031435444951057434], [1436965773.545453, 829687, 0.03166936710476875], [1436966253.586517, 839901, 0.03169429674744606], [1436966733.639348, 850120, 0.03191458433866501], [1436967213.697288, 860330, 0.03205746412277222], [1436967693.617172, 870539, 0.03206293657422066], [1436968173.593885, 880748, 0.031957853585481644], [1436968653.560836, 890955, 0.0316658616065979], [1436969133.676337, 901164, 0.031929533928632736], [1436969613.506638, 911358, 0.03174331784248352], [1436970093.595964, 921560, 0.03157960623502731], [1436970573.541227, 931756, 0.03176721930503845], [1436971053.624316, 941945, 0.031810544431209564], [1436971533.655543, 952138, 0.031946416944265366], [1436972013.604738, 962349, 0.03205405920743942], [1436972493.613199, 972551, 0.031924981623888016], [1436972973.501155, 982746, 0.03199697285890579], [1436973453.64842, 992945, 0.03204970061779022], [1436973933.689516, 1003147, 0.032020214945077896], [1436974413.577769, 1013350, 0.03207497298717499], [1436974893.542281, 1023545, 0.03221454098820686], [1436975373.638453, 1033759, 0.032191887497901917], [1436975853.524388, 1043955, 0.03240729123353958], [1436976333.625792, 1054148, 0.032219529151916504], [1436976813.610661, 1064342, 0.03200426697731018], [1436977293.601581, 1074539, 0.03198647499084473], [1436977773.575627, 1084733, 0.0320645235478878], [1436978253.564972, 1094914, 0.0322980061173439], [1436978733.673144, 1105109, 0.032482605427503586], [1436979213.540585, 1115293, 0.032628435641527176], [1436979693.699591, 1125483, 0.032744552940130234], [1436980173.613012, 1135670, 0.03268158435821533], [1436980653.575769, 1145862, 0.0324023962020874], [1436981133.719264, 1156045, 0.03237328305840492], [1436981613.563551, 1166236, 0.03202575817704201], [1436982093.553233, 1176436, 0.03216284513473511], [1436982573.577846, 1186636, 0.03232415020465851], [1436983053.605749, 1196837, 0.0324099175632], [1436983533.684994, 1207025, 0.03245137259364128], [1436984013.561492, 1217233, 0.032246463000774384], [1436984493.629873, 1227437, 0.032042667269706726], [1436984973.606714, 1237643, 0.0318642184138298], [1436985453.690084, 1247835, 0.03191140666604042], [1436985933.711388, 1257951, 0.032287366688251495], [1436986413.598807, 1268125, 0.03226638585329056], [1436986893.631797, 1278290, 0.03252791240811348], [1436987373.596962, 1288473, 0.03241675719618797], [1436987853.555549, 1298650, 0.032103829085826874], [1436988333.722032, 1308841, 0.031904906034469604], [1436988813.55697, 1319018, 0.03179024159908295], [1436989293.756905, 1329221, 0.03168707340955734], [1436989773.665141, 1339417, 0.03160175681114197], [1436990253.768302, 1349610, 0.03161788731813431], [1436990733.708919, 1359759, 0.031772397458553314], [1436991213.663033, 1369914, 0.031758904457092285], [1436991693.730925, 1380074, 0.031629469245672226], [1436992173.751791, 1390224, 0.03154703974723816], [1436992653.758682, 1400383, 0.031527940183877945], [1436993133.835604, 1410542, 0.03169580549001694], [1436993613.674655, 1420684, 0.03182605654001236], [1436994093.747454, 1430832, 0.03185024857521057], [1436994573.768973, 1440986, 0.03199737146496773], [1436995053.666661, 1451174, 0.03156095743179321], [1436995533.83439, 1461345, 0.03150693327188492], [1436996013.556996, 1471495, 0.031496383249759674], [1436996493.635477, 1481663, 0.0313432440161705], [1436996973.668684, 1491822, 0.031145794317126274], [1436997453.59326, 1501979, 0.03106667660176754], [1436997933.774019, 1512139, 0.03143244981765747], [1436998413.575162, 1522290, 0.03142988309264183], [1436998893.640468, 1532431, 0.03132546320557594], [1436999373.551661, 1542579, 0.03125471621751785], [1436999853.57906, 1552734, 0.03098788857460022], [1437000333.680409, 1562888, 0.0308846328407526], [1437000813.602383, 1573037, 0.03082612156867981], [1437001293.610337, 1583190, 0.030793681740760803], [1437001773.618199, 1593341, 0.03087364137172699], [1437002253.572966, 1603497, 0.030839646235108376], [1437002733.67994, 1613657, 0.030705047771334648], [1437003213.583266, 1623809, 0.03071814589202404], [1437003693.639943, 1633966, 0.0304812490940094], [1437004173.568287, 1644113, 0.03030412085354328], [1437004653.610772, 1654268, 0.03032425045967102], [1437005133.663045, 1664424, 0.030430471524596214], [1437005613.580984, 1674567, 0.03036225587129593], [1437006093.601019, 1684715, 0.03056645393371582], [1437006573.625314, 1694857, 0.03043070062994957], [1437007053.584514, 1704999, 0.030224520713090897], [1437007533.719303, 1715150, 0.03024231642484665], [1437008013.604962, 1725282, 0.03009769506752491], [1437008493.655091, 1735432, 0.030214866623282433], [1437008973.640165, 1745584, 0.030181538313627243], [1437009453.715067, 1755742, 0.03017231822013855], [1437009933.765712, 1765896, 0.030141284689307213], [1437010413.632128, 1776052, 0.030052203685045242], [1437010893.66766, 1786195, 0.030078601092100143], [1437011373.636164, 1796346, 0.029969291761517525], [1437011853.631224, 1806481, 0.02999536693096161], [1437012333.706205, 1816617, 0.030100464820861816], [1437012813.61987, 1826754, 0.03008824959397316], [1437013293.479904, 1836883, 0.029995709657669067], [1437013773.604574, 1847029, 0.02995096519589424], [1437014253.618884, 1857175, 0.02980179339647293], [1437014733.756419, 1867312, 0.029607007279992104], [1437015213.638607, 1877459, 0.02952035330235958], [1437015693.625763, 1887608, 0.02937002293765545], [1437016173.63194, 1897759, 0.029285306110978127], [1437016653.609074, 1907909, 0.029194746166467667], [1437017133.717601, 1918074, 0.029153630137443542], [1437017613.716011, 1928220, 0.029063496738672256], [1437018093.626005, 1938377, 0.028990253806114197], [1437018573.626522, 1948523, 0.0290801040828228], [1437019053.648174, 1958678, 0.029026925563812256], [1437019533.803011, 1968831, 0.029071522876620293], [1437020013.667751, 1978978, 0.02911040186882019], [1437020493.659028, 1989133, 0.02908971533179283], [1437020973.657346, 1999287, 0.028982823714613914], [1437021453.650634, 2009437, 0.028793631121516228], [1437021933.848661, 2019588, 0.02868799678981304], [1437022413.674963, 2029736, 0.028585929423570633], [1437022893.69086, 2039894, 0.028488371521234512], [1437023373.68883, 2050054, 0.028293771669268608], [1437023853.686116, 2060205, 0.028227869421243668], [1437024333.763876, 2070362, 0.0280953086912632], [1437024813.707845, 2080507, 0.02794187143445015], [1437025293.483294, 2090645, 0.0278786551207304], [1437025773.695712, 2100793, 0.02786232903599739], [1437026253.672994, 2110943, 0.02783624827861786], [1437026733.780775, 2121094, 0.027756746858358383], [1437027213.617849, 2131235, 0.027644069865345955], [1437027693.694451, 2141382, 0.02752004750072956], [1437028173.68596, 2151537, 0.0274327602237463], [1437028653.584833, 2161685, 0.027434347197413445], [1437029133.792483, 2171839, 0.02731819450855255], [1437029613.661672, 2181977, 0.027138520032167435], [1437030093.641009, 2192118, 0.027088932693004608], [1437030573.656274, 2202268, 0.02713087759912014], [1437031053.643631, 2212416, 0.027159670367836952], [1437031533.777478, 2222583, 0.027089878916740417], [1437032013.704008, 2232736, 0.026989545673131943], [1437032493.638393, 2242882, 0.02692277729511261], [1437032973.684986, 2253041, 0.026783647015690804], [1437033453.699562, 2263183, 0.026735099032521248], [1437033933.918074, 2273320, 0.02665248140692711], [1437034413.596351, 2283443, 0.02659791149199009], [1437034893.640496, 2293579, 0.026540575549006462], [1437035373.637761, 2303701, 0.02647154964506626], [1437035853.669947, 2313823, 0.02645135670900345], [1437036333.78905, 2323961, 0.026429900899529457], [1437036813.699727, 2334089, 0.026324935257434845], [1437037293.662592, 2344235, 0.026287639513611794], [1437037773.66716, 2354364, 0.02626391313970089], [1437038253.603687, 2364507, 0.026225272566080093], [1437038733.78864, 2374644, 0.026248561218380928], [1437039213.641799, 2384782, 0.026243599131703377], [1437039693.687078, 2394923, 0.026255469769239426], [1437040173.635717, 2405058, 0.026186810806393623], [1437040653.673331, 2415194, 0.02606010064482689], [1437041133.764768, 2425322, 0.026031550019979477], [1437041613.629279, 2435449, 0.02595149166882038], [1437042093.703985, 2445575, 0.025885630398988724], [1437042573.496029, 2455712, 0.025858554989099503], [1437043053.686022, 2465844, 0.0257696695625782], [1437043533.731929, 2475974, 0.02574242651462555], [1437044013.636245, 2486095, 0.025741754099726677], [1437044493.69923, 2496238, 0.02561314031481743], [1437044973.652155, 2506373, 0.02550213597714901], [1437045453.691467, 2516497, 0.025422468781471252], [1437045933.935804, 2526637, 0.025300107896327972], [1437046413.635583, 2536770, 0.02533198893070221], [1437046893.626337, 2546896, 0.025261884555220604], [1437047373.67437, 2557029, 0.025176096707582474], [1437047853.652939, 2567169, 0.025054505094885826], [1437048333.778436, 2577306, 0.024978378787636757], [1437048813.654248, 2587433, 0.024952610954642296], [1437049293.610609, 2597552, 0.02484666183590889], [1437049773.646573, 2607690, 0.024764036759734154], [1437050253.667925, 2617808, 0.024689028039574623], [1437050733.735291, 2627933, 0.024599267169833183], [1437051213.620222, 2638053, 0.024585112929344177], [1437051693.601978, 2648171, 0.024474989622831345], [1437052173.634985, 2658299, 0.024343013763427734], [1437052653.687176, 2668425, 0.024294432252645493], [1437053133.762819, 2678556, 0.024164099246263504], [1437053613.643698, 2688671, 0.024035055190324783], [1437054093.673047, 2698804, 0.024000361561775208], [1437054573.667371, 2708956, 0.023914529010653496], [1437055053.650441, 2719087, 0.023955287411808968], [1437055533.778469, 2729219, 0.023859601467847824], [1437056013.694082, 2739343, 0.023759596049785614], [1437056493.674871, 2749458, 0.02367720566689968], [1437056973.700234, 2759575, 0.023645451292395592], [1437057453.666129, 2769697, 0.023565715178847313], [1437057933.848506, 2779821, 0.023514313623309135], [1437058413.643799, 2789941, 0.023489659652113914], [1437058893.715386, 2800076, 0.023429812863469124], [1437059373.62596, 2810207, 0.023344023153185844], [1437059853.650848, 2820334, 0.023226741701364517], [1437060333.792248, 2830465, 0.023134270682930946], [1437060813.682955, 2840600, 0.02305578999221325], [1437061293.681795, 2850745, 0.02298513427376747], [1437061773.691182, 2860880, 0.022913720458745956], [1437062253.662987, 2871013, 0.022864067927002907], [1437062733.760419, 2881153, 0.02278953418135643], [1437063213.651969, 2891278, 0.02276339940726757], [1437063693.723523, 2901406, 0.022675812244415283], [1437064173.68663, 2911533, 0.022622767835855484], [1437064653.547643, 2921667, 0.02255198359489441], [1437065133.62645, 2931813, 0.022431762889027596], [1437065613.566569, 2941947, 0.022368362173438072], [1437066093.537804, 2952102, 0.022323831915855408], [1437066573.529332, 2962243, 0.02226843684911728], [1437067053.520098, 2972400, 0.022210361436009407], [1437067533.605733, 2982561, 0.022118505090475082], [1437068013.535467, 2992698, 0.022013112902641296], [1437068493.559976, 3002839, 0.02197197824716568], [1437068973.558743, 3012983, 0.02191166952252388], [1437069453.562661, 3023116, 0.021851476281881332], [1437069933.627071, 3033256, 0.021762533113360405], [1437070413.574131, 3043386, 0.021733969449996948], [1437070893.658803, 3053528, 0.021669406443834305], [1437071373.638711, 3063659, 0.02159426547586918], [1437071853.621384, 3073794, 0.02153114229440689], [1437072333.665269, 3083926, 0.021499117836356163], [1437072813.584388, 3094040, 0.021457014605402946], [1437073293.569178, 3104172, 0.021365314722061157]]
diff --git a/tensorflow/tensorboard/components/tf-event-dashboard/demo/data/beta/d2.json b/tensorflow/tensorboard/components/tf-event-dashboard/demo/data/beta/d2.json
new file mode 100644
index 0000000000..fb5a18d53a
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-event-dashboard/demo/data/beta/d2.json
@@ -0,0 +1 @@
+[[1436925978.257845, 7, 0.01034154836088419], [1436926413.945391, 1476, 0.03646053001284599], [1436926893.945037, 6006, 0.031110260635614395], [1436927373.995472, 13786, 0.024214591830968857], [1436927853.989794, 23650, 0.01820789836347103], [1436928334.132361, 33755, 0.01442798599600792], [1436928813.973288, 43941, 0.012150184251368046], [1436929293.975949, 54146, 0.011141776107251644], [1436929773.992781, 64316, 0.010859030298888683], [1436930253.997415, 74465, 0.011160558089613914], [1436930734.203004, 84611, 0.011997541412711143], [1436931214.03644, 94700, 0.01278648804873228], [1436931694.094564, 104766, 0.014073861762881279], [1436932174.114955, 114817, 0.01523376815021038], [1436932654.161382, 124880, 0.016527879983186722], [1436933133.960214, 134977, 0.01782997138798237], [1436933614.044337, 145062, 0.019055265933275223], [1436934094.166206, 155169, 0.02028629370033741], [1436934574.106036, 165284, 0.02116803079843521], [1436935054.150647, 175402, 0.022192901000380516], [1436935533.819562, 185538, 0.022869590669870377], [1436936013.710422, 195712, 0.023398980498313904], [1436936493.609025, 205906, 0.02443159930408001], [1436936973.683892, 216099, 0.025154944509267807], [1436937454.138383, 226331, 0.025802481919527054], [1436937933.838475, 236532, 0.027000702917575836], [1436938413.89688, 246724, 0.02752412110567093], [1436938894.018652, 256925, 0.0278119258582592], [1436939373.69067, 267137, 0.027698883786797523], [1436939853.673692, 277369, 0.028744956478476524], [1436940333.651346, 287620, 0.029281964525580406], [1436940813.599579, 297848, 0.03002205118536949], [1436941293.596313, 308088, 0.030467400327324867], [1436941773.659172, 318362, 0.03132195770740509], [1436942253.648479, 328621, 0.031431782990694046], [1436942733.752284, 338892, 0.03147844970226288], [1436943213.621881, 349144, 0.032013144344091415], [1436943693.698743, 359399, 0.03241390734910965], [1436944173.578463, 369649, 0.03261363133788109], [1436944653.692217, 379912, 0.033306822180747986], [1436945133.677298, 390180, 0.03390969708561897], [1436945613.572411, 400445, 0.03396527096629143], [1436946093.56123, 410703, 0.03388286381959915], [1436946573.542364, 420958, 0.03399669751524925], [1436947053.616578, 431216, 0.03394070267677307], [1436947533.636973, 441483, 0.03419327735900879], [1436948013.541574, 451751, 0.0342416949570179], [1436948493.560223, 462015, 0.034808479249477386], [1436948973.512541, 472260, 0.03552314639091492], [1436949453.550055, 482483, 0.036012329161167145], [1436949933.828011, 492731, 0.035826291888952255], [1436950413.603177, 502957, 0.03600003197789192], [1436950893.563009, 513185, 0.03563224524259567], [1436951373.620887, 523410, 0.03584449738264084], [1436951853.61941, 533618, 0.03587675839662552], [1436952333.694447, 543828, 0.036698292940855026], [1436952813.621004, 554042, 0.03698749095201492], [1436953293.588156, 564251, 0.03712376952171326], [1436953773.599734, 574464, 0.03729996830224991], [1436954253.621309, 584672, 0.03730553761124611], [1436954733.738119, 594882, 0.037479378283023834], [1436955213.56617, 605091, 0.03754287213087082], [1436955693.585366, 615296, 0.0377657376229763], [1436956173.626395, 625501, 0.038117796182632446], [1436956653.601937, 635705, 0.03822959586977959], [1436957133.665878, 645915, 0.03776161000132561], [1436957613.584762, 656116, 0.03816362842917442], [1436958093.549783, 666331, 0.03853853791952133], [1436958573.646778, 676543, 0.03826189786195755], [1436959053.585655, 686750, 0.0381099209189415], [1436959533.679696, 696961, 0.03844142332673073], [1436960013.633292, 707173, 0.03868117928504944], [1436960493.578778, 717383, 0.0390009842813015], [1436960973.596715, 727598, 0.0383562371134758], [1436961453.625644, 737818, 0.0382055900990963], [1436961933.740339, 748040, 0.03806299716234207], [1436962413.573845, 758252, 0.03807120397686958], [1436962893.610678, 768470, 0.03795558586716652], [1436963373.642878, 778674, 0.038018494844436646], [1436963853.558388, 788877, 0.038447774946689606], [1436964333.658419, 799099, 0.03842216357588768], [1436964813.573319, 809289, 0.03840547427535057], [1436965293.542098, 819484, 0.038492728024721146], [1436965773.545453, 829687, 0.0387515053153038], [1436966253.586517, 839901, 0.03869732841849327], [1436966733.639348, 850120, 0.03907460719347], [1436967213.697288, 860330, 0.0395859070122242], [1436967693.617172, 870539, 0.039280518889427185], [1436968173.593885, 880748, 0.0392826572060585], [1436968653.560836, 890955, 0.03899630531668663], [1436969133.676337, 901164, 0.03888440132141113], [1436969613.506638, 911358, 0.038790252059698105], [1436970093.595964, 921560, 0.03851785138249397], [1436970573.541227, 931756, 0.03913348540663719], [1436971053.624316, 941945, 0.038978900760412216], [1436971533.655543, 952138, 0.03925086557865143], [1436972013.604738, 962349, 0.039124101400375366], [1436972493.613199, 972551, 0.0390220545232296], [1436972973.501155, 982746, 0.039025235921144485], [1436973453.64842, 992945, 0.03877083212137222], [1436973933.689516, 1003147, 0.03902769833803177], [1436974413.577769, 1013350, 0.038719139993190765], [1436974893.542281, 1023545, 0.03872331231832504], [1436975373.638453, 1033759, 0.03927341103553772], [1436975853.524388, 1043955, 0.03930830955505371], [1436976333.625792, 1054148, 0.039153918623924255], [1436976813.610661, 1064342, 0.03932590410113335], [1436977293.601581, 1074539, 0.03922765702009201], [1436977773.575627, 1084733, 0.039390794932842255], [1436978253.564972, 1094914, 0.03935663774609566], [1436978733.673144, 1105109, 0.03939087316393852], [1436979213.540585, 1115293, 0.039371199905872345], [1436979693.699591, 1125483, 0.03982992097735405], [1436980173.613012, 1135670, 0.03941287472844124], [1436980653.575769, 1145862, 0.03933672979474068], [1436981133.719264, 1156045, 0.03919614478945732], [1436981613.563551, 1166236, 0.03906407952308655], [1436982093.553233, 1176436, 0.038837045431137085], [1436982573.577846, 1186636, 0.039009105414152145], [1436983053.605749, 1196837, 0.039010051637887955], [1436983533.684994, 1207025, 0.03891472890973091], [1436984013.561492, 1217233, 0.038610219955444336], [1436984493.629873, 1227437, 0.03866511583328247], [1436984973.606714, 1237643, 0.03865685313940048], [1436985453.690084, 1247835, 0.038945719599723816], [1436985933.711388, 1257951, 0.03925580158829689], [1436986413.598807, 1268125, 0.039332933723926544], [1436986893.631797, 1278290, 0.03918297216296196], [1436987373.596962, 1288473, 0.03883613646030426], [1436987853.555549, 1298650, 0.038776978850364685], [1436988333.722032, 1308841, 0.03888171166181564], [1436988813.55697, 1319018, 0.038825325667858124], [1436989293.756905, 1329221, 0.03864298388361931], [1436989773.665141, 1339417, 0.03865634649991989], [1436990253.768302, 1349610, 0.03898858651518822], [1436990733.708919, 1359759, 0.03906260430812836], [1436991213.663033, 1369914, 0.03911694139242172], [1436991693.730925, 1380074, 0.03875250369310379], [1436992173.751791, 1390224, 0.03882621228694916], [1436992653.758682, 1400383, 0.03877855837345123], [1436993133.835604, 1410542, 0.03870398923754692], [1436993613.674655, 1420684, 0.03887751325964928], [1436994093.747454, 1430832, 0.03915301710367203], [1436994573.768973, 1440986, 0.03938450664281845], [1436995053.666661, 1451174, 0.03919720649719238], [1436995533.83439, 1461345, 0.038862887769937515], [1436996013.556996, 1471495, 0.03901274502277374], [1436996493.635477, 1481663, 0.0388539656996727], [1436996973.668684, 1491822, 0.038732752203941345], [1436997453.59326, 1501979, 0.03879735246300697], [1436997933.774019, 1512139, 0.038524042814970016], [1436998413.575162, 1522290, 0.03869651257991791], [1436998893.640468, 1532431, 0.0383637398481369], [1436999373.551661, 1542579, 0.038300249725580215], [1436999853.57906, 1552734, 0.03799160569906235], [1437000333.680409, 1562888, 0.03759683296084404], [1437000813.602383, 1573037, 0.037678662687540054], [1437001293.610337, 1583190, 0.037575822323560715], [1437001773.618199, 1593341, 0.0376887246966362], [1437002253.572966, 1603497, 0.037922415882349014], [1437002733.67994, 1613657, 0.03766244649887085], [1437003213.583266, 1623809, 0.03754705190658569], [1437003693.639943, 1633966, 0.03738937899470329], [1437004173.568287, 1644113, 0.037347543984651566], [1437004653.610772, 1654268, 0.037374842911958694], [1437005133.663045, 1664424, 0.037443988025188446], [1437005613.580984, 1674567, 0.037457264959812164], [1437006093.601019, 1684715, 0.037874478846788406], [1437006573.625314, 1694857, 0.037644676864147186], [1437007053.584514, 1704999, 0.03743988648056984], [1437007533.719303, 1715150, 0.03739031031727791], [1437008013.604962, 1725282, 0.037301771342754364], [1437008493.655091, 1735432, 0.03735104575753212], [1437008973.640165, 1745584, 0.037282250821590424], [1437009453.715067, 1755742, 0.03729768097400665], [1437009933.765712, 1765896, 0.03717759624123573], [1437010413.632128, 1776052, 0.03691410645842552], [1437010893.66766, 1786195, 0.036807890981435776], [1437011373.636164, 1796346, 0.036659423261880875], [1437011853.631224, 1806481, 0.03682238608598709], [1437012333.706205, 1816617, 0.036776404827833176], [1437012813.61987, 1826754, 0.036672260612249374], [1437013293.479904, 1836883, 0.03666841238737106], [1437013773.604574, 1847029, 0.036642514169216156], [1437014253.618884, 1857175, 0.03654393553733826], [1437014733.756419, 1867312, 0.03638240322470665], [1437015213.638607, 1877459, 0.03610989451408386], [1437015693.625763, 1887608, 0.036011870950460434], [1437016173.63194, 1897759, 0.03607400134205818], [1437016653.609074, 1907909, 0.03581620752811432], [1437017133.717601, 1918074, 0.035680998116731644], [1437017613.716011, 1928220, 0.03547567501664162], [1437018093.626005, 1938377, 0.035375215113162994], [1437018573.626522, 1948523, 0.03534447029232979], [1437019053.648174, 1958678, 0.03535373508930206], [1437019533.803011, 1968831, 0.03541970252990723], [1437020013.667751, 1978978, 0.03534942492842674], [1437020493.659028, 1989133, 0.035337116569280624], [1437020973.657346, 1999287, 0.03519223630428314], [1437021453.650634, 2009437, 0.0350094810128212], [1437021933.848661, 2019588, 0.03481736779212952], [1437022413.674963, 2029736, 0.03482922539114952], [1437022893.69086, 2039894, 0.03482965752482414], [1437023373.68883, 2050054, 0.034710027277469635], [1437023853.686116, 2060205, 0.03447446599602699], [1437024333.763876, 2070362, 0.034356746822595596], [1437024813.707845, 2080507, 0.03430519998073578], [1437025293.483294, 2090645, 0.03412580490112305], [1437025773.695712, 2100793, 0.03409077599644661], [1437026253.672994, 2110943, 0.0340830534696579], [1437026733.780775, 2121094, 0.03400549292564392], [1437027213.617849, 2131235, 0.033846043050289154], [1437027693.694451, 2141382, 0.03379584103822708], [1437028173.68596, 2151537, 0.033618565648794174], [1437028653.584833, 2161685, 0.03352222591638565], [1437029133.792483, 2171839, 0.03338197246193886], [1437029613.661672, 2181977, 0.03323192894458771], [1437030093.641009, 2192118, 0.03313163295388222], [1437030573.656274, 2202268, 0.0331595316529274], [1437031053.643631, 2212416, 0.03310840204358101], [1437031533.777478, 2222583, 0.03298124670982361], [1437032013.704008, 2232736, 0.03288085386157036], [1437032493.638393, 2242882, 0.03281677886843681], [1437032973.684986, 2253041, 0.03261971473693848], [1437033453.699562, 2263183, 0.03251069411635399], [1437033933.918074, 2273320, 0.03243493288755417], [1437034413.596351, 2283443, 0.03251812607049942], [1437034893.640496, 2293579, 0.03244208171963692], [1437035373.637761, 2303701, 0.03246922418475151], [1437035853.669947, 2313823, 0.032652080059051514], [1437036333.78905, 2323961, 0.032621122896671295], [1437036813.699727, 2334089, 0.03248974680900574], [1437037293.662592, 2344235, 0.032404426485300064], [1437037773.66716, 2354364, 0.03240393102169037], [1437038253.603687, 2364507, 0.03238365799188614], [1437038733.78864, 2374644, 0.03244389593601227], [1437039213.641799, 2384782, 0.03239350765943527], [1437039693.687078, 2394923, 0.032426562160253525], [1437040173.635717, 2405058, 0.032403264194726944], [1437040653.673331, 2415194, 0.03231978043913841], [1437041133.764768, 2425322, 0.03223187103867531], [1437041613.629279, 2435449, 0.03213196247816086], [1437042093.703985, 2445575, 0.032153598964214325], [1437042573.496029, 2455712, 0.03199320286512375], [1437043053.686022, 2465844, 0.03188605234026909], [1437043533.731929, 2475974, 0.03178738057613373], [1437044013.636245, 2486095, 0.03171614184975624], [1437044493.69923, 2496238, 0.031645938754081726], [1437044973.652155, 2506373, 0.03155189007520676], [1437045453.691467, 2516497, 0.03144536912441254], [1437045933.935804, 2526637, 0.031432293355464935], [1437046413.635583, 2536770, 0.03129834309220314], [1437046893.626337, 2546896, 0.031195342540740967], [1437047373.67437, 2557029, 0.031033318489789963], [1437047853.652939, 2567169, 0.030938012525439262], [1437048333.778436, 2577306, 0.030827201902866364], [1437048813.654248, 2587433, 0.03068169392645359], [1437049293.610609, 2597552, 0.030520914122462273], [1437049773.646573, 2607690, 0.030437452718615532], [1437050253.667925, 2617808, 0.03041636385023594], [1437050733.735291, 2627933, 0.030291059985756874], [1437051213.620222, 2638053, 0.030283397063612938], [1437051693.601978, 2648171, 0.030193043872714043], [1437052173.634985, 2658299, 0.03004123829305172], [1437052653.687176, 2668425, 0.0299222432076931], [1437053133.762819, 2678556, 0.029762346297502518], [1437053613.643698, 2688671, 0.02970775216817856], [1437054093.673047, 2698804, 0.029604140669107437], [1437054573.667371, 2708956, 0.02949359640479088], [1437055053.650441, 2719087, 0.02943229116499424], [1437055533.778469, 2729219, 0.029304414987564087], [1437056013.694082, 2739343, 0.029147598892450333], [1437056493.674871, 2749458, 0.029033908620476723], [1437056973.700234, 2759575, 0.028886595740914345], [1437057453.666129, 2769697, 0.028734514489769936], [1437057933.848506, 2779821, 0.02874554693698883], [1437058413.643799, 2789941, 0.028716085478663445], [1437058893.715386, 2800076, 0.028669510036706924], [1437059373.62596, 2810207, 0.028530430048704147], [1437059853.650848, 2820334, 0.02839958481490612], [1437060333.792248, 2830465, 0.028364405035972595], [1437060813.682955, 2840600, 0.0282796248793602], [1437061293.681795, 2850745, 0.02820495329797268], [1437061773.691182, 2860880, 0.028159918263554573], [1437062253.662987, 2871013, 0.028104742988944054], [1437062733.760419, 2881153, 0.028099438175559044], [1437063213.651969, 2891278, 0.02802356891334057], [1437063693.723523, 2901406, 0.027945902198553085], [1437064173.68663, 2911533, 0.027897505089640617], [1437064653.547643, 2921667, 0.027821676805615425], [1437065133.62645, 2931813, 0.02770490199327469], [1437065613.566569, 2941947, 0.02761264331638813], [1437066093.537804, 2952102, 0.027557073161005974], [1437066573.529332, 2962243, 0.027522796764969826], [1437067053.520098, 2972400, 0.027469975873827934], [1437067533.605733, 2982561, 0.027299631386995316], [1437068013.535467, 2992698, 0.027225365862250328], [1437068493.559976, 3002839, 0.027095869183540344], [1437068973.558743, 3012983, 0.027036350220441818], [1437069453.562661, 3023116, 0.02693818509578705], [1437069933.627071, 3033256, 0.02687198854982853], [1437070413.574131, 3043386, 0.02687297947704792], [1437070893.658803, 3053528, 0.026770537719130516], [1437071373.638711, 3063659, 0.026667704805731773], [1437071853.621384, 3073794, 0.026571234688162804], [1437072333.665269, 3083926, 0.026447603479027748], [1437072813.584388, 3094040, 0.026389220729470253], [1437073293.569178, 3104172, 0.026299258694052696]] \ No newline at end of file
diff --git a/tensorflow/tensorboard/components/tf-event-dashboard/demo/data/beta/d3.json b/tensorflow/tensorboard/components/tf-event-dashboard/demo/data/beta/d3.json
new file mode 100644
index 0000000000..e489130ea7
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-event-dashboard/demo/data/beta/d3.json
@@ -0,0 +1 @@
+[[1436925978.257845, 7, 0.03425809368491173], [1436926413.945391, 1476, 0.032557398080825806], [1436926893.945037, 6006, 0.0277252234518528], [1436927373.995472, 13786, 0.021282576024532318], [1436927853.989794, 23650, 0.015578101389110088], [1436928334.132361, 33755, 0.011687012389302254], [1436928813.973288, 43941, 0.00918175745755434], [1436929293.975949, 54146, 0.00784988235682249], [1436929773.992781, 64316, 0.007188988849520683], [1436930253.997415, 74465, 0.0072308750823140144], [1436930734.203004, 84611, 0.007685060612857342], [1436931214.03644, 94700, 0.008267422206699848], [1436931694.094564, 104766, 0.008946981281042099], [1436932174.114955, 114817, 0.009664506651461124], [1436932654.161382, 124880, 0.010994983837008476], [1436933133.960214, 134977, 0.011961394920945168], [1436933614.044337, 145062, 0.012674711644649506], [1436934094.166206, 155169, 0.013640021905303001], [1436934574.106036, 165284, 0.014305224642157555], [1436935054.150647, 175402, 0.014946703799068928], [1436935533.819562, 185538, 0.015737954527139664], [1436936013.710422, 195712, 0.016330912709236145], [1436936493.609025, 205906, 0.016695979982614517], [1436936973.683892, 216099, 0.017458846792578697], [1436937454.138383, 226331, 0.018533164635300636], [1436937933.838475, 236532, 0.01949200965464115], [1436938413.89688, 246724, 0.019517479464411736], [1436938894.018652, 256925, 0.019764307886362076], [1436939373.69067, 267137, 0.02008572220802307], [1436939853.673692, 277369, 0.021091068163514137], [1436940333.651346, 287620, 0.02140945754945278], [1436940813.599579, 297848, 0.021988170221447945], [1436941293.596313, 308088, 0.0223606675863266], [1436941773.659172, 318362, 0.022547796368598938], [1436942253.648479, 328621, 0.023031413555145264], [1436942733.752284, 338892, 0.023786410689353943], [1436943213.621881, 349144, 0.024008480831980705], [1436943693.698743, 359399, 0.024148935452103615], [1436944173.578463, 369649, 0.02457556128501892], [1436944653.692217, 379912, 0.02469060942530632], [1436945133.677298, 390180, 0.024952523410320282], [1436945613.572411, 400445, 0.02554873190820217], [1436946093.56123, 410703, 0.025771528482437134], [1436946573.542364, 420958, 0.02602078951895237], [1436947053.616578, 431216, 0.02602880820631981], [1436947533.636973, 441483, 0.026351822540163994], [1436948013.541574, 451751, 0.0262188371270895], [1436948493.560223, 462015, 0.026711203157901764], [1436948973.512541, 472260, 0.027218565344810486], [1436949453.550055, 482483, 0.02734719216823578], [1436949933.828011, 492731, 0.027217986062169075], [1436950413.603177, 502957, 0.027318857610225677], [1436950893.563009, 513185, 0.027305351570248604], [1436951373.620887, 523410, 0.027760380879044533], [1436951853.61941, 533618, 0.0280567966401577], [1436952333.694447, 543828, 0.028621215373277664], [1436952813.621004, 554042, 0.028958816081285477], [1436953293.588156, 564251, 0.029186993837356567], [1436953773.599734, 574464, 0.028960207477211952], [1436954253.621309, 584672, 0.028913332149386406], [1436954733.738119, 594882, 0.02921229600906372], [1436955213.56617, 605091, 0.029444556683301926], [1436955693.585366, 615296, 0.029747728258371353], [1436956173.626395, 625501, 0.030260732397437096], [1436956653.601937, 635705, 0.030394721776247025], [1436957133.665878, 645915, 0.03041674755513668], [1436957613.584762, 656116, 0.03058660589158535], [1436958093.549783, 666331, 0.030284838750958443], [1436958573.646778, 676543, 0.030354052782058716], [1436959053.585655, 686750, 0.030551131814718246], [1436959533.679696, 696961, 0.030686482787132263], [1436960013.633292, 707173, 0.030921922996640205], [1436960493.578778, 717383, 0.031079748645424843], [1436960973.596715, 727598, 0.03077232837677002], [1436961453.625644, 737818, 0.03084420971572399], [1436961933.740339, 748040, 0.03110562451183796], [1436962413.573845, 758252, 0.031141508370637894], [1436962893.610678, 768470, 0.031010067090392113], [1436963373.642878, 778674, 0.031100917607545853], [1436963853.558388, 788877, 0.03134296461939812], [1436964333.658419, 799099, 0.031301673501729965], [1436964813.573319, 809289, 0.031290579587221146], [1436965293.542098, 819484, 0.031435515731573105], [1436965773.545453, 829687, 0.031667787581682205], [1436966253.586517, 839901, 0.03169453889131546], [1436966733.639348, 850120, 0.03191617131233215], [1436967213.697288, 860330, 0.03205711767077446], [1436967693.617172, 870539, 0.03206227719783783], [1436968173.593885, 880748, 0.03195691108703613], [1436968653.560836, 890955, 0.03166574612259865], [1436969133.676337, 901164, 0.031929291784763336], [1436969613.506638, 911358, 0.031744007021188736], [1436970093.595964, 921560, 0.0315803587436676], [1436970573.541227, 931756, 0.031766779720783234], [1436971053.624316, 941945, 0.03181062266230583], [1436971533.655543, 952138, 0.0319465771317482], [1436972013.604738, 962349, 0.032054755836725235], [1436972493.613199, 972551, 0.03192495182156563], [1436972973.501155, 982746, 0.0319976881146431], [1436973453.64842, 992945, 0.03205036744475365], [1436973933.689516, 1003147, 0.032020118087530136], [1436974413.577769, 1013350, 0.03207429125905037], [1436974893.542281, 1023545, 0.032214779406785965], [1436975373.638453, 1033759, 0.03219134360551834], [1436975853.524388, 1043955, 0.0324082113802433], [1436976333.625792, 1054148, 0.03221917897462845], [1436976813.610661, 1064342, 0.03200480341911316], [1436977293.601581, 1074539, 0.03198748826980591], [1436977773.575627, 1084733, 0.032064300030469894], [1436978253.564972, 1094914, 0.032298240810632706], [1436978733.673144, 1105109, 0.03248215466737747], [1436979213.540585, 1115293, 0.03262820467352867], [1436979693.699591, 1125483, 0.032745134085416794], [1436980173.613012, 1135670, 0.032681502401828766], [1436980653.575769, 1145862, 0.03240214288234711], [1436981133.719264, 1156045, 0.03237201273441315], [1436981613.563551, 1166236, 0.03202598914504051], [1436982093.553233, 1176436, 0.032163310796022415], [1436982573.577846, 1186636, 0.03232435882091522], [1436983053.605749, 1196837, 0.032410554587841034], [1436983533.684994, 1207025, 0.03245232254266739], [1436984013.561492, 1217233, 0.03224659338593483], [1436984493.629873, 1227437, 0.03204221650958061], [1436984973.606714, 1237643, 0.03186390548944473], [1436985453.690084, 1247835, 0.031911786645650864], [1436985933.711388, 1257951, 0.032286882400512695], [1436986413.598807, 1268125, 0.032266560941934586], [1436986893.631797, 1278290, 0.03252791985869408], [1436987373.596962, 1288473, 0.03241678699851036], [1436987853.555549, 1298650, 0.03210347890853882], [1436988333.722032, 1308841, 0.031904902309179306], [1436988813.55697, 1319018, 0.03179018944501877], [1436989293.756905, 1329221, 0.0316874124109745], [1436989773.665141, 1339417, 0.03160090371966362], [1436990253.768302, 1349610, 0.03161816671490669], [1436990733.708919, 1359759, 0.0317724235355854], [1436991213.663033, 1369914, 0.03175821527838707], [1436991693.730925, 1380074, 0.031629402190446854], [1436992173.751791, 1390224, 0.031547073274850845], [1436992653.758682, 1400383, 0.031528495252132416], [1436993133.835604, 1410542, 0.03169562667608261], [1436993613.674655, 1420684, 0.031826674938201904], [1436994093.747454, 1430832, 0.03185039013624191], [1436994573.768973, 1440986, 0.03199826925992966], [1436995053.666661, 1451174, 0.03156091645359993], [1436995533.83439, 1461345, 0.031506411731243134], [1436996013.556996, 1471495, 0.031495608389377594], [1436996493.635477, 1481663, 0.03134337440133095], [1436996973.668684, 1491822, 0.031145554035902023], [1436997453.59326, 1501979, 0.031068041920661926], [1436997933.774019, 1512139, 0.031432390213012695], [1436998413.575162, 1522290, 0.03142932057380676], [1436998893.640468, 1532431, 0.03132513165473938], [1436999373.551661, 1542579, 0.03125539794564247], [1436999853.57906, 1552734, 0.0309873279184103], [1437000333.680409, 1562888, 0.03088490664958954], [1437000813.602383, 1573037, 0.0308260228484869], [1437001293.610337, 1583190, 0.030793415382504463], [1437001773.618199, 1593341, 0.03087344579398632], [1437002253.572966, 1603497, 0.0308389812707901], [1437002733.67994, 1613657, 0.03070608340203762], [1437003213.583266, 1623809, 0.0307186096906662], [1437003693.639943, 1633966, 0.03048117645084858], [1437004173.568287, 1644113, 0.03030446544289589], [1437004653.610772, 1654268, 0.030324051156640053], [1437005133.663045, 1664424, 0.03043009154498577], [1437005613.580984, 1674567, 0.030361991375684738], [1437006093.601019, 1684715, 0.030566193163394928], [1437006573.625314, 1694857, 0.030430208891630173], [1437007053.584514, 1704999, 0.030224468559026718], [1437007533.719303, 1715150, 0.030241932719945908], [1437008013.604962, 1725282, 0.030097855255007744], [1437008493.655091, 1735432, 0.030217904597520828], [1437008973.640165, 1745584, 0.030181601643562317], [1437009453.715067, 1755742, 0.030172593891620636], [1437009933.765712, 1765896, 0.030141659080982208], [1437010413.632128, 1776052, 0.030052196234464645], [1437010893.66766, 1786195, 0.03007938154041767], [1437011373.636164, 1796346, 0.02996920794248581], [1437011853.631224, 1806481, 0.029995175078511238], [1437012333.706205, 1816617, 0.03010040894150734], [1437012813.61987, 1826754, 0.030088385567069054], [1437013293.479904, 1836883, 0.029996229335665703], [1437013773.604574, 1847029, 0.029950618743896484], [1437014253.618884, 1857175, 0.029801754280924797], [1437014733.756419, 1867312, 0.029606210067868233], [1437015213.638607, 1877459, 0.029520301148295403], [1437015693.625763, 1887608, 0.02937021106481552], [1437016173.63194, 1897759, 0.02928493171930313], [1437016653.609074, 1907909, 0.029194936156272888], [1437017133.717601, 1918074, 0.029153617098927498], [1437017613.716011, 1928220, 0.029063349589705467], [1437018093.626005, 1938377, 0.02899051643908024], [1437018573.626522, 1948523, 0.02908063493669033], [1437019053.648174, 1958678, 0.029026903212070465], [1437019533.803011, 1968831, 0.029071694239974022], [1437020013.667751, 1978978, 0.029110101982951164], [1437020493.659028, 1989133, 0.02908976934850216], [1437020973.657346, 1999287, 0.028982611373066902], [1437021453.650634, 2009437, 0.028793690726161003], [1437021933.848661, 2019588, 0.02868787571787834], [1437022413.674963, 2029736, 0.028585631400346756], [1437022893.69086, 2039894, 0.02848806604743004], [1437023373.68883, 2050054, 0.028294002637267113], [1437023853.686116, 2060205, 0.02822807803750038], [1437024333.763876, 2070362, 0.02809525839984417], [1437024813.707845, 2080507, 0.027941878885030746], [1437025293.483294, 2090645, 0.02787884697318077], [1437025773.695712, 2100793, 0.027862509712576866], [1437026253.672994, 2110943, 0.027835993096232414], [1437026733.780775, 2121094, 0.027756690979003906], [1437027213.617849, 2131235, 0.027644263580441475], [1437027693.694451, 2141382, 0.02752007730305195], [1437028173.68596, 2151537, 0.027432529255747795], [1437028653.584833, 2161685, 0.027434471994638443], [1437029133.792483, 2171839, 0.027317894622683525], [1437029613.661672, 2181977, 0.027138294652104378], [1437030093.641009, 2192118, 0.027088705450296402], [1437030573.656274, 2202268, 0.027131302282214165], [1437031053.643631, 2212416, 0.02715957537293434], [1437031533.777478, 2222583, 0.027089620009064674], [1437032013.704008, 2232736, 0.026989320293068886], [1437032493.638393, 2242882, 0.026922713965177536], [1437032973.684986, 2253041, 0.02678370475769043], [1437033453.699562, 2263183, 0.0267350971698761], [1437033933.918074, 2273320, 0.026652036234736443], [1437034413.596351, 2283443, 0.0265977680683136], [1437034893.640496, 2293579, 0.02654072269797325], [1437035373.637761, 2303701, 0.026471523568034172], [1437035853.669947, 2313823, 0.026451298967003822], [1437036333.78905, 2323961, 0.026429779827594757], [1437036813.699727, 2334089, 0.026324886828660965], [1437037293.662592, 2344235, 0.026287589222192764], [1437037773.66716, 2354364, 0.026264755055308342], [1437038253.603687, 2364507, 0.026225194334983826], [1437038733.78864, 2374644, 0.02624845691025257], [1437039213.641799, 2384782, 0.02624380588531494], [1437039693.687078, 2394923, 0.026255516335368156], [1437040173.635717, 2405058, 0.026186630129814148], [1437040653.673331, 2415194, 0.026059549301862717], [1437041133.764768, 2425322, 0.02603207901120186], [1437041613.629279, 2435449, 0.025951188057661057], [1437042093.703985, 2445575, 0.025885486975312233], [1437042573.496029, 2455712, 0.0258584376424551], [1437043053.686022, 2465844, 0.02576967515051365], [1437043533.731929, 2475974, 0.02574247308075428], [1437044013.636245, 2486095, 0.025741368532180786], [1437044493.69923, 2496238, 0.025613142177462578], [1437044973.652155, 2506373, 0.025502001866698265], [1437045453.691467, 2516497, 0.025422129780054092], [1437045933.935804, 2526637, 0.02530006691813469], [1437046413.635583, 2536770, 0.02533203549683094], [1437046893.626337, 2546896, 0.025261884555220604], [1437047373.67437, 2557029, 0.02517615258693695], [1437047853.652939, 2567169, 0.025054262951016426], [1437048333.778436, 2577306, 0.024978358298540115], [1437048813.654248, 2587433, 0.024952327832579613], [1437049293.610609, 2597552, 0.024846646934747696], [1437049773.646573, 2607690, 0.024763893336057663], [1437050253.667925, 2617808, 0.024688972160220146], [1437050733.735291, 2627933, 0.024599123746156693], [1437051213.620222, 2638053, 0.024585271254181862], [1437051693.601978, 2648171, 0.024474715813994408], [1437052173.634985, 2658299, 0.0243435837328434], [1437052653.687176, 2668425, 0.024294523522257805], [1437053133.762819, 2678556, 0.024163981899619102], [1437053613.643698, 2688671, 0.024034887552261353], [1437054093.673047, 2698804, 0.024000374600291252], [1437054573.667371, 2708956, 0.023914175108075142], [1437055053.650441, 2719087, 0.02395522966980934], [1437055533.778469, 2729219, 0.023859599605202675], [1437056013.694082, 2739343, 0.02375946193933487], [1437056493.674871, 2749458, 0.023677179589867592], [1437056973.700234, 2759575, 0.023645443841814995], [1437057453.666129, 2769697, 0.02356558106839657], [1437057933.848506, 2779821, 0.023514214903116226], [1437058413.643799, 2789941, 0.023489613085985184], [1437058893.715386, 2800076, 0.023429814726114273], [1437059373.62596, 2810207, 0.023343827575445175], [1437059853.650848, 2820334, 0.02322673238813877], [1437060333.792248, 2830465, 0.023134106770157814], [1437060813.682955, 2840600, 0.023055672645568848], [1437061293.681795, 2850745, 0.022985080257058144], [1437061773.691182, 2860880, 0.02291373908519745], [1437062253.662987, 2871013, 0.022864071652293205], [1437062733.760419, 2881153, 0.0227896086871624], [1437063213.651969, 2891278, 0.02276325598359108], [1437063693.723523, 2901406, 0.022676151245832443], [1437064173.68663, 2911533, 0.022622840479016304], [1437064653.547643, 2921667, 0.022551873698830605], [1437065133.62645, 2931813, 0.022431621327996254], [1437065613.566569, 2941947, 0.022368427366018295], [1437066093.537804, 2952102, 0.022323856130242348], [1437066573.529332, 2962243, 0.022268367931246758], [1437067053.520098, 2972400, 0.022210223600268364], [1437067533.605733, 2982561, 0.022118542343378067], [1437068013.535467, 2992698, 0.022013003006577492], [1437068493.559976, 3002839, 0.021971898153424263], [1437068973.558743, 3012983, 0.021911533549427986], [1437069453.562661, 3023116, 0.021851375699043274], [1437069933.627071, 3033256, 0.021762363612651825], [1437070413.574131, 3043386, 0.021733952686190605], [1437070893.658803, 3053528, 0.021669508889317513], [1437071373.638711, 3063659, 0.021594204008579254], [1437071853.621384, 3073794, 0.021531015634536743], [1437072333.665269, 3083926, 0.021499203518033028], [1437072813.584388, 3094040, 0.021456807851791382], [1437073293.569178, 3104172, 0.02136526256799698]] \ No newline at end of file
diff --git a/tensorflow/tensorboard/components/tf-event-dashboard/demo/data/beta/d4.json b/tensorflow/tensorboard/components/tf-event-dashboard/demo/data/beta/d4.json
new file mode 100644
index 0000000000..434b78cd0f
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-event-dashboard/demo/data/beta/d4.json
@@ -0,0 +1 @@
+[[1436925978.257845, 7, 0.5028539896011353], [1436926413.945391, 1476, 0.4976981580257416], [1436926893.945037, 6006, 0.5092837810516357], [1436927373.995472, 13786, 0.5118998885154724], [1436927853.989794, 23650, 0.5314905643463135], [1436928334.132361, 33755, 0.550969123840332], [1436928813.973288, 43941, 0.5487659573554993], [1436929293.975949, 54146, 0.5263530015945435], [1436929773.992781, 64316, 0.5077286958694458], [1436930253.997415, 74465, 0.5120566487312317], [1436930734.203004, 84611, 0.5140185952186584], [1436931214.03644, 94700, 0.5133042335510254], [1436931694.094564, 104766, 0.5233010053634644], [1436932174.114955, 114817, 0.5230671763420105], [1436932654.161382, 124880, 0.5250263810157776], [1436933133.960214, 134977, 0.5088120698928833], [1436933614.044337, 145062, 0.5097426176071167], [1436934094.166206, 155169, 0.5103482007980347], [1436934574.106036, 165284, 0.5021579265594482], [1436935054.150647, 175402, 0.49785494804382324], [1436935533.819562, 185538, 0.4970649182796478], [1436936013.710422, 195712, 0.5023221373558044], [1436936493.609025, 205906, 0.5063169002532959], [1436936973.683892, 216099, 0.50455641746521], [1436937454.138383, 226331, 0.5104150772094727], [1436937933.838475, 236532, 0.5066487193107605], [1436938413.89688, 246724, 0.5183079838752747], [1436938894.018652, 256925, 0.5163102746009827], [1436939373.69067, 267137, 0.5216323733329773], [1436939853.673692, 277369, 0.5153006315231323], [1436940333.651346, 287620, 0.5240126252174377], [1436940813.599579, 297848, 0.5263218879699707], [1436941293.596313, 308088, 0.5236956477165222], [1436941773.659172, 318362, 0.534295916557312], [1436942253.648479, 328621, 0.540306031703949], [1436942733.752284, 338892, 0.5359382033348083], [1436943213.621881, 349144, 0.540198564529419], [1436943693.698743, 359399, 0.5404431819915771], [1436944173.578463, 369649, 0.5429667234420776], [1436944653.692217, 379912, 0.5415231585502625], [1436945133.677298, 390180, 0.54068922996521], [1436945613.572411, 400445, 0.5396349430084229], [1436946093.56123, 410703, 0.5486253499984741], [1436946573.542364, 420958, 0.5451043248176575], [1436947053.616578, 431216, 0.5478819608688354], [1436947533.636973, 441483, 0.5503379106521606], [1436948013.541574, 451751, 0.5534676313400269], [1436948493.560223, 462015, 0.5574610829353333], [1436948973.512541, 472260, 0.5558810234069824], [1436949453.550055, 482483, 0.5529404878616333], [1436949933.828011, 492731, 0.5618430972099304], [1436950413.603177, 502957, 0.5641138553619385], [1436950893.563009, 513185, 0.5707159638404846], [1436951373.620887, 523410, 0.5676558613777161], [1436951853.61941, 533618, 0.5637813806533813], [1436952333.694447, 543828, 0.5682924389839172], [1436952813.621004, 554042, 0.5690237283706665], [1436953293.588156, 564251, 0.5655006766319275], [1436953773.599734, 574464, 0.553955614566803], [1436954253.621309, 584672, 0.5558924674987793], [1436954733.738119, 594882, 0.5603042840957642], [1436955213.56617, 605091, 0.5625290870666504], [1436955693.585366, 615296, 0.5668522715568542], [1436956173.626395, 625501, 0.5736584663391113], [1436956653.601937, 635705, 0.5693879723548889], [1436957133.665878, 645915, 0.576599657535553], [1436957613.584762, 656116, 0.5648065805435181], [1436958093.549783, 666331, 0.5632508397102356], [1436958573.646778, 676543, 0.5660487413406372], [1436959053.585655, 686750, 0.568809449672699], [1436959533.679696, 696961, 0.5667826533317566], [1436960013.633292, 707173, 0.5637232661247253], [1436960493.578778, 717383, 0.5675314664840698], [1436960973.596715, 727598, 0.5714674592018127], [1436961453.625644, 737818, 0.564845085144043], [1436961933.740339, 748040, 0.5700833797454834], [1436962413.573845, 758252, 0.5702976584434509], [1436962893.610678, 768470, 0.5745863914489746], [1436963373.642878, 778674, 0.5763651728630066], [1436963853.558388, 788877, 0.5721960067749023], [1436964333.658419, 799099, 0.5714120864868164], [1436964813.573319, 809289, 0.5687000155448914], [1436965293.542098, 819484, 0.5728974938392639], [1436965773.545453, 829687, 0.5738612413406372], [1436966253.586517, 839901, 0.5702064037322998], [1436966733.639348, 850120, 0.5715107321739197], [1436967213.697288, 860330, 0.5695001482963562], [1436967693.617172, 870539, 0.5783872008323669], [1436968173.593885, 880748, 0.5758792161941528], [1436968653.560836, 890955, 0.572809636592865], [1436969133.676337, 901164, 0.5752230286598206], [1436969613.506638, 911358, 0.5861247181892395], [1436970093.595964, 921560, 0.5834078788757324], [1436970573.541227, 931756, 0.5814791321754456], [1436971053.624316, 941945, 0.5803619623184204], [1436971533.655543, 952138, 0.5765199065208435], [1436972013.604738, 962349, 0.5693190693855286], [1436972493.613199, 972551, 0.5720453262329102], [1436972973.501155, 982746, 0.5741620063781738], [1436973453.64842, 992945, 0.5705713629722595], [1436973933.689516, 1003147, 0.5657351613044739], [1436974413.577769, 1013350, 0.5685256123542786], [1436974893.542281, 1023545, 0.5698860287666321], [1436975373.638453, 1033759, 0.5801734328269958], [1436975853.524388, 1043955, 0.577880322933197], [1436976333.625792, 1054148, 0.5780594348907471], [1436976813.610661, 1064342, 0.5804633498191833], [1436977293.601581, 1074539, 0.5842364430427551], [1436977773.575627, 1084733, 0.5745837092399597], [1436978253.564972, 1094914, 0.5848771333694458], [1436978733.673144, 1105109, 0.5795935392379761], [1436979213.540585, 1115293, 0.583346426486969], [1436979693.699591, 1125483, 0.5840965509414673], [1436980173.613012, 1135670, 0.5807850360870361], [1436980653.575769, 1145862, 0.5843925476074219], [1436981133.719264, 1156045, 0.5828814506530762], [1436981613.563551, 1166236, 0.5873864889144897], [1436982093.553233, 1176436, 0.5896572470664978], [1436982573.577846, 1186636, 0.5887367725372314], [1436983053.605749, 1196837, 0.5841871500015259], [1436983533.684994, 1207025, 0.5867579579353333], [1436984013.561492, 1217233, 0.5940297842025757], [1436984493.629873, 1227437, 0.5925037860870361], [1436984973.606714, 1237643, 0.5981529951095581], [1436985453.690084, 1247835, 0.5954598188400269], [1436985933.711388, 1257951, 0.5903756022453308], [1436986413.598807, 1268125, 0.5837404131889343], [1436986893.631797, 1278290, 0.583182156085968], [1436987373.596962, 1288473, 0.5860618352890015], [1436987853.555549, 1298650, 0.5829544067382812], [1436988333.722032, 1308841, 0.5798720121383667], [1436988813.55697, 1319018, 0.589148998260498], [1436989293.756905, 1329221, 0.5905702710151672], [1436989773.665141, 1339417, 0.5900465250015259], [1436990253.768302, 1349610, 0.5893078446388245], [1436990733.708919, 1359759, 0.589722752571106], [1436991213.663033, 1369914, 0.5907371640205383], [1436991693.730925, 1380074, 0.5939858555793762], [1436992173.751791, 1390224, 0.5906378626823425], [1436992653.758682, 1400383, 0.5876493453979492], [1436993133.835604, 1410542, 0.5912420153617859], [1436993613.674655, 1420684, 0.5887293219566345], [1436994093.747454, 1430832, 0.589107096195221], [1436994573.768973, 1440986, 0.5928497910499573], [1436995053.666661, 1451174, 0.5916265845298767], [1436995533.83439, 1461345, 0.5911784768104553], [1436996013.556996, 1471495, 0.5890726447105408], [1436996493.635477, 1481663, 0.5914839506149292], [1436996973.668684, 1491822, 0.5915400385856628], [1436997453.59326, 1501979, 0.591564416885376], [1436997933.774019, 1512139, 0.5926578640937805], [1436998413.575162, 1522290, 0.5942149758338928], [1436998893.640468, 1532431, 0.5931802988052368], [1436999373.551661, 1542579, 0.587592601776123], [1436999853.57906, 1552734, 0.5877953171730042], [1437000333.680409, 1562888, 0.590681791305542], [1437000813.602383, 1573037, 0.5924896001815796], [1437001293.610337, 1583190, 0.5913501381874084], [1437001773.618199, 1593341, 0.5952408909797668], [1437002253.572966, 1603497, 0.5953922271728516], [1437002733.67994, 1613657, 0.6002237200737], [1437003213.583266, 1623809, 0.6042569875717163], [1437003693.639943, 1633966, 0.6017740368843079], [1437004173.568287, 1644113, 0.6037994623184204], [1437004653.610772, 1654268, 0.6037947535514832], [1437005133.663045, 1664424, 0.6028310060501099], [1437005613.580984, 1674567, 0.603211522102356], [1437006093.601019, 1684715, 0.6052727699279785], [1437006573.625314, 1694857, 0.6032628417015076], [1437007053.584514, 1704999, 0.5978461503982544], [1437007533.719303, 1715150, 0.602828323841095], [1437008013.604962, 1725282, 0.6063790917396545], [1437008493.655091, 1735432, 0.6047347784042358], [1437008973.640165, 1745584, 0.6031648516654968], [1437009453.715067, 1755742, 0.6067507863044739], [1437009933.765712, 1765896, 0.6062817573547363], [1437010413.632128, 1776052, 0.609245240688324], [1437010893.66766, 1786195, 0.6066284775733948], [1437011373.636164, 1796346, 0.6102170944213867], [1437011853.631224, 1806481, 0.609173595905304], [1437012333.706205, 1816617, 0.6035751104354858], [1437012813.61987, 1826754, 0.604059636592865], [1437013293.479904, 1836883, 0.6039224863052368], [1437013773.604574, 1847029, 0.5974730849266052], [1437014253.618884, 1857175, 0.6040806174278259], [1437014733.756419, 1867312, 0.6017186045646667], [1437015213.638607, 1877459, 0.5987159609794617], [1437015693.625763, 1887608, 0.6047909259796143], [1437016173.63194, 1897759, 0.6033824682235718], [1437016653.609074, 1907909, 0.6038352847099304], [1437017133.717601, 1918074, 0.6083348989486694], [1437017613.716011, 1928220, 0.6044996380805969], [1437018093.626005, 1938377, 0.6009799242019653], [1437018573.626522, 1948523, 0.60047847032547], [1437019053.648174, 1958678, 0.6019382476806641], [1437019533.803011, 1968831, 0.6007305383682251], [1437020013.667751, 1978978, 0.6025127172470093], [1437020493.659028, 1989133, 0.6051828861236572], [1437020973.657346, 1999287, 0.6085876822471619], [1437021453.650634, 2009437, 0.6065122485160828], [1437021933.848661, 2019588, 0.6084572076797485], [1437022413.674963, 2029736, 0.6065473556518555], [1437022893.69086, 2039894, 0.6075063347816467], [1437023373.68883, 2050054, 0.6095973253250122], [1437023853.686116, 2060205, 0.6047213077545166], [1437024333.763876, 2070362, 0.6034210324287415], [1437024813.707845, 2080507, 0.6008927822113037], [1437025293.483294, 2090645, 0.604469895362854], [1437025773.695712, 2100793, 0.6068717837333679], [1437026253.672994, 2110943, 0.6099737882614136], [1437026733.780775, 2121094, 0.6105009317398071], [1437027213.617849, 2131235, 0.611957311630249], [1437027693.694451, 2141382, 0.6141949892044067], [1437028173.68596, 2151537, 0.6135279536247253], [1437028653.584833, 2161685, 0.6111017465591431], [1437029133.792483, 2171839, 0.6135671138763428], [1437029613.661672, 2181977, 0.6112024188041687], [1437030093.641009, 2192118, 0.6097264289855957], [1437030573.656274, 2202268, 0.6097284555435181], [1437031053.643631, 2212416, 0.6121350526809692], [1437031533.777478, 2222583, 0.6147991418838501], [1437032013.704008, 2232736, 0.6118316054344177], [1437032493.638393, 2242882, 0.6191433072090149], [1437032973.684986, 2253041, 0.6188027262687683], [1437033453.699562, 2263183, 0.6163974404335022], [1437033933.918074, 2273320, 0.6144159436225891], [1437034413.596351, 2283443, 0.6123769879341125], [1437034893.640496, 2293579, 0.6139131188392639], [1437035373.637761, 2303701, 0.6150627136230469], [1437035853.669947, 2313823, 0.6149951219558716], [1437036333.78905, 2323961, 0.6155945658683777], [1437036813.699727, 2334089, 0.613308310508728], [1437037293.662592, 2344235, 0.6153736114501953], [1437037773.66716, 2354364, 0.6160987615585327], [1437038253.603687, 2364507, 0.611574113368988], [1437038733.78864, 2374644, 0.6145234107971191], [1437039213.641799, 2384782, 0.6117951273918152], [1437039693.687078, 2394923, 0.6129845380783081], [1437040173.635717, 2405058, 0.6095831394195557], [1437040653.673331, 2415194, 0.6110679507255554], [1437041133.764768, 2425322, 0.6099690198898315], [1437041613.629279, 2435449, 0.6105908155441284], [1437042093.703985, 2445575, 0.6124749779701233], [1437042573.496029, 2455712, 0.6118302345275879], [1437043053.686022, 2465844, 0.6094756722450256], [1437043533.731929, 2475974, 0.6094986796379089], [1437044013.636245, 2486095, 0.6114639639854431], [1437044493.69923, 2496238, 0.6101082563400269], [1437044973.652155, 2506373, 0.6105718612670898], [1437045453.691467, 2516497, 0.6115666627883911], [1437045933.935804, 2526637, 0.6128115653991699], [1437046413.635583, 2536770, 0.6122986078262329], [1437046893.626337, 2546896, 0.6142017245292664], [1437047373.67437, 2557029, 0.6111341714859009], [1437047853.652939, 2567169, 0.611350417137146], [1437048333.778436, 2577306, 0.6126709580421448], [1437048813.654248, 2587433, 0.6111524105072021], [1437049293.610609, 2597552, 0.6135894060134888], [1437049773.646573, 2607690, 0.6136029362678528], [1437050253.667925, 2617808, 0.6141685843467712], [1437050733.735291, 2627933, 0.6170881390571594], [1437051213.620222, 2638053, 0.6189730167388916], [1437051693.601978, 2648171, 0.6157540678977966], [1437052173.634985, 2658299, 0.6178646683692932], [1437052653.687176, 2668425, 0.6164441108703613], [1437053133.762819, 2678556, 0.6175132393836975], [1437053613.643698, 2688671, 0.6158696413040161], [1437054093.673047, 2698804, 0.6162974238395691], [1437054573.667371, 2708956, 0.6160892844200134], [1437055053.650441, 2719087, 0.6176281571388245], [1437055533.778469, 2729219, 0.6165231466293335], [1437056013.694082, 2739343, 0.6171510219573975], [1437056493.674871, 2749458, 0.6124134659767151], [1437056973.700234, 2759575, 0.6120688319206238], [1437057453.666129, 2769697, 0.6126770377159119], [1437057933.848506, 2779821, 0.6126595139503479], [1437058413.643799, 2789941, 0.616513729095459], [1437058893.715386, 2800076, 0.6130264401435852], [1437059373.62596, 2810207, 0.6114044785499573], [1437059853.650848, 2820334, 0.6077002882957458], [1437060333.792248, 2830465, 0.6086235046386719], [1437060813.682955, 2840600, 0.6084680557250977], [1437061293.681795, 2850745, 0.6094310879707336], [1437061773.691182, 2860880, 0.6066345572471619], [1437062253.662987, 2871013, 0.6094250082969666], [1437062733.760419, 2881153, 0.609106719493866], [1437063213.651969, 2891278, 0.6080747246742249], [1437063693.723523, 2901406, 0.6081057786941528], [1437064173.68663, 2911533, 0.6066460609436035], [1437064653.547643, 2921667, 0.6057829856872559], [1437065133.62645, 2931813, 0.6092885136604309], [1437065613.566569, 2941947, 0.6089289784431458], [1437066093.537804, 2952102, 0.6070758700370789], [1437066573.529332, 2962243, 0.6096142530441284], [1437067053.520098, 2972400, 0.609714925289154], [1437067533.605733, 2982561, 0.6116167306900024], [1437068013.535467, 2992698, 0.6119107007980347], [1437068493.559976, 3002839, 0.6119140386581421], [1437068973.558743, 3012983, 0.6115538477897644], [1437069453.562661, 3023116, 0.6126777529716492], [1437069933.627071, 3033256, 0.6146017909049988], [1437070413.574131, 3043386, 0.6119789481163025], [1437070893.658803, 3053528, 0.6139205694198608], [1437071373.638711, 3063659, 0.612362802028656], [1437071853.621384, 3073794, 0.6109192371368408], [1437072333.665269, 3083926, 0.6141091585159302], [1437072813.584388, 3094040, 0.6132751703262329], [1437073293.569178, 3104172, 0.6132386922836304]] \ No newline at end of file
diff --git a/tensorflow/tensorboard/components/tf-event-dashboard/demo/data/runs.json b/tensorflow/tensorboard/components/tf-event-dashboard/demo/data/runs.json
new file mode 100644
index 0000000000..90fb0cbb09
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-event-dashboard/demo/data/runs.json
@@ -0,0 +1,22 @@
+{
+ "alpha": {
+ "scalars": [
+ "d1",
+ "d2",
+ "d3",
+ "d4"
+ ],
+ "histograms": [],
+ "images": []
+ },
+ "beta": {
+ "scalars": [
+ "d1",
+ "d2",
+ "d3",
+ "d4"
+ ],
+ "histograms": [],
+ "images": []
+ }
+}
diff --git a/tensorflow/tensorboard/components/tf-event-dashboard/demo/index.html b/tensorflow/tensorboard/components/tf-event-dashboard/demo/index.html
new file mode 100644
index 0000000000..34ad6a7263
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-event-dashboard/demo/index.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+ <link rel="import" href="../tf-event-dashboard.html">
+ <link rel="stylesheet" type="text/css" href="../../../lib/css/global.css">
+ <title>Event Dashboard Demo Demo</title>
+ </head>
+ <body>
+ <script>
+ TF.Urls.runsUrl = function() {return "data/runs.json"};
+ TF.Urls.scalarsUrl = function(tag, run) {return "data/" + run + "/" + tag + ".json";};
+ </script>
+
+ <tf-event-dashboard id="demo"></tf-event-dashboard>
+ </body>
+</html>
diff --git a/tensorflow/tensorboard/components/tf-event-dashboard/dragZoomInteraction.ts b/tensorflow/tensorboard/components/tf-event-dashboard/dragZoomInteraction.ts
new file mode 100644
index 0000000000..bf9f7b70e2
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-event-dashboard/dragZoomInteraction.ts
@@ -0,0 +1,150 @@
+module Plottable {
+export class DragZoomLayer extends Components.SelectionBoxLayer {
+ private _dragInteraction: Interactions.Drag;
+ private _doubleClickInteraction: Interactions.DoubleClick;
+ private xDomainToRestore: any[];
+ private yDomainToRestore: any[];
+ private isZoomed = false;
+ private easeFn: (t: number) => number = d3.ease("cubic-in-out");
+ private _animationTime = 750;
+
+ /* 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
+ */
+ constructor(xScale: QuantitativeScale<number | { valueOf(): number }>,
+ yScale: QuantitativeScale<number | { valueOf(): number }>) {
+ super();
+ this.xScale(xScale);
+ this.yScale(yScale);
+ this._dragInteraction = new Interactions.Drag();
+ this._dragInteraction.attachTo(this);
+ this._doubleClickInteraction = new Interactions.DoubleClick();
+ this._doubleClickInteraction.attachTo(this);
+ this.setupCallbacks();
+ }
+
+ private setupCallbacks() {
+ let dragging = false;
+ this._dragInteraction.onDragStart((startPoint: Point) => {
+ this.bounds({
+ topLeft: startPoint,
+ bottomRight: startPoint,
+ });
+ });
+ this._dragInteraction.onDrag((startPoint, endPoint) => {
+ this.bounds({topLeft: startPoint, bottomRight: endPoint});
+ this.boxVisible(true);
+ dragging = true;
+ });
+ this._dragInteraction.onDragEnd((startPoint, endPoint) => {
+ this.boxVisible(false);
+ this.bounds({topLeft: startPoint, bottomRight: endPoint});
+ if (dragging) {
+ this.zoom();
+ }
+ dragging = false;
+ });
+
+ this._doubleClickInteraction.onDoubleClick(this.unzoom.bind(this));
+ }
+
+ /* Set the time (in ms) over which the zoom will interpolate.
+ * 0 implies no interpolation. (ie zoom is instant)
+ */
+ public animationTime(): number;
+ public animationTime(animationTime: number): DragZoomLayer;
+ public animationTime(animationTime?: number): any {
+ 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. */
+ public ease(fn: (t: number) => number): DragZoomLayer {
+ if (typeof(fn) !== "function") {
+ throw new Error("ease function must be a function");
+ }
+ if (fn(0) !== 0 || fn(1) !== 1) {
+ 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
+ private zoom() {
+ let x0: number = this.xExtent()[0].valueOf();
+ let x1: number = this.xExtent()[1].valueOf();
+ let y0: number = this.yExtent()[1].valueOf();
+ let y1: number = this.yExtent()[0].valueOf();
+
+ if (x0 === x1 || y0 === y1) {
+ return;
+ }
+
+ if (!this.isZoomed) {
+ this.isZoomed = true;
+ this.xDomainToRestore = this.xScale().domain();
+ this.yDomainToRestore = this.yScale().domain();
+ }
+ this.interpolateZoom(x0, x1, y0, y1);
+ }
+
+ // Restore the scales to their state before any zoom
+ private unzoom() {
+ if (!this.isZoomed) {
+ return;
+ }
+ this.isZoomed = false;
+ this.interpolateZoom(this.xDomainToRestore[0], this.xDomainToRestore[1],
+ this.yDomainToRestore[0], this.yDomainToRestore[1]);
+ }
+
+ // If we are zooming, disable interactions, to avoid contention
+ private isZooming(isZooming: boolean) {
+ this._dragInteraction.enabled(!isZooming);
+ this._doubleClickInteraction.enabled(!isZooming);
+ }
+
+ private interpolateZoom(x0f: number, x1f: number, y0f: number, y1f: number) {
+ let x0s: number = this.xScale().domain()[0].valueOf();
+ let x1s: number = this.xScale().domain()[1].valueOf();
+ let y0s: number = this.yScale().domain()[0].valueOf();
+ let y1s: number = this.yScale().domain()[1].valueOf();
+
+ // Copy a ref to the ease fn, so that changing ease wont affect zooms in progress
+ let ease = this.easeFn;
+ let interpolator = (a: number, b: number, p: number) => d3.interpolateNumber(a, b)(ease(p));
+
+ this.isZooming(true);
+ let start = Date.now();
+ let draw = () => {
+ let now = Date.now();
+ let passed = now - start;
+ let p = this._animationTime === 0 ? 1 : Math.min(1, passed / this._animationTime);
+ let x0 = interpolator(x0s, x0f, p);
+ let x1 = interpolator(x1s, x1f, p);
+ let y0 = interpolator(y0s, y0f, p);
+ let y1 = interpolator(y1s, y1f, p);
+ this.xScale().domain([x0, x1]);
+ this.yScale().domain([y0, y1]);
+ if (p < 1) {
+ Utils.DOM.requestAnimationFramePolyfill(draw);
+ } else {
+ this.isZooming(false);
+ }
+ };
+ draw();
+ }
+}
+}
diff --git a/tensorflow/tensorboard/components/tf-event-dashboard/tf-chart.html b/tensorflow/tensorboard/components/tf-event-dashboard/tf-chart.html
new file mode 100644
index 0000000000..39ca9704c3
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-event-dashboard/tf-chart.html
@@ -0,0 +1,101 @@
+<link rel="import" href="../../bower_components/polymer/polymer.html">
+<link rel="import" href="../imports/plottable.html">
+<link rel="import" href="../imports/lodash.html">
+
+<!--
+tf-chart (TFChart) creates an element that draws a line chart for dispalying event values.
+It has the following settable properties:
+ tag: (required, string) - the name of the tag to load for this chart
+ selectedRuns: (required, string[]) - the runs the chart should display
+ xType: (required, string) - the way to display x values - allows "step" or "wall_time"
+ dataCoordinator: (required, TF.DataCoordinator) - the data coordinator for talking to backend
+ colorScale: (required, Plottable.Scales.Color) - maps from runs to colors
+ tooltipUpdater: (required, function) - allows the chart to modify tooltips
+
+It exposes the following methods:
+ redraw() - cause the chart to re-render (useful if e.g. container size changed)
+
+The data is expected to be an array of [wall_time, step, value] arrays.
+The wall_time is serialized as seconds since epoch.
+-->
+<dom-module id="tf-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;
+ }
+ svg {
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ flex-grow: 1;
+ flex-shrink: 1;
+ }
+ .plottable .crosshairs line.guide-line {
+ stroke: #777;
+ }
+ </style>
+ </template>
+ <script src="dragZoomInteraction.js"></script>
+ <script src="tf-chart.js"></script>
+ <script>
+ Polymer({
+ is: "tf-chart",
+ properties: {
+ type: String, // "scalar" or "compressedHistogram"
+ _chart: Object,
+ colorScale: Object,
+ tag: String,
+ selectedRuns: Array,
+ xType: String,
+ dataCoordinator: Object,
+ tooltipUpdater: Function,
+ _initialized: Boolean,
+ },
+ observers: [
+ "_makeChart(type, tag, dataCoordinator, tooltipUpdater, xType, colorScale, _initialized)",
+ "_changeRuns(_chart, selectedRuns.*)"
+ ],
+ _changeRuns: function(chart, change) {
+ this._chart.changeRuns(this.selectedRuns);
+ this.redraw();
+ },
+ redraw: function() {
+ this._chart.redraw();
+ },
+ _constructor: function(type) {
+ if (type === "scalar") {
+ return TF.LineChart;
+ } else if (type === "compressedHistogram") {
+ return TF.HistogramChart;
+ } else {
+ throw new Error("Unrecognized chart type");
+ }
+ },
+ _makeChart: function(type, tag, dataCoordinator, tooltipUpdater, xType, colorScale, _initialized) {
+ if (!_initialized) {
+ return;
+ }
+ if (this._chart) this._chart.destroy();
+ var cns = this._constructor(type);
+ var chart = new cns(tag, dataCoordinator, tooltipUpdater, xType, colorScale);
+ var svg = d3.select(this.$.chartsvg);
+ this.async(function() {
+ chart.renderTo(svg);
+ this._chart = chart;
+ }, 350);
+ },
+ attached: function() {
+ this._initialized = true;
+ },
+ detached: function() {
+ this._initialized = false;
+ }
+ });
+ </script>
+</dom-module>
diff --git a/tensorflow/tensorboard/components/tf-event-dashboard/tf-chart.ts b/tensorflow/tensorboard/components/tf-event-dashboard/tf-chart.ts
new file mode 100644
index 0000000000..0b0103d697
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-event-dashboard/tf-chart.ts
@@ -0,0 +1,327 @@
+/// <reference path="../../typings/tsd.d.ts" />
+/// <reference path="../../bower_components/plottable/plottable.d.ts" />
+
+module TF {
+ type TFDatum = [number, number, number];
+ type tooltipMap = {[run: string]: string};
+ type TooltipUpdater = (tooltipMap, xValue, closestRun) => void;
+
+ let Y_TOOLTIP_FORMATTER_PRECISION = 4;
+ let STEP_AXIS_FORMATTER_PRECISION = 4;
+ let Y_AXIS_FORMATTER_PRECISION = 3;
+
+ export class BaseChart {
+ protected dataCoordinator: TF.DataCoordinator;
+ protected tag: string;
+ protected tooltipUpdater: TooltipUpdater;
+
+ protected xAccessor: Plottable.Accessor<number | Date>;
+ protected xScale: Plottable.QuantitativeScale<number | Date>;
+ protected yScale: Plottable.QuantitativeScale<number>;
+ protected gridlines: Plottable.Components.Gridlines;
+ protected center: Plottable.Components.Group;
+ protected xAxis: Plottable.Axes.Numeric | Plottable.Axes.Time;
+ protected yAxis: Plottable.Axes.Numeric;
+ protected xLabel: Plottable.Components.AxisLabel;
+ protected yLabel: Plottable.Components.AxisLabel;
+ protected outer: Plottable.Components.Table;
+ protected colorScale: Plottable.Scales.Color;
+ protected xTooltipFormatter: (d: number) => string;
+ constructor(
+ tag: string,
+ dataCoordinator: TF.DataCoordinator,
+ tooltipUpdater: TooltipUpdater,
+ xType: string,
+ colorScale: Plottable.Scales.Color
+ ) {
+ this.dataCoordinator = dataCoordinator;
+ this.tag = tag;
+ this.colorScale = colorScale;
+ this.tooltipUpdater = tooltipUpdater;
+ this.buildChart(xType);
+ }
+
+ public changeRuns(runs: string[]) {
+ throw new Error("Abstract method not implemented");
+ }
+
+ protected addCrosshairs(plot: Plottable.XYPlot<number | Date, number>, yAccessor): Plottable.Components.Group {
+ var pi = new Plottable.Interactions.Pointer();
+ pi.attachTo(plot);
+ let xGuideLine = new Plottable.Components.GuideLineLayer<void>("vertical");
+ let yGuideLine = new Plottable.Components.GuideLineLayer<void>("horizontal");
+ xGuideLine.addClass("crosshairs");
+ yGuideLine.addClass("crosshairs");
+ var group = new Plottable.Components.Group([plot, xGuideLine, yGuideLine]);
+ let yfmt = multiscaleFormatter(Y_TOOLTIP_FORMATTER_PRECISION);
+
+ pi.onPointerMove((p: Plottable.Point) => {
+ let run2val: {[run: string]: string} = {};
+ let x: number = this.xScale.invert(p.x).valueOf();
+ let yMin: number = this.yScale.domain()[0];
+ let yMax: number = this.yScale.domain()[1];
+ let closestRun: string = null;
+ let minYDistToRun: number = Infinity;
+ let yValueForCrosshairs: number = p.y;
+ plot.datasets().forEach((dataset) => {
+ let run: string = dataset.metadata().run;
+ let data: TFDatum[] = dataset.data();
+ let xs: number[] = data.map((d, i) => this.xAccessor(d, i, dataset).valueOf());
+ let idx: number = _.sortedIndex(xs, x);
+ if (idx === 0 || idx === data.length) {
+ // Only find a point when the cursor is inside the range of the data
+ // if the cursor is to the left or right of all the data, dont attach.
+ return;
+ }
+ let previous = data[idx - 1];
+ let next = data[idx];
+ let x0: number = this.xAccessor(previous, idx - 1, dataset).valueOf();
+ let x1: number = this.xAccessor(next, idx, dataset).valueOf();
+ let y0: number = yAccessor(previous, idx - 1, dataset).valueOf();
+ let y1: number = yAccessor(next, idx, dataset).valueOf();
+ let slope: number = (y1 - y0) / (x1 - x0);
+ let y: number = y0 + slope * (x - x0);
+
+ if (y < yMin || y > yMax || y !== y) {
+ // don't find data that is off the top or bottom of the plot.
+ // also don't find data if it is NaN
+ return;
+ }
+ let dist = Math.abs(this.yScale.scale(y) - p.y);
+ if (dist < minYDistToRun) {
+ minYDistToRun = dist;
+ closestRun = run;
+ yValueForCrosshairs = this.yScale.scale(y);
+ }
+ // Note this tooltip will display linearly interpolated values
+ // e.g. will display a y=0 value halfway between [y=-1, y=1], even
+ // though there is not actually any 0 datapoint. This could be misleading
+ run2val[run] = yfmt(y);
+ });
+ xGuideLine.pixelPosition(p.x);
+ yGuideLine.pixelPosition(yValueForCrosshairs);
+ this.tooltipUpdater(run2val, this.xTooltipFormatter(x), closestRun);
+
+ });
+
+ pi.onPointerExit(() => {
+ this.tooltipUpdater(null, null, null);
+ xGuideLine.pixelPosition(-1);
+ yGuideLine.pixelPosition(-1);
+ });
+
+ return group;
+
+ }
+
+ protected buildChart(xType: string) {
+ if (this.outer) {
+ this.outer.destroy();
+ }
+ var xComponents = getXComponents(xType);
+ this.xAccessor = xComponents.accessor;
+ this.xScale = xComponents.scale;
+ this.xAxis = xComponents.axis;
+ this.xAxis.margin(0).tickLabelPadding(3);
+ this.xTooltipFormatter = xComponents.tooltipFormatter;
+ this.yScale = new Plottable.Scales.Linear();
+ this.yAxis = new Plottable.Axes.Numeric(this.yScale, "left");
+ let yFormatter = multiscaleFormatter(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);
+
+ var dzl = new Plottable.DragZoomLayer(this.xScale, this.yScale);
+
+ this.center = new Plottable.Components.Group([center, this.gridlines, dzl]);
+ this.outer = new Plottable.Components.Table([
+ [this.yAxis, this.center],
+ [null, this.xAxis]
+ ]);
+ }
+
+ protected buildPlot(xAccessor, xScale, yScale): Plottable.Component {
+ throw new Error("Abstract method not implemented.");
+ }
+
+ public renderTo(target: d3.Selection<any>) {
+ this.outer.renderTo(target);
+ }
+
+ public redraw() {
+ this.outer.redraw();
+ }
+
+ protected destroy() {
+ this.outer.destroy();
+ }
+ }
+
+ export class LineChart extends BaseChart {
+ private plot: Plottable.Plots.Line<number | Date>;
+ protected buildPlot(xAccessor, xScale, yScale): Plottable.Component {
+ var yAccessor = accessorize("2");
+ var plot = new Plottable.Plots.Line<number | Date>();
+ plot.x(xAccessor, xScale);
+ plot.y(yAccessor, yScale);
+ plot.attr("stroke", (d: any, i: number, m: any) => m.run, this.colorScale);
+ this.plot = plot;
+ var group = this.addCrosshairs(plot, yAccessor);
+ return group;
+ }
+
+ public changeRuns(runs: string[]) {
+ var datasets = this.dataCoordinator.getDatasets(this.tag, runs);
+ this.plot.datasets(datasets);
+ }
+
+ }
+
+ export class HistogramChart extends BaseChart {
+ private plots: Plottable.XYPlot<number | Date, number>[];
+
+ public changeRuns(runs: string[]) {
+ var datasets = this.dataCoordinator.getDatasets(this.tag, runs);
+ this.plots.forEach((p) => p.datasets(datasets));
+ }
+
+ protected buildPlot(xAccessor, xScale, yScale): Plottable.Component {
+ var percents = [0, 228, 1587, 3085, 5000, 6915, 8413, 9772, 10000];
+ var opacities = _.range(percents.length - 1).map((i) => (percents[i + 1] - percents[i]) / 2500);
+ var accessors = percents.map((p, i) => (datum) => datum[2][i][1]);
+ var median = 4;
+ var medianAccessor = accessors[median];
+
+ var plots = _.range(accessors.length - 1).map((i) => {
+ var p = new Plottable.Plots.Area<number | Date>();
+ 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", (d: any, i: number, m: any) => m.run, this.colorScale);
+ p.attr("stroke", (d: any, i: number, m: any) => m.run, this.colorScale);
+ p.attr("stroke-weight", (d: any, i: number, m: any) => "0.5px");
+ p.attr("stroke-opacity", () => opacities[i]);
+ p.attr("fill-opacity", () => opacities[i]);
+ return p;
+ });
+
+ var medianPlot = new Plottable.Plots.Line<number | Date>();
+ medianPlot.x(xAccessor, xScale);
+ medianPlot.y(medianAccessor, yScale);
+ medianPlot.attr("stroke", (d: any, i: number, m: any) => m.run, this.colorScale);
+
+ this.plots = plots;
+ var group = this.addCrosshairs(medianPlot, medianAccessor);
+ return new Plottable.Components.Group([new Plottable.Components.Group(plots), group]);
+ }
+ }
+
+ /* 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: number): ((v: number) => string) {
+ return (v: number) => {
+ var absv = Math.abs(v);
+ if (absv < 1E-15) {
+ // Sometimes zero-like values get an annoying representation
+ absv = 0;
+ }
+ var f: (x: number) => string;
+ 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);
+ };
+ }
+
+ function accessorize(key: string): Plottable.Accessor<number> {
+ return (d: any, index: number, dataset: Plottable.Dataset) => d[key];
+ }
+
+ interface XComponents {
+ /* tslint:disable */
+ scale: Plottable.Scales.Linear | Plottable.Scales.Time,
+ axis: Plottable.Axes.Numeric | Plottable.Axes.Time,
+ accessor: Plottable.Accessor<number | Date>,
+ tooltipFormatter: (d: number) => string;
+ /* tslint:enable */
+ }
+
+ function stepX(): XComponents {
+ var scale = new Plottable.Scales.Linear();
+ var axis = new Plottable.Axes.Numeric(scale, "bottom");
+ var formatter = Plottable.Formatters.siSuffix(STEP_AXIS_FORMATTER_PRECISION);
+ axis.formatter(formatter);
+ return {
+ scale: scale,
+ axis: axis,
+ accessor: accessorize("1"),
+ tooltipFormatter: formatter,
+ };
+ }
+
+ function wallX(): XComponents {
+ var scale = new Plottable.Scales.Time();
+ var formatter = Plottable.Formatters.time("%a %b %e, %H:%M:%S");
+ return {
+ scale: scale,
+ axis: new Plottable.Axes.Time(scale, "bottom"),
+ accessor: (d: any, index: number, dataset: Plottable.Dataset) => {
+ return d[0] * 1000; // convert seconds to ms
+ },
+ tooltipFormatter: (d: number) => formatter(new Date(d)),
+ };
+ }
+
+ function relativeX(): XComponents {
+ var scale = new Plottable.Scales.Linear();
+ var formatter = (n: number) => {
+ var days = Math.floor(n / 24);
+ n -= (days * 24);
+ var hours = Math.floor(n);
+ n -= hours;
+ n *= 60;
+ var minutes = Math.floor(n);
+ n -= minutes;
+ n *= 60;
+ var seconds = Math.floor(n);
+ return days + "d " + hours + "h " + minutes + "m " + seconds + "s";
+ };
+ return {
+ scale: scale,
+ axis: new Plottable.Axes.Numeric(scale, "bottom"),
+ accessor: (d: any, index: number, dataset: Plottable.Dataset) => {
+ var data = dataset && 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][0] : 0;
+ return (d[0] - first) / (60 * 60); // convert seconds to hours
+ },
+ tooltipFormatter: formatter,
+ };
+ }
+
+ function getXComponents(xType: string): XComponents {
+ switch (xType) {
+ case "step":
+ return stepX();
+ case "wall_time":
+ return wallX();
+ case "relative":
+ return relativeX();
+ default:
+ throw new Error("invalid xType: " + xType);
+ }
+ }
+}
diff --git a/tensorflow/tensorboard/components/tf-event-dashboard/tf-color-scale.html b/tensorflow/tensorboard/components/tf-event-dashboard/tf-color-scale.html
new file mode 100644
index 0000000000..b559cab9cd
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-event-dashboard/tf-color-scale.html
@@ -0,0 +1,69 @@
+<link rel="import" href="../../bower_components/polymer/polymer.html">
+<link rel="import" href="../imports/lodash.html">
+<link rel="import" href="../imports/plottable.html">
+
+<!--
+tf-color-scale is a plumbing component that takes in an array of runs, and produces
+an upward-bindable outColorScale, which is a color scale mapping from those runs to
+a set of colors.
+
+Right now, the colors are hard-coded and must be manually synchronized with the colors expected in
+tf-run-selector. TODO(danmane): we should enshrine the mapping elsewhere.
+-->
+<dom-module id="tf-color-scale">
+ <script>
+ (function() {
+ // TODO(danmane) - get Plottable team to make an API point for this
+ Plottable.Scales.Color._LOOP_LIGHTEN_FACTOR = 0;
+ var classColorPairs = [
+ ["light-blue", "#03A9F4"],
+ ["red" , "#f44366"],
+ ["green" , "#4CAF50"],
+ ["purple" , "#9c27b0"],
+ ["teal" , "#009688"],
+ ["pink" , "#e91e63"],
+ ["orange" , "#ff9800"],
+ ["brown" , "#795548"],
+ ["indigo" , "#3f51b5"],
+ ];
+ var classes = _.pluck(classColorPairs, 0);
+ var colors = _.pluck(classColorPairs, 1);
+ Polymer({
+ is: "tf-color-scale",
+ properties: {
+ runs: Array,
+ outClassScale: {
+ type: Object,
+ notify: true,
+ readOnly: true,
+ value: function() {
+ return new d3.scale.ordinal().range(classes);
+ },
+ // TODO(danmane): the class scale will not update if the domain changes.
+ // this behavior is inconsistent with the ColorScale.
+ // in practice we don't change runs after initial load so it's not currently an issue
+ },
+ outColorScale: {
+ type: Object,
+ notify: true,
+ readOnly: true,
+ value: function() {
+ var scale = new Plottable.Scales.Color().range(colors);
+ scale.onUpdate(this._notifyColorScaleDomainChange.bind(this));
+ return scale;
+ },
+ },
+ },
+ observers: ["_changeRuns(runs.*)"],
+ _changeRuns: function(runs) {
+ this.outClassScale.domain(this.runs);
+ this.outColorScale.domain(this.runs);
+ },
+ _notifyColorScaleDomainChange: function() {
+ this.notifyPath("outColorScale.domain_path", this.outColorScale.domain());
+ this.outColorScale.domain_path = null;
+ },
+ });
+ })();
+ </script>
+</dom-module>
diff --git a/tensorflow/tensorboard/components/tf-event-dashboard/tf-data-coordinator.html b/tensorflow/tensorboard/components/tf-event-dashboard/tf-data-coordinator.html
new file mode 100644
index 0000000000..454dff4a9e
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-event-dashboard/tf-data-coordinator.html
@@ -0,0 +1,29 @@
+<link rel="import" href="../../bower_components/polymer/polymer.html">
+<link rel="import" href="../imports/plottable.html">
+<link rel="import" href="../imports/lodash.html">
+
+<!--
+tf-data-coordinator is a simple plumbing component that takes in a value url generator
+(a function that takes a tag and a run and returns a url), and produces an upward-bindable
+TF.DataCoordinator for consumption elsewhere.
+-->
+<dom-module id="tf-data-coordinator">
+ <script src="dataCoordinator.js"></script>
+ <script src="dataset.js"></script>
+ <script>
+ Polymer({
+ is: "tf-data-coordinator",
+ properties: {
+ urlGenerator: Object,
+ outDataCoordinator: {
+ type: Object,
+ computed: "getCoordinator(urlGenerator, runToTag)",
+ notify: true,
+ },
+ },
+ getCoordinator: function(generator, runToTag) {
+ return new TF.DataCoordinator(generator, runToTag);
+ }
+ });
+ </script>
+</dom-module>
diff --git a/tensorflow/tensorboard/components/tf-event-dashboard/tf-event-dashboard.html b/tensorflow/tensorboard/components/tf-event-dashboard/tf-event-dashboard.html
new file mode 100644
index 0000000000..534f62072f
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-event-dashboard/tf-event-dashboard.html
@@ -0,0 +1,208 @@
+<link rel="import" href="../../bower_components/polymer/polymer.html">
+<link rel="import" href="tf-data-coordinator.html">
+<link rel="import" href="tf-tooltip-coordinator.html">
+<link rel="import" href="tf-run-selector.html">
+<link rel="import" href="tf-x-type-selector.html">
+<link rel="import" href="../tf-dashboard-common/tf-run-generator.html">
+<link rel="import" href="tf-color-scale.html">
+<link rel="import" href="../tf-dashboard-common/tf-url-generator.html">
+<link rel="import" href="../tf-dashboard-common/tf-dashboard-layout.html">
+<link rel="import" href="../tf-dashboard-common/tensorboard-color.html">
+<link rel="import" href="../tf-dashboard-common/dashboard-style.html">
+<link rel="import" href="../tf-dashboard-common/tf-downloader.html">
+<link rel="import" href="../tf-categorizer/tf-categorizer.html">
+<link rel="import" href="tf-chart.html">
+<link rel="import" href="../tf-collapsable-pane/tf-collapsable-pane.html">
+<link rel="import" href="../../bower_components/iron-collapse/iron-collapse.html">
+<link rel="import" href="../../bower_components/paper-icon-button/paper-icon-button.html">
+<link rel="import" href="../imports/lodash.html">
+<link rel="import" href="../tf-dashboard-common/warning-style.html">
+
+<!--
+tf-event-dashboard is a complete frontend that loads runs from a backend,
+and creates chart panes that display data for those runs.
+
+It provides a categorizer, run selector, and x type selector, by which the user
+can customize how data is organized and displayed.
+
+Each chart has a button that can toggle whether it is "selected"; selectedRuns
+charts are larger.
+
+Organizationally, the #plumbing div contains components that have no concrete
+manifestation and just effect data bindings or data loading. The #sidebar contains
+shared controls like the tf-categorizer, tf-run-selector, and tf-x-type-selector.
+The #center div contains tf-charts embedded inside tf-collapsable-panes.
+-->
+<dom-module id="tf-event-dashboard">
+ <template>
+ <div id="plumbing">
+ <tf-url-generator
+ out-runs-url="{{runsUrl}}"
+ out-scalars-url-generator="{{scalarsUrlGen}}"
+ id="urlGenerator"
+ ></tf-url-generator>
+
+ <tf-data-coordinator
+ id="dataCoordinator"
+ url-generator="[[scalarsUrlGen]]"
+ run-to-tag="[[runToScalars]]"
+ color-scale="[[colorScale]]"
+ out-data-coordinator="{{dataCoordinator}}"
+ /></tf-data-coordinator>
+
+ <tf-run-generator
+ id="runGenerator"
+ url="[[runsUrl]]"
+ out-run-to-scalars="{{runToScalars}}"
+ /></tf-run-generator>
+
+ <tf-color-scale
+ id="colorScale"
+ runs="[[_runs]]"
+ out-color-scale="{{colorScale}}"
+ out-class-scale="{{classScale}}"
+ ></tf-color-scale>
+
+ <tf-tooltip-coordinator
+ id="tooltipCoordinator"
+ out-tooltip-updater="{{tooltipUpdater}}"
+ out-tooltip-map="{{tooltipMap}}"
+ out-x-value="{{tooltipXValue}}"
+ out-closest-run="{{closestRun}}"
+ ></tf-tooltip-coordinator>
+
+ </div>
+
+ <tf-dashboard-layout>
+ <div class="sidebar">
+ <tf-categorizer
+ id="categorizer"
+ tags="[[_visibleTags]]"
+ categories="{{categories}}"
+ ></tf-categorizer>
+ <span id="download-option">
+ Show Data Download Links:
+ <paper-toggle-button checked="{{_show_download_links}}"></paper-toggle-button>
+ </span>
+
+ <tf-x-type-selector
+ id="xTypeSelector"
+ out-x-type="{{xType}}"
+ ></tf-x-type-selector>
+
+ <tf-run-selector
+ id="runSelector"
+ runs="[[_runs]]"
+ class-scale="[[classScale]]"
+ out-selected="{{selectedRuns}}"
+ tooltips="[[tooltipMap]]"
+ closest-run="[[closestRun]]"
+ x-value="[[tooltipXValue]]"
+ x-type="[[xType]]"
+ ></tf-run-selector>
+
+ </div>
+ <div class="center">
+ <template is="dom-if" if="[[!categories.length]]">
+ <div class="warning">
+ <p>
+ No scalar summary tags were found.
+ </p>
+ <p>
+ Maybe data hasn't loaded yet, or maybe you need
+ to add some <code>tf.scalar_summary</code> ops to your graph, and
+ serialize them using the <code>tf.training.summary_io.SummaryWriter</code>.
+ </p>
+ </div>
+ </template>
+ <template is="dom-repeat" items="[[categories]]">
+ <tf-collapsable-pane name="[[item.name]]" count="[[item.tags.length]]">
+ <div class="layout horizontal wrap">
+ <template is="dom-repeat" items="[[item.tags]]" as="tag">
+ <div class="card">
+ <span class="card-title">[[tag]]</span>
+ <div class="card-content">
+ <tf-chart
+ tag="[[tag]]"
+ type="scalar"
+ id="chart"
+ selected-runs="[[selectedRuns]]"
+ x-type="[[xType]]"
+ data-coordinator="[[dataCoordinator]]"
+ color-scale="[[colorScale]]"
+ on-keyup="toggleSelected"
+ tabindex="2"
+ tooltip-updater="[[tooltipUpdater]]"
+ ></tf-chart>
+ <paper-icon-button
+ class="expand-button"
+ shift$="[[_show_download_links]]"
+ icon="fullscreen"
+ on-tap="toggleSelected"
+ ></paper-icon-button>
+ </div>
+ <template is="dom-if" if="[[_show_download_links]]">
+ <div class="card-bottom-row">
+ <tf-downloader
+ selected-runs="[[selectedRuns]]"
+ tag="[[tag]]"
+ url-fn="[[scalarsUrlGen]]"
+ run-to-tag="[[runToScalars]]"
+ >
+ </tf-downloader>
+ </div>
+ </template>
+ </div>
+ </template>
+ </div>
+ </tf-collapsable-pane>
+ </template>
+ </div>
+ </tf-dashboard-layout>
+
+ <style include="dashboard-style"></style>
+ <style include="warning-style"></style>
+
+ </template>
+
+ <script>
+ Polymer({
+ is: "tf-event-dashboard",
+ properties: {
+ _runs: {
+ type: Array,
+ computed: "_getRuns(runToScalars)",
+ },
+ _visibleTags: {
+ type: Array,
+ computed: "_getVisibleTags(selectedRuns.*, runToScalars.*)"
+ },
+ _show_download_links: Boolean,
+ },
+ observers: ['redraw(_show_download_links)'],
+ redraw: function(_show_download_links) {
+ var els = this.getElementsByTagName("tf-chart");
+ for (var i=0; i<els.length; i++) {
+ els[i].redraw();
+ }
+ },
+ _getRuns: function(runToScalars) {
+ return _.keys(runToScalars);
+ },
+ _getVisibleTags: function(selectedRunsChange, runsToScalarsChange) {
+ var keys = selectedRunsChange.base;
+ var dict = runsToScalarsChange.base;
+ return _.union.apply(null, keys.map(function(k) {return dict[k]}));
+ },
+ toggleSelected: function(e) {
+ var currentTarget = Polymer.dom(e.currentTarget);
+ var parentDiv = currentTarget.parentNode.parentNode;
+ parentDiv.classList.toggle("selected");
+ var chart = currentTarget.previousElementSibling;
+ if (chart) {
+ chart.redraw();
+ }
+ },
+ });
+ </script>
+</dom-module>
diff --git a/tensorflow/tensorboard/components/tf-event-dashboard/tf-run-selector.html b/tensorflow/tensorboard/components/tf-event-dashboard/tf-run-selector.html
new file mode 100644
index 0000000000..92cecb2e5a
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-event-dashboard/tf-run-selector.html
@@ -0,0 +1,104 @@
+<link rel="import" href="../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../bower_components/paper-checkbox/paper-checkbox.html">
+<link rel="import" href="../imports/lodash.html">
+<link rel="import" href="../tf-dashboard-common/scrollbar-style.html">
+<link rel="import" href="../tf-multi-checkbox/tf-multi-checkbox.html">
+
+<!--
+tf-run-selector creates a set of checkboxes to display which runs are selected.
+It also displays tooltips.
+
+Properties in:
+- runs: Array of strings representing the runs that may be selected
+- tooltips: An object that maps from a run to the associated tooltip string.
+When tooltips are available, runs that have no associated tooltip will be
+hidden. When tooltips are available, the runs will be sorted by their tooltip.
+- closestRun: The name of the run that is closest to the cursor (present when
+tooltips are active). It will be highlighted
+- classScale: An object (generated by tf-dashboard-common/tf-color-scale) that
+maps from a run name to a class name, which will be used to color the run.
+- xValue: The string that represents the x-value associated with the tooltips.
+- xType: The string that describes what kind of data is displayed on the x axis.
+
+Properties out:
+- outSelected: The array of run names that are currently checked by the user.
+
+-->
+<dom-module id="tf-run-selector">
+ <template>
+ <div id="top-text">
+ <template is="dom-if" if="[[xValue]]">
+ <div class="x-tooltip tooltip-container">
+ <div class="x-tooltip-label">[[xType]]</div>
+ <div class="x-tooltip-value">[[xValue]]</div>
+ </div>
+ </template>
+ <template is="dom-if" if="[[!xValue]]">
+ <div id="tooltip-help" class="tooltip-container">
+ Selected Runs:
+ </div>
+ </template>
+ </div>
+ <tf-multi-checkbox
+ names="[[runs]]"
+ tooltips="[[tooltips]]"
+ highlights="[[_arrayify(closestRun)]]"
+ out-selected="{{outSelected}}"
+ class-scale="[[classScale]]"
+ hide-missing-tooltips
+ ></tf-multi-checkbox>
+ <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-left: 35px;
+ padding-right: 16px;
+ padding-bottom: 6px;
+ box-sizing: border-box;
+ color: var(--paper-grey-800);
+ }
+ tf-multi-checkbox {
+ display: flex;
+ flex-grow: 1;
+ flex-shrink: 1;
+ height: 0px; /* hackhack So the flex-grow takes over and gives it space */
+ }
+ .x-tooltip {
+ display: flex;
+ flex-direction: row;
+ }
+ .x-tooltip-label {
+ flex-grow: 1;
+ align-self: flex-start;
+ }
+ .x-tooltip-value {
+ align-self: flex-end;
+ }
+ </style>
+ </template>
+ <script>
+ Polymer({
+ is: "tf-run-selector",
+ properties: {
+ outSelected: {type: Array, notify: true},
+ // runs: an array of strings, representing the run names that may be chosen
+ runs: Array,
+ tooltips: {type: Object, value: null}, // {[run: string]: string}
+ xValue: {type: String, value: null}, // the string representing run's x val
+ xType: String, // string: relative, stpe, wall_time
+ classScale: Object, // map from run name to color class (css)
+ closestRun: {type: String, value: null}, // which run has a value closest to mouse coordinate
+ },
+ _arrayify: function(item) {
+ return [item];
+ },
+ });
+ </script>
+</dom-module>
diff --git a/tensorflow/tensorboard/components/tf-event-dashboard/tf-tooltip-coordinator.html b/tensorflow/tensorboard/components/tf-event-dashboard/tf-tooltip-coordinator.html
new file mode 100644
index 0000000000..23b0cba87a
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-event-dashboard/tf-tooltip-coordinator.html
@@ -0,0 +1,48 @@
+<link rel="import" href="../../bower_components/polymer/polymer.html">
+
+<!-- tf-tooltip-coordinator is a plumbing component that provides a TooltipUpdater,
+which is a function that allows modification of the values within the tooltip-coordinator
+from javascript logic elsewhere. It then propagates the values to other Polymer components.
+
+Thus, the tooltip-coordinator allows many JS pieces of the application to modify a single
+piece of shared state.
+ -->
+<dom-module id="tf-tooltip-coordinator">
+ <script>
+ Polymer({
+ is: "tf-tooltip-coordinator",
+ properties: {
+ outTooltipUpdater: {
+ type: Function,
+ value: function() {
+ return (function(tooltipMap, xValue, closestRun) {
+ this._setOutTooltipMap(tooltipMap);
+ this._setOutXValue(xValue);
+ this._setOutClosestRun(closestRun);
+ }).bind(this);
+ },
+ notify: true,
+ readOnly: true,
+ },
+ outTooltipMap: {
+ // a {runName: tooltipValue} map, where runName and tooltipValue are strings.
+ type: Object,
+ notify: true,
+ readOnly: true,
+ },
+ outXValue: {
+ // a string representation of the closest x value for the tooltips
+ type: Number,
+ notify: true,
+ readOnly: true,
+ },
+ outClosestRun: {
+ // the name of the run that is closest to the user cursor (if any)
+ type: String,
+ notify: true,
+ readOnly: true,
+ },
+ },
+ });
+ </script>
+</dom-module>
diff --git a/tensorflow/tensorboard/components/tf-event-dashboard/tf-x-type-selector.html b/tensorflow/tensorboard/components/tf-event-dashboard/tf-x-type-selector.html
new file mode 100644
index 0000000000..078e456a0d
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-event-dashboard/tf-x-type-selector.html
@@ -0,0 +1,75 @@
+<link rel="import" href="../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../bower_components/paper-button/paper-button.html">
+<link rel="import" href="../tf-dashboard-common/tensorboard-color.html">
+
+<!--
+tf-x-type-selector is a simple component that creates buttons labeled "step" and "wall",
+ and provides (as upward bindable) an outXType property that is either "step" or "wall_time".
+-->
+<dom-module id="tf-x-type-selector">
+ <template>
+ <div id="buttons">
+ <p>X Type: </p>
+ <paper-button
+ class="x-button selected"
+ id="step"
+ on-tap="_select"
+ raised
+ >
+ step
+ </paper-button>
+ <paper-button
+ class="x-button"
+ id="relative"
+ on-tap="_select"
+ >
+ relative
+ </paper-button>
+ <paper-button
+ class="x-button"
+ id="wall_time"
+ on-tap="_select"
+ >
+ wall
+ </paper-button>
+ </div>
+ <style>
+ .x-button {
+ width: 29%;
+ font-size: 14px;
+ background-color: var(--paper-grey-500);
+ margin-top: 5px;
+ color: white;
+ }
+
+ .x-button.selected {
+ font-weight: bold;
+ background-color: var(--tb-orange-strong) !important;
+ }
+
+ #buttons p {
+ text-align: center;
+ font-size: 12px;
+ margin: 0;
+ }
+ </style>
+ </template>
+ <script>
+ Polymer({
+ is: "tf-x-type-selector",
+ properties: {
+ outXType: {type: String, notify: true, readOnly: true, value: "step"},
+ },
+ _select: function(e) {
+ var _this = this;
+ ["step", "wall_time", "relative"].forEach(function(id) {
+ _this.$[id].raised = false;
+ _this.$[id].classList.remove("selected");
+ });
+ e.currentTarget.raised = true;
+ this._setOutXType(e.currentTarget.id);
+ e.currentTarget.classList.add("selected");
+ },
+ });
+ </script>
+</dom-module>
diff --git a/tensorflow/tensorboard/components/tf-graph-board/tf-graph-board.html b/tensorflow/tensorboard/components/tf-graph-board/tf-graph-board.html
new file mode 100644
index 0000000000..c053d6f7a7
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-graph-board/tf-graph-board.html
@@ -0,0 +1,152 @@
+<link rel="import" href="../../bower_components/polymer/polymer.html">
+<link rel="import" href="../tf-graph/tf-graph.html">
+<link rel="import" href="../tf-graph-info/tf-graph-info.html">
+<link rel="import" href="../../bower_components/paper-progress/paper-progress.html">
+<!-- Element for putting tf-graph and tf-graph-info side by side.
+
+Example
+
+<tf-graph-board graph=[[graph]]></tf-graph-board>
+
+-->
+
+<dom-module id="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;
+}
+</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]]"
+ selected-node="{{_selectedNode}}"
+ highlighted-node="{{_highlightedNode}}"
+ color-by="[[colorBy]]"
+ color-by-params="{{colorByParams}}"
+ graph-name="[[graphName]]"
+ progress="[[progress]]"
+ ></tf-graph>
+ </div>
+ <div id="info">
+ <tf-graph-info id="graph-info"
+ title="selected"
+ graph-hierarchy="[[graphHierarchy]]"
+ graph="[[graph]]"
+ selected-node="{{_selectedNode}}"
+ highlighted-node="{{_highlightedNode}}"
+ ></tf-graph-info>
+ </div>
+</div>
+</template>
+</dom-module>
+
+<script>
+Polymer({
+ is: 'tf-graph-board',
+ properties: {
+ // Public API.
+ graphHierarchy: Object,
+ graph: Object,
+ graphName: String,
+ // True if the graph data has also run-time stats.
+ hasStats: Boolean,
+ /**
+ * @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,
+ colorByParams: {
+ type: Object,
+ notify: true,
+ },
+ // Private API: Data routing between child components.
+ _selectedNode: String,
+ _highlightedNode: String,
+ },
+ /** 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;
+ }
+});
+</script>
diff --git a/tensorflow/tensorboard/components/tf-graph-common/lib/colors.ts b/tensorflow/tensorboard/components/tf-graph-common/lib/colors.ts
new file mode 100644
index 0000000000..8912483c09
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-graph-common/lib/colors.ts
@@ -0,0 +1,133 @@
+module tf {
+
+/**
+ * Mapping from color palette name to color pallette, which contains
+ * exact colors for multiple states of a single color pallette.
+ */
+export let 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", // 500
+ "active": "#424242", // 800
+ "disabled": "F5F5F5" // 100
+ }
+].reduce((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";
+ */
+export let 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((m, c) => {
+ c.groups.forEach(function(group) {
+ m[group] = c.color;
+ });
+ return m;
+}, {});
+
+}
diff --git a/tensorflow/tensorboard/components/tf-graph-common/lib/common.ts b/tensorflow/tensorboard/components/tf-graph-common/lib/common.ts
new file mode 100644
index 0000000000..ed148bf719
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-graph-common/lib/common.ts
@@ -0,0 +1,236 @@
+/// <reference path="../../../typings/tsd.d.ts" />
+
+declare module graphlib {
+
+ interface GraphOptions {
+ name: string;
+ /**
+ * Direction for rank nodes. Can be TB, BT, LR, or RL, where T = top,
+ * B = bottom, L = left, and R = right.
+ */
+ rankdir: string;
+ type: string|number;
+ /** Number of pixels between each rank in the layout. */
+ ranksep?: number;
+ /** Number of pixels that separate nodes horizontally in the layout. */
+ nodesep?: number;
+ }
+
+ export interface EdgeObject {
+ v: string;
+ w: string;
+ name?: string;
+ }
+
+ export class Graph<N, E> {
+ constructor(opt?: Object);
+ setNode(name: string, value?: N): void;
+ hasNode(name: string): boolean;
+ setEdge(fromName: string, toName: string, value?: E): void;
+ hasEdge(fromName: string, toName: string): boolean;
+ edge(fromName: string, toName: string): E;
+ edge(edgeObject: EdgeObject): E;
+ removeEdge(v: string, w: string): void;
+ nodes(): string[];
+ node(name: string): N;
+ removeNode(name: string): void;
+ setGraph(graphOptions: GraphOptions): void;
+ graph(): GraphOptions;
+ nodeCount(): number;
+ neighbors(name: string): string[];
+ successors(name: string): string[];
+ predecessors(name: string): string[];
+ edges(): EdgeObject[];
+ outEdges(name: string): E[];
+ inEdges(name: string): E[];
+ /** Returns those nodes in the graph that have no in-edges. Takes O(|V|) time. */
+ sources(): string[];
+ /**
+ * Remove the node with the id v in the graph or do nothing if
+ * the node is not in the graph. If the node was removed this
+ * function also removes any incident edges. Returns the graph,
+ * allowing this to be chained with other functions. Takes O(|E|) time.
+ */
+ removeNode(name: string): Graph<N, E>;
+ setParent(name: string, parentName: string): void;
+ }
+}
+
+module tf {
+/**
+ * Recommended delay (ms) when running an expensive task asynchronously
+ * that gives enough time for the progress bar to update its UI.
+ */
+const ASYNC_TASK_DELAY = 20;
+
+export function time<T>(msg: string, task: () => T) {
+ let start = Date.now();
+ let result = task();
+ /* tslint:disable */
+ console.log(msg, ":", Date.now() - start, "ms");
+ /* tslint:enable */
+ return result;
+}
+
+/**
+ * Tracks task progress. Each task being passed a progress tracker needs
+ * to call the below-defined methods to notify the caller about the gradual
+ * progress of the task.
+ */
+export interface ProgressTracker {
+ updateProgress(incrementValue: number): void;
+ setMessage(msg: string): void;
+ reportError(msg: string): void;
+}
+
+/**
+ * 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.
+ */
+export function getSubtaskTracker(parentTracker: ProgressTracker,
+ impactOnTotalProgress: number, subtaskMsg: string): ProgressTracker {
+ 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(errorMsg) {
+ // The parent should show a concatenation of its message along with
+ // its subtask error message.
+ parentTracker.reportError(subtaskMsg + " : " + errorMsg);
+ }
+ };
+}
+
+/**
+ * Runs an expensive task asynchronously and returns a promise of the result.
+ */
+export function runAsyncTask<T>(msg: string, incProgressValue: number,
+ task: () => T, tracker: ProgressTracker): Promise<T> {
+ return new Promise((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.time(msg, task);
+ // Update the progress value.
+ tracker.updateProgress(incProgressValue);
+ // Return the result to be used by other tasks.
+ resolve(result);
+ } catch (e) {
+ reject(result);
+ }
+ }, ASYNC_TASK_DELAY);
+ });
+}
+
+/**
+ * Returns a query selector with escaped special characters that are not
+ * allowed in a query selector.
+ */
+export function escapeQuerySelector(querySelector: string): string {
+ return querySelector.replace( /([:.\[\],/\\\(\)])/g, "\\$1" );
+}
+
+/**
+ * TensorFlow node definition as definied in the graph proto file.
+ */
+export interface TFNode {
+ /** Name of the node */
+ name: string;
+ /** List of nodes that are inputs for this node. */
+ input: string[];
+ /** The name of the device where the computation will run. */
+ device: string;
+ /** The name of the operation associated with this node. */
+ op: string;
+ /** List of attributes that describe/modify the operation. */
+ attr: {key: string, value: Object}[];
+}
+
+/**
+ * TensorFlow stats file definition as defined in the stats proto file.
+ */
+export interface TFStats {
+ devStats: {device: string, nodeStats: TFNodeStats[]}[];
+}
+
+/**
+ * TensorFlow stats for a node as defined in the stats proto file.
+ */
+export interface TFNodeStats {
+ nodeName: string;
+ // The next 4 properties are currently stored as string in json
+ // and must be parsed.
+ allStartMicros: number;
+ opStartRelMicros: number;
+ opEndRelMicros: number;
+ allEndRelMicros: number;
+ memory: {
+ allocatorName: string;
+ totalBytes: number; // Stored as string in json and should be parsed.
+ peakBytes: number; // Stored as string in json and should be parsed.
+ }[];
+ /** Output sizes recorded for a single execution of a graph node */
+ output: TFNodeOutput[];
+ timelineLabel: string;
+ scheduledMicros: string;
+ threadId: string;
+}
+
+/**
+ * Description for the output tensor(s) of an operation in the graph.
+ */
+export interface TFNodeOutput {
+ slot: number; // Stored as string in json and should be parsed.
+ /** Was the tensor allocated by this Op or a previous computation */
+ allocationType: string;
+ tensorDescription: {
+ /** Data type of tensor elements */
+ dtype: string;
+ /** Shape of the tensor */
+ shape: {
+ /**
+ * Dimensions of the tensor, such as [{name: "input", size: 30},
+ * {name: "output", size: 40}] for a 30 x 40 2D tensor. The names
+ * are optional. The order of entries in "dim" matters: It indicates
+ * the layout of the values in the tensor in-memory representation.
+ */
+ dim: {
+ /** Size of the tensor in that dimension */
+ size: number, // Stored as string in json and should be parsed.
+ /** Optional name of the tensor dimension */
+ name?: string
+ }[];
+ };
+ /** Information about the size and allocator used for the data */
+ allocationDescription: {
+ // The next 2 properties are stored as string in json and
+ // should be parsed.
+ /** Total number of bytes requested */
+ requestedBytes: number;
+ /** Total number of bytes allocated, if known */
+ allocatedBytes?: number;
+ /** Name of the allocator used */
+ allocatorName: string;
+ };
+ };
+}
+} // close module tf
+
+/**
+ * Declaring dagre var used for dagre layout.
+ */
+declare var dagre: { layout(graph: graphlib.Graph<any, any>): void; };
diff --git a/tensorflow/tensorboard/components/tf-graph-common/lib/graph.ts b/tensorflow/tensorboard/components/tf-graph-common/lib/graph.ts
new file mode 100644
index 0000000000..64e537154b
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-graph-common/lib/graph.ts
@@ -0,0 +1,889 @@
+/// <reference path="../../../typings/tsd.d.ts" />
+/// <reference path="common.ts" />
+module tf.graph {
+
+/** Delimiter used in node names to denote namespaces. */
+export const NAMESPACE_DELIM = "/";
+const FULL_GRAPH_NAME = "fullGraph";
+export const ROOT_NAME = "__root__";
+
+// Separator between the source and the destination name of the edge.
+export const EDGE_KEY_DELIM = "--";
+
+export enum GraphType {FULL, EMBEDDED, META, SERIES, CORE, SHADOW, BRIDGE,
+ EDGE};
+export enum NodeType {META, OP, SERIES, BRIDGE, ELLIPSIS};
+
+/**
+ * A BaseEdge is the label object (in the graphlib sense) for an edge in the
+ * original, full graph produced after parsing. Subsequent graphs, like those
+ * which belong to Metanodes, should not use BaseEdge objects, but instead
+ * contain Metaedges (which in turn may contain any number of BaseEdges).
+ */
+export interface BaseEdge extends graphlib.EdgeObject {
+ isControlDependency: boolean;
+ isReferenceEdge: boolean;
+}
+
+/**
+ * A SlimGraph is inspired by graphlib.Graph, but having only the functionality
+ * that we need.
+ */
+export class SlimGraph {
+ nodes: { [nodeName: string]: OpNode };
+ edges: BaseEdge[];
+
+ constructor() {
+ this.nodes = {};
+ this.edges = [];
+ }
+}
+
+interface NormalizedInput {
+ name: string;
+ hasNumberPart: boolean;
+ isControlDependency: boolean;
+}
+
+interface BuildParams {
+ enableEmbedding: boolean;
+ inEmbeddingTypes: string[];
+ outEmbeddingTypes: string[];
+ refEdges: { [inputEdge: string]: boolean };
+}
+
+/**
+ * The most basic information about a node in the hierarchical graph.
+ */
+export interface Node {
+ /** The name of the node, used frequently to look up nodes by name. */
+ name: string;
+ /** Which type of node this is. */
+ type: NodeType;
+ /**
+ * Whether this node is a type that may contain other nodes. Those types
+ * should extend from GroupNode.
+ *
+ * For an OpNode, isGroupNode will be false, even though it may have
+ * embeddings. These embedding Nodes will have their parentNode set to the
+ * OpNode. However, embeddings are later rendered as annotations, not as
+ * children to be made visible on expansion (like a Metanode or SeriesNode).
+ */
+ isGroupNode: boolean;
+ /**
+ * The number of nodes this node represents. For OpNodes, this will be 1, and
+ * for GroupNodes it will be a count of the total number of descendents it
+ * contains.
+ */
+ cardinality: number;
+ /**
+ * The Node which is this Node's parent. This is of type Node and not
+ * GroupNode because of embeddings, which will have a parent OpNode.
+ */
+ parentNode: Node;
+ /** Runtime execution stats for this node, if available */
+ stats: NodeStats;
+}
+
+export interface OpNode extends Node {
+ op: string;
+ device: string;
+ attr: {key: string, value: Object}[];
+ inputs: NormalizedInput[];
+ inEmbeddings: OpNode[];
+ outEmbeddings: OpNode[];
+}
+
+export interface BridgeNode extends Node {
+ /**
+ * Whether this bridge node represents edges coming into its parent node.
+ */
+ inbound: boolean;
+}
+
+/**
+ * A node that is used when there are more than the maximum number of allowed
+ * annotations hanging off of a node. This node represents an ellipsis
+ * annotation, indicating a number of additional annotations.
+ */
+export interface EllipsisNode extends Node {
+ /**
+ * The number of nodes this ellipsis represents.
+ */
+ numMoreNodes: number;
+
+ /**
+ * Sets the number of nodes this ellipsis represents and changes the node
+ * name accordingly.
+ */
+ setNumMoreNodes(numNodes: number);
+}
+
+export interface GroupNode extends Node {
+ /**
+ * The metagraph contains nodes and metaedges between the immediate children
+ * of this group. The node label objects may be other GroupNodes (like
+ * SeriesNodes and Metanodes) or individual OpNodes. All edge label objects
+ * are Metaedges, each of which contains references to the original
+ * BaseEdge(s) from which it was created.
+ */
+ metagraph: graphlib.Graph<GroupNode|OpNode, Metaedge>;
+
+ /**
+ * The bridgegraph contains only edges which link immediate children of this
+ * group with nodes outside of the metagraph. As in the metagraph, all edge
+ * label objects are Metaedges which contain references to the original
+ * BaseEdge(s) that contribute to it.
+ *
+ * For a Metaedge in the bridgegraph, its external endpoint will be the same
+ * as the metagraph edge from which it came. This is most easily explained
+ * by example.
+ *
+ * Consider an original graph that contains a BaseEdge A/B/C->Z/Y/X.
+ *
+ * +-------+ (BaseEdge) +-------+
+ * | A/B/C |>----------------->| Z/Y/X |
+ * +-------+ +-------+
+ *
+ * When we construct the Root's metagraph, it will contain nodes for A and Z,
+ * and a Metaedge A->Z. The A->Z Metaedge will contain the original BaseEdge
+ * A/B/C->Z/Y/X in its baseEdgeGraph. The Root's bridgegraph will always be
+ * empty.
+ *
+ * +---+ (Root.metagraph edge) +---+
+ * | A |>--------------------------->| Z |
+ * +---+ +---+
+ *
+ * Now consider the Metanode A. Its metagraph will contain a Metanode for A/B
+ * and no edges. A's bridgegraph will have one Metaedge from A/B->Z, which
+ * was derived from the Root's Metaedge A->Z. That Metaedge will contain the
+ * original BaseEdge in its baseEdgeGraph.
+ *
+ * +---------+
+ * | A |
+ * | +---+ | (A.bridgegraph edge) +---+
+ * | | B |>---------------------------->| Z |
+ * | +---+ | +---+
+ * +---------+
+ *
+ * Finally, consider the Metanode A/B. Its metagraph will contain a Metanode
+ * for A/B/C and again no edges. A/B's bridgegraph will have one Metaedge
+ * from A/B/C->Z, which was derived from A's bridgegraph Metaedge A/B->Z.
+ * As before, the A/B/C->Z Metaedge will contain the original BaseEdge in its
+ * baseEdgeGraph.
+ *
+ * +---------------+
+ * | A |
+ * | +---------+ |
+ * | | B | |
+ * | | +---+ | | (A/B.bridgegraph edge) +---+
+ * | | | C |>----------------------------------->| Z |
+ * | | +---+ | | +---+
+ * | +---------+ |
+ * +---------------+
+ *
+ * Likewise, under the Metanode Z and Z/Y, to compute the bridgegraph, we'll
+ * end up with Metaedges A->Z/Y and A->Z/Y/X respectively. So the original
+ * BaseEdge A/B/C->Z/Y/X becomes four different Metaedges in four different
+ * bridgegraphs:
+ *
+ * + A/B->Z in GroupNode A's bridgegraph,
+ * + A/B/C->Z in GroupNode A/B's bridgegraph,
+ * + A->Z/Y in GroupNode Z's bridgegraph, and
+ * + A->Z/Y/X in GroupNode Z/Y's bridgegraph.
+ *
+ * Considering any BaseEdge then, if N is the number of path segments in the
+ * source and M is the number of path semgents in the destination, then the
+ * total number of bridgegraph edges you could create would be (N-1)(M-1).
+ *
+ * For this reason, it is computationally expensive to generate all the
+ * bridgegraphs for all the Metanodes, and instead they should be computed
+ * on demand as needed.
+ */
+ bridgegraph: graphlib.Graph<GroupNode|OpNode, Metaedge>;
+
+ /**
+ * Stores how many times each device name appears in its children
+ * op nodes. Used to color group nodes by devices.
+ */
+ deviceHistogram: {[device: string]: number};
+
+ /**
+ * Flag indicating whether this GroupNode's metagraph contains any edges that
+ * are not control edges. Used to quickly determine how to draw a collapsed
+ * series (vertically or horizontally).
+ */
+ hasNonControlEdges: boolean;
+}
+
+export interface Metanode extends GroupNode {
+ depth: number;
+ templateId: string;
+ opHistogram: {[op: string]: number};
+ getFirstChild(): GroupNode|OpNode;
+ getRootOp(): OpNode;
+ /** Return name of all leaves inside a metanode. */
+ leaves(): string[];
+}
+
+export interface SeriesNode extends GroupNode {
+ hasLoop: boolean;
+ prefix: string;
+ suffix: string;
+ clusterId: number;
+ ids: number[];
+ parent: string;
+}
+
+export class EllipsisNodeImpl implements EllipsisNode {
+ name: string;
+ numMoreNodes: number;
+ stats: NodeStats;
+ type: NodeType;
+ isGroupNode: boolean;
+ cardinality: number;
+ parentNode: Node;
+
+ /**
+ * Constructs a new ellipsis annotation node.
+ *
+ * @param numNodes The number of additional annotations this node represents.
+ */
+ constructor(numNodes: number) {
+ this.type = NodeType.ELLIPSIS;
+ this.isGroupNode = false;
+ this.cardinality = 1;
+ this.parentNode = null;
+ this.stats = null;
+ this.setNumMoreNodes(numNodes);
+ }
+
+ setNumMoreNodes(numNodes: number) {
+ this.numMoreNodes = numNodes;
+ this.name = "... " + numNodes + " more";
+ }
+};
+
+/**
+ * A label object for nodes in the full graph and leaf nodes in the render
+ * graph.
+ */
+class OpNodeImpl implements OpNode {
+ name: string;
+ op: string;
+ device: string;
+ stats: NodeStats;
+ attr: {key: string, value: Object}[];
+ inputs: NormalizedInput[];
+ type: NodeType;
+ isGroupNode: boolean;
+ cardinality: number;
+ inEmbeddings: OpNode[];
+ outEmbeddings: OpNode[];
+ parentNode: Node;
+
+ /**
+ * Constructs a new Op node.
+ *
+ * @param rawNode The raw node.
+ * @param normalizedInputs 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.
+ */
+ constructor(rawNode: tf.TFNode, normalizedInputs: NormalizedInput[]) {
+ this.op = rawNode.op;
+ this.name = rawNode.name;
+ this.device = rawNode.device;
+ this.attr = rawNode.attr;
+ this.inputs = normalizedInputs;
+ // additional properties
+ this.type = NodeType.OP;
+ this.isGroupNode = false;
+ this.cardinality = 1;
+ this.inEmbeddings = [];
+ this.outEmbeddings = [];
+ this.parentNode = null;
+ }
+};
+
+export function createMetanode(name: string, opt = {}): Metanode {
+ return new MetanodeImpl(name, opt);
+}
+
+/**
+ * Joins the information from the stats file (memory, compute time) with the graph
+ * information.
+ */
+export function joinStatsInfoWithGraph(graph: SlimGraph,
+ statsJson: TFStats): void {
+ _.each(statsJson.devStats, stats => {
+ _.each(stats.nodeStats, 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.
+ let nodeName = nodeStats.nodeName in graph.nodes ?
+ nodeStats.nodeName :
+ nodeStats.nodeName + NAMESPACE_DELIM + "(" + nodeStats.nodeName + ")";
+ if (nodeName in graph.nodes) {
+ // Compute the total bytes used.
+ let totalBytes = 0;
+ if (nodeStats.memory) {
+ _.each(nodeStats.memory, alloc => {
+ if (alloc.totalBytes) {
+ totalBytes += Number(alloc.totalBytes);
+ }
+ });
+ }
+ let outputSize: number[][] = null;
+ if (nodeStats.output) {
+ outputSize = _.map(nodeStats.output, output => {
+ return _.map(output.tensorDescription.shape.dim,
+ dim => Number(dim.size));
+ });
+ }
+ graph.nodes[nodeName].stats = new NodeStats(totalBytes,
+ Number(nodeStats.allEndRelMicros), outputSize);
+ }
+ });
+ });
+}
+
+/**
+ * Execution stats for the node.
+ */
+class NodeStats {
+ constructor(totalBytes: number, totalMicros: number, outputSize: number[][]) {
+ this.totalBytes = totalBytes;
+ this.totalMicros = totalMicros;
+ this.outputSize = outputSize;
+ }
+
+ /**
+ * Total number of bytes used for the node. Sum of all childen
+ * if it is a Group node.
+ */
+ totalBytes: number;
+ /**
+ * Total number of compute time in microseconds used for the node.
+ * Sum of all children if it is a Group node.
+ */
+ totalMicros: number;
+ /**
+ * The shape of each output tensors, if there are any.
+ * Empty if it is a Group node.
+ */
+ outputSize: number[][];
+
+ /**
+ * Combines the specified stats with the current stats.
+ * Modifies the current object. This methos is used to
+ * compute aggregate stats for group nodes.
+ */
+ combine(stats: NodeStats): void {
+ if (stats.totalBytes != null) {
+ this.totalBytes += stats.totalBytes;
+ }
+ if (stats.totalMicros != null) {
+ this.totalMicros += stats.totalMicros;
+ }
+ }
+}
+
+class MetanodeImpl implements Metanode {
+ name: string;
+ stats: NodeStats;
+ type: NodeType;
+ depth: number;
+ isGroupNode: boolean;
+ cardinality: number;
+ metagraph: graphlib.Graph<GroupNode|OpNode, Metaedge>;
+ bridgegraph: graphlib.Graph<GroupNode|OpNode, Metaedge>;
+ templateId: string;
+ opHistogram: {[op: string]: number};
+ deviceHistogram: {[op: string]: number};
+ parentNode: Node;
+ hasNonControlEdges: boolean;
+
+ /** A label object for meta-nodes in the graph hierarchy */
+ constructor(name: string, 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<GroupNode|OpNode, Metaedge>(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.stats = new NodeStats(0, 0, null);
+ this.hasNonControlEdges = false;
+ }
+
+ getFirstChild(): GroupNode|OpNode {
+ 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).
+ */
+ getRootOp(): OpNode {
+ let nameSplit = this.name.split("/");
+ let rootOpName = this.name + "/(" + nameSplit[nameSplit.length - 1] + ")";
+ return <OpNode>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.
+ */
+ leaves(): string[] {
+ let leaves = [];
+ let queue = [<Node> this];
+ let metagraph; // Defined here due to a limitation of ES6->5 compilation.
+ while (queue.length) {
+ let node = queue.shift();
+ if (node.isGroupNode) {
+ metagraph = (<GroupNode> node).metagraph;
+ _.each(metagraph.nodes(), name => queue.push(metagraph.node(name)));
+ } else {
+ leaves.push(node.name);
+ }
+ }
+ return leaves;
+ }
+};
+
+export interface Metaedge extends graphlib.EdgeObject {
+
+ /**
+ * Stores the original BaseEdges represented by this Metaedge.
+ */
+ baseEdgeList: BaseEdge[];
+
+ /**
+ * Whether this edge represents a relationship that is inbound (or outbound)
+ * to the object which contains this information. For example, in a Metanode's
+ * bridgegraph, each edge connects an immediate child to something outside
+ * the Metanode. If the destination of the edge is inside the Metanode, then
+ * its inbound property should be true. If the destination is outside the
+ * Metanode, then its inbound property should be false.
+ *
+ * The property is optional because not all edges can be described as
+ * inbound/outbound. For example, in a Metanode's metagraph, all of the edges
+ * connect immediate children of the Metanode. None should have an inbound
+ * property, or they should be null/undefined.
+ */
+ inbound?: boolean;
+
+ /**
+ * Number of regular edges (not control dependency edges).
+ */
+ numRegularEdges: number;
+
+ /**
+ * Number of control dependency edges.
+ */
+ numControlEdges: number;
+
+ /**
+ * Number of reference edges, which is an edge to an operation
+ * that takes a reference to its input and changes its value.
+ */
+ numRefEdges: number;
+
+ addBaseEdge(edge: BaseEdge): void;
+}
+
+export function createMetaedge(v: string, w: string): Metaedge {
+ return new MetaedgeImpl(v, w);
+}
+
+/**
+ * A label object for edges between metanodes of subgraphs in the render graph.
+ */
+class MetaedgeImpl implements Metaedge {
+ v: string;
+ w: string;
+ baseEdgeList: BaseEdge[];
+ inbound: boolean;
+ numRegularEdges: number;
+ numControlEdges: number;
+ numRefEdges: number;
+
+ constructor(v: string, w: string) {
+ this.v = v;
+ this.w = w;
+ this.baseEdgeList = [];
+ this.inbound = null;
+ this.numRegularEdges = 0;
+ this.numControlEdges = 0;
+ this.numRefEdges = 0;
+ }
+
+ addBaseEdge(edge: BaseEdge): void {
+ this.baseEdgeList.push(edge);
+ if (edge.isControlDependency) {
+ this.numControlEdges += 1;
+ } else {
+ this.numRegularEdges += 1;
+ }
+ if (edge.isReferenceEdge) {
+ this.numRefEdges += 1;
+ }
+ }
+}
+
+export function createSeriesNode(prefix: string, suffix: string,
+ parent: string, clusterId: number, name: string): SeriesNode {
+ return new SeriesNodeImpl(prefix, suffix, parent, clusterId, name);
+}
+
+export function getSeriesNodeName(prefix: string, suffix: string,
+ parent: string, startId?: number, endId?: number): string {
+ let numRepresentation =
+ (typeof startId !== "undefined" && typeof endId !== "undefined") ?
+ "[" + startId + "-" + endId + "]" : "#";
+ let pattern = prefix + numRepresentation + suffix;
+ return (parent ? parent + "/" : "") + pattern;
+}
+
+class SeriesNodeImpl implements SeriesNode {
+ name: string;
+ type: NodeType;
+ stats: NodeStats;
+ hasLoop: boolean;
+ prefix: string;
+ suffix: string;
+ clusterId: number;
+ ids: number[];
+ parent: string;
+ isGroupNode: boolean;
+ cardinality: number;
+ metagraph: graphlib.Graph<GroupNode|OpNode, Metaedge>;
+ bridgegraph: graphlib.Graph<GroupNode|OpNode, Metaedge>;
+ parentNode: Node;
+ deviceHistogram: {[op: string]: number};
+ hasNonControlEdges: boolean;
+
+ constructor(prefix: string, suffix: string, parent: string,
+ clusterId: number, name: string) {
+ 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<Metanode, Metaedge>(name, GraphType.SERIES);
+ // bridgegraph must be constructed lazily-see hierarchy.getBridgegraph()
+ this.bridgegraph = null;
+ this.parentNode = null;
+ this.deviceHistogram = {};
+ this.hasNonControlEdges = false;
+ this.stats = new NodeStats(0, 0, 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: string[]): NormalizedInput[] {
+ return _.reduce(inputs, function(normalizedInputs, inputName) {
+ let start = inputName[0] === "^";
+ let colon = inputName.lastIndexOf(":");
+ let end = colon !== -1 &&
+ inputName.length - colon > 1 &&
+ !(/\D/).test(inputName.substring(colon + 1)) ?
+ colon : inputName.length;
+ let name = inputName.substring(start ? 1 : 0, end);
+ if (normalizedInputs.length === 0 ||
+ name !== normalizedInputs[normalizedInputs.length - 1].name) {
+ normalizedInputs.push({
+ name: name,
+ hasNumberPart: end !== inputName.length,
+ isControlDependency: start
+ });
+ }
+ return normalizedInputs;
+ }, []);
+}
+
+export function build(rawNodes: tf.TFNode[], params: BuildParams,
+ tracker: ProgressTracker): Promise<SlimGraph|void> {
+ /**
+ * A dictionary that maps each in-embedding node name to its host node label
+ * object.
+ */
+ let inEmbedding: {[nodeName: string]: OpNode} = {};
+ /**
+ * A dictionary that maps each node name to an array of the node's
+ * out-embedding node label objects.
+ */
+ let outEmbeddings: {[inputName: string]: OpNode[]} = {};
+ let isInEmbeddedPred = getEmbedPredicate(params.inEmbeddingTypes);
+ let isOutEmbeddedPred = getEmbedPredicate(params.outEmbeddingTypes);
+ let embeddingNodeNames: string[] = [];
+ /**
+ * 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.
+ */
+ let nodeNames = new Array<string>(rawNodes.length);
+
+ return runAsyncTask("Normalizing names", 30, () => {
+ let opNodes = new Array<OpNode>(rawNodes.length);
+ let index = 0;
+ _.each(rawNodes, rawNode => {
+ let normalizedInputs = normalizeInputs(rawNode.input);
+ let opNode = new OpNodeImpl(rawNode, normalizedInputs);
+ if (isInEmbeddedPred(opNode)) {
+ embeddingNodeNames.push(opNode.name);
+ inEmbedding[opNode.name] = opNode;
+ return;
+ }
+
+ if (isOutEmbeddedPred(opNode)) {
+ embeddingNodeNames.push(opNode.name);
+ _.each(opNode.inputs, input => {
+ let 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((opNodes) => {
+ // Create the graph data structure from the graphlib library.
+ return runAsyncTask("Building the data structure", 70, () => {
+ let normalizedNameDict = mapStrictHierarchy(nodeNames,
+ embeddingNodeNames);
+ let graph = new SlimGraph;
+
+ // Add the nodes to the graph.
+ _.each(opNodes, opNode => {
+ let normalizedName = normalizedNameDict[opNode.name] || opNode.name;
+ graph.nodes[normalizedName] = opNode;
+ // Check if the node has out-embeddings. If yes, add them to to the
+ // node.
+ if (opNode.name in outEmbeddings) {
+ opNode.outEmbeddings = outEmbeddings[opNode.name];
+ // Normalize the names of the out-embeddings.
+ _.each(opNode.outEmbeddings, 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, opNode => {
+ _.each(opNode.inputs, (input, i) => {
+ let inputName = input.name;
+ if (inputName in inEmbedding) {
+ opNode.inEmbeddings.push(inEmbedding[inputName]);
+ } else {
+ graph.edges.push({
+ v: normalizedNameDict[inputName] || inputName,
+ w: opNode.name,
+ isControlDependency: input.isControlDependency,
+ // Check if this op type and input number corresponds to a
+ // reference edge using the refEdges dictionary in the params.
+ isReferenceEdge: (params.refEdges[opNode.op + " " + i] === true)
+ });
+ }
+ });
+ });
+
+ // Normalize the names of in-embeddings.
+ _.each(inEmbedding, (node, name) => {
+ node.name = normalizedNameDict[node.name] || node.name;
+ });
+
+ return graph;
+ }, tracker);
+ })
+ .catch(function(reason) {
+ throw new Error("Failure creating graph");
+ });
+};
+
+/**
+ * Create a new graphlib.Graph() instance with default parameters
+ */
+export function createGraph<N, E>(name: string, type, opt = {}):
+ graphlib.Graph<N, E> {
+ let graph = new graphlib.Graph<N, E>(opt);
+ graph.setGraph({
+ name: name,
+ rankdir: "BT", // BT,TB,LR,RL
+ type: type
+ });
+ return graph;
+};
+
+/**
+ * Create a predicate for checking whether a node should be embedded based on
+ * the specified types.
+ */
+function getEmbedPredicate(types: string[]) {
+ return function(node) {
+ // check types
+ for (let i = 0; i < types.length; i++) {
+ let 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: string): string {
+ let parts = name.split(NAMESPACE_DELIM);
+ return name + NAMESPACE_DELIM + "(" + parts[parts.length - 1] + ")";
+}
+
+/**
+ * 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: string[],
+ embeddingNodeNames: string[]): {[oldName: string]: string} {
+ /** Dictionary that maps the old new to the new name */
+ let newNameDictionary: {[oldName: string]: string} = {};
+ /** Set used to store all namespaces. */
+ let namespaceSet: {[namespace: string]: boolean} = {};
+ // sort the nodes to make prefix check faster
+ nodeNames.sort();
+ // look for nodes with a prefix a,a/b -> a/(a),a/b
+ for (let i = 0; i < nodeNames.length - 1; ++i) {
+ let 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), ns => {
+ namespaceSet[ns] = true;
+ });
+ let b = nodeNames[i + 1];
+ if (_.startsWith(b, a + NAMESPACE_DELIM)) {
+ newNameDictionary[a] = getStrictName(a);
+ }
+ }
+ // Go through all the embedding node names and rename them in case they
+ // collide with namespaces.
+ _.each(embeddingNodeNames, 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: graphlib.Graph<any, any>): number[] {
+ let 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.
+ */
+export function hasSimilarDegreeSequence(graph1: graphlib.Graph<any, any>,
+ graph2: graphlib.Graph<any, any>): boolean {
+ let dg1 = degreeSequence(graph1);
+ let dg2 = degreeSequence(graph2);
+
+ for (let i = 0; i < dg1.length; i++) {
+ if (dg1[i] !== dg2[i]) {
+ return false;
+ }
+ }
+ return true;
+};
+
+/**
+ * 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'].
+ */
+export function getHierarchicalPath(name: string,
+ seriesNames?: { [name: string]: string }): string[] {
+ let path: string[] = [];
+ let i = name.indexOf(NAMESPACE_DELIM);
+ // Push all parent portions of the path.
+ while (i >= 0) {
+ path.push(name.substring(0, i));
+ i = name.indexOf(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) {
+ let seriesName = seriesNames[name];
+ if (seriesName) {
+ path.push(seriesName);
+ }
+ }
+ // Push the leaf of the path.
+ path.push(name);
+ return path;
+};
+
+} // close module tf.graph
diff --git a/tensorflow/tensorboard/components/tf-graph-common/lib/hierarchy.ts b/tensorflow/tensorboard/components/tf-graph-common/lib/hierarchy.ts
new file mode 100644
index 0000000000..6c9333de4c
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-graph-common/lib/hierarchy.ts
@@ -0,0 +1,715 @@
+/// <reference path="graph.ts" />
+/// <reference path="template.ts" />
+
+/**
+ * Package for the Graph Hierarchy for TensorFlow graph.
+ */
+module tf.graph.hierarchy {
+
+const LOG_PREFIX_MSG = "Graph hierarchy: ";
+
+/**
+ * Class used as output for getPredecessors and getSuccessors methods
+ */
+interface Edges {
+ control: string[];
+ regular: string[];
+}
+
+export interface Hierarchy {
+ root: Metanode;
+ templates: {[templateId: string]: string[]};
+ /** List of all device names */
+ devices: string[];
+ getNodeMap(): {[nodeName: string]: GroupNode|OpNode};
+ node(name: string): GroupNode|OpNode;
+ setNode(name: string, node: GroupNode|OpNode): void;
+ getBridgegraph(nodeName: string): graphlib.Graph<GroupNode|OpNode, Metaedge>;
+ getPredecessors(nodeName: string): Edges;
+ getSuccessors(nodeName: string): Edges;
+ getTopologicalOrdering(nodeName: string): { [childName: string]: number };
+}
+
+/**
+ * Class for the Graph Hierarchy for TensorFlow graph.
+ */
+class HierarchyImpl implements Hierarchy {
+ root: Metanode;
+ templates: {[templateId: string]: string[]};
+ private index: {[nodeName: string]: GroupNode|OpNode};
+ devices: string[];
+ orderings: { [nodeName: string]: { [childName: string]: number } };
+
+ constructor() {
+ this.root = createMetanode(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[ROOT_NAME] = this.root;
+ this.orderings = {};
+ }
+
+ getNodeMap(): {[nodeName: string]: GroupNode|OpNode} {
+ return this.index;
+ }
+
+ node(name: string): GroupNode|OpNode {
+ return this.index[name];
+ }
+
+ setNode(name: string, node: GroupNode|OpNode): void {
+ 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.
+ */
+ getBridgegraph(nodeName: string): graphlib.Graph<GroupNode|OpNode, Metaedge> {
+ let node = this.index[nodeName];
+ if (!node) {
+ throw Error("Could not find node in hierarchy: " + nodeName);
+ }
+ if (!("metagraph" in node)) {
+ return null;
+ }
+ let groupNode = <GroupNode> node;
+ if (groupNode.bridgegraph) {
+ return groupNode.bridgegraph;
+ }
+ let bridgegraph = groupNode.bridgegraph =
+ createGraph<GroupNode|OpNode, Metaedge>(
+ "BRIDGEGRAPH", GraphType.BRIDGE);
+ if (!node.parentNode || !("metagraph" in node.parentNode)) {
+ return bridgegraph;
+ }
+
+ let parentNode = <GroupNode>node.parentNode;
+ let parentMetagraph = parentNode.metagraph;
+ let 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], parentGraph => {
+ _(parentGraph.edges())
+ .filter(e => e.v === nodeName || e.w === nodeName)
+ .each(parentEdgeObj => {
+
+ let inbound = parentEdgeObj.w === nodeName;
+ let 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, baseEdge => {
+
+ // Based on the direction, figure out which is the descendant node
+ // and which is the "other" node (sibling of parent or ancestor).
+ let [descendantName, otherName] =
+ inbound ?
+ [baseEdge.w, parentEdgeObj.v] :
+ [baseEdge.v, parentEdgeObj.w];
+
+ // Determine the immediate child containing this descendant node.
+ let 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.
+ let bridgeEdgeObj = <graphlib.EdgeObject> {
+ v: inbound ? otherName : childName,
+ w: inbound ? childName : otherName,
+ };
+ let bridgeMetaedge = bridgegraph.edge(bridgeEdgeObj);
+ if (!bridgeMetaedge) {
+ bridgeMetaedge = 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);
+ });
+ })
+ .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.
+ */
+ getChildName(nodeName: string, descendantName: string): string {
+ // Walk up the hierarchy from the descendant to find the child.
+ let currentNode: Node = 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 the names of its predecssors.
+ * For an OpNode, this will contain the targets from the underlying BaseEdges.
+ * For a GroupNode, this will contain the targets truncated to siblings of
+ * the shared ancestor.
+ *
+ * For example, consider an original non-control BaseEdge A/B/C->Z/Y/X. Their
+ * shared ancestor is the ROOT node. A and Z are the highest siblings. Here
+ * are the results of calling getPredecessors():
+ *
+ * - getPredecessors("Z/Y/X") === {regular: ["A/B/C"], control: []};
+ * - getPredecessors("Z/Y") === {regular: ["A"], control: []};
+ * - getPredecessors("Z") === {regular: ["A"], control: []};
+ *
+ * The reason getPredecessors("Z/Y") returns ["A"] (and not ["A/B"] as you
+ * might intuitively expect) is because it's not clear how far down the
+ * other end of the hierarchy to traverse in the general case.
+ *
+ * Continuing this example, say there was another BaseEdge A/K->Z/Y/W. When
+ * we look at Z/Y's predecessors, the best we can say is ["A"] without getting
+ * into the details of which of of Z/Y's descendant nodes have predecessors to
+ * which of A's descendants.
+ *
+ * On the other hand, for an OpNode it's clear what the final predecssors
+ * ought to be. There is no ambiguity.
+ */
+ getPredecessors(nodeName: string): Edges {
+ let node = this.index[nodeName];
+ if (!node) {
+ throw Error("Could not find node with name: " + nodeName);
+ }
+
+ let predecessors = this.getOneWayEdges(node, true);
+
+ // Add embedded predecessors, such as constants.
+ if (!node.isGroupNode) {
+ _.each((<OpNode>node).inEmbeddings, embeddedNode => {
+ predecessors.regular.push(embeddedNode.name);
+ });
+ }
+ return predecessors;
+ }
+
+ /**
+ * Given the name of a node, return an array of the names of its successors.
+ * For an OpNode, this will contain the targets from the underlying BaseEdges.
+ * For a GroupNode, this will contain the targets truncated to sibling of
+ * the shared ancestor.
+ *
+ * This is the inverse of getPredecessors(). See that method's documentation
+ * for an in-depth example.
+ */
+ getSuccessors(nodeName: string): Edges {
+ let node = this.index[nodeName];
+ if (!node) {
+ throw Error("Could not find node with name: " + nodeName);
+ }
+
+ let successors = this.getOneWayEdges(node, false);
+
+ // Add embedded successors, such as summaries.
+ if (!node.isGroupNode) {
+ _.each((<OpNode>node).outEmbeddings, embeddedNode => {
+ successors.regular.push(embeddedNode.name);
+ });
+ }
+ return successors;
+ }
+
+ /** Helper method for getPredeccessors and getSuccessors */
+ getOneWayEdges(node: GroupNode|OpNode, inEdges: boolean) {
+ let edges = { control: [], regular: [] };
+ // A node with no parent cannot have any edges.
+ if (!node.parentNode) {
+ return edges;
+ }
+ if (node.parentNode.isGroupNode) {
+ let parentNode = <GroupNode>node.parentNode;
+ let metagraph = parentNode.metagraph;
+ let 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.
+ */
+ getTopologicalOrdering(nodeName: string): { [childName: string]: number } {
+ let 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.
+ let successors: { [childName: string]: string[] } = {};
+
+ // Set of node names which have appeared as a destination.
+ let destinations: { [childName: string]: boolean } = {};
+
+ let metagraph = (<GroupNode> node).metagraph;
+ _.each(metagraph.edges(), (e: graphlib.EdgeObject) => {
+ 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).
+ let queue: string[] =
+ _.difference(_.keys(successors), _.keys(destinations));
+
+ // Produce an ordering by traversing the graph breadth first.
+ let ordering = this.orderings[nodeName] = {};
+ let index = 0;
+ while (queue.length) {
+ let childName = queue.shift();
+ ordering[childName] = index++;
+ _.each(successors[childName], succName => queue.push(succName));
+ delete successors[childName]; // Prevent cycles from infinite looping.
+ }
+ return ordering;
+ }
+
+}
+
+/**
+ * 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: graphlib.Graph<GroupNode|OpNode, Metaedge>,
+ node: Node, inbound: boolean, targets: Edges): void {
+ _.each(<Metaedge[]> graph.edges(), e => {
+ let [selfName, otherName] = inbound ? [e.w, e.v] : [e.v, e.w];
+ if (selfName === node.name) {
+ if (node.isGroupNode) {
+ let targetList = graph.edge(e).numRegularEdges
+ ? targets.regular : targets.control;
+ targetList.push(otherName);
+ } else {
+ _.each(graph.edge(e).baseEdgeList, baseEdge => {
+ let targetList = baseEdge.isControlDependency
+ ? targets.control : targets.regular;
+ targetList.push(inbound ? baseEdge.v : baseEdge.w);
+ });
+ }
+ }
+ });
+}
+
+interface HierarchyParams {
+ verifyTemplate: boolean;
+ groupSeries: boolean;
+}
+
+/**
+ * @param graph The raw graph.
+ * @param params Parameters used when building a hierarchy.
+ */
+export function build(graph: tf.graph.SlimGraph, params: HierarchyParams,
+ tracker: ProgressTracker): Promise<Hierarchy|void> {
+ let h = new HierarchyImpl();
+ let seriesNames: { [name: string]: string } = {};
+ return runAsyncTask("Adding nodes", 20, () => {
+ // Get all the possible device names.
+ let deviceNames = {};
+ _.each(graph.nodes, (node, nodeName) => {
+ if (node.device != null) {
+ deviceNames[node.device] = true;
+ }
+ });
+ h.devices = _.keys(deviceNames);
+ addNodes(h, graph);
+ }, tracker)
+ .then(() => {
+ return runAsyncTask("Detect series", 20, () => {
+ if (params.groupSeries) {
+ groupSeries(h.root, h, seriesNames);
+ }
+ }, tracker);
+ })
+ .then(() => {
+ return runAsyncTask("Adding edges", 30, () => {
+ addEdges(h, graph, seriesNames);
+ }, tracker);
+ })
+ .then(() => {
+ return runAsyncTask("Finding similar subgraphs", 30, () => {
+ h.templates = template.detect(h, params.verifyTemplate);
+ }, tracker);
+ })
+ .then(() => {
+ return h;
+ }).catch(function(reason) {
+ throw new Error("Failure creating graph hierarchy");
+ });
+};
+
+/**
+ * Creates the metanodes in the hierarchical graph and assigns parent-child
+ * relationship between them.
+ */
+function addNodes(h: Hierarchy, graph: SlimGraph) {
+ _.each(graph.nodes, (node, nodeName) => {
+ let path = getHierarchicalPath(node.name);
+ let parent: Metanode = 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 (let 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.stats) {
+ parent.stats.combine(node.stats);
+ }
+ if (node.device != null) {
+ parent.deviceHistogram[node.device] =
+ (parent.deviceHistogram[node.device] || 0) + 1;
+ }
+ if (i === path.length - 1) { break; }
+ let name = path[i];
+ let child = <Metanode>h.node(name);
+ if (!child) {
+ child = createMetanode(name);
+ child.parentNode = parent;
+ h.setNode(name, child);
+ parent.metagraph.setNode(name, 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: Hierarchy, graph: SlimGraph,
+ seriesNames: { [name: string]: string }) {
+
+ let 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.
+ let sourcePath: string[] = [];
+ let destPath: string[] = [];
+
+ // 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).
+ let getPath = (node: Node, path: string[]): number => {
+ let i = 0;
+ while (node) {
+ path[i++] = node.name;
+ node = node.parentNode;
+ }
+ return i - 1;
+ };
+
+ _.each(graph.edges, baseEdge => {
+
+ // Get the hierarchical paths for the source and destination of the edge.
+ let sourceAncestorIndex = getPath(graph.nodes[baseEdge.v], sourcePath);
+ let destAncestorIndex = getPath(graph.nodes[baseEdge.w], destPath);
+
+ // 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.");
+ }
+ }
+
+ let sharedAncestorNode =
+ <GroupNode>nodeIndex[sourcePath[sourceAncestorIndex + 1]];
+ let sourceAncestorName = sourcePath[sourceAncestorIndex];
+ let destAncestorName = destPath[destAncestorIndex];
+
+ // Find or create the Metaedge which should contain this BaseEdge inside
+ // the shared ancestor.
+ let metaedge =
+ sharedAncestorNode.metagraph.edge(sourceAncestorName, destAncestorName);
+ if (!metaedge) {
+ metaedge = createMetaedge(sourceAncestorName, destAncestorName);
+ sharedAncestorNode.metagraph
+ .setEdge(sourceAncestorName, destAncestorName, metaedge);
+ }
+ if (!sharedAncestorNode.hasNonControlEdges &&
+ !baseEdge.isControlDependency) {
+ sharedAncestorNode.hasNonControlEdges = true;
+ }
+ metaedge.addBaseEdge(baseEdge);
+
+ });
+
+};
+
+/**
+ * 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
+ * @return A dictionary from node name to series node name that contains the node
+ */
+function groupSeries(metanode: Metanode, hierarchy: Hierarchy,
+ seriesNames: { [name: string]: string }) {
+ let metagraph = metanode.metagraph;
+ _.each(metagraph.nodes(), n => {
+ let child = metagraph.node(n);
+ if (child.type === tf.graph.NodeType.META) {
+ groupSeries(<Metanode>child, hierarchy, seriesNames);
+ }
+ });
+
+ let clusters = clusterNodes(metagraph);
+ let seriesDict = detectSeries(clusters, metagraph);
+
+ // Add each series node to the graph and add its grouped children to its own
+ // metagraph.
+ _.each(seriesDict, function(seriesNode: SeriesNode, seriesName: string) {
+ let nodeMemberNames = seriesNode.metagraph.nodes();
+ let firstMember = seriesNode.metagraph.node(nodeMemberNames[0]);
+ let seriesType = firstMember.type;
+
+ hierarchy.setNode(seriesName, seriesNode); // add to the index
+ metagraph.setNode(seriesName, seriesNode);
+ _.each(nodeMemberNames, n => {
+ let child = <OpNode> 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;
+
+ if (child.stats) {
+ seriesNode.stats.combine(child.stats);
+ }
+
+ // Remove now-grouped node from its original parent's metagraph.
+ metagraph.removeNode(n);
+ });
+ });
+};
+
+/** cluster op-nodes with similar op */
+function clusterNodes(metagraph: graphlib.Graph<GroupNode|OpNode, Metaedge>):
+ {[clusterId: string]: string[]} {
+ let result: {[clusterId: string]: string[]} = {};
+ return _.reduce(metagraph.nodes(), function(clusters: {[clusterId: string]: string[]}, n: string) {
+ let child = metagraph.node(n);
+ if (child.type === NodeType.META) {
+ // skip metanodes
+ return clusters;
+ }
+ let template = (<OpNode>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: {[clusterId: string]: string[]},
+ metagraph: graphlib.Graph<GroupNode|OpNode, Metaedge>):
+ {[seriesName: string]: SeriesNode} {
+ let seriesDict: {[seriesName: string]: SeriesNode} = {};
+ _.each(clusters, function(members, clusterId: string) {
+ 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.
+ */
+ let 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: string) {
+ let isGroup = name.charAt(name.length - 1) === "*";
+ let namepath = name.split("/");
+ let leaf = namepath[namepath.length - 1];
+ let parent = namepath.slice(0, namepath.length - 1).join("/");
+ let matches = leaf.match(/^(\D*)_(\d+)$/);
+
+ let prefix;
+ let id;
+ let suffix = "";
+ if (matches) { // if found "<number>" in the name, assign id.
+ prefix = matches[1]; // the front non-numeric characters
+ id = matches[2]; // the digits
+ } else { // for node without "_<number>", make them zero-th items.
+ prefix = isGroup ? leaf.substr(0, leaf.length - 1) : leaf;
+ if (prefix.charAt(prefix.length - 1) !== "_") {
+ prefix += "_";
+ }
+ id = 0;
+ suffix = isGroup ? "*" : "";
+ }
+ let seriesName = getSeriesNodeName(prefix, suffix, parent);
+ candidatesDict[seriesName] = candidatesDict[seriesName] || [];
+ let seriesNode = 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: SeriesNode[], 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.
+ let seriesNodes = [seriesInfoArray[0]];
+ for (let index = 1; index < seriesInfoArray.length; index++) {
+ let 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: SeriesNode[],
+ seriesDict: {[seriesName: string] : SeriesNode},
+ clusterId: number,
+ metagraph: graphlib.Graph<GroupNode|OpNode, Metaedge>) {
+ if (seriesNodes.length > 1) {
+ let curSeriesName = getSeriesNodeName(
+ seriesNodes[0].prefix, seriesNodes[0].suffix,
+ seriesNodes[0].parent, seriesNodes[0].clusterId,
+ seriesNodes[seriesNodes.length - 1].clusterId);
+ let curSeriesNode = createSeriesNode(seriesNodes[0].prefix,
+ seriesNodes[0].suffix, seriesNodes[0].parent, clusterId,
+ curSeriesName);
+ _.each(seriesNodes, function(node) {
+ curSeriesNode.ids.push(node.clusterId);
+ curSeriesNode.metagraph.setNode(node.name, metagraph.node(node.name));
+ });
+ seriesDict[curSeriesName] = curSeriesNode;
+ }
+}
+
+} // close module tf.graph.hierarchy
diff --git a/tensorflow/tensorboard/components/tf-graph-common/lib/layout.ts b/tensorflow/tensorboard/components/tf-graph-common/lib/layout.ts
new file mode 100644
index 0000000000..4eb3cab011
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-graph-common/lib/layout.ts
@@ -0,0 +1,628 @@
+/// <reference path="graph.ts" />
+/// <reference path="render.ts" />
+
+module tf.graph.layout {
+
+/** Set of parameters that define the look and feel of the graph. */
+export const 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: 110,
+ /**
+ * 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
+ },
+ /** 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: 90,
+ /**
+ * 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,
+ },
+ /**
+ * 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: 50,
+ /** 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,
+ /** 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, // for making annotation touching ellipse
+ labelOffset: -8
+ },
+ /** 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, // Forces annotations to center line.
+ 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: {
+ /** 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,
+ /** Estimate max width for annotation label */
+ labelWidth: 35
+ },
+ 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. */
+export function scene(renderNodeInfo: render.RenderGroupNodeInformation)
+ : void {
+ // 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 === NodeType.META) {
+ layoutMetanode(renderNodeInfo);
+ } else if (renderNodeInfo.node.type === NodeType.SERIES) {
+ layoutSeriesNode(renderNodeInfo);
+ }
+};
+
+/**
+ * Update layout, size, and annotations of its children nodes and edges.
+ */
+function layoutChildren(renderNodeInfo: render.RenderGroupNodeInformation)
+ : void {
+ let children = renderNodeInfo.coreGraph.nodes().map(n => {
+ return renderNodeInfo.coreGraph.node(n);
+ }).concat(renderNodeInfo.isolatedInExtract,
+ renderNodeInfo.isolatedOutExtract);
+
+ _.each(children, childNodeInfo => {
+ // Set size of each child
+ switch (childNodeInfo.node.type) {
+ case NodeType.OP:
+ _.extend(childNodeInfo, PARAMS.nodeSize.op);
+ break;
+ case NodeType.BRIDGE:
+ _.extend(childNodeInfo, PARAMS.nodeSize.bridge);
+ break;
+ case NodeType.META:
+ if (!childNodeInfo.expanded) {
+ // set fixed width and scalable height based on cardinality
+ _.extend(childNodeInfo, PARAMS.nodeSize.meta);
+ childNodeInfo.height =
+ PARAMS.nodeSize.meta.height(childNodeInfo.node.cardinality);
+ } else {
+ let childGroupNodeInfo =
+ <render.RenderGroupNodeInformation>childNodeInfo;
+ scene(childGroupNodeInfo); // Recursively layout its subscene.
+ }
+ break;
+ case NodeType.SERIES:
+ if (childNodeInfo.expanded) {
+ _.extend(childNodeInfo, PARAMS.nodeSize.series.expanded);
+ let childGroupNodeInfo =
+ <render.RenderGroupNodeInformation>childNodeInfo;
+ scene(childGroupNodeInfo); // Recursively layout its subscene.
+ } else {
+ let childGroupNodeInfo =
+ <render.RenderGroupNodeInformation>childNodeInfo;
+ let seriesParams =
+ childGroupNodeInfo.node.hasNonControlEdges ?
+ PARAMS.nodeSize.series.vertical :
+ PARAMS.nodeSize.series.horizontal;
+ _.extend(childNodeInfo, seriesParams);
+ }
+ break;
+ default:
+ throw Error("Unrecognized node type: " + childNodeInfo.node.type);
+ }
+
+ // 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: graphlib.Graph<any, any>, params)
+ : {height: number, width: number} {
+ _.extend(graph.graph(), {
+ nodeSep: params.nodeSep,
+ rankSep: params.rankSep
+ });
+
+ let bridgeNodeNames = [];
+ let nonBridgeNodeNames = [];
+
+ // Split out nodes into bridge and non-bridge nodes, and calculate the total
+ // width we should use for bridge nodes.
+ _.each(graph.nodes(), nodeName => {
+ let nodeInfo = graph.node(nodeName);
+ if (nodeInfo.node.type === 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);
+
+ let graphLabel = graph.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.
+ let minX = Infinity;
+ let minY = Infinity;
+ let maxX = -Infinity;
+ let maxY = -Infinity;
+ _.each(nonBridgeNodeNames, nodeName => {
+ let nodeInfo = graph.node(nodeName);
+ let w = 0.5 * nodeInfo.width;
+ let x1 = nodeInfo.x - w - nodeInfo.inboxWidth;
+ let x2 = nodeInfo.x + w + nodeInfo.outboxWidth;
+ minX = x1 < minX ? x1 : minX;
+ maxX = x2 > maxX ? x2 : maxX;
+ let labelLength =
+ nodeName.length - nodeName.lastIndexOf(NAMESPACE_DELIM);
+ // TODO(jimbo): Account for font width rather than using a magic number.
+ let charWidth = 3; // 3 pixels per character.
+ let lw = 0.5 * labelLength * charWidth;
+ let lx1 = nodeInfo.x - lw;
+ let lx2 = nodeInfo.x + lw;
+ minX = lx1 < minX ? lx1 : minX;
+ maxX = lx2 > maxX ? lx2 : maxX;
+ // TODO(jimbo): Account for the height of labels above op nodes here.
+ let h = 0.5 * nodeInfo.outerHeight;
+ let y1 = nodeInfo.y - h;
+ let y2 = nodeInfo.y + h;
+ minY = y1 < minY ? y1 : minY;
+ maxY = y2 > maxY ? y2 : maxY;
+ });
+ _.each(graph.edges(), edgeObj => {
+ let renderMetaedgeInfo = graph.edge(edgeObj);
+ if (renderMetaedgeInfo.structural) {
+ return; // Skip structural edges from min/max calculations.
+ }
+ _.each(renderMetaedgeInfo.points,
+ (point: { x: number, y: number }) => {
+ 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 invisble bridge nodes.
+ _.each(graph.nodes(), nodeName => {
+ let nodeInfo = graph.node(nodeName);
+ nodeInfo.x -= minX;
+ nodeInfo.y -= minY;
+ });
+ _.each(graph.edges(), edgeObj => {
+ _.each(graph.edge(edgeObj).points,
+ (point: { x: number, y: number }) => {
+ point.x -= minX;
+ point.y -= minY;
+ });
+ });
+
+ return {
+ width: maxX - minX,
+ height: maxY - minY,
+ };
+}
+
+/** Layout a metanode. */
+function layoutMetanode(renderNodeInfo): void {
+ // First, copy params specific to meta nodes onto this render info object.
+ let params = PARAMS.subscene.meta;
+ renderNodeInfo = _.extend(renderNodeInfo, params);
+
+ // Invoke dagre.layout() on the core graph and record the bounding box
+ // dimensions.
+ _.extend(renderNodeInfo.coreBox,
+ dagreLayout(renderNodeInfo.coreGraph, 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.
+ let hasInExtract = renderNodeInfo.isolatedInExtract.length > 0;
+
+ renderNodeInfo.inExtractBox.width = hasInExtract ?
+ _(renderNodeInfo.isolatedInExtract).pluck("outerWidth").max() : 0;
+
+ renderNodeInfo.inExtractBox.height =
+ _.reduce(renderNodeInfo.isolatedInExtract, (height, child: any, i) => {
+ let yOffset = i > 0 ? params.extractYOffset : 0;
+ // use outerWidth/Height here to avoid overlaps between extracts
+ child.x = renderNodeInfo.inExtractBox.width / 2;
+ child.y = height + yOffset + child.outerHeight / 2;
+ return height + yOffset + child.outerHeight;
+ }, 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.
+ let hasOutExtract = renderNodeInfo.isolatedOutExtract.length > 0;
+ renderNodeInfo.outExtractBox.width = hasOutExtract ?
+ _(renderNodeInfo.isolatedOutExtract).pluck("outerWidth").max() : 0;
+
+ renderNodeInfo.outExtractBox.height =
+ _.reduce(renderNodeInfo.isolatedOutExtract, (height, child: any, i) => {
+ let yOffset = i > 0 ? params.extractYOffset : 0;
+ // use outerWidth/Height here to avoid overlaps between extracts
+ child.x = renderNodeInfo.outExtractBox.width / 2;
+ child.y = height + yOffset + child.outerHeight / 2;
+ return height + yOffset + child.outerHeight;
+ }, 0);
+
+ // Determine the whole metanode's width (from left to right).
+ renderNodeInfo.width =
+ params.paddingLeft + renderNodeInfo.coreBox.width + params.paddingRight +
+ (hasInExtract ?
+ renderNodeInfo.inExtractBox.width + params.extractXOffset : 0) +
+ (hasOutExtract ?
+ params.extractXOffset + renderNodeInfo.outExtractBox.width : 0);
+
+ // TODO(jimbo): Remove labelHeight and instead incorporate into box sizes.
+ // Determine the whole metanode's height (from top to bottom).
+ renderNodeInfo.height =
+ renderNodeInfo.labelHeight +
+ params.paddingTop +
+ Math.max(
+ renderNodeInfo.inExtractBox.height,
+ renderNodeInfo.coreBox.height,
+ renderNodeInfo.outExtractBox.height
+ ) +
+ params.paddingBottom;
+}
+
+/**
+ * Calculate layout for series node's core graph. Only called for an expanded
+ * series.
+ */
+function layoutSeriesNode(node: render.RenderGroupNodeInformation): void {
+ let graph = node.coreGraph;
+
+ let params = PARAMS.subscene.series;
+ _.extend(node, params);
+
+ // Layout the core.
+ _.extend(node.coreBox,
+ dagreLayout(node.coreGraph, PARAMS.graph.series));
+
+ _.each(graph.nodes(), 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 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: render.RenderNodeInformation): void {
+ // 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) {
+ _.extend(renderNodeInfo, {
+ inboxWidth: 0,
+ inboxHeight: 0,
+ outboxWidth: 0,
+ outboxHeight: 0,
+ outerWidth: renderNodeInfo.width,
+ outerHeight: renderNodeInfo.height
+ });
+ return;
+ }
+
+ let inAnnotations = renderNodeInfo.inAnnotations.list;
+ let outAnnotations = renderNodeInfo.outAnnotations.list;
+
+ // Calculate size for in-annotations
+ _.each(inAnnotations, a => sizeAnnotation(a));
+
+ // Calculate size for out-annotations
+ _.each(outAnnotations, a => sizeAnnotation(a));
+
+ let params = PARAMS.annotations;
+ renderNodeInfo.inboxWidth =
+ inAnnotations.length > 0 ?
+ (<any>_(inAnnotations).pluck("width").max()) +
+ params.xOffset + params.labelWidth + params.labelOffset :
+ 0;
+
+ renderNodeInfo.outboxWidth =
+ outAnnotations.length > 0 ?
+ (<any>_(outAnnotations).pluck("width").max()) +
+ params.xOffset + params.labelWidth + params.labelOffset :
+ 0;
+
+ // 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)
+ let inboxHeight = _.reduce(inAnnotations,
+ (height, a: any, i) => {
+ let yOffset = i > 0 ? params.yOffset : 0;
+ a.dx = -(renderNodeInfo.width + a.width) / 2 - params.xOffset;
+ a.dy = height + yOffset + a.height / 2;
+ return height + yOffset + a.height;
+ }, 0);
+
+ _.each(inAnnotations, (a: any) => {
+ a.dy -= inboxHeight / 2;
+
+ a.labelOffset = params.labelOffset;
+ });
+
+ // Calculate annotation node position 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)
+ let outboxHeight = _.reduce(outAnnotations,
+ (height, a: any, i) => {
+ let yOffset = i > 0 ? params.yOffset : 0;
+ a.dx = (renderNodeInfo.width + a.width) / 2 + params.xOffset;
+ a.dy = height + yOffset + a.height / 2;
+ return height + yOffset + a.height;
+ }, 0);
+
+ _.each(outAnnotations, (a: any) => {
+ // 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.
+
+ let inTouchHeight =
+ Math.min(renderNodeInfo.height / 2 - renderNodeInfo.radius,
+ inboxHeight / 2);
+ inTouchHeight = inTouchHeight < 0 ? 0 : inTouchHeight;
+
+ let inY = d3.scale.linear()
+ .domain([0, inAnnotations.length - 1])
+ .range([-inTouchHeight, inTouchHeight]);
+
+ // Calculate annotation edge position
+ _.each(inAnnotations, (a: any, i) => {
+ a.points = [
+ // The annotation node end
+ {
+ dx: a.dx + a.width / 2,
+ dy: a.dy
+ },
+
+ // The host node end
+ {
+ dx: - renderNodeInfo.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.
+ let outTouchHeight =
+ Math.min(renderNodeInfo.height / 2 - renderNodeInfo.radius,
+ outboxHeight / 2);
+ outTouchHeight = outTouchHeight < 0 ? 0 : outTouchHeight;
+ let outY = d3.scale.linear()
+ .domain([0, outAnnotations.length - 1])
+ .range([-outTouchHeight, outTouchHeight]);
+
+ _.each(outAnnotations, (a: any, i) => {
+ // Add point from the border of the annotation node
+ a.points = [
+ // The host node end
+ {
+ dx: renderNodeInfo.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.outerWidth = renderNodeInfo.width + renderNodeInfo.inboxWidth +
+ renderNodeInfo.outboxWidth;
+ renderNodeInfo.outerHeight =
+ Math.max(renderNodeInfo.height, inboxHeight, outboxHeight);
+}
+
+/**
+ * Set size of an annotation node.
+ */
+function sizeAnnotation(a: render.Annotation): void {
+ switch (a.annotationType) {
+ case render.AnnotationType.CONSTANT:
+ _.extend(a, PARAMS.constant.size);
+ break;
+ case render.AnnotationType.SHORTCUT:
+ if (a.node.type === NodeType.OP) {
+ _.extend(a, PARAMS.shortcutSize.op);
+ } else if (a.node.type === NodeType.META) {
+ _.extend(a, PARAMS.shortcutSize.meta);
+ } else if (a.node.type === NodeType.SERIES) {
+ _.extend(a, PARAMS.shortcutSize.series);
+ } else {
+ throw Error("Invalid node type: " + a.node.type);
+ }
+ break;
+ case render.AnnotationType.SUMMARY:
+ _.extend(a, PARAMS.constant.size);
+ break;
+ }
+}
+
+} // close module
diff --git a/tensorflow/tensorboard/components/tf-graph-common/lib/parser.ts b/tensorflow/tensorboard/components/tf-graph-common/lib/parser.ts
new file mode 100644
index 0000000000..b4864738a9
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-graph-common/lib/parser.ts
@@ -0,0 +1,189 @@
+/// <reference path="../../../typings/tsd.d.ts" />
+/// <reference path="common.ts" />
+module tf.graph.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: string): string|number|boolean {
+ if (value === "true") {
+ return true;
+ }
+ if (value === "false") {
+ return false;
+ }
+ let firstChar = value[0];
+ if (firstChar === "\"") {
+ return value.substring(1, value.length - 1);
+ }
+ let num = parseFloat(value);
+ return isNaN(num) ? value : num;
+}
+
+/**
+ * Fetches a text file and returns a promise of the result.
+ */
+export function readPbTxt(filepath: string): Promise<string> {
+ return new Promise<string>(function(resolve, reject) {
+ d3.text(filepath, function(error, text) {
+ if (error) {
+ reject(error);
+ return;
+ }
+ resolve(text);
+ });
+ });
+}
+
+/**
+ * Fetches and parses a json file and returns a promise of the result.
+ */
+export function readJson(filepath: string): Promise<Object> {
+ return new Promise<Object>(function(resolve, reject) {
+ d3.json(filepath, function(error, text) {
+ if (error) {
+ reject(error);
+ return;
+ }
+ resolve(text);
+ });
+ });
+}
+
+/**
+ * Reads the graph and stats file (if available), parses them and returns a
+ * promise of the result.
+ */
+export function readAndParseData(dataset: {path: string, statsPath: string},
+ pbTxtContent: string, tracker: ProgressTracker):
+ Promise<{ nodes: TFNode[], statsJson: Object }|void> {
+ let graphPbTxt;
+ let statsJson;
+ return runAsyncTask("Reading graph.pbtxt", 20, () => {
+ return pbTxtContent || readPbTxt(dataset.path);
+ }, tracker)
+ .then(function(text) {
+ graphPbTxt = text;
+ return runAsyncTask("Reading stats.pbtxt", 20, () => {
+ return (dataset != null && dataset.statsPath != null) ?
+ readJson(dataset.statsPath) : null;
+ }, tracker);
+ })
+ .then(function(json) {
+ statsJson = json;
+ return runAsyncTask("Parsing graph.pbtxt", 60, () => {
+ return parsePbtxt(graphPbTxt);
+ }, tracker);
+ })
+ .then(function(nodes) {
+ return {
+ nodes: nodes,
+ statsJson: statsJson
+ };
+ })
+ .catch(function(reason) {
+ throw new Error("Failure parsing graph definition");
+ });
+}
+
+/**
+ * Parses a proto txt file into a javascript object.
+ *
+ * @param input The string contents of the proto txt file.
+ * @return The parsed object.
+ */
+export function parsePbtxt(input: string): TFNode[] {
+ let output: { [name: string]: any; } = { node: [] };
+ let stack = [];
+ let path: string[] = [];
+ let current: { [name: string]: any; } = output;
+
+ function splitNameAndValueInAttribute(line: string) {
+ let colonIndex = line.indexOf(":");
+ let name = line.substring(0, colonIndex).trim();
+ let value = parseValue(line.substring(colonIndex + 2).trim());
+ return {
+ name: name,
+ value: value
+ };
+ }
+
+ /**
+ * 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.
+ */
+ let ARRAY_ATTRIBUTES: {[attrPath: string] : boolean} = {
+ "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
+ };
+
+ /**
+ * 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: Object, name: string,
+ value: Object|string|number|boolean, path: string[]): void {
+ // We treat "node" specially since it is done so often.
+ let existingValue = obj[name];
+ if (existingValue == null) {
+ obj[name] = path.join(".") in ARRAY_ATTRIBUTES ? [value] : value;
+ } else if (Array.isArray(existingValue)) {
+ existingValue.push(value);
+ } else {
+ obj[name] = [existingValue, value];
+ }
+ }
+
+ // Run through the file a line at a time.
+ let startPos = 0;
+ while (startPos < input.length) {
+ let endPos = input.indexOf("\n", startPos);
+ if (endPos === -1) {
+ endPos = input.length;
+ }
+ let line = input.substring(startPos, endPos);
+ startPos = endPos + 1;
+ if (!line) {
+ continue;
+ }
+ switch (line[line.length - 1]) {
+ case "{": // create new object
+ let name = line.substring(0, line.length - 2).trim();
+ let newValue: { [name: string]: any; } = {};
+ stack.push(current);
+ path.push(name);
+ addAttribute(current, name, newValue, path);
+ current = newValue;
+ break;
+ case "}":
+ current = stack.pop();
+ path.pop();
+ break;
+ default:
+ let x = splitNameAndValueInAttribute(line);
+ addAttribute(current, x.name, x.value, path.concat(x.name));
+ break;
+ }
+ }
+
+ return output["node"];
+}
+
+} // Close module tf.graph.parser.
diff --git a/tensorflow/tensorboard/components/tf-graph-common/lib/render.ts b/tensorflow/tensorboard/components/tf-graph-common/lib/render.ts
new file mode 100644
index 0000000000..363f006fd5
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-graph-common/lib/render.ts
@@ -0,0 +1,1360 @@
+/// <reference path="graph.ts" />
+/// <reference path="hierarchy.ts" />
+
+/**
+ * Package for the Render Hierarchy for TensorFlow graph.
+ */
+
+module tf.graph.render {
+
+/**
+ * Color parameters for node encoding.
+ * @type {Object}
+ */
+export let MetanodeColors = {
+ 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: number, lightened? : boolean) {
+ // The code below is a flexible way to computationally create a set
+ // of colors that go well together.
+ let hues = MetanodeColors.HUES;
+ let n = hues.length;
+ let hue = hues[id % n];
+ let m = Math.sin(hue * Math.PI / 360);
+ let sat = lightened ? 30 : 90 - 60 * m;
+ let light = lightened ? 95 : 80;
+ return d3.hsl(hue, .01 * sat, .01 * light).toString();
+ },
+ DEVICE_PALETTE: function (index: number): string {
+ return MetanodeColors.STRUCTURE_PALETTE(index);
+ },
+ UNKNOWN: "#eee",
+ GRADIENT_OUTLINE: "#888"
+};
+
+/**
+ * Parameters that affect how the graph is rendered on the screen.
+ */
+interface RenderGraphParams {
+ /**
+ * Whether to extract high degree nodes from the core part of the graph.
+ */
+ enableExtraction: boolean;
+ /**
+ * Maximum in-degree that a node can have without being considered as
+ * high in-degree node.
+ */
+ maxInDegree: number;
+ /**
+ * Maximum out-degree that a node can have without being considered as
+ * high out-degree node.
+ */
+ maxOutDegree: number;
+ /**
+ * Maximum number of control edges a node can have before they aren't
+ * displayed.
+ */
+ maxControlDegree: number;
+ /**
+ * Types patterns for predefined out-extract nodes, which are
+ * sink-like nodes that will be extracted from the main graph.
+ */
+ outExtractTypes: string[];
+
+ /**
+ * Types patterns for predefined in-extract nodes, which are
+ * source-like nodes that will be extracted from the main graph.
+ */
+ inExtractTypes: string[];
+
+ /**
+ * 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: boolean;
+
+ /**
+ * 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: boolean;
+
+ /**
+ * Whether to add bridge nodes and edges to the core when building the
+ * subhierarchy of an expanded metanode. See buildSubhierarchy().
+ */
+ enableBridgegraph: boolean;
+
+ /**
+ * 2 colors, for the minimum and maximum value respectively, whenever we
+ * have a gradient scale.
+ */
+ minMaxColors: string[];
+
+ /**
+ * Maximum number of annotations to be displayed on a node.
+ */
+ maxAnnotations: number;
+}
+
+/**
+ * Stores the rendering information, such as x and y coordinates,
+ * for each node in the graph.
+ */
+export class RenderGraphInformation {
+ private hierarchy: hierarchy.Hierarchy;
+ private index: {[nodeName: string]: RenderNodeInformation};
+ private params: RenderGraphParams;
+ private deviceColorMap: d3.scale.Ordinal<string, string>;
+ private memoryUsageScale: d3.scale.Linear<string, string>;
+ private computeTimeScale: d3.scale.Linear<string, string>;
+ // Since the rendering information for each node is constructed lazily,
+ // upon node's expansion by the user, we keep a map between the node's name and
+ // whether the rendering information was already constructed for that node.
+ private hasSubhierarchy: {[nodeName: string]: boolean};
+ root: RenderGroupNodeInformation;
+
+ constructor(hierarchy: hierarchy.Hierarchy, params: RenderGraphParams) {
+ this.hierarchy = hierarchy;
+ this.index = {};
+ this.deviceColorMap = d3.scale.ordinal<string>()
+ .domain(hierarchy.devices)
+ .range(_.map(d3.range(hierarchy.devices.length),
+ MetanodeColors.DEVICE_PALETTE));
+
+ let topLevelGraph = hierarchy.root.metagraph;
+ // Find the maximum and minimum memory usage.
+ let memoryExtent = d3.extent(topLevelGraph.nodes(),
+ (nodeName, index) => {
+ let 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<string, string>()
+ .domain(memoryExtent)
+ .range(params.minMaxColors);
+
+ // Find also the minimum and maximum compute time.
+ let computeTimeExtent = d3.extent(topLevelGraph.nodes(), (nodeName, index) => {
+ let 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<string, string>()
+ .domain(computeTimeExtent)
+ .range(params.minMaxColors);
+
+ // Maps node name to whether the rendering hierarchy was already constructed.
+ this.hasSubhierarchy = {};
+ this.params = params;
+ this.root = new RenderGroupNodeInformation(hierarchy.root);
+ this.index[hierarchy.root.name] = this.root;
+ this.buildSubhierarchy(hierarchy.root.name);
+ this.root.expanded = true;
+ }
+
+ getRenderNodeByName(nodeName: string): RenderNodeInformation {
+ 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.
+ */
+ getNearestVisibleAncestor(name: string): string {
+ let path = getHierarchicalPath(name);
+ for (let i = 0; i < path.length; i++) {
+ let 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).
+ setDepth(depth: number): void {
+ setGroupNodeDepth(this.root, +depth);
+ }
+
+ buildSubhierarchy(nodeName: string): void {
+ // Terminate if the rendering hierarchy was already constructed
+ // for this node.
+ if (nodeName in this.hasSubhierarchy) {
+ return;
+ }
+
+ let renderNodeInfo = this.index[nodeName];
+
+ // If it is not a meta node or a series node, don't do anything.
+ if (renderNodeInfo.node.type !== NodeType.META &&
+ renderNodeInfo.node.type !== NodeType.SERIES) {
+ return;
+ }
+
+ // At this point we know the rendering information is about a group node.
+ let renderGroupNodeInfo = <RenderGroupNodeInformation> renderNodeInfo;
+ let metagraph = renderGroupNodeInfo.node.metagraph;
+ let 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(), childName => {
+
+ let childNode = metagraph.node(childName);
+ let childRenderInfo = childNode.isGroupNode ?
+ new RenderGroupNodeInformation(<GroupNode>childNode) :
+ new RenderNodeInformation(childNode);
+ this.index[childName] = childRenderInfo;
+ coreGraph.setNode(childName, childRenderInfo);
+
+ if (childRenderInfo.node.stats != null) {
+ childRenderInfo.memoryColor =
+ this.memoryUsageScale(childRenderInfo.node.stats.totalBytes);
+ childRenderInfo.computeTimeColor =
+ this.computeTimeScale(childRenderInfo.node.stats.totalMicros);
+ }
+
+ if (!childNode.isGroupNode) {
+ _.each((<OpNode>childNode).inEmbeddings, embedding => {
+ let renderMetaedgeInfo = new RenderMetaedgeInformation(null);
+ addInAnnotation(childRenderInfo, embedding, null, renderMetaedgeInfo,
+ AnnotationType.CONSTANT, this.params);
+ this.index[embedding.name] = new RenderNodeInformation(embedding);
+ });
+ _.each((<OpNode>childNode).outEmbeddings, embedding => {
+ let renderMetaedgeInfo = new RenderMetaedgeInformation(null);
+ addOutAnnotation(childRenderInfo, embedding, null, renderMetaedgeInfo,
+ AnnotationType.SUMMARY, this.params);
+ this.index[embedding.name] = new RenderNodeInformation(embedding);
+ });
+ let device = (<OpNode>childRenderInfo.node).device;
+ if (device != null) {
+ childRenderInfo.deviceColors = [{
+ color: this.deviceColorMap(device),
+ proportion: 1.0
+ }];
+ }
+ } else {
+ // Make a list of tuples (device, proportion), where proportion
+ // is the fraction of op nodes that have that device.
+ let pairs = _.pairs((<GroupNode> childNode).deviceHistogram);
+ if (pairs.length > 0) {
+ // Compute the total # of devices.
+ let numDevices = _.sum(pairs, _.last);
+ childRenderInfo.deviceColors = _.map(pairs, pair => {
+ return {
+ color: this.deviceColorMap(pair[0]),
+ // Normalize to a proportion of total # of devices.
+ proportion: pair[1] / numDevices
+ };
+ });
+ }
+ }
+ });
+
+ // Add render metaedge info for edges in the metagraph.
+ _.each(metagraph.edges(), edgeObj => {
+ let metaedge = metagraph.edge(edgeObj);
+ let renderMetaedgeInfo = new RenderMetaedgeInformation(metaedge);
+ coreGraph.setEdge(edgeObj.v, edgeObj.w, renderMetaedgeInfo);
+ });
+
+ if (this.params.enableExtraction &&
+ renderGroupNodeInfo.node.type === NodeType.META) {
+ extractHighDegrees(renderGroupNodeInfo, this.params);
+ }
+
+ // 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.
+ let parentNode = renderGroupNodeInfo.node.parentNode;
+ if (!parentNode) {
+ return;
+ }
+ let parentNodeInfo =
+ <RenderGroupNodeInformation> this.index[parentNode.name];
+
+ // Utility function for computing the name of a bridge node.
+ let getBridgeNodeName = (inbound, ...rest) =>
+ rest.concat([inbound ? "IN" : "OUT"]).join("~~");
+
+ // Build out the bridgegraph.
+ let bridgegraph = this.hierarchy.getBridgegraph(nodeName);
+
+ // Look for popular nodes so we can make annotations instead of paths.
+ let otherCounts = {
+ // Counts of edges coming INTO other nodes by name (outgoing from self).
+ in: <{[nodeName: string]: number}> {},
+ // Counts of edges going OUT from other nodes by name (coming into self).
+ out: <{[nodeName: string]: number}> {},
+ // Counts of all control edges involving other nodes by name.
+ control: <{[nodeName: string]: number}> {},
+ };
+ _.each(bridgegraph.edges(), e => {
+ // An edge is inbound if its destination node is in the metagraph.
+ let inbound = !!metagraph.node(e.w);
+ let otherName = inbound ? e.v : e.w;
+ let 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.
+ let hierarchyNodeMap = this.hierarchy.getNodeMap();
+ _.each(bridgegraph.edges(), bridgeEdgeObj => {
+ let bridgeMetaedge = bridgegraph.edge(bridgeEdgeObj);
+
+ // Determine whether this bridge edge is incoming by checking the
+ // metagraph for a node that matches the destination end.
+ let 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).
+ let [childName, otherName] =
+ inbound ?
+ [bridgeEdgeObj.w, bridgeEdgeObj.v] :
+ [bridgeEdgeObj.v, bridgeEdgeObj.w];
+
+ let childRenderInfo = this.index[childName];
+ let otherRenderInfo = this.index[otherName];
+ let 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.
+ let isHighDegreeControlEdge = !bridgeMetaedge.numRegularEdges &&
+ otherCounts.control[otherName] > this.params.maxControlDegree;
+
+ let [annotations, childAnnotations] =
+ inbound ?
+ [renderNodeInfo.inAnnotations, childRenderInfo.inAnnotations] :
+ [renderNodeInfo.outAnnotations, childRenderInfo.outAnnotations];
+
+ let isOtherHighDegree =
+ inbound ?
+ otherCounts.out[otherName] > this.params.maxOutDegree :
+ otherCounts.in[otherName] > this.params.maxInDegree;
+
+ // 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.
+ let 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.
+ let canDrawBridgePath = false;
+ if (this.params.enableBridgegraph &&
+ !isOtherHighDegree &&
+ !isHighDegreeControlEdge &&
+ childRenderInfo.isInCore()) {
+
+ // Utility function for finding an adjoining metaedge.
+ let findAdjoiningMetaedge = targetName => {
+ let adjoiningEdgeObj: graphlib.EdgeObject =
+ inbound ?
+ { v: targetName, w: nodeName } :
+ { v: nodeName, w: targetName };
+ return <RenderMetaedgeInformation>
+ 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 |>----->|>------->| 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 preceeding 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.
+ let backwards = false;
+ if (adjoiningMetaedge && !bridgeMetaedge.numRegularEdges) {
+ // Find the top-most adjoining render metaedge information, and the
+ // GroupNode whose metagraph must contain the associated metaedge.
+ let topAdjoiningMetaedge = adjoiningMetaedge;
+ let topGroupNode = parentNodeInfo.node;
+ while (topAdjoiningMetaedge.adjoiningMetaedge) {
+ topAdjoiningMetaedge = topAdjoiningMetaedge.adjoiningMetaedge;
+ topGroupNode = <GroupNode>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.
+ let ordering = this.hierarchy.getTopologicalOrdering(topGroupNode.name);
+ let 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 RenderMetaedgeInformation(bridgeMetaedge),
+ AnnotationType.SHORTCUT,
+ inbound), this.params);
+ return;
+ }
+
+ // At this point, all conditions have been met for drawing a bridge path.
+
+ // Find or create the IN/OUT node representing otherNode.
+ let bridgeContainerName = getBridgeNodeName(inbound, nodeName);
+ let bridgeNodeName = getBridgeNodeName(inbound, otherName, nodeName);
+ let bridgeNodeRenderInfo = coreGraph.node(bridgeNodeName);
+ if (!bridgeNodeRenderInfo) {
+
+ // Find or create the directional container for the bridge node.
+ let bridgeContainerInfo = coreGraph.node(bridgeContainerName);
+ if (!bridgeContainerInfo) {
+ let bridgeContainerNode: BridgeNode = {
+ // Important node properties.
+ name: bridgeContainerName,
+ type: NodeType.BRIDGE,
+ // Unused node properties.
+ isGroupNode: false,
+ cardinality: 0,
+ parentNode: null,
+ stats: null,
+ // BridgeNode properties.
+ inbound: inbound,
+ };
+ bridgeContainerInfo =
+ new RenderNodeInformation(bridgeContainerNode);
+ this.index[bridgeContainerName] = bridgeContainerInfo;
+ coreGraph.setNode(bridgeContainerName, bridgeContainerInfo);
+ }
+
+ let bridgeNode: BridgeNode = {
+ // Important node properties.
+ name: bridgeNodeName,
+ type: NodeType.BRIDGE,
+ // Unimportant node properties.
+ isGroupNode: false,
+ cardinality: 1,
+ parentNode: null,
+ stats: null,
+ // BridgeNode properties.
+ inbound: inbound,
+ };
+ bridgeNodeRenderInfo = new RenderNodeInformation(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.
+ let bridgeRenderMetaedge =
+ new RenderMetaedgeInformation(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'v alse added a bridge node Z' (with parent IN)
+ // to A, and a bridge edge from Z'->C.
+ //
+ // +----------------------+
+ // | A +---+ |
+ // | +------>| 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 +---+ |
+ // | +--->| 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], inbound => {
+ let bridgeContainerName = getBridgeNodeName(inbound, nodeName);
+ let bridgeContainerInfo = coreGraph.node(bridgeContainerName);
+ if (!bridgeContainerInfo) {
+ return;
+ }
+ _.each(coreGraph.nodes(), childName => {
+ // Short-circuit if this child is a bridge node or it's not a terminal
+ // node in the direction we're interested in.
+ let childNodeInfo = coreGraph.node(childName);
+ if (childNodeInfo.node.type === NodeType.BRIDGE) {
+ return;
+ }
+ let 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).
+ let structuralNodeName =
+ getBridgeNodeName(inbound, nodeName, "STRUCTURAL_TARGET");
+ let structuralRenderInfo = coreGraph.node(structuralNodeName);
+ if (!structuralRenderInfo) {
+ let bridgeNode: BridgeNode = {
+ // Important Node properties.
+ name: structuralNodeName,
+ type: NodeType.BRIDGE,
+ // Unimportant Node properties.
+ isGroupNode: false,
+ cardinality: 1,
+ parentNode: null,
+ stats: null,
+ // BridgeNode properties.
+ inbound: inbound,
+ };
+ structuralRenderInfo = new RenderNodeInformation(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.
+ let structuralMetaedgeInfo = new RenderMetaedgeInformation(null);
+ structuralMetaedgeInfo.structural = true;
+ structuralMetaedgeInfo.weight--; // Reduce weight for dagre layout.
+ inbound ?
+ coreGraph.setEdge(
+ structuralNodeName, childName, structuralMetaedgeInfo) :
+ coreGraph.setEdge(
+ childName, structuralNodeName, structuralMetaedgeInfo);
+ });
+ });
+ }
+}
+
+/**
+ * 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.
+ */
+export class Annotation {
+ node: Node;
+ renderNodeInfo: RenderNodeInformation;
+ renderMetaedgeInfo: RenderMetaedgeInformation;
+ annotationType: AnnotationType;
+ /**
+ * Center position of annotation relative to the host
+ * node's center x.
+ */
+ dx: number;
+ /**
+ * Center position of annotation relative to the host
+ * node's center y.
+ */
+ dy: number;
+ width: number;
+ height: number;
+ /**
+ * A flag whether it is an in-annotation (if true) or
+ * out-annotation (if false).
+ */
+ isIn: boolean;
+ /** Label horizontal offset from the end of the node shape */
+ labelOffset: number;
+ /**
+ * Array of points for edges from the annotation to its host
+ * node. Each point contains the point location, relative to
+ * the host node's center.
+ */
+ points: {dx: number, dy: number}[];
+
+ /**
+ * 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.
+ */
+ constructor(node: Node, renderNodeInfo: RenderNodeInformation,
+ renderMetaedgeInfo: RenderMetaedgeInformation, type: AnnotationType,
+ isIn: boolean) {
+ 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;
+
+ this.isIn = isIn;
+ this.points = [];
+ }
+};
+
+export enum AnnotationType {SHORTCUT, CONSTANT, SUMMARY, ELLIPSIS};
+
+/**
+ * Manages a list of annotations. Two will be used for each
+ * RenderNodeInformation, one for in annotations and one for out annotations.
+ */
+export class AnnotationList {
+ /**
+ * List of visually drawable annotations, may include an ellipses annotation
+ * if the number added exceeds the number specified by maxAnnotations.
+ */
+ list: Annotation[];
+
+ /**
+ * Set of nodes which have been added as annotations to this list, so we can
+ * prevent duplicates.
+ */
+ nodeNames: { [nodeName: string]: boolean };
+
+ constructor() {
+ this.list = [];
+ this.nodeNames = {};
+ }
+
+ /**
+ * Append an annotation to the list, or a stand-in ellipsis annotation instead
+ * if this would make it too many.
+ */
+ push(annotation: Annotation, params: RenderGraphParams): void {
+ 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;
+ }
+
+ let lastAnnotation = this.list[this.list.length - 1];
+ if (lastAnnotation.annotationType === AnnotationType.ELLIPSIS) {
+ let ellipsisNode = <EllipsisNode>lastAnnotation.node;
+ ellipsisNode.setNumMoreNodes(++ellipsisNode.numMoreNodes);
+ return;
+ }
+
+ let ellipsisNode = new tf.graph.EllipsisNodeImpl(1);
+ this.list.push(new Annotation(ellipsisNode,
+ new RenderNodeInformation(ellipsisNode), null,
+ AnnotationType.ELLIPSIS, annotation.isIn));
+ }
+}
+
+/**
+ * Contains rendering information about a node in the hierarchical graph.
+ */
+export class RenderNodeInformation {
+ /** Reference to the original underlying Node from the hierarchical graph. */
+ node: Node;
+ /** Whether the node is expanded or not. */
+ expanded: boolean;
+ /**
+ * List of rendering information about in-annotations like constants and
+ * shortcuts to high-degree nodes.
+ */
+ inAnnotations: AnnotationList;
+ /** List of rendering information about out-annotations (e.g. summary nodes) */
+ outAnnotations: AnnotationList;
+
+ // --- Params specified by layout --- //
+
+ /** Center x position */
+ x: number;
+ /** Center y position */
+ y: number;
+ /** Width of the node's shape */
+ width: number;
+ /** Height of the node's shape. */
+ height: number;
+ /** Width of the bounding box for all in-annotations. */
+ inboxWidth: number;
+ /** Width of the bounding box for all out-annotations. */
+ outboxWidth: number;
+ /**
+ * Whether the node should be excluded from the scene.
+ * This is only used when there are too many items in a series so we only
+ * want to include top N ones.
+ */
+ // TODO(jimbo): Now that series rendering is non-recursive, remove this and
+ // all its uses from the code base.
+ excluded: boolean;
+
+ // --- Params used in drawing the bridge paths --- //
+
+ /**
+ * All bridge nodes are meant to be invisible, but whereas most represent a
+ * relationship from the underlying graph hierarchy, some exist solely for
+ * layout reasons. Specifically, those bridge nodes which have only structural
+ * rendering metaedges.
+ */
+ structural: boolean;
+
+ // --- Params for the size of the node box --- //
+
+ /** Label vertical offset from the center of node shape */
+ labelOffset: number;
+ /** X-space between each extracted node and the core graph. */
+ extractXOffset: number;
+ /** Rectangle radius (for making rounded rectangle) */
+ radius: number;
+
+ // --- Params for expanded node --- //
+
+ /** Label height for expanded node. */
+ labelHeight: number;
+ // Paddings between inner subscene and the border of the expanded node.
+ paddingTop: number;
+ paddingLeft: number;
+ paddingRight: number;
+ paddingBottom: number;
+
+ /** Width of the whole node including its shape and its annotations */
+ outerWidth: number;
+ /** Height of the whole node including its shape and its annotations */
+ outerHeight: number;
+ /**
+ * Whether a node is extracted as source-like (having high out-degree or matching
+ * predefined in-extract pattern.)
+ */
+ isInExtract: boolean;
+ /**
+ * Whether a node is extracted as sink-like (having high in-degree or matching
+ * predefined out-extract pattern.)
+ */
+ isOutExtract: boolean;
+
+ /**
+ * List of (color, proportion) tuples based on the proportion of devices of
+ * its children. If this node is an op node, this list will have only one
+ * color with proportion 1.0.
+ */
+ deviceColors: {color: string, proportion: number}[];
+
+ /**
+ * Color according to the memory usage of this node.
+ */
+ memoryColor: string;
+
+ /**
+ * Color according to the compute time of this node.
+ */
+ computeTimeColor: string;
+
+ constructor(node: 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.extractXOffset = 0;
+ this.radius = 0;
+
+ // Params for expanded node
+ this.labelHeight = 0;
+ this.paddingTop = 0;
+ this.paddingLeft = 0;
+ this.paddingRight = 0;
+ this.paddingBottom = 0;
+
+ this.outerWidth = 0;
+ this.outerHeight = 0;
+ this.isInExtract = false;
+ this.isOutExtract = false;
+ }
+
+ isInCore(): boolean {
+ return !this.isInExtract && !this.isOutExtract;
+ }
+}
+
+/**
+ * Contains rendering information about a Metaedge from the underlying
+ * hierarchical graph. It may be from either a metagraph or a bridgegraph.
+ */
+export class RenderMetaedgeInformation {
+ /**
+ * Reference to the original underlying Metaedge from the hierarchical graph,
+ * if any. This will be null for the edges which connect OpNodes to their
+ * embeddings, for example.
+ */
+ metaedge: Metaedge;
+
+ /**
+ * Reference to the adjoining RenderMeteaedgeInformation from the parent's
+ * coreGraph. This is used during layout to determine the point at which this
+ * edge should touch the node's bounding box. This property will be null for
+ * edges which terminate at a node on both ends (all non-bridge edges).
+ */
+ adjoiningMetaedge: RenderMetaedgeInformation;
+
+ /**
+ * Most of the time, a RenderMetaedgeInformation object represents a real
+ * edge between nodes in the underlying graph structure. But sometimes, an
+ * edge only exsts for layout purposes. These structural edges are added
+ * during buildSubhierarchy() to force dagre.layout() to put bridge nodes
+ * at the ends of the flow.
+ * @see buildSubhierarchy()
+ */
+ structural: boolean;
+
+ /**
+ * Weight of the edge, used by dagre when deciding how important an edge is.
+ * Edges with higher weight are made shorter and straighter. The default
+ * dagre uses is 1.
+ */
+ weight: number;
+
+ /**
+ * X and Y coordinate pairs of the points in the path of the edge.
+ * @see tf.graph.node.subsceneAdjustPaths
+ */
+ points: any[];
+
+ /**
+ * D3 selection of the group containing the path that displays this edge.
+ */
+ edgeGroup: d3.Selection<RenderMetaedgeInformation>;
+
+ constructor(metaedge: Metaedge) {
+ this.metaedge = metaedge;
+ this.adjoiningMetaedge = null;
+ this.structural = false;
+ this.weight = 1;
+ }
+}
+
+function addInAnnotation(node: RenderNodeInformation, predecessor: Node,
+ predecessorRenderInfo: RenderNodeInformation, edge: any,
+ type: AnnotationType, params: RenderGraphParams): void {
+ let annotation = new Annotation(predecessor, predecessorRenderInfo, edge,
+ type, true);
+ node.inAnnotations.push(annotation, params);
+}
+
+function addOutAnnotation(node: RenderNodeInformation, successor: Node,
+ successorRenderInfo: RenderNodeInformation, edge: any,
+ type: AnnotationType, params: RenderGraphParams): void {
+ let annotation = new Annotation(successor, successorRenderInfo, edge,
+ type, false);
+ node.outAnnotations.push(annotation, params);
+}
+
+function setGraphDepth(graph: graphlib.Graph<RenderNodeInformation, any>,
+ depth: number) {
+ _.each(graph.nodes(), nodeName => {
+ let child = graph.node(nodeName);
+ child.expanded = depth > 1; // set all child of depth 1 to collapsed
+ if (depth > 0) {
+ switch (child.node.type) {
+ case NodeType.META:
+ case NodeType.SERIES:
+ setGroupNodeDepth(<RenderGroupNodeInformation>child, depth - 1);
+ break;
+ // Do nothing for leaf
+ }
+ }
+ });
+};
+
+export class RenderGroupNodeInformation extends RenderNodeInformation {
+ node: GroupNode;
+ /**
+ * The core graph is derived from the underlying node's metagraph, minus
+ * the extracted source-like and sink-like nodes.
+ */
+ coreGraph: graphlib.Graph<RenderNodeInformation, RenderMetaedgeInformation>;
+ /** Size of the bounding box for a metanode's core graph. */
+ coreBox: {
+ width: number,
+ height: number,
+ };
+ /** Size of the bounding box for a metanode's isolated in-extract children. */
+ inExtractBox: {width: number, height: number};
+ /** Size of the bounding box for a metanode's isolated out-extract children. */
+ outExtractBox: {width: number, height: number};
+ /** Array of isolated in-extract nodes. */
+ isolatedInExtract: RenderNodeInformation[];
+ /** Array of isolated out-extract nodes. */
+ isolatedOutExtract: RenderNodeInformation[];
+
+ constructor(groupNode: GroupNode) {
+ super(groupNode);
+ let metagraph = groupNode.metagraph;
+ let gl = metagraph.graph();
+ this.coreGraph =
+ createGraph<RenderNodeInformation, RenderMetaedgeInformation>(
+ gl.name, GraphType.CORE, { compound: true });
+ this.coreBox = {width: 0, height: 0};
+ this.inExtractBox = {width: 0, height: 0};
+ this.outExtractBox = {width: 0, height: 0};
+ this.isolatedInExtract = [];
+ this.isolatedOutExtract = [];
+ }
+}
+
+function setGroupNodeDepth(renderInfo: RenderGroupNodeInformation,
+ depth: number): void {
+ 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: graphlib.Graph<RenderNodeInformation, {}>,
+ v: string, w: string, params: RenderGraphParams) {
+ let src = graph.node(v);
+ let sink = graph.node(w);
+ let edge = graph.edge(v, w);
+
+ // Add each annotation.
+ addOutAnnotation(src, sink.node, sink, edge, AnnotationType.SHORTCUT, params);
+ addInAnnotation(sink, src.node, src, edge, AnnotationType.SHORTCUT, params);
+
+ // 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 is true, extract all of its edges.
+ * Otherwise, only extract all in-edges.
+ */
+function makeOutExtract(renderNode: RenderGroupNodeInformation, n: string,
+ params: RenderGraphParams) {
+ let graph = renderNode.coreGraph;
+
+ graph.node(n).isOutExtract = true;
+
+ _.each(graph.predecessors(n), (p, index) => {
+ createShortcut(graph, p, n, params);
+ });
+
+ if (params.detachAllEdgesForHighDegree) {
+ _.each(graph.successors(n), (s, index) => {
+ createShortcut(graph, n, s, params);
+ });
+ }
+
+ if (params.detachAllEdgesForHighDegree || graph.neighbors(n).length === 0) {
+ renderNode.isolatedOutExtract.push(graph.node(n));
+ 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 is true, extract all of its edges.
+ * Otherwise, only remove all out-edges.
+ */
+function makeInExtract(renderNode: RenderGroupNodeInformation, n: string,
+ params: RenderGraphParams) {
+ let graph = renderNode.coreGraph;
+ graph.node(n).isInExtract = true;
+
+ _.each(graph.successors(n), (s, index) => {
+ createShortcut(graph, n, s, params);
+ });
+
+ if (params.detachAllEdgesForHighDegree) {
+ _.each(graph.predecessors(n), (p, index) => {
+ createShortcut(graph, p, n, params);
+ });
+ }
+
+ // Remove the node from the core graph if conditions are met.
+ if (params.detachAllEdgesForHighDegree || graph.neighbors(n).length === 0) {
+ renderNode.isolatedInExtract.push(graph.node(n));
+ graph.removeNode(n);
+ }
+}
+
+/**
+ * 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: Node, types: string[]): boolean {
+ if (node.type === NodeType.OP) {
+ for (let i = 0; i < types.length; i++) {
+ if ((<OpNode>node).op === types[i]) { return true; }
+ }
+ } else if (node.type === NodeType.META) {
+ let rootOpNode = (<Metanode>node).getRootOp();
+ if (rootOpNode) {
+ for (let i = 0; i < types.length; i++) {
+ if (rootOpNode.op === types[i]) { return true; }
+ }
+ }
+ }
+ return false;
+}
+
+/** Remove edges from pre-defined out-extract patterns */
+function extractPredefinedSink(renderNode: RenderGroupNodeInformation,
+ params: RenderGraphParams) {
+ let graph = renderNode.coreGraph;
+ _.each(graph.nodes(), n => {
+ let renderInfo = graph.node(n);
+ if (hasTypeIn(renderInfo.node, params.outExtractTypes)) {
+ makeOutExtract(renderNode, n, params);
+ }
+ });
+}
+
+/** Remove edges from pre-defined in-extract patterns */
+function extractPredefinedSource(renderNode: RenderGroupNodeInformation,
+ params: RenderGraphParams) {
+ let graph = renderNode.coreGraph;
+
+ _.each(graph.nodes(), n => {
+ let renderInfo = graph.node(n);
+ if (hasTypeIn(renderInfo.node, params.inExtractTypes)) {
+ makeInExtract(renderNode, n, params);
+ }
+ });
+}
+
+/** Extract from nodes with in-degree > maxInDegree */
+function extractHighInDegree(renderNode: RenderGroupNodeInformation,
+ params: RenderGraphParams) {
+ let graph = renderNode.coreGraph;
+ let maxInDegree = params.maxInDegree;
+
+ // detect first so degrees don't get affected by other removal
+ let highInDegreeNames = _.filter(graph.nodes(), n => {
+ // Count the in-degree 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 effect if nodes are extracted
+ // from the core graph, unless the node is only used for control.
+ let numEdgesToCount = _.reduce(graph.predecessors(n), (numEdgesToCount, pred) => {
+ let metaedge = graph.edge(pred, n).metaedge;
+ return numEdgesToCount + (metaedge.numRegularEdges ? 1 : 0);
+ }, 0);
+ if (numEdgesToCount === 0 && graph.predecessors(n).length > 0) {
+ numEdgesToCount = graph.predecessors(n).length;
+ }
+ return numEdgesToCount > maxInDegree;
+ });
+
+ _.each(highInDegreeNames, n => {
+ makeOutExtract(renderNode, n, params);
+ });
+}
+
+/** Extract nodes with out-degree > maxOutDegree */
+function extractHighOutDegree(renderNode: RenderGroupNodeInformation,
+ params: RenderGraphParams) {
+ let graph = renderNode.coreGraph;
+ let maxOutDegree = params.maxOutDegree;
+
+ // detect first so degrees don't get affected by other removal
+ let highOutDegreeNames = _.filter(graph.nodes(), n => {
+ // Count the out-degree 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 effect if nodes are extracted
+ // from the core graph, unless the node is only used for control.
+ let numEdgesToCount = _.reduce(graph.successors(n), (numEdgesToCount, succ) => {
+ let metaedge = graph.edge(n, succ).metaedge;
+ return numEdgesToCount + (metaedge.numRegularEdges ? 1 : 0);
+ }, 0);
+ if (numEdgesToCount === 0 && graph.successors(n).length > 0) {
+ numEdgesToCount = graph.successors(n).length;
+ }
+ return numEdgesToCount > maxOutDegree;
+ });
+
+ _.each(highOutDegreeNames, n => {
+ makeInExtract(renderNode, n, params);
+ });
+}
+
+/** Remove control edges from nodes that have too many control edges */
+function removeControlEdges(renderNode: RenderGroupNodeInformation,
+ params: RenderGraphParams) {
+ let graph = renderNode.coreGraph;
+
+ // Collect control edges into a map by node name.
+ let map = <{[nodeName: string]: graphlib.EdgeObject[]}>{};
+ _.each(graph.edges(), 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, (edges, nodeName) => {
+ if (edges.length > params.maxControlDegree) {
+ _.each(edges, e => createShortcut(graph, e.v, e.w, params));
+ }
+ });
+}
+
+/**
+ * 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
+ */
+export function mapIndexToHue(id: number): number {
+ let GOLDEN_RATIO = 1.61803398875;
+ // Hue of 0 is reserved for the gray nodes.
+ let MIN_HUE = 1;
+ let MAX_HUE = 359;
+ let COLOR_RANGE = MAX_HUE - MIN_HUE;
+ return MIN_HUE + ((COLOR_RANGE * GOLDEN_RATIO * id) % COLOR_RANGE);
+};
+
+/**
+ * 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.
+ * @param {Object} params render Graph construction parameters. See
+ * <tf-graph-params>'s output
+ */
+function extractHighDegrees(renderNode: RenderGroupNodeInformation,
+ params: RenderGraphParams) {
+ if (params.outExtractTypes) {
+ extractPredefinedSink(renderNode, params);
+ }
+
+ // This has to come before extract high in-degree to protect the core part
+ // that takes many variables.
+ if (params.inExtractTypes) {
+ extractPredefinedSource(renderNode, params);
+ }
+
+ // This has to come before extract high out-degree to protect the core part
+ // that output to many places as there are more high-degree sinks than
+ // sources.
+
+ if (params.maxInDegree) {
+ extractHighInDegree(renderNode, params);
+ }
+
+ if (params.maxOutDegree) {
+ extractHighOutDegree(renderNode, params);
+ }
+
+ if (params.maxControlDegree) {
+ removeControlEdges(renderNode, params);
+ }
+
+ // 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.
+
+ let graph = renderNode.coreGraph;
+ _.each(graph.nodes(), n => {
+ let child = graph.node(n);
+ let degree = graph.neighbors(n).length;
+
+ if (degree === 0) {
+ let hasOutAnnotations = child.outAnnotations.list.length > 0;
+ let hasInAnnotations = child.inAnnotations.list.length > 0;
+
+ if (child.isInExtract) { // Is source-like.
+ // This case only happens if detachAllEdgesForHighDegree is false.
+ // (Otherwise all source-like nodes are all isolated already.)
+ renderNode.isolatedInExtract.push(child);
+ graph.removeNode(n);
+ } else if (child.isOutExtract) { // Is sink-like.
+ // This case only happens if detachAllEdgesForHighDegree is false.
+ // // (Otherwise all sink-like nodes are all isolated already.)
+ renderNode.isolatedOutExtract.push(child);
+ graph.removeNode(n);
+ } else if (params.extractIsolatedNodesWithAnnotationsOnOneSide) {
+ if (hasOutAnnotations && !hasInAnnotations) {
+ child.isInExtract = true; // for ones with high out-annotations
+ renderNode.isolatedInExtract.push(child);
+ graph.removeNode(n);
+ } else if (hasInAnnotations && !hasOutAnnotations) {
+ child.isOutExtract = true; // for ones with high in-annotations
+ renderNode.isolatedOutExtract.push(child);
+ 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.
+ }
+ }
+ }
+ });
+}
+} // close module tf.graph.render
diff --git a/tensorflow/tensorboard/components/tf-graph-common/lib/scene/annotation.ts b/tensorflow/tensorboard/components/tf-graph-common/lib/scene/annotation.ts
new file mode 100644
index 0000000000..82609e8652
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-graph-common/lib/scene/annotation.ts
@@ -0,0 +1,223 @@
+/// <reference path="../graph.ts" />
+/// <reference path="../render.ts" />
+/// <reference path="scene.ts" />
+/// <reference path="edge.ts" />
+
+module tf.graph.scene.annotation {
+
+/**
+ * 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">
+ * <!--
+ * Content here determined by Scene.node.buildGroup.
+ * -->
+ * </g>
+ * </g>
+ *
+ * @param container selection of the container.
+ * @param annotationData node.{in|out}Annotations
+ * @param d node to build group for.
+ * @param sceneBehavior polymer scene element.
+ * @return selection of appended objects
+ */
+export function buildGroup(container, annotationData: render.AnnotationList,
+ d: render.RenderNodeInformation, sceneBehavior) {
+ // Select all children and join with data.
+ let 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, d => { return d.node.name; });
+
+ annotationGroups.enter()
+ .append("g")
+ .attr("data-name", a => { return a.node.name; })
+ .each(function(a) {
+ let aGroup = d3.select(this);
+
+ // Add annotation to the index in the scene
+ sceneBehavior.addAnnotationGroup(a, d, aGroup);
+ // Append annotation edge
+ let edgeType = Class.Annotation.EDGE;
+ let metaedge = a.renderMetaedgeInfo && a.renderMetaedgeInfo.metaedge;
+ if (metaedge && !metaedge.numRegularEdges) {
+ edgeType += " " + Class.Annotation.CONTROL_EDGE;
+ }
+ // If any edges are reference edges, add the reference edge class.
+ if (metaedge && metaedge.numRefEdges) {
+ edgeType += " " + Class.Edge.REF_LINE;
+ }
+ edge.appendEdge(aGroup, a, sceneBehavior, edgeType);
+
+ if (a.annotationType !== tf.graph.render.AnnotationType.ELLIPSIS) {
+ addAnnotationLabelFromNode(aGroup, a);
+ buildShape(aGroup, a, sceneBehavior);
+ } else {
+ addAnnotationLabel(aGroup, a.node.name, a, Class.Annotation.ELLIPSIS);
+ }
+ });
+
+ annotationGroups
+ .attr("class", a => {
+ return Class.Annotation.GROUP + " " +
+ annotationToClassName(a.annotationType) +
+ " " + node.nodeClass(a);
+ })
+ .each(function(a) {
+ let aGroup = d3.select(this);
+ update(aGroup, d, a, sceneBehavior);
+ if (a.annotationType !== tf.graph.render.AnnotationType.ELLIPSIS) {
+ addInteraction(aGroup, d, sceneBehavior);
+ }
+ });
+
+ annotationGroups.exit()
+ .each(function(a) {
+ let aGroup = d3.select(this);
+
+ // Remove annotation from the index in the scene
+ sceneBehavior.removeAnnotationGroup(a, d, aGroup);
+ })
+ .remove();
+ return annotationGroups;
+};
+
+/**
+ * Maps an annotation enum to a class name used in css rules.
+ */
+function annotationToClassName(annotationType: render.AnnotationType) {
+ return (tf.graph.render.AnnotationType[annotationType] || "")
+ .toLowerCase() || null;
+}
+
+function buildShape(aGroup, a: render.Annotation, sceneBehavior) {
+ if (a.annotationType === tf.graph.render.AnnotationType.SUMMARY) {
+ let image = scene.selectOrCreateChild(aGroup, "image");
+ image.attr({
+ "xlink:href": sceneBehavior.resolveUrl("../../lib/svg/summary-icon.svg"),
+ "height": "12px",
+ "width": "12px",
+ "cursor": "pointer"
+ });
+ } else {
+ let shape = node.buildShape(aGroup, a, Class.Annotation.NODE);
+ // add title tag to get native tooltips
+ scene.selectOrCreateChild(shape, "title").text(a.node.name);
+ }
+}
+
+function addAnnotationLabelFromNode(aGroup, a: render.Annotation) {
+ let namePath = a.node.name.split("/");
+ let text = namePath[namePath.length - 1];
+ let shortenedText = text.length > 8 ? text.substring(0, 8) + "..." : text;
+ return addAnnotationLabel(aGroup, shortenedText, a, null, text);
+}
+
+function addAnnotationLabel(aGroup, label, a, additionalClassNames,
+ fullLabel?) {
+ let classNames = Class.Annotation.LABEL;
+ if (additionalClassNames) {
+ classNames += " " + additionalClassNames;
+ }
+ let titleText = fullLabel ? fullLabel : label;
+ return aGroup.append("text")
+ .attr("class", classNames)
+ .attr("dy", ".35em")
+ .attr("text-anchor", a.isIn ? "end" : "start")
+ .text(label)
+ .append("title").text(titleText);
+}
+
+function addInteraction(selection, d: render.RenderNodeInformation,
+ sceneBehavior) {
+ selection
+ .on("mouseover", a => {
+ sceneBehavior.fire("annotation-highlight", {
+ name: a.node.name,
+ hostName: d.node.name
+ });
+ })
+ .on("mouseout", a => {
+ sceneBehavior.fire("annotation-unhighlight", {
+ name: a.node.name,
+ hostName: d.node.name
+ });
+ })
+ .on("click", a => {
+ // Stop this event"s propagation so that it isn't also considered a
+ // graph-select.
+ (<Event>d3.event).stopPropagation();
+ sceneBehavior.fire("annotation-select", {
+ name: a.node.name,
+ hostName: d.node.name
+ });
+ });
+};
+
+/**
+ * Adjust annotation's position.
+ *
+ * @param aGroup selection of a "g.annotation" element.
+ * @param d Host node data.
+ * @param a annotation node data.
+ * @param scene Polymer scene element.
+ */
+function update(aGroup, d: render.RenderNodeInformation, a: render.Annotation,
+ sceneBehavior) {
+ // 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 !== tf.graph.render.AnnotationType.ELLIPSIS) {
+ node.stylize(aGroup, a.renderNodeInfo, sceneBehavior,
+ Class.Annotation.NODE);
+ }
+
+ if (a.annotationType === tf.graph.render.AnnotationType.SUMMARY) {
+ // Update the width of the annotation to give space for the image.
+ a.width += 10;
+ }
+
+ // label position
+ aGroup.select("text." + Class.Annotation.LABEL).transition().attr({
+ x: d.x + 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 ommited 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("image").transition().attr({
+ x: d.x + 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("." + Class.Annotation.NODE + " ellipse"),
+ d.x + a.dx, d.y + a.dy, a.width, a.height);
+ scene.positionRect(aGroup.select("." + Class.Annotation.NODE + " rect"),
+ d.x + a.dx, d.y + a.dy, a.width, a.height);
+ scene.positionRect(aGroup.select("." + Class.Annotation.NODE + " use"),
+ d.x + a.dx, d.y + a.dy, a.width, a.height);
+
+ // Edge position
+ aGroup.select("path." + Class.Annotation.EDGE).transition().attr("d", a => {
+ // map relative position to absolute position
+ let points = a.points.map(p => {
+ return {x: p.dx + d.x, y: p.dy + d.y};
+ });
+ return edge.interpolate(points);
+ });
+};
+
+} // close module
diff --git a/tensorflow/tensorboard/components/tf-graph-common/lib/scene/edge.ts b/tensorflow/tensorboard/components/tf-graph-common/lib/scene/edge.ts
new file mode 100644
index 0000000000..e11ec97f80
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-graph-common/lib/scene/edge.ts
@@ -0,0 +1,177 @@
+/// <reference path="../graph.ts" />
+/// <reference path="../render.ts" />
+/// <reference path="scene.ts" />
+
+module tf.graph.scene.edge {
+
+let Scene = tf.graph.scene; // Aliased
+
+export function getEdgeKey(edgeObj) {
+ return edgeObj.v + tf.graph.EDGE_KEY_DELIM + edgeObj.w;
+}
+
+/**
+ * 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 sceneBehavior Parent scene module.
+ * @return selection of the created nodeGroups
+ */
+export function buildGroup(sceneGroup,
+ graph: graphlib.Graph<tf.graph.render.RenderNodeInformation,
+ tf.graph.render.RenderMetaedgeInformation>, sceneBehavior) {
+ let edgeData = _.reduce(graph.edges(), (edges, edgeObj) => {
+ let edgeLabel = graph.edge(edgeObj);
+ edges.push({
+ v: edgeObj.v,
+ w: edgeObj.w,
+ label: edgeLabel
+ });
+ return edges;
+ }, []);
+
+ let container = scene.selectOrCreateChild(sceneGroup, "g",
+ Class.Edge.CONTAINER);
+ let containerNode = container.node();
+
+ // Select all children and join with data.
+ // (Note that all children of g.edges are g.edge)
+ let 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(edgeData, getEdgeKey);
+
+ // Make edges a group to support rendering multiple lines for metaedge
+ edgeGroups.enter()
+ .append("g")
+ .attr("class", Class.Edge.GROUP)
+ .attr("data-edge", getEdgeKey)
+ .each(function(d) {
+ let edgeGroup = d3.select(this);
+ d.label.edgeGroup = edgeGroup;
+ // index node group for quick highlighting
+ sceneBehavior._edgeGroupIndex[getEdgeKey(d)] = edgeGroup;
+
+ // If any edges are reference edges, add the reference edge class.
+ let extraEdgeClass = d.label.metaedge && d.label.metaedge.numRefEdges
+ ? Class.Edge.REF_LINE + " " + Class.Edge.LINE
+ : undefined;
+ // Add line during enter because we're assuming that type of line
+ // normally does not change.
+ appendEdge(edgeGroup, d, scene, extraEdgeClass);
+ });
+
+ edgeGroups.each(position);
+ edgeGroups.each(function(d) {
+ stylize(d3.select(this), d, sceneBehavior);
+ });
+
+ edgeGroups.exit()
+ .each(d => {
+ delete sceneBehavior._edgeGroupIndex[getEdgeKey(d)];
+ })
+ .remove();
+ return edgeGroups;
+};
+
+/**
+ * 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 RenderMetaedgeInformation instance. It
+ * will sometimes be undefined, for example for some Annotation edges for which
+ * there is no underlying Metaedge in the hierarchical graph.
+ */
+export function appendEdge(edgeGroup, d, sceneBehavior, edgeClass?) {
+ edgeClass = edgeClass || Class.Edge.LINE; // set default type
+
+ if (d.label && d.label.structural) {
+ edgeClass += " " + Class.Edge.STRUCTURAL;
+ }
+
+ edgeGroup.append("path")
+ .attr("class", edgeClass);
+};
+
+/**
+ * Returns a tween interpolator for the endpoint of an edge path.
+ */
+function getEdgePathInterpolator(d, i, a) {
+ let renderMetaedgeInfo = d.label;
+ let adjoiningMetaedge = renderMetaedgeInfo.adjoiningMetaedge;
+ if (!adjoiningMetaedge) {
+ return d3.interpolate(a, interpolate(renderMetaedgeInfo.points));
+ }
+
+ let renderPath = this;
+
+ // Get the adjoining path that matches the adjoining metaedge.
+ let adjoiningPath =
+ <SVGPathElement>((<HTMLElement>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).
+ let inbound = renderMetaedgeInfo.metaedge.inbound;
+
+ return function(t) {
+ let 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.
+ let points = renderMetaedgeInfo.points;
+ let index = inbound ? 0 : points.length - 1;
+ points[index].x = adjoiningPoint.x;
+ points[index].y = adjoiningPoint.y;
+ let dPath = interpolate(points);
+ return dPath;
+ };
+}
+
+export let interpolate = d3.svg.line()
+ .interpolate("basis")
+ .x((d: any) => { return d.x; })
+ .y((d: any) => { return d.y; });
+
+function position(d) {
+ d3.select(this).select("path." + Class.Edge.LINE)
+ .each(function(d) {
+ let path = d3.select(this);
+ path.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 RenderMetaedgeInformation object.
+ */
+function stylize(edgeGroup, d, stylize) {
+ let a;
+ let metaedge = d.label.metaedge;
+ edgeGroup
+ .select("path." + Class.Edge.LINE)
+ .classed("control-dep", metaedge && !metaedge.numRegularEdges);
+};
+
+} // close module
diff --git a/tensorflow/tensorboard/components/tf-graph-common/lib/scene/minimap.ts b/tensorflow/tensorboard/components/tf-graph-common/lib/scene/minimap.ts
new file mode 100644
index 0000000000..1a34132765
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-graph-common/lib/scene/minimap.ts
@@ -0,0 +1,269 @@
+/// <reference path="../../../../typings/tsd.d.ts" />
+/// <reference path="../common.ts" />
+
+module tf.scene {
+
+/** Show minimap when the viewpoint area is less than X% of the whole area. */
+const FRAC_VIEWPOINT_AREA: number = 0.8;
+
+export class Minimap {
+ /** The minimap container. */
+ private minimap: HTMLElement;
+ /** The canvas used for drawing the mini version of the svg. */
+ private canvas: HTMLCanvasElement;
+ /** A buffer canvas used for temporary drawing to avoid flickering. */
+ private canvasBuffer: HTMLCanvasElement;
+
+ /** The minimap svg used for holding the viewpoint rectangle. */
+ private minimapSvg: SVGSVGElement;
+ /** The rectangle showing the current viewpoint. */
+ private viewpoint: SVGRectElement;
+ /**
+ * The scale factor for the minimap. The factor is determined automatically
+ * so that the minimap doesn't violate the maximum width/height specified
+ * in the constructor. The minimap maintains the same aspect ratio as the
+ * original svg.
+ */
+ private scaleMinimap: number;
+ /** The main svg element. */
+ private svg: SVGSVGElement;
+ /** The svg group used for panning and zooming the main svg. */
+ private zoomG: SVGGElement;
+ /** The zoom behavior of the main svg. */
+ private mainZoom: d3.behavior.Zoom<any>;
+ /** The maximum width and height for the minimap. */
+ private maxWandH: number;
+ /** The last translation vector used in the main svg. */
+ private translate: [number, number];
+ /** The last scaling factor used in the main svg. */
+ private scaleMain: number;
+ /** The coordinates of the viewpoint rectangle. */
+ private viewpointCoord: {x: number, y: number};
+ /** The current size of the minimap */
+ private minimapSize: {width: number, height: number};
+ /** Padding (px) due to the main labels of the graph. */
+ private labelPadding: number;
+ /**
+ * 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.
+ */
+ constructor(svg: SVGSVGElement, zoomG: SVGGElement,
+ mainZoom: d3.behavior.Zoom<any>, minimap: HTMLElement,
+ maxWandH: number, labelPadding: number) {
+ this.svg = svg;
+ this.labelPadding = labelPadding;
+ this.zoomG = zoomG;
+ this.mainZoom = mainZoom;
+ this.maxWandH = maxWandH;
+ let $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.
+ let $minimapSvg = $minimap.select("svg");
+
+ // Make the viewpoint rectangle draggable.
+ let $viewpoint = $minimapSvg.select("rect");
+ let dragmove = (d) => {
+ this.viewpointCoord.x = (<DragEvent>d3.event).x;
+ this.viewpointCoord.y = (<DragEvent>d3.event).y;
+ this.updateViewpoint();
+ };
+ this.viewpointCoord = {x: 0, y: 0};
+ let drag = d3.behavior.drag().origin(Object).on("drag", dragmove);
+ $viewpoint.datum(this.viewpointCoord).call(drag);
+
+ // Make the minimap clickable.
+ $minimapSvg.on("click", () => {
+ if ((<Event>d3.event).defaultPrevented) {
+ // This click was part of a drag event, so suppress it.
+ return;
+ }
+ // Update the coordinates of the viewpoint.
+ let width = Number($viewpoint.attr("width"));
+ let height = Number($viewpoint.attr("height"));
+ let clickCoords = d3.mouse($minimapSvg.node());
+ this.viewpointCoord.x = clickCoords[0] - width / 2;
+ this.viewpointCoord.y = clickCoords[1] - height / 2;
+ this.updateViewpoint();
+ });
+ this.viewpoint = <SVGRectElement> $viewpoint.node();
+ this.minimapSvg = <SVGSVGElement> $minimapSvg.node();
+ this.minimap = minimap;
+ this.canvas = <HTMLCanvasElement> $minimap.select("canvas.first").node();
+ this.canvasBuffer =
+ <HTMLCanvasElement> $minimap.select("canvas.second").node();
+ }
+
+ /**
+ * Updates the position and the size of the viewpoint rectangle.
+ * It also notifies the main svg about the new panned position.
+ */
+ private updateViewpoint(): void {
+ // 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.
+ let mainX = - this.viewpointCoord.x * this.scaleMain / this.scaleMinimap;
+ let mainY = - this.viewpointCoord.y * this.scaleMain / this.scaleMinimap;
+ let 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).
+ */
+ update(): void {
+ let $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.
+ let stylesText = "";
+ for (let k = 0; k < document.styleSheets.length; k++) {
+ try {
+ let cssRules = (<any>document.styleSheets[k]).cssRules ||
+ (<any>document.styleSheets[k]).rules;
+ if (cssRules == null) {
+ continue;
+ }
+ for (let i = 0; i < cssRules.length; i++) {
+ stylesText += cssRules[i].cssText + "\n";
+ }
+ } catch (e) {
+ if (e.name !== "SecurityError") {
+ throw e;
+ }
+ }
+ }
+
+ // Temporarily add the css rules to the main svg.
+ let 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.
+ let $zoomG = d3.select(this.zoomG);
+ let zoomTransform = $zoomG.attr("transform");
+ $zoomG.attr("transform", null);
+
+ // Get the size of the entire scene.
+ let sceneSize = this.zoomG.getBBox();
+ // Since we add padding, account for that here.
+ sceneSize.height += this.labelPadding;
+
+ // 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(<any>this.minimapSize);
+ d3.select(this.canvasBuffer).attr(<any>this.minimapSize);
+ if (this.translate != null && this.zoom != null) {
+ // Update the viewpoint rectangle shape since the aspect ratio of the
+ // map has changed.
+ requestAnimationFrame(() => this.zoom());
+ }
+
+ // Serialize the main svg to a string which will be used as the rendering
+ // content for the canvas.
+ let 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);
+ let image = new Image();
+ image.onload = () => {
+ // Draw the svg content onto the buffer canvas.
+ let 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(() => {
+ // 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.
+ [this.canvas, this.canvasBuffer] = [this.canvasBuffer, this.canvas];
+ });
+ };
+ image.src = "data:image/svg+xml;base64," + btoa(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.
+ */
+ zoom(translate?: [number, number], scale?: number): void {
+ // 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.
+ let svgRect = this.svg.getBoundingClientRect();
+ let $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;
+ let viewpointWidth = svgRect.width * this.scaleMinimap / this.scaleMain;
+ let 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.
+ let mapWidth = this.minimapSize.width;
+ let mapHeight = this.minimapSize.height;
+ let x = this.viewpointCoord.x;
+ let y = this.viewpointCoord.y;
+ let w = Math.min(Math.max(0, x + viewpointWidth), mapWidth) -
+ Math.min(Math.max(0, x), mapWidth);
+ let h = Math.min(Math.max(0, y + viewpointHeight), mapHeight) -
+ Math.min(Math.max(0, y), mapHeight);
+ let fracIntersect = (w * h) / (mapWidth * mapHeight);
+ if (fracIntersect < FRAC_VIEWPOINT_AREA) {
+ this.minimap.classList.remove("hidden");
+ } else {
+ this.minimap.classList.add("hidden");
+ }
+ }
+}
+
+} // close module tf.scene
diff --git a/tensorflow/tensorboard/components/tf-graph-common/lib/scene/node.ts b/tensorflow/tensorboard/components/tf-graph-common/lib/scene/node.ts
new file mode 100644
index 0000000000..8c74b37e07
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-graph-common/lib/scene/node.ts
@@ -0,0 +1,525 @@
+/// <reference path="../graph.ts" />
+/// <reference path="scene.ts" />
+/// <reference path="annotation.ts" />
+
+module tf.graph.scene.node {
+
+/**
+ * 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">
+ * <!--
+ * 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.
+ * -->
+ * </g>
+ * <text class="label">node name</text>
+ * <g class="subscene">
+ * <!--
+ * 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.
+ * -->
+ * </g>
+ * </g>
+ * ...
+ * </g>
+ *
+ *
+ * @param sceneGroup selection of the container
+ * @param nodeData array of render node information to map
+ * @param sceneBehavior parent scene module
+ * @return selection of the created nodeGroups
+ */
+export function buildGroup(sceneGroup,
+ nodeData: render.RenderNodeInformation[], sceneBehavior) {
+ let container = scene.selectOrCreateChild(sceneGroup, "g",
+ Class.Node.CONTAINER);
+ // Select all children and join with data.
+ // (Note that all children of g.nodes are g.node)
+ let 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, (d: any) => {
+ // 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", d => { return d.node.name; })
+ .each(function(d) {
+ let nodeGroup = d3.select(this);
+ // index node group for quick stylizing
+ sceneBehavior.addNodeGroup(d.node.name, nodeGroup);
+ });
+
+ // UPDATE
+ nodeGroups
+ .attr("class", d => {
+ return Class.Node.GROUP + " " + nodeClass(d);
+ })
+ .each(function(d) {
+ let nodeGroup = d3.select(this);
+ // add g.in-annotations (always add -- to keep layer order consistent.)
+ let inAnnotationBox = scene.selectOrCreateChild(nodeGroup, "g",
+ Class.Annotation.INBOX);
+ annotation.buildGroup(inAnnotationBox, d.inAnnotations, d,
+ sceneBehavior);
+
+ // add g.out-annotations (always add -- to keep layer order consistent.)
+ let outAnnotationBox = scene.selectOrCreateChild(nodeGroup, "g",
+ Class.Annotation.OUTBOX);
+ annotation.buildGroup(outAnnotationBox, d.outAnnotations, d,
+ sceneBehavior);
+
+ // label
+ let label = labelBuild(nodeGroup, d, sceneBehavior);
+ // Do not add interaction to metanode labels as they live inside the
+ // metanode shape which already has the same interactions.
+ addInteraction(label, d, sceneBehavior, d.node.type === NodeType.META);
+
+ // build .shape below label
+ let shape = buildShape(nodeGroup, d, Class.Node.SHAPE, label.node());
+ if (d.node.isGroupNode) {
+ addButton(shape, d, sceneBehavior);
+ }
+ addInteraction(shape, d, sceneBehavior);
+
+ // build subscene on the top
+ subsceneBuild(nodeGroup, d, sceneBehavior);
+
+ stylize(nodeGroup, d, sceneBehavior);
+ position(nodeGroup, d, sceneBehavior);
+ });
+
+ // EXIT
+ nodeGroups.exit()
+ .each(function(d) {
+ // remove all indices on remove
+ sceneBehavior.removeNodeGroup(d.node.name);
+
+ let nodeGroup = d3.select(this);
+ if (d.inAnnotations.list.length > 0) {
+ nodeGroup.select("." + Class.Annotation.INBOX)
+ .selectAll("." + Class.Annotation.GROUP)
+ .each(a => {
+ sceneBehavior.removeAnnotationGroup(a, d);
+ });
+ }
+ if (d.outAnnotations.list.length > 0) {
+ nodeGroup.select("." + Class.Annotation.OUTBOX)
+ .selectAll("." + Class.Annotation.GROUP)
+ .each(a => {
+ sceneBehavior.removeAnnotationGroup(a, d);
+ });
+ }
+ })
+ .remove();
+ return nodeGroups;
+};
+
+/**
+ * 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 sceneBehavior parent scene module
+ * @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: render.RenderGroupNodeInformation, sceneBehavior) {
+ if (renderNodeInfo.node.isGroupNode) {
+ if (renderNodeInfo.expanded) {
+ // Recursively build the subscene.
+ return scene.buildGroup(nodeGroup, renderNodeInfo, sceneBehavior,
+ Class.Subscene.GROUP);
+ }
+ // Clean out existing subscene if the node is not expanded.
+ scene.selectChild(nodeGroup, "g", Class.Subscene.GROUP).remove();
+ }
+ return null;
+};
+
+/**
+ * Translate the subscene of the given node group
+ */
+function subscenePosition(nodeGroup, d: render.RenderNodeInformation) {
+ let x0 = d.x - d.width / 2.0 + d.paddingLeft;
+ let y0 = d.y - d.height / 2.0 + d.paddingTop;
+
+ let subscene = scene.selectChild(nodeGroup, "g", 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 sceneBehavior parent scene module.
+ */
+function addButton(selection, d: render.RenderNodeInformation, sceneBehavior) {
+ let group = scene.selectOrCreateChild(
+ selection, "g", Class.Node.BUTTON_CONTAINER);
+ scene.selectOrCreateChild(group, "circle", Class.Node.BUTTON_CIRCLE);
+ scene.selectOrCreateChild(group, "path", Class.Node.EXPAND_BUTTON).attr(
+ "d", "M0,-2.2 V2.2 M-2.2,0 H2.2");
+ scene.selectOrCreateChild(group, "path", Class.Node.COLLAPSE_BUTTON).attr(
+ "d", "M-2.2,0 H2.2");
+ group.on("click", d => {
+ // Stop this event's propagation so that it isn't also considered a
+ // node-select.
+ (<Event>d3.event).stopPropagation();
+ sceneBehavior.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: render.RenderNodeInformation,
+ sceneBehavior, disableInteraction?: boolean) {
+ if (disableInteraction) {
+ selection.attr("pointer-events", "none");
+ return;
+ }
+ selection.on("dblclick", d => {
+ sceneBehavior.fire("node-toggle-expand", { name: d.node.name });
+ })
+ .on("mouseover", d => {
+ // don't send mouseover over expanded group,
+ // otherwise it is causing too much glitches
+ if (sceneBehavior.isNodeExpanded(d)) { return; }
+
+ sceneBehavior.fire("node-highlight", { name: d.node.name });
+ })
+ .on("mouseout", d => {
+ // don't send mouseover over expanded group,
+ // otherwise it is causing too much glitches
+ if (sceneBehavior.isNodeExpanded(d)) { return; }
+
+ sceneBehavior.fire("node-unhighlight", { name: d.node.name });
+ })
+ .on("click", d => {
+ // Stop this event's propagation so that it isn't also considered
+ // a graph-select.
+ (<Event>d3.event).stopPropagation();
+ sceneBehavior.fire("node-select", { name: d.node.name });
+ });
+};
+
+/**
+ * Append svg text for label and assign data.
+ * @param nodeGroup
+ * @param renderNodeInfo The render node information for the label.
+ * @param sceneBehavior parent scene module.
+ */
+function labelBuild(nodeGroup, renderNodeInfo: render.RenderNodeInformation,
+ sceneBehavior) {
+ let namePath = renderNodeInfo.node.name.split("/");
+ let text = namePath[namePath.length - 1];
+
+ // Truncate long labels for unexpanded Metanodes.
+ let useFontScale = renderNodeInfo.node.type === NodeType.META &&
+ !renderNodeInfo.expanded;
+
+ let label = scene.selectOrCreateChild(nodeGroup, "text", Class.Node.LABEL);
+ label.attr("dy", ".35em")
+ .attr("text-anchor", "middle");
+ if (useFontScale) {
+ if (text.length > sceneBehavior.maxMetanodeLabelLength) {
+ text = text.substr(0, sceneBehavior.maxMetanodeLabelLength - 2) + "...";
+ }
+ let scale = getLabelFontScale(sceneBehavior);
+ label.attr("font-size", scale(text.length) + "px");
+ }
+ label.text(text);
+ return label;
+};
+
+/**
+ * d3 scale used for sizing font of labels, used by labelBuild,
+ * initialized once by getLabelFontScale.
+ */
+let fontScale = null;
+function getLabelFontScale(sceneBehavior) {
+ if (!fontScale) {
+ fontScale = d3.scale.linear()
+ .domain([sceneBehavior.maxMetanodeLabelLengthLargeFont,
+ sceneBehavior.maxMetanodeLabelLength])
+ .range([sceneBehavior.maxMetanodeLabelLengthFontSize,
+ sceneBehavior.minMetanodeLabelLengthFontSize]).clamp(true);
+ }
+ return fontScale;
+}
+/**
+ * Set label position of a given node group
+ */
+function labelPosition(nodeGroup, d: render.RenderNodeInformation,
+ yOffset: number) {
+ scene.selectChild(nodeGroup, "text", Class.Node.LABEL).transition()
+ .attr("x", d.x)
+ .attr("y", d.y + yOffset);
+};
+
+/**
+ * Select or append/insert shape for a node and assign renderNode
+ * as the shape's data.
+ *
+ * @param nodeGroup
+ * @param d RenderNodeInformation
+ * @param nodeClass class for the element.
+ * @param before Reference DOM node for insertion.
+ * @return Selection of the shape.
+ */
+export function buildShape(nodeGroup, d, nodeClass: string, before?) {
+ // Create a group to house the underlying visual elements.
+ let shapeGroup = scene.selectOrCreateChild(nodeGroup, "g", nodeClass,
+ before);
+ // TODO(jimbo): DOM structure should be templated in HTML somewhere, not JS.
+ switch (d.node.type) {
+ case NodeType.OP:
+ scene.selectOrCreateChild(shapeGroup, "ellipse",
+ Class.Node.COLOR_TARGET);
+ break;
+ case NodeType.SERIES:
+ // Choose the correct stamp to use to represent this series.
+ let stampType = "annotation";
+ let groupNodeInfo = <render.RenderGroupNodeInformation>d;
+ if (groupNodeInfo.coreGraph) {
+ stampType = groupNodeInfo.node.hasNonControlEdges
+ ? "vertical" : "horizontal";
+ }
+ scene.selectOrCreateChild(shapeGroup, "use", Class.Node.COLOR_TARGET)
+ .attr("xlink:href", "#op-series-" + stampType + "-stamp");
+ scene.selectOrCreateChild(shapeGroup, "rect", Class.Node.COLOR_TARGET)
+ .attr({ rx: d.radius, ry: d.radius });
+ break;
+ case NodeType.BRIDGE:
+ scene.selectOrCreateChild(shapeGroup, "rect", Class.Node.COLOR_TARGET)
+ .attr({ rx: d.radius, ry: d.radius });
+ break;
+ case NodeType.META:
+ scene.selectOrCreateChild(shapeGroup, "rect", Class.Node.COLOR_TARGET)
+ .attr({ rx: d.radius, ry: d.radius });
+ break;
+ default:
+ throw Error("Unrecognized node type: " + d.node.type);
+ }
+ return shapeGroup;
+};
+
+export function nodeClass(d: render.RenderNodeInformation) {
+ switch (d.node.type) {
+ case NodeType.OP:
+ return Class.OPNODE;
+ case NodeType.META:
+ return Class.METANODE;
+ case NodeType.SERIES:
+ return Class.SERIESNODE;
+ case NodeType.BRIDGE:
+ return Class.BRIDGENODE;
+ case NodeType.ELLIPSIS:
+ return Class.ELLIPSISNODE;
+ };
+ throw Error("Unrecognized node type: " + d.node.type);
+};
+
+/** Modify node and its subscene and its label's positional attributes */
+function position(nodeGroup, d: render.RenderNodeInformation, sceneBehavior) {
+ let shapeGroup = scene.selectChild(nodeGroup, "g", Class.Node.SHAPE);
+ switch (d.node.type) {
+ case NodeType.OP: {
+ // position shape
+ let shape = scene.selectChild(shapeGroup, "ellipse");
+ scene.positionEllipse(shape, d.x, d.y, d.width, d.height);
+ labelPosition(nodeGroup, d, d.labelOffset);
+ break;
+ }
+ case NodeType.META: {
+ // position shape
+ let shape = scene.selectChild(shapeGroup, "rect");
+ scene.positionRect(shape, d.x, d.y, d.width, d.height);
+
+ if (d.expanded) {
+ subscenePosition(nodeGroup, d);
+
+ // put label on top
+ labelPosition(nodeGroup, d,
+ - d.height / 2 + d.labelHeight / 2);
+ } else {
+ labelPosition(nodeGroup, d, 0);
+ }
+ break;
+ }
+ case NodeType.SERIES: {
+ let shape = scene.selectChild(shapeGroup, "use");
+ scene.positionRect(shape, d.x, d.y, d.width, d.height);
+ if (d.expanded) {
+ subscenePosition(nodeGroup, d);
+
+ // put label on top
+ labelPosition(nodeGroup, d,
+ - d.height / 2 + d.labelHeight / 2);
+ } else {
+ labelPosition(nodeGroup, d, d.labelOffset);
+ }
+ }
+ case 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.
+ let 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 */
+let ColorBy = {
+ STRUCTURE: 0,
+ DEVICE: 1,
+ COMPUTE_TIME: 2,
+ MEMORY: 3
+};
+
+/**
+ * Returns the fill color for the node given its state and the "color by"
+ * option.
+ */
+function getFillForNode(sceneBehavior, colorBy,
+ renderInfo: render.RenderNodeInformation, isExpanded: boolean): string {
+ let colorParams = tf.graph.render.MetanodeColors;
+ switch (colorBy) {
+ case ColorBy.STRUCTURE:
+ if (renderInfo.node.type === tf.graph.NodeType.META) {
+ let tid = (<Metanode>renderInfo.node).templateId;
+ return tid === null ? colorParams.UNKNOWN : colorParams.STRUCTURE_PALETTE(
+ sceneBehavior.templateIndex(tid), renderInfo.expanded);
+ } else if (renderInfo.node.type === tf.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 renderInfo.expanded ? colorParams.EXPANDED_COLOR : "white";
+ } else if (renderInfo.node.type === NodeType.BRIDGE) {
+ return renderInfo.structural ? "#f0e" :
+ (<BridgeNode>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;
+ }
+ let id = renderInfo.node.name;
+ let escapedId = tf.escapeQuerySelector(id);
+ let gradientDefs = d3.select("svg#svg defs #linearGradients");
+ let linearGradient =
+ gradientDefs.select("linearGradient#" + escapedId);
+ // If the linear gradient is not there yet, create it.
+ if (linearGradient.size() === 0) {
+ linearGradient = gradientDefs.append("linearGradient").attr("id", id);
+ // Re-create the stops of the linear gradient.
+ linearGradient.selectAll("*").remove();
+ let cumulativeProportion = 0;
+ // For each device, create a stop using the proportion of that device.
+ _.each(renderInfo.deviceColors, (d: any) => {
+ let color = d.color;
+ linearGradient.append("stop")
+ .attr("offset", cumulativeProportion)
+ .attr("stop-color", color);
+ linearGradient.append("stop")
+ .attr("offset", cumulativeProportion + d.proportion)
+ .attr("stop-color", color);
+ cumulativeProportion += d.proportion;
+ });
+ }
+ return isExpanded ? colorParams.EXPANDED_COLOR : `url(#${escapedId})`;
+ 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");
+ }
+}
+
+/**
+ * Modify node style by toggling class and assign attributes (only for things
+ * that can't be done in css).
+ */
+export function stylize(nodeGroup, renderInfo: render.RenderNodeInformation,
+ sceneBehavior, nodeClass?) {
+ nodeClass = nodeClass || Class.Node.SHAPE;
+ let isHighlighted = sceneBehavior.isNodeHighlighted(renderInfo.node.name);
+ let isSelected = sceneBehavior.isNodeSelected(renderInfo.node.name);
+ let isExtract = renderInfo.isInExtract || renderInfo.isOutExtract;
+ let isExpanded = renderInfo.expanded;
+ nodeGroup.classed("highlighted", isHighlighted);
+ nodeGroup.classed("selected", isSelected);
+ nodeGroup.classed("extract", isExtract);
+ nodeGroup.classed("expanded", isExpanded);
+
+ // Main node always exists here and it will be reached before subscene,
+ // so d3 selection is fine here.
+ let node = nodeGroup.select("." + nodeClass + " ." + Class.Node.COLOR_TARGET);
+ let fillColor = getFillForNode(sceneBehavior,
+ ColorBy[sceneBehavior.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.
+ if (isSelected) {
+ node.style("stroke", null);
+ } else {
+ // If node is colored by a gradient, then use a dark gray outline.
+ let outlineColor = fillColor.substring(0, 3) === "url" ?
+ tf.graph.render.MetanodeColors.GRADIENT_OUTLINE :
+ d3.rgb(fillColor).darker().toString();
+ node.style("stroke", outlineColor);
+ }
+};
+
+} // close module
diff --git a/tensorflow/tensorboard/components/tf-graph-common/lib/scene/scene.ts b/tensorflow/tensorboard/components/tf-graph-common/lib/scene/scene.ts
new file mode 100644
index 0000000000..2e2467f039
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-graph-common/lib/scene/scene.ts
@@ -0,0 +1,409 @@
+/// <reference path="../graph.ts" />
+/// <reference path="edge.ts" />
+/// <reference path="node.ts" />
+/// <reference path="../layout.ts" />
+
+module tf.graph.scene {
+
+/** Enums element class of objects in the scene */
+export let 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"
+};
+
+/**
+ * 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.
+ */
+export function fit(svg, zoomG, d3zoom, callback) {
+ let svgRect = svg.getBoundingClientRect();
+ let sceneSize = zoomG.getBBox();
+ let scale = 0.9 * Math.min(
+ svgRect.width / sceneSize.width,
+ svgRect.height / sceneSize.height,
+ 2
+ );
+ let params = layout.PARAMS.graph;
+ let zoomEvent = d3zoom.scale(scale)
+ .on("zoomend.fitted", () => {
+ // 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);
+};
+
+/**
+ * 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.
+ */
+export function panToNode(nodeName: String, svg, zoomG, d3zoom): boolean {
+ let node: any = d3.selectAll("[data-name='" + nodeName + "']."
+ + Class.Node.GROUP)[0][0];
+ if (!node) {
+ return false;
+ }
+ let translate = d3zoom.translate();
+ // Check if the selected node is off-screen in either
+ // X or Y dimension in either direction.
+ let nodeBox = node.getBBox();
+ let nodeCtm = node.getScreenCTM();
+ let pointTL = svg.createSVGPoint();
+ let 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);
+ let isOutsideOfBounds = (start, end, bound) => {
+ return end < 0 || start > bound;
+ };
+ let 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.
+ let centerX = (pointTL.x + pointBR.x) / 2;
+ let centerY = (pointTL.y + pointBR.y) / 2;
+ let dx = ((svgRect.width / 2) - centerX);
+ let dy = ((svgRect.height / 2) - centerY);
+ let zoomEvent = d3zoom.translate([translate[0] + dx, translate[1] + dy])
+ .event;
+ d3.select(zoomG).transition().duration(500).call(zoomEvent);
+ return true;
+ }
+ return false;
+};
+
+/**
+ * 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.
+ * @param before (optional) reference DOM node for insertion.
+ * @return selection of the element
+ */
+export function selectOrCreateChild(container, tagName: string,
+ className?: string, before?) {
+ let child = selectChild(container, tagName, className);
+ if (!child.empty()) {
+ return child;
+ }
+ let newElement = document.createElementNS("http://www.w3.org/2000/svg",
+ tagName);
+ if (className) {
+ newElement.classList.add(className);
+ }
+
+ if (before) { // if before exists, insert
+ container.node().insertBefore(newElement, before);
+ } else { // otherwise, append
+ container.node().appendChild(newElement);
+ }
+ return d3.select(newElement)
+ // need to bind data to emulate d3_selection.append
+ .datum(container.datum());
+};
+
+/**
+ * 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.
+ * @return selection of the element, or an empty selection
+ */
+export function selectChild(container, tagName: string, className?: string) {
+ let children = container.node().childNodes;
+ for (let i = 0; i < children.length; i++) {
+ let child = children[i];
+ if (child.tagName === tagName &&
+ (!className || child.classList.contains(className))
+ ) {
+ return d3.select(child);
+ }
+ }
+ return d3.select(null);
+};
+
+/**
+ * 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 sceneBehavior Parent scene module.
+ * @param sceneClass class attribute of the scene (default="scene").
+ */
+export function buildGroup(container,
+ renderNode: render.RenderGroupNodeInformation,
+ sceneBehavior,
+ sceneClass: string) {
+ sceneClass = sceneClass || Class.Scene.GROUP;
+ let isNewSceneGroup = selectChild(container, "g", sceneClass).empty();
+ let sceneGroup = selectOrCreateChild(container, "g", sceneClass);
+
+ // core
+ let coreGroup = selectOrCreateChild(sceneGroup, "g", Class.Scene.CORE);
+ let coreNodes = _.reduce(renderNode.coreGraph.nodes(), (nodes, name) => {
+ let node = renderNode.coreGraph.node(name);
+ if (!node.excluded) {
+ nodes.push(node);
+ }
+ return nodes;
+ }, []);
+
+ if (renderNode.node.type === 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).
+ edge.buildGroup(coreGroup, renderNode.coreGraph, sceneBehavior);
+
+ // Create the layer of nodes for this scene (ellipses, rects etc).
+ node.buildGroup(coreGroup, coreNodes, sceneBehavior);
+
+ // In-extract
+ if (renderNode.isolatedInExtract.length > 0) {
+ let inExtractGroup = selectOrCreateChild(sceneGroup, "g",
+ Class.Scene.INEXTRACT);
+ node.buildGroup(inExtractGroup, renderNode.isolatedInExtract,
+ sceneBehavior);
+ } else {
+ selectChild(sceneGroup, "g", Class.Scene.INEXTRACT).remove();
+ }
+
+ // Out-extract
+ if (renderNode.isolatedOutExtract.length > 0) {
+ let outExtractGroup = selectOrCreateChild(sceneGroup, "g",
+ Class.Scene.OUTEXTRACT);
+ node.buildGroup(outExtractGroup, renderNode.isolatedOutExtract,
+ sceneBehavior);
+ } else {
+ selectChild(sceneGroup, "g", 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;
+};
+
+/**
+ * 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: render.RenderGroupNodeInformation) {
+ // 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.
+ let yTranslate = renderNode.node.type === NodeType.SERIES ?
+ 0 : layout.PARAMS.subscene.meta.labelHeight;
+
+ // core
+ translate(selectChild(sceneGroup, "g", Class.Scene.CORE),
+ 0, yTranslate);
+
+ // in-extract
+ let inExtractX = renderNode.coreBox.width === 0 ?
+ 0 : renderNode.coreBox.width;
+ let hasInExtract = renderNode.isolatedInExtract.length > 0;
+ if (hasInExtract) {
+ translate(selectChild(sceneGroup, "g", Class.Scene.INEXTRACT),
+ inExtractX, yTranslate);
+ }
+
+ // out-extract
+ let hasOutExtract = renderNode.isolatedOutExtract.length > 0;
+ if (hasOutExtract) {
+ let outExtractX = inExtractX + renderNode.inExtractBox.width
+ + renderNode.extractXOffset;
+ translate(selectChild(sceneGroup, "g", Class.Scene.OUTEXTRACT),
+ outExtractX, yTranslate);
+ }
+};
+
+/** Adds a click listener to a group that fires a graph-select event */
+export function addGraphClickListener(graphGroup, sceneBehavior) {
+ d3.select(graphGroup).on("click", () => {
+ sceneBehavior.fire("graph-select");
+ });
+};
+
+/** Helper for adding transform: translate(x0, y0) */
+export function translate(selection, x0: number, y0: number) {
+ selection.attr("transform", "translate(" + x0 + "," + y0 + ")");
+};
+
+/**
+ * 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.
+ */
+export function positionRect(rect, cx: number, cy: number, width: number,
+ height: number) {
+ rect.transition().attr({
+ x: cx - width / 2,
+ y: cy - height / 2,
+ width: width,
+ height: height
+ });
+};
+
+/**
+ * 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.
+ */
+export function positionButton(button,
+ renderNode: render.RenderNodeInformation) {
+ // Position the button in the top-right corner of the group node,
+ // with space given the draw the button inside of the corner.
+ let x = renderNode.x + renderNode.width / 2 - 6;
+ let y = renderNode.y - renderNode.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 === NodeType.SERIES && !renderNode.expanded) {
+ x += 10;
+ y -= 2;
+ }
+ let translateStr = "translate(" + x + "," + y + ")";
+ button.selectAll("path").transition().attr("transform", translateStr);
+ button.select("circle").transition().attr({
+ cx: x,
+ cy: y,
+ r: layout.PARAMS.nodeSize.meta.expandButtonRadius
+ });
+};
+
+/**
+ * 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.
+ */
+export function positionEllipse(ellipse, cx: number, cy: number,
+ width: number, height: number) {
+ ellipse.transition().attr({
+ cx: cx,
+ cy: cy,
+ rx: width / 2,
+ ry: height / 2
+ });
+};
+
+} // close module
diff --git a/tensorflow/tensorboard/components/tf-graph-common/lib/template.ts b/tensorflow/tensorboard/components/tf-graph-common/lib/template.ts
new file mode 100644
index 0000000000..b5aafc55e5
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-graph-common/lib/template.ts
@@ -0,0 +1,282 @@
+/// <reference path="graph.ts" />
+/// <reference path="hierarchy.ts" />
+
+module tf.graph.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)
+ */
+export function detect(h, verifyTemplate): {[templateId: string]: string[]} {
+ // 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.
+ let nnGroups = clusterSimilarSubgraphs(h);
+
+ // For each metanode, compare its subgraph (starting from shallower groups)
+ // and assign template id.
+ let 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();
+};
+
+/**
+ * @return Unique string for a metanode based on depth, |V|, |E| and
+ * op type histogram.
+ */
+ function getSignature(metanode) {
+ // depth=<number> |V|=<number> |E|=<number>
+ let 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
+ let 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: hierarchy.Hierarchy) {
+ /** a dict from metanode.signature() => Array of tf.graph.Groups */
+ let hashDict = _(h.getNodeMap()).reduce(function(hash, node: OpNode|Metanode, name) {
+ if (node.type !== NodeType.META) {
+ return hash;
+ }
+ let levelOfMetaNode = name.split("/").length - 1;
+ let signature = getSignature(node);
+ let 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 nn metanode with only one member
+ .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.
+ return _.reduce(nnGroups, function(templates, nnGroupPair) {
+ let signature = nnGroupPair[0],
+ nnGroup = nnGroupPair[1].nodes,
+ clusters = [];
+
+ nnGroup.forEach(function(metanode) {
+ // check with each existing cluster
+ for (let i = 0; i < clusters.length; i++) {
+ let 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;
+ }, {});
+}
+
+function sortNodes(names: string[], graph: graphlib.Graph<Metanode|OpNode, Metaedge>,
+ prefix: string) {
+ return _.sortByAll(names,
+ function(name) {
+ let node = graph.node(name);
+ return (<OpNode>node).op;
+ },
+ function(name) {
+ let node = graph.node(name);
+ return (<Metanode>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: graphlib.Graph<any, any>, g2: graphlib.Graph<any, any>) {
+ if (!tf.graph.hasSimilarDegreeSequence(g1, g2)) {
+ return false;
+ }
+
+ // if we want to skip, just return true here.
+ // return true;
+
+ // Verify sequence by running DFS
+ let g1prefix = g1.graph().name;
+ let g2prefix = g2.graph().name;
+
+ let visited1 = {};
+ let visited2 = {};
+ let stack = [];
+
+ /**
+ * push sources or successors into the stack
+ * if the visiting pattern has been similar.
+ */
+ function stackPushIfNotDifferent(n1, n2) {
+ let 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]) { // implied && !visited2[sub2]
+ visited1[sub1] = visited2[sub2] = true;
+ stack.push({n1: n1, n2: n2});
+ }
+
+ return false;
+ }
+
+ // check if have same # of sources then sort and push
+ let sources1 = g1.sources();
+ let 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 (let i = 0; i < sources1.length; i++) {
+ let different = stackPushIfNotDifferent(sources1[i], sources2[i]);
+ if (different) {
+ return false;
+ }
+ }
+
+ while (stack.length > 0) {
+ let cur = stack.pop();
+
+ // check node
+ let similar = isSimilarNode(g1.node(cur.n1), g2.node(cur.n2));
+ if (!similar) {
+ return false;
+ }
+
+ // check if have same # of successors then sort and push
+ let 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 (let j = 0; j < succ1.length; j++) {
+ let different = stackPushIfNotDifferent(succ1[j], succ2[j]);
+ if (different) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Returns if two nodes have identical structure.
+ */
+ function isSimilarNode(n1: OpNode|Metanode|SeriesNode, n2: OpNode|Metanode|SeriesNode): boolean {
+ if (n1.type === NodeType.META) {
+ // compare metanode
+ let metanode1 = <Metanode> n1;
+ let metanode2 = <Metanode> n2;
+ return metanode1.templateId && metanode2.templateId && metanode1.templateId === metanode2.templateId;
+ } else if (n1.type === NodeType.OP && n2.type === NodeType.OP) {
+ // compare leaf node
+ return (<OpNode>n1).op === (<OpNode>n2).op;
+ } else if (n1.type === NodeType.SERIES && n2.type === NodeType.SERIES) {
+ // compare series node sizes and operations
+ // (only need to check one op as all op nodes are identical in series)
+ let seriesnode1 = <SeriesNode> n1;
+ let seriesnode2 = <SeriesNode> n2;
+ let seriesnode1Count = seriesnode1.metagraph.nodeCount();
+ return (seriesnode1Count === seriesnode2.metagraph.nodeCount() &&
+ (seriesnode1Count === 0 ||
+ ((<OpNode>seriesnode1.metagraph.node(seriesnode1.metagraph.nodes()[0])).op ===
+ (<OpNode>seriesnode2.metagraph.node(seriesnode2.metagraph.nodes()[0])).op)));
+ }
+ return false;
+}
+}
diff --git a/tensorflow/tensorboard/components/tf-graph-common/tf-graph-common.html b/tensorflow/tensorboard/components/tf-graph-common/tf-graph-common.html
new file mode 100644
index 0000000000..e4cd153113
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-graph-common/tf-graph-common.html
@@ -0,0 +1,16 @@
+<script src="../../bower_components/d3/d3.js"></script>
+<script src="../../bower_components/lodash/lodash.js"></script>
+<script src="../../bower_components/graphlib/dist/graphlib.core.js"></script>
+
+<script src="lib/common.js"></script>
+<script src="lib/graph.js"></script>
+<script src="lib/parser.js"></script>
+<script src="lib/hierarchy.js"></script>
+<script src="lib/render.js"></script>
+<script src="lib/template.js"></script>
+<script src="lib/scene/scene.js"></script>
+<script src="lib/scene/annotation.js"></script>
+<script src="lib/scene/edge.js"></script>
+<script src="lib/scene/node.js"></script>
+<script src="lib/layout.js"></script>
+<script src="lib/colors.js"></script>
diff --git a/tensorflow/tensorboard/components/tf-graph-dashboard/tf-graph-dashboard.html b/tensorflow/tensorboard/components/tf-graph-dashboard/tf-graph-dashboard.html
new file mode 100644
index 0000000000..779d2d3fe9
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-graph-dashboard/tf-graph-dashboard.html
@@ -0,0 +1,118 @@
+<link rel="import" href="../../bower_components/polymer/polymer.html">
+<link rel="import" href="../tf-graph-loader/tf-graph-loader.html">
+<link rel="import" href="../tf-graph-board/tf-graph-board.html">
+<link rel="import" href="../tf-graph/tf-graph-controls.html">
+<link rel="import" href="../tf-dashboard-common/warning-style.html">
+
+<!--
+tf-graph-dashboard displays a graph from a TensorFlow run.
+
+It has simple behavior: Creates a url-generator and run-generator
+to talk to the backend, and then passes the runsWithGraph (list of runs with
+associated graphs) along with the url generator into tf-graph-board for display.
+
+If there are multiple runs with graphs, the first run's graph is shown
+by default. The user can select a different run from a dropdown menu.
+-->
+
+<dom-module id="tf-graph-dashboard">
+<template>
+<div id="plumbing">
+ <tf-url-generator
+ out-runs-url="{{_runsUrl}}"
+ out-graph-url-generator="{{_graphUrlGen}}"
+ id="urlGenerator"
+ ></tf-url-generator>
+ <tf-run-generator
+ id="runGenerator"
+ url="[[_runsUrl]]"
+ out-runs-with-graph="{{_runsWithGraph}}"
+ /></tf-run-generator>
+</div>
+<template is="dom-if" if="[[_datasetsEmpty(_datasets)]]">
+<div class="warning">
+ <p>
+ No graph definition files were found.
+ </p>
+ <p>
+ To store a graph, create a
+ <code>tf.python.training.summary_io.SummaryWriter</code>
+ and pass the graph either via the constructor, or by calling its
+ <code>add_graph()</code> method.
+ </p>
+</div>
+</template>
+<template is="dom-if" if="[[!_datasetsEmpty(_datasets)]]">
+<tf-dashboard-layout>
+<div class="sidebar">
+ <tf-graph-controls id="controls"
+ color-by-params="[[_colorByParams]]"
+ has-stats="[[_hasStats]]"
+ color-by="{{_colorBy}}",
+ datasets="[[_datasets]]",
+ selected-dataset="{{_selectedDataset}}"
+ selected-file="{{_selectedFile}}"
+ ></tf-graph-controls>
+ <tf-graph-loader id="loader"
+ datasets="[[_datasets]]",
+ selected-dataset="[[_selectedDataset]]"
+ selected-file="[[_selectedFile]]"
+ out-graph-hierarchy="{{_graphHierarchy}}"
+ out-graph="{{_graph}}"
+ out-graph-name="{{_graphName}}"
+ has-stats="{{_hasStats}}"
+ progress="{{_progress}}"
+ ></tf-graph-loader>
+</div>
+<div class="center">
+ <tf-graph-board id="graphboard"
+ graph-hierarchy="[[_graphHierarchy]]"
+ graph="[[_graph]]"
+ has-stats="[[_hasStats]]"
+ graph-name="[[_graphName]]"
+ progress="[[_progress]]"
+ color-by="[[_colorBy]]"
+ color-by-params="{{_colorByParams}}">
+ </tf-graph-board>
+</div>
+</template>
+<style>
+
+:host /deep/ {
+ font-family: 'Roboto', sans-serif;
+}
+
+.center {
+ height: 100%;
+}
+
+</style>
+<style include="warning-style"></style>
+</template>
+</dom-module>
+
+<script>
+(function() {
+Polymer({
+ is: 'tf-graph-dashboard',
+ properties: {
+ _runsWithGraph: Array,
+ _datasets: {
+ type: Object,
+ computed: '_getDatasets(_runsWithGraph, _graphUrlGen)'
+ }
+ },
+ _getDatasets: function(runsWithGraph, graphUrlGen) {
+ return _.map(runsWithGraph, function(runName) {
+ return {
+ name: runName,
+ path: graphUrlGen(runName)
+ };
+ });
+ },
+ _datasetsEmpty: function(datasets) {
+ return !datasets || !datasets.length;
+ }
+});
+})();
+</script>
diff --git a/tensorflow/tensorboard/components/tf-graph-info/tf-graph-info.html b/tensorflow/tensorboard/components/tf-graph-info/tf-graph-info.html
new file mode 100644
index 0000000000..b7900f86de
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-graph-info/tf-graph-info.html
@@ -0,0 +1,65 @@
+<link rel="import" href="../../bower_components/polymer/polymer.html">
+<link rel="import" href="tf-node-info.html">
+<dom-module id="tf-graph-info">
+<template>
+<style>
+:host {
+ font-size: 12px;
+ margin: 0;
+ padding: 0;
+ display: block;
+}
+
+h2 {
+ padding: 0;
+ text-align: center;
+ margin: 0;
+}
+</style>
+<template is="dom-if" if="{{selectedNode}}">
+ <paper-material elevation="1" class="card">
+ <tf-node-info graph-hierarchy='[[graphHierarchy]]'
+ flat-graph="[[graph]]"
+ node-name='[[selectedNode]]'
+ highlighted-node='{{highlightedNode}}'>
+ </tf-node-info>
+ </paper-material>
+</template>
+</template>
+<script>
+(function() {
+ Polymer({
+ is: 'tf-graph-info',
+
+ properties: {
+ title: String,
+ graphHierarchy: Object,
+ graph: Object,
+ // Two-ways
+ selectedNode: {
+ type: String,
+ notify: true
+ },
+ highlightedNode: {
+ type: String,
+ notify: true
+ }
+ },
+ 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;
+ }
+ });
+})();
+</script>
+</dom-module>
diff --git a/tensorflow/tensorboard/components/tf-graph-info/tf-node-info.html b/tensorflow/tensorboard/components/tf-graph-info/tf-node-info.html
new file mode 100644
index 0000000000..5044bf2bb1
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-graph-info/tf-node-info.html
@@ -0,0 +1,345 @@
+<link rel="import" href="../../bower_components/iron-collapse/iron-collapse.html">
+<link rel="import" href="../../bower_components/iron-list/iron-list.html">
+<link rel="import" href="../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../bower_components/paper-icon-button/paper-icon-button.html">
+<link rel="import" href="../../bower_components/paper-item/all-imports.html">
+<link rel="import" href="../tf-graph-common/tf-graph-common.html">
+<link rel="import" href="../tf-graph/tf-graph-icon.html">
+<link rel="import" href="tf-node-list-item.html">
+
+<dom-module id="tf-node-info">
+ <style>
+ .sub-list-group {
+ padding: 8px 12px 0px;
+ font-weight: 500;
+ font-size: 12pt;
+ }
+
+ .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;
+ }
+
+ paper-item {
+ padding: 0;
+ background: #e9e9e9;
+ }
+
+ paper-item-body[two-line] {
+ min-height: 0;
+ padding: 8px 12px 4px;
+ }
+
+ .expandedInfo {
+ padding: 0 0 8px;
+ }
+
+ .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;
+ }
+ </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">[[_getNodeName(nodeName)]]</div>
+ </div>
+ <div secondary>
+ <tf-graph-icon class="node-icon" node="[[_node]]"></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 card-node="[[_node]]"
+ item-node="[[_getNode(item, graphHierarchy)]]"
+ name="[[item]]"
+ item-type="predecessors">
+ </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}}">
+ <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="[[_getNode(item, graphHierarchy)]]"
+ name="[[item]]"
+ item-type="predecessors">
+ </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 card-node="[[_node]]"
+ item-node="[[_getNode(item, graphHierarchy)]]"
+ name="[[item]]"
+ item-type="successor">
+ </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}}">
+ <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="[[_getNode(item, graphHierarchy)]]"
+ name="[[item]]"
+ item-type="successors">
+ </tf-node-list-item>
+ </template>
+ </iron-list>
+ </template>
+ </iron-collapse>
+ </div>
+ </template>
+ </div>
+ </div>
+ </template>
+ </iron-collapse>
+ </template>
+
+ <script>
+ (function() {
+ Polymer({
+ is: 'tf-node-info',
+
+ properties: {
+ nodeName: String,
+ graphHierarchy: Object,
+ _node: {
+ type: Object,
+ computed: '_getNode(nodeName, graphHierarchy)',
+ observer: '_resetState'
+ },
+ _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
+ },
+ },
+ expandNode: function() {
+ this.fire('_node.expand', this.node);
+ },
+ _getNode: function(n, graphHierarchy) {
+ return graphHierarchy.node(n);
+ },
+ _getNodeName: function(nodeName) {
+ // Insert a zero-width whitespace character before each slash so that
+ // long node names wrap cleanly at path boundaries.
+ return (nodeName || '').replace(/\//g, '\u200B/');
+ },
+ _getAttributes: function(node) {
+ this.async(this._resizeList.bind(this, "#attributesList"));
+ return node && node.attr ? node.attr.map(function(entry) {
+ return {key: entry.key, value: JSON.stringify(entry.value)};
+ }) : [];
+
+ },
+ _getDevice: function(node) {
+ return node ? node.device : null;
+ },
+ _getSuccessors: function(node, hierarchy) {
+ this.async(this._resizeList.bind(this, "#inputsList"));
+ return node ? hierarchy.getSuccessors(node.name) : [[], []];
+ },
+ _getPredecessors: function(node, hierarchy) {
+ this.async(this._resizeList.bind(this, "#outputsList"));
+ return node ? hierarchy.getPredecessors(node.name) : [[], []];
+ },
+ _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;
+ },
+ _resizeList: function(selector) {
+ var list = document.querySelector(selector);
+ if (list) {
+ list.fire('iron-resize');
+ }
+ }
+ });
+ })();
+ </script>
+</dom-module>
diff --git a/tensorflow/tensorboard/components/tf-graph-info/tf-node-list-item.html b/tensorflow/tensorboard/components/tf-graph-info/tf-node-list-item.html
new file mode 100644
index 0000000000..f16e9e4511
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-graph-info/tf-node-list-item.html
@@ -0,0 +1,91 @@
+<link rel="import" href="../../bower_components/polymer/polymer.html">
+<link rel="import" href="../tf-graph/tf-graph-icon.html">
+
+<dom-module id="tf-node-list-item">
+ <style>
+ #list-item {
+ width: 100%;
+ color: #565656;
+ font-size: 11pt;
+ font-weight: 400;
+ position: relative;
+ }
+
+ #list-item:hover {
+ background-color: var(--google-yellow-100);
+ }
+
+ .clickable {
+ cursor: pointer;
+ }
+
+ #list-item span {
+ display: block;
+ margin-left: 40px;
+ }
+
+ #list-item.excluded span {
+ color: #999;
+ }
+
+ .node-icon {
+ position: absolute;
+ top: 1px;
+ left: 2px;
+ }
+ </style>
+ <template>
+ <div id="list-item"
+ on-mouseover="_nodeListener"
+ on-mouseout="_nodeListener"
+ on-click="_nodeListener">
+ <tf-graph-icon class="node-icon"
+ node="[[itemNode]]" height="12"></tf-graph-icon>
+ <span title$="[[name]]">[[name]]</span>
+ </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,
+ name: String,
+ itemType: {
+ type: String,
+ observer: '_itemTypeChanged'
+ }
+ },
+
+ _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
+ });
+ }
+
+ });
+ })();
+ </script>
+</dom-module>
diff --git a/tensorflow/tensorboard/components/tf-graph-loader/tf-graph-loader.html b/tensorflow/tensorboard/components/tf-graph-loader/tf-graph-loader.html
new file mode 100644
index 0000000000..3f290f2152
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-graph-loader/tf-graph-loader.html
@@ -0,0 +1,172 @@
+<link rel="import" href="../../bower_components/polymer/polymer.html">
+
+<!--
+An element which provides a filter parsing for pbtxt to graph output.
+-->
+<dom-module id="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,
+ readOnly: true // Produces, does not consume.
+ },
+ datasets: Array,
+ hasStats: {
+ type: Boolean,
+ readOnly: true, // This property produces data.
+ notify: true
+ },
+ 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
+ },
+ outGraphName: {
+ type: String,
+ readOnly: true,
+ notify: true
+ }
+ },
+ observers: [
+ '_selectedDatasetChanged(selectedDataset, datasets)'
+ ],
+ _parseAndConstructHierarchicalGraph: function(dataset, pbTxtContent) {
+ var self = this;
+ // Reset the progress bar to 0.
+ self._setProgress({
+ value: 0,
+ msg: ''
+ });
+ var tracker = {
+ setMessage: function(msg) {
+ self._setProgress({
+ value: self.progress.value,
+ msg: msg
+ });
+ },
+ updateProgress: function(value) {
+ self._setProgress({
+ value: self.progress.value + value,
+ msg: self.progress.msg
+ });
+ },
+ reportError: function(msg) {
+ self._setProgress({
+ value: self.progress.value,
+ msg: msg,
+ error: true
+ });
+ },
+ };
+ var statsJson;
+ var dataTracker = tf.getSubtaskTracker(tracker, 30, 'Data');
+ tf.graph.parser.readAndParseData(dataset, pbTxtContent, dataTracker)
+ .then(function(result) {
+ // Build the flat graph (consists only of Op nodes).
+ var nodes = result.nodes;
+ statsJson = result.statsJson;
+
+ // 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.getSubtaskTracker(tracker, 20,
+ 'Graph');
+ return tf.graph.build(nodes, buildParams, graphTracker);
+ })
+ .then(function(graph) {
+ this._setOutGraph(graph);
+ if (statsJson) {
+ // If there are associated stats, join them with the graph.
+ tf.time('Joining stats info with graph...', function() {
+ tf.graph.joinStatsInfoWithGraph(graph, statsJson);
+ });
+ }
+ var hierarchyParams = {
+ verifyTemplate: true,
+ groupSeries: true,
+ };
+ var hierarchyTracker = tf.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._setHasStats(statsJson != null);
+ this._setOutGraphHierarchy(graphHierarchy);
+ }.bind(this))
+ .catch(function(reason) {
+ tracker.reportError("Graph visualization failed: " + reason);
+ });
+ },
+ _selectedDatasetChanged: function(datasetIndex, datasets) {
+ var dataset = datasets[datasetIndex];
+ this._parseAndConstructHierarchicalGraph(dataset);
+ this._setOutGraphName(dataset.name);
+ },
+ _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 = '';
+
+ var reader = new FileReader();
+
+ reader.onload = function(e) {
+ this._parseAndConstructHierarchicalGraph(null, e.target.result);
+ }.bind(this);
+
+ reader.readAsText(file);
+ }
+});
+</script>
diff --git a/tensorflow/tensorboard/components/tf-graph/demo/tf-graph-demo.html b/tensorflow/tensorboard/components/tf-graph/demo/tf-graph-demo.html
new file mode 100644
index 0000000000..d6e736d185
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-graph/demo/tf-graph-demo.html
@@ -0,0 +1,185 @@
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../tf-graph-board/tf-graph-board.html">
+<link rel="import" href="../../tf-graph-loader/tf-graph-loader.html">
+<link rel="import" href="../../tf-graph/tf-graph-controls.html">
+<!-- Element for tf-graph demo page
+
+Example
+
+<tf-graph-demo></tf-graph-demo>
+
+-->
+
+<dom-module id="tf-graph-demo">
+<template>
+<style>
+
+:host /deep/ {
+ font-family: 'Roboto', sans-serif;
+}
+
+.main {
+ position: absolute;
+ right: 0;
+ left: 250px;
+ height: 100%;
+}
+
+.side {
+ position: absolute;
+ left: 0;
+ width: 250px;
+ height: 100%;
+ border: 1px solid black;
+ box-sizing: border-box;
+}
+
+.all {
+ position: relative;
+ width: 100%;
+ height: 100%
+}
+
+</style>
+<div class="all">
+ <div class="side">
+ <tf-graph-controls
+ color-by-params="[[colorByParams]]"
+ has-stats="[[hasStats]]"
+ color-by="{{colorBy}}"
+ datasets="[[datasets]]",
+ selected-dataset="{{selectedDataset}}"
+ selected-file="{{selectedFile}}"
+ ></tf-graph-controls>
+ <tf-graph-loader id="loader"
+ datasets="[[datasets]]",
+ selected-dataset="[[selectedDataset]]"
+ selected-file="[[selectedFile]]"
+ out-graph-hierarchy="{{graphHierarchy}}"
+ out-graph="{{graph}}"
+ out-graph-name="{{graphName}}"
+ has-stats="{{hasStats}}"
+ progress="{{_progress}}"
+ ></tf-graph-loader>
+ </div>
+ <div class="main">
+ <tf-graph-board id="graphboard"
+ graph-hierarchy="[[graphHierarchy]]"
+ graph="[[graph]]"
+ has-stats="[[hasStats]]"
+ graph-name="[[graphName]]"
+ progress="[[_progress]]"
+ color-by="[[colorBy]]"
+ color-by-params="{{colorByParams}}"
+ ></tf-graph-board>
+ </div>
+</div>
+</template>
+</dom-module>
+
+<script>
+(function(){
+
+var datasets = [
+ {
+ name: "Mnist Eval",
+ path: "mnist_eval.pbtxt",
+ },
+ {
+ name: "Mnist Train (with stats)",
+ path: "mnist_train.pbtxt",
+ statsPath: "mnist_train_stats.json"
+ },
+ {
+ name: "Inception Train (huge)",
+ path: "inception_train.pbtxt",
+ },
+ {
+ name: "Inception Train Eval",
+ path: "inception_train_eval.pbtxt",
+ },
+ {
+ name: "Inception Test",
+ path: "inception_test_eval.pbtxt",
+ },
+ {
+ name: "PTB Word LSTM Train",
+ path: "ptb_word_lstm_train.pbtxt",
+ },
+ {
+ name: "PTB Word LSTM Train Eval",
+ path: "ptb_word_lstm_train_eval.pbtxt",
+ },
+ {
+ name: "PTB Word LSTM Test",
+ path: "ptb_word_lstm_test_eval.pbtxt",
+ },
+ {
+ name: "Cifar10 Train",
+ path: "cifar10_train.pbtxt",
+ },
+ {
+ name: "Cifar10 Multi-GPU Train",
+ path: "cifar10_multi_gpu_train.pbtxt",
+ },
+ {
+ name: "Cifar10 Eval",
+ path: "cifar10_eval.pbtxt",
+ },
+ {
+ name: "Fatcat LSTM",
+ path: "fatcat_lstm.pbtxt",
+ },
+ {
+ name: "Legacy Inception Renamed",
+ path: "legacy_inception_renamed.pbtxt",
+ },
+ {
+ name: "Wolfe (Broken)",
+ path: "wolfe1.pbtxt",
+ },
+ {
+ name: "Wolfe (Fixed)",
+ path: "wolfe2.pbtxt",
+ },
+ {
+ name: "AlexNet",
+ path: "alexnet.pbtxt",
+ },
+ {
+ name: "TestError404",
+ path: "nofile"
+ }
+];
+
+Polymer({
+ is: 'tf-graph-demo',
+ properties: {
+ hasStats: Boolean,
+ datasets: {
+ type: Object,
+ value: function() {
+ return this._getDatasets();
+ }
+ },
+ selectedDataset: {
+ type: Number,
+ value: 1
+ },
+ _progress: Object
+ },
+ _getDatasets: function() {
+ return _.map(datasets, function(dataset) {
+ var result = {
+ name: dataset.name,
+ path: this.resolveUrl('tf_model_zoo/' + dataset.path)
+ };
+ if (dataset.statsPath != null) {
+ result.statsPath = this.resolveUrl('tf_model_zoo/' + dataset.statsPath);
+ }
+ return result;
+ }, this);
+ }
+});
+})();
+</script>
diff --git a/tensorflow/tensorboard/components/tf-graph/tf-graph-controls.html b/tensorflow/tensorboard/components/tf-graph/tf-graph-controls.html
new file mode 100644
index 0000000000..4a6901e911
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-graph/tf-graph-controls.html
@@ -0,0 +1,487 @@
+<link rel="import" href="../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../bower_components/paper-menu/paper-menu.html">
+<link rel="import" href="../../bower_components/paper-dropdown-menu/paper-dropdown-menu.html">
+
+<dom-module id="tf-graph-controls">
+<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 {
+ padding: 10px;
+}
+
+.legend-holder {
+ position: absolute;
+ bottom: 0;
+ padding-bottom: 10px;
+}
+
+#fit {
+ color: var(--paper-orange-500);
+}
+
+paper-radio-button {
+ 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 {
+ width: 165px;
+}
+.domainStart {
+ float: left;
+}
+.domainEnd {
+ float: right;
+}
+.colorBox {
+ width: 20px;
+}
+
+.image-icon {
+ width: 24px;
+ height: 24px;
+}
+
+.gray {
+ color: #666;
+}
+
+.title {
+ font-size: 16px;
+ margin: 8px 5px 8px 0;
+ color: black;
+}
+.title small {
+ font-weight: normal;
+}
+.deviceList {
+ max-height: 100px;
+ overflow-y: auto;
+}
+
+#file {
+ padding: 8px 0;
+}
+
+.color-text {
+ padding: 0 0 0 55px;
+}
+
+.fit-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;
+}
+
+.fit-button {
+ padding: 2px;
+ width: 30px;
+ height: 30px;
+}
+
+.hidden-input {
+ height: 0px;
+ width: 0px;
+ overflow:hidden;
+}
+
+.allcontrols .control-holder {
+ display: flex;
+ clear: both;
+}
+</style>
+<div class="allcontrols">
+ <div class="control-holder">
+ <paper-icon-button id="fit" icon="aspect-ratio" class="fit-button" on-click="fit" alt="Fit to screen">
+ </paper-icon-button>
+ <paper-button class="fit-button-text" on-click="fit">Fit to screen
+ </paper-button>
+ </div>
+ <div class="control-holder">
+ <div class="title">Run</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>
+ <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>
+ <div class="control-holder">
+ <div class="title">Color</div>
+ <paper-dropdown-menu no-label-float no-animations noink class="color-dropdown">
+ <paper-menu class="dropdown-content" selected="{{_colorByIndex}}">
+ <paper-item>Structure</paper-item>
+ <paper-item>Device</paper-item>
+ <template is="dom-if" if="[[hasStats]]">
+ <paper-item>Compute time</paper-item>
+ <paper-item>Memory</paper-item>
+ </template>
+ </paper-menu>
+ </paper-dropdown-menu>
+ </div>
+ <div>
+ <template is="dom-if" if="[[_isGradientColoring(colorBy)]]">
+ <svg width="160" 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 class="end" offset="100%"
+ stop-color$="[[_currentGradientParams.endColor]]"/>
+ </linearGradient>
+ </defs>
+ <rect x="0" y="0" width="160" height="20" fill="url(#linearGradient)"
+ stroke="black" />
+ </svg>
+ <div class="domainValues color-text">
+ <div class="domainStart">[[_currentGradientParams.minValue]]</div>
+ <div class="domainEnd">[[_currentGradientParams.maxValue]]</div>
+ </div>
+ </template>
+ <template is="dom-if" if="[[_equals(colorBy, 'structure')]]">
+ <div class="color-text">
+ color: same substructure<br/>
+ gray: unique substructure
+ </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/>
+ gray: unknown device
+ </div>
+ </template>
+ </div>
+ <div class="legend-holder">
+ <table>
+ <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"/>
+ </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" />
+ </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"/>
+ </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"/>
+ </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"/>
+ </svg>
+ </td>
+ <td>Constant</td>
+ </tr>
+ <tr>
+ <td>
+ <svg class="image-icon">
+ <image id="summary-icon" width="24" height="24" x="0" y="0"
+ class="image-icon"/>
+ </svg>
+ </td>
+ <td>Summary</td>
+ </tr>
+ <tr>
+ <td>
+ <svg class="icon" height="15px"
+ preserveAspectRatio="xMinYMid meet" viewBox="0 0 15 15">
+ <defs>
+ <marker id="arrowhead-legend" fill="#bbb" markerWidth="10"
+ markerHeight="10" refX="9" refY="5" orient="auto">
+ <path d="M 0,0 L 10,5 L 0,10 C 3,7 3,3 0,0"/>
+ </marker>
+ <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"/>
+ </marker>
+ </defs>
+ <path marker-end="url(#arrowhead-legend)" stroke="#bbb"
+ d="M2 9 l 23 0" stroke-linecap="round" />
+ </svg>
+ </td>
+ <td>Dataflow edge</td>
+ </tr>
+ <tr>
+ <td>
+ <svg class="icon" height="15px"
+ preserveAspectRatio="xMinYMid meet" viewBox="0 0 15 15">
+ <path marker-end="url(#arrowhead-legend)" stroke="#bbb"
+ d="M2 9 l 23 0" stroke-linecap="round" stroke-dasharray="2, 2" />
+ </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)"
+ marker-end="url(#arrowhead-legend)" stroke="#bbb" d="M2 9 l 23 0"
+ stroke-linecap="round" />
+ </svg>
+ </td>
+ <td>Reference edge</td>
+ </tr>
+ </table>
+ </div>
+ </div>
+</template>
+<script>
+(function() { // Private scope.
+Polymer({
+ is: 'tf-graph-controls',
+ ready: function() {
+ // Set the url to download the summary icon.
+ d3.select(this.$['summary-icon'])
+ .attr('xlink:href', this.resolveUrl('../../lib/svg/summary-icon.svg'));
+ },
+ properties: {
+ // Public API.
+ hasStats: {
+ type: Boolean
+ },
+ colorBy: {
+ type: String,
+ notify: true,
+ computed: '_getColorBy(_colorByIndex)'
+ },
+ colorByParams: Object,
+ datasets: {
+ type: Array,
+ observer: '_datasetsChanged'
+ },
+ selectedDataset: {
+ type: Number,
+ notify: true,
+ value: 0,
+ },
+ selectedFile: {
+ type: Object,
+ notify: true
+ },
+ // Private API.
+ _colorByIndex: {
+ type: Number,
+ value: 0 // Defaults to 'structure'.
+ },
+ _currentGradientParams: {
+ type: Object,
+ computed: '_getCurrentGradientParams(colorByParams, colorBy)'
+ }
+ },
+ _getColorBy: function(colorByIndex) {
+ return ["structure", "device", "compute_time", "memory"][colorByIndex];
+ },
+ _getBackgroundColor: function(color) {
+ return 'background-color:' + color;
+ },
+ fit: function() {
+ document.querySelector('#scene').fit();
+ },
+ _isGradientColoring: function(colorBy) {
+ return ["compute_time", "memory"].indexOf(colorBy) !== -1;
+ },
+ _equals: function(a, b) {
+ return a === b;
+ },
+ _getCurrentGradientParams: function(colorByParams, colorBy) {
+ if (!this._isGradientColoring(colorBy)) {
+ return;
+ }
+ var params = colorByParams[colorBy];
+ var minValue = params.minValue;
+ var maxValue = params.maxValue;
+ if (colorBy === 'memory') {
+ minValue = convertToHumanReadable(minValue, MEMORY_UNITS);
+ maxValue = convertToHumanReadable(maxValue, MEMORY_UNITS);
+ } else if (colorBy === 'compute_time') {
+ minValue = convertToHumanReadable(minValue, TIME_UNITS);
+ maxValue = convertToHumanReadable(maxValue, TIME_UNITS);
+ }
+ return {
+ minValue: minValue,
+ maxValue: maxValue,
+ startColor: params.startColor,
+ endColor: params.endColor
+ };
+ },
+ _updateFileInput: function(e) {
+ this.set('selectedFile', e);
+ },
+ _datasetsChanged: function(newDatasets, oldDatasets) {
+ if (oldDatasets != null || this.selected == null) {
+ // Select the first dataset by default.
+ this.set('selectedDataset', 0);
+ }
+ },
+ _getFile: function() {
+ this.$.file.click();
+ }
+});
+
+// Private methods.
+var 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}
+];
+var 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 convertToHumanReadable(value, units, unitIndex) {
+ unitIndex = unitIndex == null ? 0 : unitIndex;
+ if (unitIndex + 1 < units.length && value >= units[unitIndex + 1].numUnits) {
+ return convertToHumanReadable(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;
+}
+})(); // Closing private scope.
+</script>
+</dom-module>
diff --git a/tensorflow/tensorboard/components/tf-graph/tf-graph-icon.html b/tensorflow/tensorboard/components/tf-graph/tf-graph-icon.html
new file mode 100644
index 0000000000..fafaa3b954
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-graph/tf-graph-icon.html
@@ -0,0 +1,164 @@
+<link rel="import" href="../../bower_components/polymer/polymer.html">
+
+<dom-module id="tf-graph-icon">
+ <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 fill="white" stroke="#848484" cx="5" cy="5" r="3" />
+ </svg>
+ </template>
+ <template is="dom-if" if="[[_isSummary(node, summary)]]">
+ <img height$="[[height]]"
+ src="[[resolveUrl('../../lib/svg/summary-icon.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="white"
+ stroke="#ccc"
+ x="8" y="4" />
+ </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="#d9d9d9" stroke="#ccc" stroke-width="2px"
+ height="14" width="35"
+ rx="5" ry="5" />
+ </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="white"
+ stroke="#ccc"
+ x="0" y="2" />
+ </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="white"
+ stroke="#ccc"
+ x="0" y="1" />
+ </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
+ },
+
+ /** Type of node to draw. */
+ 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
+ },
+
+ /** Height of the SVG element in pixels, used for scaling. */
+ height: {
+ type: Number,
+ value: 20
+ }
+ },
+
+ /**
+ * 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);
+ }
+ });
+ })();
+ </script>
+</dom-module>
diff --git a/tensorflow/tensorboard/components/tf-graph/tf-graph-minimap.html b/tensorflow/tensorboard/components/tf-graph/tf-graph-minimap.html
new file mode 100644
index 0000000000..2b6beeaded
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-graph/tf-graph-minimap.html
@@ -0,0 +1,69 @@
+<link rel="import" href="../../bower_components/polymer/polymer.html">
+<script src="../tf-graph-common/lib/scene/minimap.js"></script>
+<dom-module id="tf-graph-minimap">
+<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>
+<!-- Additional canvas to use as buffer to avoid flickering between updates -->
+<canvas class="second"></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>
diff --git a/tensorflow/tensorboard/components/tf-graph/tf-graph-params.html b/tensorflow/tensorboard/components/tf-graph/tf-graph-params.html
new file mode 100644
index 0000000000..576816ddd0
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-graph/tf-graph-params.html
@@ -0,0 +1,113 @@
+<link rel="import" href="../../bower_components/polymer/polymer.html">
+<!--
+Module for adjusting render graph building parameter.
+-->
+<dom-module id="tf-graph-params">
+</dom-module>
+<script>
+ Polymer({
+
+ is: 'tf-graph-params',
+
+ properties: {
+ // PARAMETERS
+
+ enableExtraction: {
+ type: Boolean,
+ value: true
+ },
+
+ /** Maximum in-degree that a node can have without being considered as
+ * high in-degree node. */
+ maxInDegree: {
+ type: Number,
+ value: 4
+ },
+ /** Maximum out-degree that a node can have without being considered as
+ * high out-degree node. */
+ maxOutDegree: {
+ type: Number,
+ value: 4
+ },
+ /** Maximum number of control edges a node can have before they aren't
+ * displayed. */
+ maxControlDegree: {
+ type: Number,
+ value: 4
+ },
+
+ /**
+ * Types patterns for predefined out-extract nodes, which are
+ * sink-like nodes that will be extracted from the main graph.
+ */
+ outExtractTypes: {
+ type: Array,
+ value: function() {
+ return [
+ 'NoOp' // for "sgd", "momentum" group
+ ];
+ }
+ },
+
+ /**
+ * Types patterns for predefined in-extract nodes, which are
+ * source-like nodes that will be extracted from the main graph.
+ */
+ inExtractTypes: {
+ type: Array,
+ value: function() {
+ return ['Variable'];
+ }
+ },
+
+ /**
+ * 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: {
+ type: Boolean,
+ value: false
+ },
+
+ /**
+ * 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: {
+ type: Boolean,
+ value: true
+ },
+
+ /**
+ * Whether to draw bridge paths inside of expanded group nodes.
+ */
+ enableBridgegraph: {
+ type: Boolean,
+ value: true
+ },
+
+ /**
+ * Colors for the minimum and maximum values whenever we have a gradient
+ * scale.
+ */
+ minMaxColors: {
+ type: Array,
+ value: function() {
+ return ["#fff5f0", "#fb6a4a"];
+ }
+ },
+
+ /**
+ * Maximum number of annotations to be displayed on a node before an
+ * ellipsis is used.
+ */
+ maxAnnotations: {
+ type: Number,
+ value: 5
+ }
+ }
+ });
+</script>
diff --git a/tensorflow/tensorboard/components/tf-graph/tf-graph-scene.html b/tensorflow/tensorboard/components/tf-graph/tf-graph-scene.html
new file mode 100644
index 0000000000..34c2d3dc3d
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-graph/tf-graph-scene.html
@@ -0,0 +1,475 @@
+<link rel="import" href="../../bower_components/polymer/polymer.html">
+<link rel="import" href="tf-graph-style.html">
+<link rel="import" href="tf-graph-minimap.html">
+<script src="../tf-graph-common/lib/layout.js"></script>
+<script src="../../bower_components/dagre/dist/dagre.core.js"></script>
+<!--
+ A module that takes graph-hierarchy as input and produce
+ a svg dom using dagre and d3.
+-->
+<dom-module id="tf-graph-scene">
+<template>
+<style include="tf-graph-style">
+ :host {
+ font-size: 20px;
+ }
+ .titleContainer {
+ position: relative;
+ }
+ .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>
+ <!-- Arrow head for edge paths. -->
+ <marker id="arrowhead" markerWidth="10" markerHeight="10"
+ refX="9" refY="5" orient="auto">
+ <path d="M 0,0 L 10,5 L 0,10 C 3,7 3,3 0,0"/>
+ </marker>
+ <marker id="ref-arrowhead" 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"/>
+ </marker>
+ <!-- Arrow head for annotation edge paths. -->
+ <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"/>
+ </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"/>
+ </marker>
+ <!-- Template for an Op node ellipse. -->
+ <ellipse id="op-node-stamp"
+ rx="7.5" ry="3" stroke="inherit" fill="inherit" />
+ <!-- Template for an Op node annotation ellipse (smaller). -->
+ <ellipse id="op-node-annotation-stamp"
+ rx="5" ry="2" stroke="inherit" fill="inherit" />
+ <!-- Vertically stacked series of Op nodes when unexpanded. -->
+ <g id="op-series-vertical-stamp">
+ <use xlink:href="#op-node-stamp" x="8" y="9" />
+ <use xlink:href="#op-node-stamp" x="8" y="6" />
+ <use xlink:href="#op-node-stamp" x="8" y="3" />
+ </g>
+ <!-- Horizontally stacked series of Op nodes when unexpanded. -->
+ <g id="op-series-horizontal-stamp">
+ <use xlink:href="#op-node-stamp" x="16" y="4" />
+ <use xlink:href="#op-node-stamp" x="12" y="4" />
+ <use xlink:href="#op-node-stamp" x="8" y="4" />
+ </g>
+ <!-- Horizontally stacked series of Op nodes for annotation. -->
+ <g id="op-series-annotation-stamp">
+ <use xlink:href="#op-node-annotation-stamp" x="9" y="2" />
+ <use xlink:href="#op-node-annotation-stamp" x="7" y="2" />
+ <use xlink:href="#op-node-annotation-stamp" x="5" y="2" />
+ </g>
+ <!--
+ Where the linearGradient for each node is stored. Used when coloring
+ by proportions of devices.
+ -->
+ <g id="linearGradients"></g>
+ </defs>
+ <!-- Make a large rectangle that fills the svg space so that
+ zoom events get captured on safari -->
+ <rect fill="white" width="10000" height="10000"></rect>
+ <g id="root"></g>
+</svg>
+<tf-graph-minimap id="minimap"></tf-graph-minimap>
+</template>
+</dom-module>
+<script>
+Polymer({
+ is: 'tf-graph-scene',
+ properties: {
+ graphHierarchy: Object,
+ name: String,
+ colorBy: {
+ type: String,
+ observer: '_colorByChanged'
+ },
+ /** @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.
+ */
+ templateIndex: Object,
+ /**
+ * @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
+ },
+ observers: [
+ '_buildAndFit(graphHierarchy)'
+ ],
+ getNode: function(nodeName) {
+ return this.graphHierarchy.getRenderNodeByName(nodeName);
+ },
+ isNodeExpanded: function(node) {
+ return node.expanded;
+ },
+ setNodeExpanded: function(renderNode) {
+ this._build(this.graphHierarchy);
+ },
+ /**
+ * 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(graphHierarchy) {
+ if (!graphHierarchy) { return; } //handle untruthy input
+ var templateNames = d3.keys(graphHierarchy.hierarchy.templates);
+
+ this.templateIndex = d3.scale.ordinal()
+ .domain(templateNames)
+ .range(d3.range(0, templateNames.length));
+ tf.time('tf-graph-scene (layout):', function() {
+ // layout the scene for this meta / series node
+ tf.graph.layout.scene(graphHierarchy.root, this);
+ }.bind(this));
+
+ tf.time('tf-graph-scene (build scene):', function() {
+ tf.graph.scene.buildGroup(d3.select(this.$.root), graphHierarchy.root, this);
+ tf.graph.scene.addGraphClickListener(this.$.svg, this);
+ }.bind(this));
+ // Update the minimap again when the graph is done animating.
+ setTimeout(function() {
+ 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);
+ },
+ _buildAndFit: function(graphHierarchy) {
+ this._resetState();
+ this._build(graphHierarchy);
+ // Fit to screen after the graph is done animating.
+ setTimeout(this.fit.bind(this), tf.graph.layout.PARAMS.animation.duration);
+ },
+ _updateLabels: function(showLabels) {
+ var titleStyle = this.getElementsByClassName('title')[0].style;
+ var auxTitleStyle = this.getElementsByClassName('auxTitle')[0].style;
+ var core = this.getElementsByClassName(tf.graph.scene.Class.Scene.CORE)[0];
+ // Only show labels if the graph is fully loaded.
+ if (showLabels && core && this.progress && this.progress.value === 100) {
+ var aux =
+ this.getElementsByClassName(tf.graph.scene.Class.Scene.INEXTRACT)[0] ||
+ this.getElementsByClassName(tf.graph.scene.Class.Scene.OUTEXTRACT)[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';
+ 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() {
+ // 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() {
+ 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];
+ },
+ /**
+ * 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);
+ },
+
+ _selectedNodeChanged: function(selectedNode, oldSelectedNode) {
+ if (selectedNode === oldSelectedNode) {
+ return;
+ }
+
+ if (selectedNode) {
+ this._updateNodeState(selectedNode);
+ }
+ if (oldSelectedNode) {
+ this._updateNodeState(oldSelectedNode);
+ }
+
+ if (!selectedNode) {
+ return;
+ }
+ // Update the minimap to reflect the highlighted (selected) node.
+ this.minimap.update();
+ var node = this.graphHierarchy.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.graphHierarchy.buildSubhierarchy(parentName);
+ var renderNode = this.graphHierarchy.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>
diff --git a/tensorflow/tensorboard/components/tf-graph/tf-graph-style.html b/tensorflow/tensorboard/components/tf-graph/tf-graph-style.html
new file mode 100644
index 0000000000..3e6f7f2112
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-graph/tf-graph-style.html
@@ -0,0 +1,339 @@
+<dom-module id="tf-graph-style">
+<template>
+<style>
+:host {
+ display: flex;
+ width: 100%;
+}
+
+::content #svg {
+ overflow: hidden;
+ flex: 1;
+}
+
+::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;
+}
+
+
+/* --- 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 .annotation > .annotation-edge.refline {
+ marker-start: url(#ref-annotation-arrowhead);
+}
+
+::content .annotation > .annotation-control-edge {
+ stroke-dasharray: 1, 1;
+}
+
+::content #annotation-arrowhead {
+ fill: #aaa;
+}
+
+::content #ref-annotation-arrowhead {
+ fill: #aaa;
+}
+
+::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;
+ marker-end: url(#arrowhead);
+ stroke: #bbb;
+ stroke-linecap: round;
+ stroke-width: 0.75;
+}
+
+::content .edge > path.edgeline.refline {
+ marker-start: url(#ref-arrowhead);
+}
+
+::content #arrowhead {
+ fill: #bbb;
+}
+
+::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;
+}
+</style>
+</template>
+</dom-module>
diff --git a/tensorflow/tensorboard/components/tf-graph/tf-graph.html b/tensorflow/tensorboard/components/tf-graph/tf-graph.html
new file mode 100644
index 0000000000..905d96e237
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-graph/tf-graph.html
@@ -0,0 +1,221 @@
+<link rel="import" href="../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../bower_components/iron-flex-layout/iron-flex-layout.html">
+<link rel="import" href="../../bower_components/iron-icons/iron-icons.html">
+<link rel="import" href="../../bower_components/paper-button/paper-button.html">
+<link rel="import" href="../../bower_components/paper-input/paper-input.html">
+<link rel="import" href="../../bower_components/paper-toggle-button/paper-toggle-button.html">
+<link rel="import" href="../tf-graph-common/tf-graph-common.html">
+<link rel="import" href="tf-graph-scene.html">
+<link rel="import" href="tf-graph-params.html">
+<dom-module id="tf-graph">
+<template>
+<style>
+.container {
+ width: 100%;
+ height: 100%;
+}
+
+.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">
+ <tf-graph-params id="graphParams"></tf-graph-params>
+ <div class="vertical">
+ <h2>[[title]]</h2>
+ <tf-graph-scene id="scene" class="auto"
+ graph-hierarchy="[[_renderHierarchy]]"
+ highlighted-node="[[_getVisible(highlightedNode)]]"
+ selected-node="[[selectedNode]]"
+ color-by="[[colorBy]]"
+ name="[[graphName]]"
+ progress="[[progress]]"
+ ></tf-graph-scene>
+ </div>
+</div>
+</template>
+</dom-module>
+
+<script>
+Polymer({
+
+ is: 'tf-graph',
+
+ properties: {
+ graphHierarchy: {
+ type: Object,
+ notify: true,
+ observer: '_graphChanged'
+ },
+ 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.
+ },
+ // internal properties
+ _graphParams: {
+ type: Object,
+ value: function() {
+ return this.$.graphParams;
+ }
+ },
+ _renderDepth: {
+ type: Number,
+ value: 1
+ },
+ _renderHierarchy: {
+ type: Object,
+ readOnly: true,
+ notify: true,
+ computed: '_buildRenderHierarchy(graphHierarchy, _graphParams)'
+ },
+ _allowGraphSelect: {
+ type: Boolean,
+ value: true
+ }
+ },
+ _buildRenderHierarchy: function(graphHierarchy, params) {
+ return tf.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.RenderGraphInformation(
+ graphHierarchy, params);
+ // 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)
+ };
+ })
+ });
+ return renderGraph;
+ }.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',
+
+ // 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) {
+ 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;
+ this.querySelector('#scene').setNodeExpanded(renderNode);
+ // Also select the expanded node.
+ this._nodeSelected(event);
+ },
+ not: function(x) {
+ return !x;
+ }
+});
+</script>
diff --git a/tensorflow/tensorboard/components/tf-histogram-dashboard/tf-histogram-dashboard.html b/tensorflow/tensorboard/components/tf-histogram-dashboard/tf-histogram-dashboard.html
new file mode 100644
index 0000000000..8f8f159964
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-histogram-dashboard/tf-histogram-dashboard.html
@@ -0,0 +1,210 @@
+<link rel="import" href="../../bower_components/polymer/polymer.html">
+<link rel="import" href="../tf-event-dashboard/tf-data-coordinator.html">
+<link rel="import" href="../tf-event-dashboard/tf-tooltip-coordinator.html">
+<link rel="import" href="../tf-event-dashboard/tf-run-selector.html">
+<link rel="import" href="../tf-event-dashboard/tf-x-type-selector.html">
+<link rel="import" href="../tf-dashboard-common/tf-run-generator.html">
+<link rel="import" href="../tf-event-dashboard/tf-color-scale.html">
+<link rel="import" href="../tf-dashboard-common/tf-url-generator.html">
+<link rel="import" href="../tf-dashboard-common/tf-dashboard-layout.html">
+<link rel="import" href="../tf-dashboard-common/dashboard-style.html">
+<link rel="import" href="../tf-dashboard-common/warning-style.html">
+<link rel="import" href="../tf-categorizer/tf-categorizer.html">
+<link rel="import" href="../tf-event-dashboard/tf-chart.html">
+<link rel="import" href="../tf-collapsable-pane/tf-collapsable-pane.html">
+<link rel="import" href="../../bower_components/iron-collapse/iron-collapse.html">
+<link rel="import" href="../../bower_components/paper-icon-button/paper-icon-button.html">
+<link rel="import" href="../imports/lodash.html">
+
+<!--
+tf-histogram-dashboard is a complete frontend that loads runs from a backend,
+and creates chart panes that display data for those runs.
+
+It provides a categorizer, run selector, and x type selector, by which the user
+can customize how data is organized and displayed.
+
+Each chart has a button that can toggle whether it is "selected"; selectedRuns
+charts are larger.
+
+Organizationally, the #plumbing div contains components that have no concrete
+manifestation and just effect data bindings or data loading. The #sidebar contains
+shared controls like the tf-categorizer, tf-run-selector, and tf-x-type-selector.
+The #center div contains tf-charts embedded inside tf-collapsable-panes.
+-->
+<dom-module id="tf-histogram-dashboard">
+ <template>
+ <div id="plumbing">
+ <tf-url-generator
+ out-runs-url="{{runsUrl}}"
+ out-compressed-histograms-url-generator="{{compressedHistogramsUrlGen}}"
+ id="urlGenerator"
+ ></tf-url-generator>
+
+ <tf-data-coordinator
+ id="dataCoordinator"
+ url-generator="[[compressedHistogramsUrlGen]]"
+ run-to-tag="[[runToCompressedHistograms]]"
+ color-scale="[[colorScale]]"
+ out-data-coordinator="{{dataCoordinator}}"
+ /></tf-data-coordinator>
+
+ <tf-run-generator
+ id="runGenerator"
+ url="[[runsUrl]]"
+ out-run-to-compressed-histograms="{{runToCompressedHistograms}}"
+ /></tf-run-generator>
+
+ <tf-color-scale
+ id="colorScale"
+ runs="[[_runs]]"
+ out-color-scale="{{colorScale}}"
+ out-class-scale="{{classScale}}"
+ ></tf-color-scale>
+
+ <tf-tooltip-coordinator
+ id="tooltipCoordinator"
+ out-tooltip-updater="{{tooltipUpdater}}"
+ out-tooltip-map="{{tooltipMap}}"
+ out-x-value="{{tooltipXValue}}"
+ out-closest-run="{{closestRun}}"
+ ></tf-tooltip-coordinator>
+ </div>
+
+ <tf-dashboard-layout>
+ <div class="sidebar">
+
+ <tf-categorizer
+ id="categorizer"
+ tags="[[_visibleTags]]"
+ categories="{{categories}}"
+ ></tf-categorizer>
+
+ <tf-x-type-selector
+ id="xTypeSelector"
+ out-x-type="{{xType}}"
+ ></tf-x-type-selector>
+
+ <tf-run-selector
+ id="runSelector"
+ runs="[[_runs]]"
+ class-scale="[[classScale]]"
+ out-selected="{{selectedRuns}}"
+ tooltips="[[tooltipMap]]"
+ closest-run="[[closestRun]]"
+ x-value="[[tooltipXValue]]"
+ x-type="[[xType]]"
+ ></tf-run-selector>
+
+ </div>
+
+ <div class="center">
+ <template is="dom-if" if="[[!categories.length]]">
+ <div class="warning">
+ <p>
+ No histogram tags were found.
+ </p>
+ <p>
+ Maybe data hasn't loaded yet, or maybe you need
+ to add some <code>tf.histogram_summary</code> ops to your graph, and
+ serialize them using the <code>tf.training.summary_io.SummaryWriter</code>.
+ </p>
+ </div>
+ </template>
+ <template is="dom-repeat" items="[[categories]]">
+ <tf-collapsable-pane name="[[item.name]]" count="[[_count(item.tags, selectedRuns.*, runToCompressedHistograms.*)]]">
+ <div class="layout horizontal wrap">
+ <template is="dom-repeat" items="[[item.tags]]" as="tag">
+ <template is="dom-repeat" items="[[selectedRuns]]" as="run">
+ <template is="dom-if" if="[[_exists(run, tag, runToCompressedHistograms.*)]]">
+ <div class="card">
+ <span class="card-title">[[tag]]</span>
+ <div class="card-content">
+ <tf-chart
+ tag="[[tag]]"
+ type="compressedHistogram"
+ id="chart"
+ selected-runs="[[_array(run)]]"
+ x-type="[[xType]]"
+ data-coordinator="[[dataCoordinator]]"
+ color-scale="[[colorScale]]"
+ on-keyup="toggleSelected"
+ tabindex="2"
+ tooltip-updater="[[tooltipUpdater]]"
+ ></tf-chart>
+ <paper-icon-button
+ class="expand-button"
+ icon="fullscreen"
+ on-tap="toggleSelected"
+ ></paper-icon-button>
+ </div>
+ </div>
+ </template>
+ </template>
+ </template>
+ </div>
+ </tf-collapsable-pane>
+ </template>
+ </div>
+ </tf-dashboard-layout>
+
+ <style include="dashboard-style"></style>
+ <style include="warning-style"></style>
+ </template>
+
+ <script>
+ Polymer({
+ is: "tf-histogram-dashboard",
+ properties: {
+ _runs: {
+ type: Array,
+ computed: "_getRuns(runToCompressedHistograms)",
+ },
+ _visibleTags: {
+ type: Array,
+ computed: "_getVisibleTags(selectedRuns.*, runToCompressedHistograms.*)"
+ }
+ },
+ _exists: function(run, tag, runToCompressedHistogramsChange) {
+ var runToCompressedHistograms = runToCompressedHistogramsChange.base;
+ return runToCompressedHistograms[run].indexOf(tag) !== -1;
+ },
+ _array: function(x) {
+ return [x];
+ },
+ _count: function(tags, selectedRunsChange, runToCompressedHistogramsChange) {
+ var selectedRuns = selectedRunsChange.base;
+ var runToCompressedHistograms = runToCompressedHistogramsChange.base;
+ var targetTags = {};
+ tags.forEach(function(t) {
+ targetTags[t] = true;
+ });
+ var count = 0;
+ selectedRuns.forEach(function(r) {
+ runToCompressedHistograms[r].forEach(function(t) {
+ if (targetTags[t]) {
+ count++;
+ }
+ });
+ });
+ return count;
+ },
+ _getRuns: function(runToCompressedHistograms) {
+ return _.keys(runToCompressedHistograms);
+ },
+ _getVisibleTags: function(selectedRunsChange, runToCompressedHistogramsChange) {
+ var keys = selectedRunsChange.base;
+ var dict = runToCompressedHistogramsChange.base;
+ return _.union.apply(null, keys.map(function(k) {return dict[k]}));
+ },
+ toggleSelected: function(e) {
+ var currentTarget = Polymer.dom(e.currentTarget);
+ var parentDiv = currentTarget.parentNode.parentNode;
+ parentDiv.classList.toggle("selected");
+ var chart = currentTarget.previousElementSibling;
+ if (chart) {
+ chart.redraw();
+ }
+ },
+ });
+ </script>
+</dom-module>
diff --git a/tensorflow/tensorboard/components/tf-image-dashboard/demo/image-loader-demo.html b/tensorflow/tensorboard/components/tf-image-dashboard/demo/image-loader-demo.html
new file mode 100644
index 0000000000..7aafd247f3
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-image-dashboard/demo/image-loader-demo.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+ <script src="../../../bower_components/d3/d3.js"></script>
+ <script src="../../../bower_components/plottable/plottable.js"></script>
+ <link rel="stylesheet" type="text/css" href="../../../bower_components/plottable/plottable.css">
+ <link rel="import" href="../tf-image-dashboard.html">
+ <title>Event Dashboard Demo Demo</title>
+ </head>
+ <body>
+ <script>
+ TF.Urls.runsUrl = function() {
+ return "data/runs.json"
+ };
+ TF.Urls.scalarsUrl = function(tag, run) {
+ return "data/" + run + "/" + tag + ".json";
+ };
+ </script>
+
+ <dom-module id="x-demo">
+ <style>
+ #loader {
+ width: 300px;
+ height: 300px;
+ }
+ </style>
+ <template>
+ <tf-image-loader
+ id="loader"
+ run="[[run]]"
+ tag="[[tag]]"
+ images-generator="[[imagesGenerator]]"
+ individual-image-generator="[[individualImageGenerator]]"
+ ></tf-image-loader>
+ </template>
+ <script>
+ var imagesUrl = function(tag, run) {
+ return "data/images/" + run + "/" + tag + ".json";
+ };
+ var individualImageUrl = function(query) {
+ return "data/individualImage/" + query + ".png";
+ };
+ Polymer({
+ is: "x-demo",
+ properties: {
+ run: {
+ type: String,
+ value: "train",
+ },
+ tag: {
+ type: String,
+ value: "reconstruction_07%2Fimage%2F2"
+ },
+ imagesGenerator: {
+ type: Function,
+ value: function() {
+ return imagesUrl;
+ },
+ },
+ individualImageGenerator: {
+ type: Function,
+ value: function() {
+ return individualImageUrl;
+ },
+ },
+ },
+ });
+ </script>
+ </dom-module>
+ <x-demo id="demo"></x-demo>
+ </body>
+</html>
diff --git a/tensorflow/tensorboard/components/tf-image-dashboard/demo/index.html b/tensorflow/tensorboard/components/tf-image-dashboard/demo/index.html
new file mode 100644
index 0000000000..4645b4b783
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-image-dashboard/demo/index.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+ <link rel="stylesheet" type="text/css" href="../../../lib/css/global.css">
+ <link rel="import" href="../tf-image-dashboard.html">
+ <title>Image Dashboard Demo</title>
+ <style>
+ #container{
+ width: 500px;
+ height: 800px;
+ border: 2px solid grey;
+ }
+ html,body {
+ height: 100%;
+ }
+ </style>
+ </head>
+ <body>
+ <script>
+ TF.Urls.runsUrl = function() {
+ return "data/runs.json"
+ };
+ TF.Urls.imagesUrl = function(tag, run) {
+ return "data/images/" + run + "/" + tag + ".json";
+ };
+ TF.Urls.individualImageUrl = function(query) {
+ return "data/individualImage/" + query + ".png";
+ }
+ </script>
+
+ <p>The image dashboard is deliberately inside a small container
+ so that it's easy to test that the scroll bars display properly.</p>
+ <p>Looks goofy though.</p>
+ <div id="container">
+ <tf-image-dashboard id="demo"></tf-image-dashboard>
+ </div>
+ </body>
+</html>
diff --git a/tensorflow/tensorboard/components/tf-image-dashboard/tf-image-dashboard.html b/tensorflow/tensorboard/components/tf-image-dashboard/tf-image-dashboard.html
new file mode 100644
index 0000000000..726a420e9f
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-image-dashboard/tf-image-dashboard.html
@@ -0,0 +1,90 @@
+<link rel="import" href="../../bower_components/polymer/polymer.html">
+<link rel="import" href="../tf-dashboard-common/tf-run-generator.html">
+<link rel="import" href="../tf-dashboard-common/tf-url-generator.html">
+<link rel="import" href="../../bower_components/paper-icon-button/paper-icon-button.html">
+<link rel="import" href="tf-image-grid.html">
+<link rel="import" href="../tf-dashboard-common/warning-style.html">
+
+<!--
+tf-image-dashboard displays a dashboard that loads images from a TensorFlow run.
+
+Right now it has very simple behavior: Creates a url-generator and run-generator
+to talk to the backend, and then passes the runToImages map and urlGenerators into
+a tf-image-grid for display.
+
+Likely we will add more in the future, e.g. a sidebar like in the event
+dashboard that allows filtering and organizing the tags and runs, and a
+mechanism for loading older images rather than always getting the most recent one.
+-->
+<dom-module id="tf-image-dashboard">
+ <template>
+ <div id="plumbing">
+ <tf-url-generator
+ out-runs-url="{{runsUrl}}"
+ out-images-url-generator="{{imagesUrlGen}}"
+ out-individual-image-url-generator="{{individualImageUrlGen}}"
+ id="urlGenerator"
+ ></tf-url-generator>
+
+ <tf-run-generator
+ id="runGenerator"
+ url="[[runsUrl]]"
+ out-run-to-images="{{runToImages}}"
+ /></tf-run-generator>
+ </div>
+
+ <div class="center">
+ <template is="dom-if" if="[[!_hasImages(runToImages.*)]]">
+ <div class="warning">
+ <p>
+ No image tags were found.
+ </p>
+ <p>
+ Maybe data hasn't loaded yet, or maybe you need
+ to add some <code>tf.image_summary</code> ops to your graph, and
+ serialize them using the <code>tf.training.summary_io.SummaryWriter</code>.
+ </p>
+ </div>
+ </template>
+ <tf-image-grid
+ id="imageGrid"
+ run-to-images="[[runToImages]]"
+ images-generator="[[imagesUrlGen]]"
+ individual-image-generator="[[individualImageUrlGen]]"
+ ></tf-image-grid>
+ </div>
+
+ <style>
+ .center {
+ padding-left: 10px;
+ padding-right: 10px;
+ height: 100%;
+ width: 100%;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ }
+ :host {
+ height: 100%;
+ display: block;
+ }
+
+ </style>
+ <style include="warning-style"></style>
+ </template>
+ <script>
+ Polymer({
+ is: "tf-image-dashboard",
+ properties: {
+ runToImages: Object,
+ imagesUrlGen: Function,
+ individualImageUrlGen: Function,
+ },
+ _hasImages: function(runToImagesChange) {
+ return _.values(runToImagesChange.base).some(function(arr) {
+ return arr.length > 0;
+ });
+ },
+ });
+ </script>
+</dom-module>
diff --git a/tensorflow/tensorboard/components/tf-image-dashboard/tf-image-grid.html b/tensorflow/tensorboard/components/tf-image-dashboard/tf-image-grid.html
new file mode 100644
index 0000000000..b7787b98c4
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-image-dashboard/tf-image-grid.html
@@ -0,0 +1,166 @@
+<link rel="import" href="../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../bower_components/paper-styles/paper-styles.html">
+<link rel="import" href="tf-image-loader.html">
+<link rel="import" href="../imports/lodash.html">
+<link rel="import" href="../tf-dashboard-common/scrollbar-style.html">
+
+<!--
+tf-image-grid creates a grid for examining image data. The columsn correspond
+to runs and the rows correspond to tags. Each cell is an image.
+
+Structurally, it makes extensive use of flexbox for layout: it has a top-level
+columnar flexbox that contains the topRow (run names) and then a
+bottomContainer. The bottomContainer is another columnar flexbox which contains
+repeated image-rows. Each image-row is a row flexbox which contains a tag name
+cell, and then image cells.
+
+In the future, we should improve on the layout by making the tag names and run names have fixed positions
+within the image-grid, so that when you scroll you always have context (e.g. row and column names in a spreadsheet).
+For now, it just scrolls.
+
+The image grid provides internal scroll bars (with styling) so that it can be dropped into
+a dashboard in a predictable fashion, even though the internal image grid may be enormous.
+
+Room for future improvement:
+
+- Make it obvious when an image didn't load due to the image not existing.
+- Find some way to collapse sparse image grids into denser ones (when sparsity
+is high)
+- Fix column/row names
+- Include hook for loading past images (by step/timestamp? or index?)
+
+-->
+<dom-module id="tf-image-grid">
+ <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-repeat"
+ items="[[_runs]]"
+ as="run"
+ >
+ <div class="run-name-cell noshrink">
+ <span>[[run]]</span>
+ </div>
+ </template>
+ </div>
+ <div id="bottomContainer" class="container">
+ <template
+ is="dom-repeat"
+ items="[[_tags]]"
+ sort
+ as="tag"
+ >
+ <div class="image-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="image-cell noshrink">
+ <template is="dom-if" if="[[_exists(run, tag, runToImages.*)]]">
+ <tf-image-loader
+ id="loader"
+ run="[[run]]"
+ tag="[[tag]]"
+ images-generator="[[imagesGenerator]]"
+ individual-image-generator="[[individualImageGenerator]]"
+ >
+ </tf-image-loader>
+ </template>
+ </div>
+ </template>
+ </div>
+ </template>
+ </div>
+ </div>
+ <style>
+ :host {
+ display: block;
+ height: 100%;
+ }
+ .container {
+ display: flex;
+ flex-wrap: nowrap;
+ }
+ #fullContainer {
+ width: 100%;
+ height: 100%;
+ flex-direction: column;
+ padding-top: 20px;
+ overflow: scroll;
+ -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%;
+ }
+ .image-row {
+ flex-direction: row;
+ }
+ .image-cell {
+ width: 300px;
+ height: 300px;
+ border: 1px solid black;
+ }
+ .tag-name-cell {
+ height: 300px;
+ width: 300px;
+ 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;
+ height: 30px;
+ text-align: center;
+ }
+ .noshrink {
+ flex-shrink: 0;
+ }
+ #paddingCell {
+ width: 300px;
+ height: 30px;
+ }
+ </style>
+ </template>
+ <script>
+ Polymer({
+ is: "tf-image-grid",
+ properties: {
+ runToImages: Object,
+ _tags: {type: Array, computed: "_getTags(runToImages.*)"},
+ _runs: {type: Array, computed: "_getRuns(runToImages.*)"},
+ imagesGenerator: Function,
+ individualImageGenerator: Function,
+ },
+ _getTags: function(runToImages) {
+ return _.chain(runToImages.base).values().flatten().union().value();
+ },
+ _getRuns(runToImages) {
+ var r2i = runToImages.base;
+ return _.keys(r2i).filter(function(x) {return r2i[x].length > 0;});
+ },
+ _exists: function (run, tag, runToImages) {
+ runToImages = runToImages.base;
+ return runToImages[run].indexOf(tag) !== -1;
+ },
+ });
+ </script>
+</dom-module>
diff --git a/tensorflow/tensorboard/components/tf-image-dashboard/tf-image-loader.html b/tensorflow/tensorboard/components/tf-image-dashboard/tf-image-loader.html
new file mode 100644
index 0000000000..e70f189c73
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-image-dashboard/tf-image-loader.html
@@ -0,0 +1,64 @@
+<link rel="import" href="../../bower_components/polymer/polymer.html">
+<link rel="import" href="../imports/lodash.html">
+
+<!--
+tf-image-loader loads an individual image from the TensorBoard backend.
+
+Right now it always loads the most recent image. We should add support in the
+future for loading older images.
+-->
+<dom-module id="tf-image-loader">
+ <style>
+ :host {
+ display: block;
+ }
+ img {
+ width: 100%;
+ height: 100%;
+ }
+ </style>
+ <template>
+ <iron-ajax
+ id="ajax"
+ auto
+ url="[[metadataUrl]]"
+ handle-as="json"
+ debounce="50"
+ last-response="{{imageMetadata}}"
+ verbose="true"
+ ></iron-ajax>
+ <template is="dom-if" if="[[imageUrl]]">
+ <img src="[[imageUrl]]"></img>
+ </template is="dom-if">
+ </template>
+ <script>
+ Polymer({
+ is: "tf-image-loader",
+ properties: {
+ run: String,
+ tag: String,
+ imagesGenerator: Function,
+ individualImageGenerator: Function,
+ imageMetadata: Array,
+ metadataUrl: {
+ type: String,
+ computed: "apply(imagesGenerator, tag, run)",
+ },
+ imageUrl: {
+ type: String,
+ computed: "getLastImage(imageMetadata, individualImageGenerator)",
+ },
+ },
+ apply: function(imagesGenerator, run, tag) {
+ return imagesGenerator(run, tag);
+ },
+ getLastImage: function(imageMetadata, individualImageGenerator) {
+ if (imageMetadata == null) {
+ return null;
+ }
+ var query = _.last(imageMetadata).query;
+ return individualImageGenerator(query);
+ },
+ });
+ </script>
+</dom-module>
diff --git a/tensorflow/tensorboard/components/tf-multi-checkbox/demo/index.html b/tensorflow/tensorboard/components/tf-multi-checkbox/demo/index.html
new file mode 100644
index 0000000000..e5661b98bc
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-multi-checkbox/demo/index.html
@@ -0,0 +1,162 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+<script src="../../../bower_components/d3/d3.js"></script>
+<link rel="import" href="../tf-multi-checkbox.html">
+<link rel="import" href="../../tf-event-dashboard/tf-color-scale.html">
+<link rel="stylesheet" type="text/css" href="../../../lib/css/global.css">
+
+</head>
+<body>
+<script>
+var seed = 1;
+function random() {
+ var x = Math.sin(seed++) * 10000;
+ return x - Math.floor(x);
+}
+</script>
+<style>
+</style>
+
+<dom-module id="mc-demo">
+ <template>
+ <tf-multi-checkbox
+ id="multiCheckbox"
+ names="[[names]]"
+ tooltips="[[_tooltips]]"
+ class-scale="[[classScale]]"
+ highlights="[[highlights]]"
+ ></tf-multi-checkbox>
+ <tf-color-scale
+ id="colorScale"
+ runs="[[names]]"
+ out-class-scale="{{classScale}}"
+ ></tf-color-scale>
+ <style>
+ </style>
+ </template>
+ <script>
+
+ function randomTooltip() {
+ var s = "";
+ while (random() < 0.8) {
+ s += String(10*random())[0];
+ }
+ return s;
+ }
+ Polymer({
+ is: "mc-demo",
+ properties: {
+ names: Array,
+ tooltips: Object,
+ autoGenerateTooltips: {value: true},
+ _tooltips: Object,
+ classScale: Function,
+ highlights: Array,
+ },
+ observers: [
+ 'autogenerate(names, autoGenerateTooltips)',
+ 'randomHighlights(names)'
+ ],
+ autogenerate: function(names, autoGenerateTooltips) {
+ if (autoGenerateTooltips) {
+ var tooltips = {};
+ names.forEach(function(n) {
+ if (random() > 0.5) {
+ tooltips[n] = randomTooltip();
+ }
+ });
+ this._tooltips = tooltips;
+ }
+ },
+ randomHighlights: function(names) {
+ var h = [];
+ names.forEach(function(n) {
+ if (random() > 0.6) {
+ h.push(n);
+ }
+ });
+ this.highlights = h;
+ }
+ });
+ </script>
+</dom-module>
+
+<dom-module id="x-demo">
+<style>
+.small {
+ width: 200px;
+ height: 500px;
+}
+.large {
+ width: 500px;
+ height: 900px;
+}
+html,body {
+ height: 100%;
+}
+mc-demo {
+ padding: 5px;
+ border: 1px solid var(--paper-red-500);
+ display: inline-block;
+}
+</style>
+<template>
+ <div class="demo-block">
+ <mc-demo id="demo1" class="small" names="[[long_names]]"></mc-demo>
+ <mc-demo class="small" names="[[many_names]]"></mc-demo>
+ <mc-demo class="small" names="[[many_long_names]]"></mc-demo>
+ </div>
+
+ <div class="demo-block">
+ <mc-demo class="large" names="[[long_names]]"></mc-demo>
+ <mc-demo class="large" names="[[many_names]]"></mc-demo>
+ <mc-demo class="large" names="[[many_long_names]]"></mc-demo>
+ </div>
+
+</template>
+<script>
+
+function long_names() {
+ return [
+ "foo_bar very long name with spaces",
+ "the quick brown fox jumped over the lazy dog",
+ "supercalifragilisticexpialodcious/bar/foo/zod/longer/longer",
+ ];
+}
+
+function many_names() {
+ var out = [];
+ for (var i=0; i<20; i++) {
+ out.push("foo_bar-" + i);
+ out.push("bar_zod_bing-" + i);
+ out.push("lol-" + i);
+ }
+ return out;
+}
+
+function many_long_names() {
+ var out = [];
+ for (var i=0; i<20; i++) {
+ out.push("foo_bar very very very long some spaces though-" + i);
+ out.push("bar_zod_bing_bas_womp_wub_wub_dub_wub_wub-" + i);
+ out.push("rightly_to_be_great_is_not_to_stir_without_great_argument_but_greatly_to_find_quarrel_in_a_straw_when_honors_at_the_stake-" + i);
+ }
+ return out;
+}
+
+Polymer({
+ is: "x-demo",
+ properties: {
+ long_names: {type: Array, value: long_names},
+ many_names: {type: Array, value: many_names},
+ many_long_names: {type: Array, value: many_long_names},
+},
+});
+</script>
+</dom-module>
+
+<x-demo id="demo"></x-demo>
+</body>
+</html>
diff --git a/tensorflow/tensorboard/components/tf-multi-checkbox/tf-multi-checkbox.html b/tensorflow/tensorboard/components/tf-multi-checkbox/tf-multi-checkbox.html
new file mode 100644
index 0000000000..a5447e8f5e
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-multi-checkbox/tf-multi-checkbox.html
@@ -0,0 +1,228 @@
+<link rel="import" href="../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../bower_components/paper-checkbox/paper-checkbox.html">
+<link rel="import" href="../imports/lodash.html">
+<link rel="import" href="../tf-dashboard-common/scrollbar-style.html">
+<link rel="import" href="../tf-dashboard-common/run-color-style.html">
+<!--
+tf-multi-checkbox creates a list of checkboxes that can be used to toggle on or off
+a large number of values. Each checkbox displays a name, and may also have an
+assosciated tooltip value. Checkboxes can be highlighted, hidden, and re-ordered.
+
+tf-multi-checkbox assumes that the names may be very long compared to the width
+of the checkbox, and the number of names may also be very large, and works to
+handle these situations gracefully.
+
+API:
+
+Properties in:
+names: The string names to associate with checkboxes.
+tooltips: An object mapping from name to tooltip value.
+tooltipOrderer: A function that is used to compute how to order the names based
+on tooltip values (when available). If tooltip values and a tooltip orderer are
+present, the tooltipOrderer computes a numeric value for each tooltip, tooltips
+with higher values are ordered first, tooltips with equal values are ordered
+lexicogrpahically, and tooltips without a value are placed last. If the
+tooltipOrderer is set to a falsey value (eg null), then the names are not
+re-ordered based on tooltip value.
+classScale: A function that maps from name to class name, which is applied as
+the special property color-class. This is intended to be used to color the names.
+hideMissingTooltips: If set, then when tooltips are present, any names that do
+not have an associate non-empty tooltip value will be hidden.
+
+Properties out:
+outSelected: An array of names that the user has checked.
+If the user does not interact, everything will be checked.
+
+-->
+
+<dom-module id="tf-multi-checkbox">
+ <style include="scrollbar-style"></style>
+ <style include="run-color-style"></style>
+
+ <template>
+ <div id="outer-container" class="scrollbar">
+ <template
+ is="dom-repeat"
+ items="[[names]]"
+ sort="[[_tooltipComparator(tooltips, tooltipOrderer)]]"
+ >
+ <div
+ class="run-row"
+ color-class$="[[_applyColorClass(item, classScale)]]"
+ null-tooltip$="[[_isNullTooltip(item, tooltips)]]"
+ highlight$="[[_isHighlighted(item, highlights.*)]]"
+ >
+ <div class="checkbox-container vertical-align-container">
+ <paper-checkbox
+ class="checkbox vertical-align-center"
+ name="[[item]]"
+ checked$="[[_isChecked(item,outSelected.*)]]"
+ on-change="_checkboxChange"
+ ></paper-checkbox>
+ </div>
+ <div class="item-label-container">
+ <span>[[item]]</span>
+ </div>
+ <div class="tooltip-value-container vertical-align-container">
+ <span class="vertical-align-top">[[_lookupTooltip(item,tooltips)]]</span>
+ </div>
+ </div>
+ </template>
+ </div>
+ <style>
+ :host {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ }
+ #outer-container {
+ overflow-y: scroll;
+ overflow-x: hidden;
+ width: 100%;
+ 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;
+ }
+ .checkbox-container {
+ flex-grow: 0;
+ flex-shrink: 0;
+ }
+ .checkbox {
+ padding-left: 2px;
+ width: 32px;
+ }
+ .item-label-container {
+ 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;
+ }
+ [null-tooltip] {
+ display: none;
+ }
+ [highlight] {
+ font-weight: bold;
+ }
+ </style>
+ </template>
+
+ <script>
+ Polymer({
+ is: "tf-multi-checkbox",
+ properties: {
+ names: Array,
+ tooltipOrderer: {
+ /* Used to compute how to order the tooltips based on the tooltip value.
+ * By default, it parses the tooltip strings as numbers.
+ * If set to a falsey value, tooltips are always ordered lexicographically.
+ */
+ type: Function,
+ value: function() {
+ return function(x) {return +x;}
+ },
+ },
+ tooltips: Object,
+ highlights: Array,
+ outSelected: {
+ type: Array,
+ notify: true,
+ value: function() {
+ return [];
+ },
+ },
+ hideMissingTooltips: {
+ // If we have tooltips, but some names are missing, do we hide them?
+ type: Boolean,
+ value: true,
+ },
+ classScale: Function, // map from run name to css class
+ },
+ observers: [
+ "_initializeOutSelected(names.*)",
+ ],
+ _lookupTooltip: function(item, tooltips) {
+ return tooltips != null ? tooltips[item] : null;
+ },
+ _isNullTooltip: function(item, tooltips) {
+ if (!this.hideMissingTooltips) {
+ return true;
+ }
+ if (tooltips == null) {
+ return false;
+ }
+ return tooltips[item] == null;
+ },
+ _initializeOutSelected: function(change) {
+ this.outSelected = change.base.slice();
+ },
+ _tooltipComparator: function(tooltips, tooltipOrderer) {
+ return function(a, b) {
+ if (!tooltips || !tooltipOrderer) {
+ // if we're missing tooltips or orderer, do lexicogrpahic sort
+ return a.localeCompare(b);
+ }
+ function getValue(x) {
+ var value = tooltipOrderer(tooltips[x]);
+ return value == null || _.isNaN(value) ? -Infinity : value;
+ }
+ var aValue = getValue(a);
+ var bValue = getValue(b);
+ return aValue === bValue ? a.localeCompare(b) : bValue - aValue;
+ }
+ },
+ _checkboxChange: function(e) {
+ var name = e.srcElement.name;
+ var idx = this.outSelected.indexOf(name);
+ var checked = e.srcElement.checked;
+ if (checked && idx === -1) {
+ this.push("outSelected", name);
+ } else if (!checked && idx !== -1) {
+ this.splice("outSelected", idx, 1);
+ }
+ },
+ _isChecked: function(item, outSelectedChange) {
+ var outSelected = outSelectedChange.base;
+ return outSelected.indexOf(item) !== -1;
+ },
+ _initializeRuns: function(change) {
+ this.outSelected = change.base.slice();
+ },
+ _applyColorClass: function(item, classScale) {
+ // TODO: Update style just on the element that changes
+ // and apply at microtask timing
+ this.debounce("restyle", function (){
+ this.updateStyles();
+ }, 16);
+ return classScale(item);
+ },
+ _isHighlighted: function(item, highlights) {
+ return highlights.base.indexOf(item) !== -1;
+ },
+ });
+ </script>
+
+</dom-module>
diff --git a/tensorflow/tensorboard/components/tf-regex-group/demo/index.html b/tensorflow/tensorboard/components/tf-regex-group/demo/index.html
new file mode 100644
index 0000000000..efef84e0fc
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-regex-group/demo/index.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+ <link rel="import" href="../tf-regex-group.html">
+ </head>
+ <body>
+ <style>
+ .container {
+ width: 255px;
+ padding: 10px;
+ border: 1px solid #3f51b5;
+ border-radius: 5px;
+ }
+ :host {
+ margin: 0px;
+ }
+ </style>
+ <template id="page-template" is="dom-bind">
+ <div class="container">
+ <tf-regex-group regexes="{{regexes}}" id="demo"></tf-regex-group>
+ </div>
+ <p> Regexes:</p>
+ <template is="dom-repeat" items="[[regexes]]">
+ <p>"<span>[[item]]</span>"</p>
+ </template>
+ </template>
+ </body>
+ <script>
+
+ </script>
+</html>
diff --git a/tensorflow/tensorboard/components/tf-regex-group/index.html b/tensorflow/tensorboard/components/tf-regex-group/index.html
new file mode 100644
index 0000000000..0238a8d326
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-regex-group/index.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<html>
+<head>
+
+ <title>tf-regex-group</title>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+
+ <script src="../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+ <link rel="import" href="../../bower_components/iron-component-page/iron-component-page.html">
+
+</head>
+<body>
+
+ <iron-component-page></iron-component-page>
+
+</body>
+</html>
diff --git a/tensorflow/tensorboard/components/tf-regex-group/tf-regex-group.html b/tensorflow/tensorboard/components/tf-regex-group/tf-regex-group.html
new file mode 100644
index 0000000000..e9673e85d9
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-regex-group/tf-regex-group.html
@@ -0,0 +1,151 @@
+<link rel="import" href="../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../bower_components/paper-icon-button/paper-icon-button.html">
+<link rel="import" href="../../bower_components/iron-icons/iron-icons.html">
+<link rel="import" href="../../bower_components/paper-toggle-button/paper-toggle-button.html">
+<link rel="import" href="../../bower_components/paper-input/paper-input.html">
+
+<!--
+`tf-regex-group` provides an input component for a group of regular expressions.
+
+Example:
+ <tf-regex-group regexes="{{regexes}}"></tf-regex-group>
+
+It contains a series of regular expression input fields. From this, it computes
+`regexes', an array in which every element is either a string representing an
+active, valid, nonempty regular expression, or the value `null`
+
+Public Properties:
+`regexes` a readonly, notifying array of strings, where each string is an
+ active, valid, nonempty regex
+
+It maintains an invariant that the final regex should always be an empty string,
+so the user can easily add more regular expressions. It does this by adding
+a new empty regex when the final one is nonempty.
+
+Pressing "enter" moves focus to the next regex (or just blurs if there are no
+more regexes).
+-->
+<dom-module id="tf-regex-group">
+ <template>
+ <div class="regex-list">
+ <template is="dom-repeat" items="{{rawRegexes}}">
+ <div class="regex-line">
+ <paper-input
+ id="text-input"
+ class="regex-input"
+ label="input new regex"
+ no-label-float
+ bind-value="{{item.regex}}"
+ invalid="[[!item.valid]]"
+ on-keyup="moveFocus"
+ ></paper-input>
+ <paper-toggle-button
+ class="active-button"
+ checked="{{item.active}}"
+ disabled="[[!item.valid]]"
+ ></paper-toggle-button>
+
+ <paper-icon-button
+ icon="delete"
+ class="delete-button"
+ aria-label="Delete Regex"
+ tabindex="0"
+ on-tap="deleteRegex"
+ ></paper-icon-button>
+ </div>
+ <style>
+ .regex-input {
+ width: 210px;
+ display: inline-block;
+ padding-left: 8px;
+ padding-right: 5px;
+ }
+
+ .active-button {
+ --paper-toggle-button-checked-button-color: var(--tb-orange-strong);
+ --paper-toggle-button-checked-bar-color: var(--tb-orange-weak);
+ border: none;
+ }
+
+ .delete-button {
+ color: var(--paper-pink-900);
+ width: 24px;
+ height: 24px;
+ }
+ .regex-list {
+ margin-bottom: 10px;
+ }
+ paper-input {
+ --paper-input-container-focus-color: var(--tb-orange-strong);
+ }
+ </style>
+ </template>
+ </div>
+ </template>
+ <script>
+ Polymer({
+ is: "tf-regex-group",
+ properties: {
+ rawRegexes: {
+ type: Array,
+ value: function() {
+ return [{regex: "", active: true, valid: true}];
+ }
+ },
+ regexes: {type: Array, computed: "usableRegexes(rawRegexes.*)", notify: true},
+ },
+ observers: [
+ "addNewRegexIfNeeded(rawRegexes.*)",
+ "checkValidity(rawRegexes.*)",
+ ],
+ 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 !== "" && r.active && 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: "", active: true, 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>