aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--WORKSPACE14
-rw-r--r--bower.BUILD1
-rw-r--r--tensorflow/tensorboard/bower.json8
-rw-r--r--tensorflow/tensorboard/dist/tf-tensorboard.html5504
4 files changed, 2927 insertions, 2600 deletions
diff --git a/WORKSPACE b/WORKSPACE
index c1e6d7e317..9b267609e7 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -110,14 +110,14 @@ new_git_repository(
name = "iron_dropdown",
build_file = "bower.BUILD",
remote = "https://github.com/polymerelements/iron-dropdown.git",
- tag = "v1.3.1",
+ tag = "v1.4.0",
)
new_git_repository(
name = "iron_fit_behavior",
build_file = "bower.BUILD",
remote = "https://github.com/polymerelements/iron-fit-behavior.git",
- tag = "v1.0.6",
+ tag = "v1.2.0",
)
new_git_repository(
@@ -187,7 +187,7 @@ new_git_repository(
name = "iron_overlay_behavior",
build_file = "bower.BUILD",
remote = "https://github.com/polymerelements/iron-overlay-behavior.git",
- tag = "v1.6.4",
+ tag = "1.7.2",
)
new_git_repository(
@@ -215,7 +215,7 @@ new_git_repository(
name = "iron_validatable_behavior",
build_file = "bower.BUILD",
remote = "https://github.com/polymerelements/iron-validatable-behavior.git",
- tag = "v1.1.0",
+ tag = "v1.1.1",
)
new_git_repository(
@@ -264,7 +264,7 @@ new_git_repository(
name = "paper_dialog_behavior",
build_file = "bower.BUILD",
remote = "https://github.com/polymerelements/paper-dialog-behavior.git",
- tag = "v1.2.5",
+ tag = "v1.2.6",
)
new_git_repository(
@@ -285,7 +285,7 @@ new_git_repository(
name = "paper_icon_button",
build_file = "bower.BUILD",
remote = "https://github.com/polymerelements/paper-icon-button.git",
- tag = "v1.0.7",
+ tag = "v1.1.1",
)
new_git_repository(
@@ -411,7 +411,7 @@ new_git_repository(
name = "web_animations_js",
build_file = "bower.BUILD",
remote = "https://github.com/web-animations/web-animations-js.git",
- tag = "2.2.0",
+ tag = "2.2.1",
)
new_git_repository(
diff --git a/bower.BUILD b/bower.BUILD
index 3ad6da4c41..ea393cafe3 100644
--- a/bower.BUILD
+++ b/bower.BUILD
@@ -387,6 +387,7 @@ filegroup(
srcs = [
"index.html",
"paper-icon-button.html",
+ "paper-icon-button-light.html",
],
)
diff --git a/tensorflow/tensorboard/bower.json b/tensorflow/tensorboard/bower.json
index 1d9bb74bee..b9a6657f37 100644
--- a/tensorflow/tensorboard/bower.json
+++ b/tensorflow/tensorboard/bower.json
@@ -39,7 +39,7 @@
"font-roboto": "PolymerElements/font-roboto#1.0.1",
"graphlib": "1.0.7",
"iron-a11y-announcer": "PolymerElements/iron-a11y-announcer#1.0.4",
- "iron-a11y-keys-behavior": "PolymerElements/iron-a11y-keys-behavior#1.1.1",
+ "iron-a11y-keys-behavior": "PolymerElements/iron-a11y-keys-behavior#1.1.2",
"iron-ajax": "PolymerElements/iron-ajax#1.1.1",
"iron-autogrow-textarea": "PolymerElements/iron-autogrow-textarea#1.0.11",
"iron-behaviors": "PolymerElements/iron-behaviors#1.0.13",
@@ -70,7 +70,7 @@
"paper-dialog-behavior": "PolymerElements/paper-dialog-behavior#1.2.5",
"paper-dropdown-menu": "PolymerElements/paper-dropdown-menu#1.1.3",
"paper-header-panel": "PolymerElements/paper-header-panel#1.1.4",
- "paper-icon-button": "PolymerElements/paper-icon-button#1.0.7",
+ "paper-icon-button": "PolymerElements/paper-icon-button#1.1.1",
"paper-input": "PolymerElements/paper-input#1.1.5",
"paper-item": "PolymerElements/paper-item#1.1.4",
"paper-material": "PolymerElements/paper-material#1.0.6",
@@ -112,7 +112,7 @@
"font-roboto": "1.0.1",
"graphlib": "1.0.7",
"iron-a11y-announcer": "1.0.4",
- "iron-a11y-keys-behavior": "1.1.1",
+ "iron-a11y-keys-behavior": "1.1.2",
"iron-ajax": "1.1.1",
"iron-autogrow-textarea": "1.0.11",
"iron-behaviors": "1.0.13",
@@ -143,7 +143,7 @@
"paper-dialog-behavior": "1.2.5",
"paper-dropdown-menu": "1.1.3",
"paper-header-panel": "1.1.4",
- "paper-icon-button": "1.0.7",
+ "paper-icon-button": "1.1.1",
"paper-input": "1.1.5",
"paper-item": "1.1.4",
"paper-material": "1.0.6",
diff --git a/tensorflow/tensorboard/dist/tf-tensorboard.html b/tensorflow/tensorboard/dist/tf-tensorboard.html
index 75bc976785..3180190a0f 100644
--- a/tensorflow/tensorboard/dist/tf-tensorboard.html
+++ b/tensorflow/tensorboard/dist/tf-tensorboard.html
@@ -36,7 +36,7 @@ var TF;
(function (TF) {
var TensorBoard;
(function (TensorBoard) {
- TensorBoard.TABS = ['events', 'images', 'graphs', 'histograms'];
+ TensorBoard.TABS = ['events', 'images', 'audio', 'graphs', 'histograms'];
})(TensorBoard = TF.TensorBoard || (TF.TensorBoard = {}));
})(TF || (TF = {}));
</script>
@@ -101,46 +101,6 @@ var TF;
})(TF || (TF = {}));
</script>
</head><body><div hidden="" by-vulcanize="">
-<dom-module id="tf-tooltip-coordinator" assetpath="../tf-event-dashboard/">
- <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>
-
<dom-module id="scrollbar-style" assetpath="../tf-dashboard-common/">
<template>
<style>
@@ -235,17 +195,14 @@ var TF;
<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.*)]]">
+ <template is="dom-repeat" items="[[names]]">
+ <div class="run-row" color-class$="[[_applyColorClass(item, classScale)]]">
<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>
@@ -301,12 +258,6 @@ var TF;
.vertical-align-container .vertical-align-top {
align-self: start;
}
- [null-tooltip] {
- display: none;
- }
- [highlight] {
- font-weight: bold;
- }
</style>
</template>
@@ -315,18 +266,6 @@ var TF;
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,
@@ -334,46 +273,14 @@ var TF;
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);
@@ -399,9 +306,6 @@ var TF;
}, 16);
return classScale(item);
},
- _isHighlighted: function(item, highlights) {
- return highlights.base.indexOf(item) !== -1;
- },
});
</script>
@@ -410,19 +314,11 @@ var TF;
<dom-module id="tf-run-selector" assetpath="../tf-event-dashboard/">
<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]]">
- <h3 id="tooltip-help" class="tooltip-container">
- Runs
- </h3>
- </template>
+ <h3 id="tooltip-help" class="tooltip-container">
+ Runs
+ </h3>
</div>
- <tf-multi-checkbox names="[[runs]]" tooltips="[[tooltips]]" highlights="[[_arrayify(closestRun)]]" out-selected="{{outSelected}}" class-scale="[[classScale]]" hide-missing-tooltips=""></tf-multi-checkbox>
+ <tf-multi-checkbox names="[[runs]]" out-selected="{{outSelected}}" class-scale="[[classScale]]"></tf-multi-checkbox>
<paper-button class="x-button" id="toggle-all" on-tap="_toggleAll">
Toggle All Runs
</paper-button>
@@ -453,17 +349,6 @@ var TF;
margin-top: 5px;
color: var(--tb-ui-dark-accent);
}
- .x-tooltip {
- display: flex;
- flex-direction: row;
- }
- .x-tooltip-label {
- flex-grow: 1;
- align-self: flex-start;
- }
- .x-tooltip-value {
- align-self: flex-end;
- }
#tooltip-help {
color: var(--paper-grey-800);
margin: 0;
@@ -483,11 +368,7 @@ var TF;
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
},
_toggleAll: function() {
if (this.outSelected.length > 0) {
@@ -496,9 +377,6 @@ var TF;
this.outSelected = this.runs.slice();
}
},
- _arrayify: function(item) {
- return [item];
- },
});
</script>
</dom-module>
@@ -772,14 +650,14 @@ var TF;
</template>
<script>/* Copyright 2015 Google Inc. All Rights Reserved.
-Licensed under the Apache License, Version 2.0 (the "License");
+Licensed under the Apache License, Version 2.0 (the 'License');
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
+distributed under the License is distributed on an 'AS IS' BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -797,16 +675,17 @@ var Categorizer;
Categorizer.legacyUnderscoreCategorizer = splitCategorizer(/[\/_]/);
function fallbackCategorizer(s) {
switch (s) {
- case "TopLevelNamespaceCategorizer":
+ case 'TopLevelNamespaceCategorizer':
return Categorizer.topLevelNamespaceCategorizer;
- case "LegacyUnderscoreCategorizer":
+ case 'LegacyUnderscoreCategorizer':
return Categorizer.legacyUnderscoreCategorizer;
default:
- throw new Error("Unrecognized categorization strategy: " + s);
+ throw new Error('Unrecognized categorization strategy: ' + s);
}
}
Categorizer.fallbackCategorizer = fallbackCategorizer;
- /* An "extractor" is a function that takes a tag name, and "extracts" a category name.
+ /* 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.
@@ -928,6 +807,18 @@ var Categorizer;
<dom-module id="tf-chart" assetpath="../tf-event-dashboard/">
<template>
<svg id="chartsvg"></svg>
+ <div id="tooltip">
+ <h4 id="headline"></h4>
+ <div class="tooltip-row">
+ Step: <span id="step"></span>
+ </div>
+ <div class="tooltip-row">
+ Time: <span id="time"></span>
+ </div>
+ <div class="tooltip-row">
+ Value: <span id="value"></span>
+ </div>
+ </div>
<style>
:host {
-webkit-user-select: none;
@@ -936,6 +827,7 @@ var Categorizer;
flex-direction: column;
flex-grow: 1;
flex-shrink: 1;
+ position: relative;
}
svg {
-webkit-user-select: none;
@@ -943,21 +835,49 @@ var Categorizer;
flex-grow: 1;
flex-shrink: 1;
}
+ .tooltip-row{
+ white-space: nowrap;
+ }
+ #tooltip {
+ pointer-events: none;
+ position: absolute;
+ opacity: 0;
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
+ font-size: 14px;
+ background: rgba(0, 0, 0, 0.8);
+ color: white;
+ border-radius: 4px;
+ line-height: 1.4em;
+ padding: 8px;
+ z-index: 5;
+ cursor: none;
+ }
+ #tooltip #headline {
+ margin: 0 0 2px 0;
+ font-weight: bold;
+ }
+ #tooltip span {
+ font-weight: bold;
+ }
.plottable .crosshairs line.guide-line {
stroke: #777;
}
+ text.tooltip {
+ font-size: 3;
+
+ }
</style>
</template>
<script>/* Copyright 2015 Google Inc. All Rights Reserved.
-Licensed under the Apache License, Version 2.0 (the "License");
+Licensed under the Apache License, Version 2.0 (the 'License');
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
+distributed under the License is distributed on an 'AS IS' BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -971,18 +891,22 @@ var Plottable;
(function (Plottable) {
var DragZoomLayer = (function (_super) {
__extends(DragZoomLayer, _super);
- /* Constructs a SelectionBoxLayer with an attached DragInteraction and ClickInteraction.
- * On drag, it triggers an animated zoom into the box that was dragged.
- * On double click, it zooms back out to the original view, before any zooming.
- * The zoom animation uses an easing function (default d3.ease("cubic-in-out")) and is customizable.
- * Usage: Construct the selection box layer and attach x and y scales, and then add the layer
- * over the plot you are zooming on using a Component Group.
+ /**
+ * Constructs a SelectionBoxLayer with an attached DragInteraction and
+ * ClickInteraction. On drag, it triggers an animated zoom into the box
+ * that was dragged. On double click, it zooms back out to the original
+ * view, before any zooming.
+ * The zoom animation uses an easing function (default
+ * d3.ease('cubic-in-out')) and is customizable.
+ * Usage: Construct the selection box layer and attach x and y scales,
+ * and then add the layer over the plot you are zooming on using a
+ * Component Group.
* TODO(danmane) - merge this into Plottable
*/
function DragZoomLayer(xScale, yScale) {
_super.call(this);
this.isZoomed = false;
- this.easeFn = d3.ease("cubic-in-out");
+ this.easeFn = d3.ease('cubic-in-out');
this._animationTime = 750;
this.xScale(xScale);
this.yScale(yScale);
@@ -992,6 +916,14 @@ var Plottable;
this._doubleClickInteraction.attachTo(this);
this.setupCallbacks();
}
+ /**
+ * Register a method that calls when the DragZoom interaction starts.
+ */
+ DragZoomLayer.prototype.interactionStart = function (cb) { this.onStart = cb; };
+ /**
+ * Register a method that calls when the DragZoom interaction ends.
+ */
+ DragZoomLayer.prototype.interactionEnd = function (cb) { this.onEnd = cb; };
DragZoomLayer.prototype.setupCallbacks = function () {
var _this = this;
var dragging = false;
@@ -1000,6 +932,7 @@ var Plottable;
topLeft: startPoint,
bottomRight: startPoint,
});
+ _this.onStart();
});
this._dragInteraction.onDrag(function (startPoint, endPoint) {
_this.bounds({ topLeft: startPoint, bottomRight: endPoint });
@@ -1012,6 +945,9 @@ var Plottable;
if (dragging) {
_this.zoom();
}
+ else {
+ _this.onEnd();
+ }
dragging = false;
});
this._doubleClickInteraction.onDoubleClick(this.unzoom.bind(this));
@@ -1021,18 +957,22 @@ var Plottable;
return this._animationTime;
}
if (animationTime < 0) {
- throw new Error("animationTime cannot be negative");
+ throw new Error('animationTime cannot be negative');
}
this._animationTime = animationTime;
return this;
};
- /* Set the easing function, which determines how the zoom interpolates over time. */
+ /**
+ * Set the easing function, which determines how the zoom interpolates
+ * over time.
+ */
DragZoomLayer.prototype.ease = function (fn) {
- if (typeof (fn) !== "function") {
- throw new Error("ease function must be a function");
+ if (typeof (fn) !== 'function') {
+ throw new Error('ease function must be a function');
}
if (fn(0) !== 0 || fn(1) !== 1) {
- Plottable.Utils.Window.warn("Easing function does not maintain invariant f(0)==0 && f(1)==1. Bad behavior may result.");
+ Plottable.Utils.Window.warn('Easing function does not maintain invariant ' +
+ 'f(0)==0 && f(1)==1. Bad behavior may result.');
}
this.easeFn = fn;
return this;
@@ -1072,15 +1012,20 @@ var Plottable;
var x1s = this.xScale().domain()[1].valueOf();
var y0s = this.yScale().domain()[0].valueOf();
var y1s = this.yScale().domain()[1].valueOf();
- // Copy a ref to the ease fn, so that changing ease wont affect zooms in progress
+ // Copy a ref to the ease fn, so that changing ease wont affect zooms in
+ // progress.
var ease = this.easeFn;
- var interpolator = function (a, b, p) { return d3.interpolateNumber(a, b)(ease(p)); };
+ var interpolator = function (a, b, p) {
+ return d3.interpolateNumber(a, b)(ease(p));
+ };
this.isZooming(true);
var start = Date.now();
var draw = function () {
var now = Date.now();
var passed = now - start;
- var p = _this._animationTime === 0 ? 1 : Math.min(1, passed / _this._animationTime);
+ var p = _this._animationTime === 0 ?
+ 1 :
+ Math.min(1, passed / _this._animationTime);
var x0 = interpolator(x0s, x0f, p);
var x1 = interpolator(x1s, x1f, p);
var y0 = interpolator(y0s, y0f, p);
@@ -1091,6 +1036,7 @@ var Plottable;
Plottable.Utils.DOM.requestAnimationFramePolyfill(draw);
}
else {
+ _this.onEnd();
_this.isZooming(false);
}
};
@@ -1125,13 +1071,17 @@ var TF;
var Y_TOOLTIP_FORMATTER_PRECISION = 4;
var STEP_AXIS_FORMATTER_PRECISION = 4;
var Y_AXIS_FORMATTER_PRECISION = 3;
+ var TOOLTIP_Y_PIXEL_OFFSET = 15;
+ var TOOLTIP_X_PIXEL_OFFSET = 0;
+ var TOOLTIP_CIRCLE_SIZE = 3;
+ var TOOLTIP_CLOSEST_CIRCLE_SIZE = 6;
var BaseChart = (function () {
- function BaseChart(tag, dataFn, tooltipUpdater, xType, colorScale) {
+ function BaseChart(tag, dataFn, xType, colorScale, tooltip) {
this.dataFn = dataFn;
this.datasets = {};
this.tag = tag;
this.colorScale = colorScale;
- this.tooltipUpdater = tooltipUpdater;
+ this.tooltip = tooltip;
this.buildChart(xType);
}
/**
@@ -1158,74 +1108,11 @@ var TF;
};
BaseChart.prototype.getDataset = function (run) {
if (this.datasets[run] === undefined) {
- this.datasets[run] = new Plottable.Dataset([], { run: run, tag: this.tag });
+ this.datasets[run] =
+ new Plottable.Dataset([], { run: run, tag: this.tag });
}
return this.datasets[run];
};
- BaseChart.prototype.addCrosshairs = function (plot, yAccessor) {
- var _this = this;
- var pi = new Plottable.Interactions.Pointer();
- pi.attachTo(plot);
- var xGuideLine = new Plottable.Components.GuideLineLayer("vertical");
- var yGuideLine = new Plottable.Components.GuideLineLayer("horizontal");
- xGuideLine.addClass("crosshairs");
- yGuideLine.addClass("crosshairs");
- var group = new Plottable.Components.Group([plot, xGuideLine, yGuideLine]);
- var yfmt = multiscaleFormatter(Y_TOOLTIP_FORMATTER_PRECISION);
- pi.onPointerMove(function (p) {
- var run2val = {};
- var x = _this.xScale.invert(p.x).valueOf();
- var yMin = _this.yScale.domain()[0];
- var yMax = _this.yScale.domain()[1];
- var closestRun = null;
- var minYDistToRun = Infinity;
- var yValueForCrosshairs = p.y;
- plot.datasets().forEach(function (dataset) {
- var run = dataset.metadata().run;
- var data = dataset.data();
- var xs = data.map(function (d, i) { return _this.xAccessor(d, i, dataset).valueOf(); });
- var idx = _.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, don't
- // attach.
- return;
- }
- var previous = data[idx - 1];
- var next = data[idx];
- var x0 = _this.xAccessor(previous, idx - 1, dataset).valueOf();
- var x1 = _this.xAccessor(next, idx, dataset).valueOf();
- var y0 = yAccessor(previous, idx - 1, dataset).valueOf();
- var y1 = yAccessor(next, idx, dataset).valueOf();
- var slope = (y1 - y0) / (x1 - x0);
- var y = 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;
- }
- var 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(function () {
- _this.tooltipUpdater(null, null, null);
- xGuideLine.pixelPosition(-1);
- yGuideLine.pixelPosition(-1);
- });
- return group;
- };
BaseChart.prototype.buildChart = function (xType) {
if (this.outer) {
this.outer.destroy();
@@ -1237,21 +1124,23 @@ var TF;
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");
+ this.yAxis = new Plottable.Axes.Numeric(this.yScale, 'left');
var yFormatter = multiscaleFormatter(Y_AXIS_FORMATTER_PRECISION);
this.yAxis.margin(0).tickLabelPadding(5).formatter(yFormatter);
this.yAxis.usesTextWidthApproximation(true);
+ this.dzl = new Plottable.DragZoomLayer(this.xScale, this.yScale);
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.gridlines =
+ new Plottable.Components.Gridlines(this.xScale, this.yScale);
+ this.center =
+ new Plottable.Components.Group([this.gridlines, center, this.dzl]);
this.outer = new Plottable.Components.Table([
[this.yAxis, this.center],
[null, this.xAxis]
]);
};
BaseChart.prototype.buildPlot = function (xAccessor, xScale, yScale) {
- throw new Error("Abstract method not implemented.");
+ throw new Error('Abstract method not implemented.');
};
BaseChart.prototype.renderTo = function (target) {
this.outer.renderTo(target);
@@ -1271,19 +1160,130 @@ var TF;
_super.apply(this, arguments);
}
LineChart.prototype.buildPlot = function (xAccessor, xScale, yScale) {
- var yAccessor = function (d) { return d.scalar; };
+ this.yAccessor = function (d) { return d.scalar; };
var plot = new Plottable.Plots.Line();
plot.x(xAccessor, xScale);
- plot.y(yAccessor, yScale);
- plot.attr("stroke", function (d, i, dataset) { return dataset.metadata().run; }, this.colorScale);
+ plot.y(this.yAccessor, yScale);
+ plot.attr('stroke', function (d, i, dataset) {
+ return dataset.metadata().run;
+ }, this.colorScale);
this.plot = plot;
- var group = this.addCrosshairs(plot, yAccessor);
+ var group = this.setupTooltips(plot);
+ return group;
+ };
+ LineChart.prototype.setupTooltips = function (plot) {
+ var _this = this;
+ var pi = new Plottable.Interactions.Pointer();
+ pi.attachTo(plot);
+ // PointsComponent is a Plottable Component that will hold the little
+ // circles we draw over the closest data points
+ var pointsComponent = new Plottable.Component();
+ var group = new Plottable.Components.Group([plot, pointsComponent]);
+ var hideTooltips = function () {
+ _this.tooltip.style('opacity', 0);
+ pointsComponent.content().selectAll('.point').remove();
+ };
+ var enabled = true;
+ var disableTooltips = function () {
+ enabled = false;
+ hideTooltips();
+ };
+ var enableTooltips = function () { enabled = true; };
+ this.dzl.interactionStart(disableTooltips);
+ this.dzl.interactionEnd(enableTooltips);
+ pi.onPointerMove(function (p) {
+ if (!enabled) {
+ return;
+ }
+ var target = {
+ run: null,
+ x: p.x,
+ y: p.y,
+ datum: null,
+ };
+ var centerBBox = _this.gridlines.content().node().getBBox();
+ var points = plot.datasets()
+ .map(function (dataset) { return _this.findClosestPoint(target, dataset); })
+ .filter(function (p) {
+ // Only choose Points that are within window (if we zoomed)
+ return Plottable.Utils.DOM.intersectsBBox(p.x, p.y, centerBBox);
+ });
+ points.reverse(); // if multiple points are equidistant, choose 1st run
+ var closestPoint = _.min(points, function (p) { return dist(p, target); });
+ points.reverse(); // draw 1st run last, to get the right occlusions
+ var pts = pointsComponent.content().selectAll('.point').data(points, function (p) { return p.run; });
+ if (points.length !== 0) {
+ pts.enter().append('circle').classed('point', true);
+ pts.attr('r', function (p) { return p === closestPoint ? TOOLTIP_CLOSEST_CIRCLE_SIZE :
+ TOOLTIP_CIRCLE_SIZE; })
+ .attr('cx', function (p) { return p.x; })
+ .attr('cy', function (p) { return p.y; })
+ .style('stroke', 'none')
+ .attr('fill', function (p) { return _this.colorScale.scale(p.run); });
+ pts.exit().remove();
+ _this.drawTooltips(closestPoint);
+ }
+ else {
+ hideTooltips();
+ }
+ });
+ pi.onPointerExit(hideTooltips);
return group;
};
+ LineChart.prototype.drawTooltips = function (closestPoint) {
+ var _this = this;
+ // Formatters for value, step, and wall_time
+ var valueFormatter = multiscaleFormatter(Y_TOOLTIP_FORMATTER_PRECISION);
+ var stepFormatter = stepX().tooltipFormatter;
+ var wall_timeFormatter = wallX().tooltipFormatter;
+ var datum = closestPoint.datum;
+ this.tooltip.select('#headline')
+ .text(closestPoint.run)
+ .style('color', this.colorScale.scale(closestPoint.run));
+ var step = stepFormatter(datum.step);
+ var date = wall_timeFormatter(+datum.wall_time);
+ var value = valueFormatter(datum.scalar);
+ this.tooltip.select('#step').text(step);
+ this.tooltip.select('#time').text(date);
+ this.tooltip.select('#value').text(value);
+ this.tooltip.style('top', closestPoint.y + TOOLTIP_Y_PIXEL_OFFSET + 'px')
+ .style('left', function () { return _this.yAxis.width() + TOOLTIP_X_PIXEL_OFFSET +
+ closestPoint.x + 'px'; })
+ .style('opacity', 1);
+ };
+ LineChart.prototype.findClosestPoint = function (target, dataset) {
+ var _this = this;
+ var run = dataset.metadata().run;
+ var points = dataset.data().map(function (d, i) {
+ var x = _this.xAccessor(d, i, dataset);
+ var y = _this.yAccessor(d, i, dataset);
+ return {
+ x: _this.xScale.scale(x),
+ y: _this.yScale.scale(y),
+ datum: d,
+ run: run,
+ };
+ });
+ var idx = _.sortedIndex(points, target, function (p) { return p.x; });
+ if (idx === points.length) {
+ return points[points.length - 1];
+ }
+ else if (idx === 0) {
+ return points[0];
+ }
+ else {
+ var prev = points[idx - 1];
+ var next = points[idx];
+ var prevDist = Math.abs(prev.x - target.x);
+ var nextDist = Math.abs(next.x - target.x);
+ return prevDist < nextDist ? prev : next;
+ }
+ };
LineChart.prototype.changeRuns = function (runs) {
var _this = this;
_super.prototype.changeRuns.call(this, runs);
var datasets = runs.map(function (r) { return _this.getDataset(r); });
+ datasets.reverse(); // draw first run on top
this.plot.datasets(datasets);
};
return LineChart;
@@ -1303,7 +1303,8 @@ var TF;
HistogramChart.prototype.buildPlot = function (xAccessor, xScale, yScale) {
var _this = this;
var percents = [0, 228, 1587, 3085, 5000, 6915, 8413, 9772, 10000];
- var opacities = _.range(percents.length - 1).map(function (i) { return (percents[i + 1] - percents[i]) / 2500; });
+ var opacities = _.range(percents.length - 1)
+ .map(function (i) { return (percents[i + 1] - percents[i]) / 2500; });
var accessors = percents.map(function (p, i) { return function (datum) { return datum[i][1]; }; });
var median = 4;
var medianAccessor = accessors[median];
@@ -1314,24 +1315,23 @@ var TF;
var y = i > median ? accessors[i + 1] : accessors[i];
p.y(y, yScale);
p.y0(y0);
- p.attr("fill", function (d, i, dataset) {
+ p.attr('fill', function (d, i, dataset) {
return dataset.metadata().run;
}, _this.colorScale);
- p.attr("stroke", function (d, i, dataset) {
+ p.attr('stroke', function (d, i, dataset) {
return dataset.metadata().run;
}, _this.colorScale);
- p.attr("stroke-weight", function (d, i, m) { return "0.5px"; });
- p.attr("stroke-opacity", function () { return opacities[i]; });
- p.attr("fill-opacity", function () { return opacities[i]; });
+ p.attr('stroke-weight', function (d, i, m) { return '0.5px'; });
+ p.attr('stroke-opacity', function () { return opacities[i]; });
+ p.attr('fill-opacity', function () { return opacities[i]; });
return p;
});
var medianPlot = new Plottable.Plots.Line();
medianPlot.x(xAccessor, xScale);
medianPlot.y(medianAccessor, yScale);
- medianPlot.attr("stroke", function (d, i, m) { return m.run; }, this.colorScale);
+ medianPlot.attr('stroke', function (d, i, m) { return m.run; }, this.colorScale);
this.plots = plots;
- var group = this.addCrosshairs(medianPlot, medianAccessor);
- return new Plottable.Components.Group([new Plottable.Components.Group(plots), group]);
+ return new Plottable.Components.Group(plots);
};
return HistogramChart;
}(BaseChart));
@@ -1349,13 +1349,13 @@ var TF;
}
var f;
if (absv >= 1E4) {
- f = d3.format("." + digits + "e");
+ f = d3.format('.' + digits + 'e');
}
else if (absv > 0 && absv < 0.01) {
- f = d3.format("." + digits + "e");
+ f = d3.format('.' + digits + 'e');
}
else {
- f = d3.format("." + digits + "g");
+ f = d3.format('.' + digits + 'g');
}
return f(v);
};
@@ -1365,7 +1365,7 @@ var TF;
}
function stepX() {
var scale = new Plottable.Scales.Linear();
- var axis = new Plottable.Axes.Numeric(scale, "bottom");
+ var axis = new Plottable.Axes.Numeric(scale, 'bottom');
var formatter = Plottable.Formatters.siSuffix(STEP_AXIS_FORMATTER_PRECISION);
axis.formatter(formatter);
return {
@@ -1377,10 +1377,10 @@ var TF;
}
function wallX() {
var scale = new Plottable.Scales.Time();
- var formatter = Plottable.Formatters.time("%a %b %e, %H:%M:%S");
+ var formatter = Plottable.Formatters.time('%a %b %e, %H:%M:%S');
return {
scale: scale,
- axis: new Plottable.Axes.Time(scale, "bottom"),
+ axis: new Plottable.Axes.Time(scale, 'bottom'),
accessor: function (d) { return d.wall_time; },
tooltipFormatter: function (d) { return formatter(new Date(d)); },
};
@@ -1397,31 +1397,36 @@ var TF;
n -= minutes;
n *= 60;
var seconds = Math.floor(n);
- return days + "d " + hours + "h " + minutes + "m " + seconds + "s";
+ return days + 'd ' + hours + 'h ' + minutes + 'm ' + seconds + 's';
};
return {
scale: scale,
- axis: new Plottable.Axes.Numeric(scale, "bottom"),
+ axis: new Plottable.Axes.Numeric(scale, 'bottom'),
accessor: function (d, index, dataset) {
var data = dataset.data();
- // I can't imagine how this function would be called when the data is empty
- // (after all, it iterates over the data), but lets guard just to be safe.
+ // I can't imagine how this function would be called when the data
+ // is empty
+ // (after all, it iterates over the data), but lets guard just to be
+ // safe.
var first = data.length > 0 ? +data[0].wall_time : 0;
return (+d.wall_time - first) / (60 * 60 * 1000); // ms to hours
},
tooltipFormatter: formatter,
};
}
+ function dist(p1, p2) {
+ return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
+ }
function getXComponents(xType) {
switch (xType) {
- case "step":
+ case 'step':
return stepX();
- case "wall_time":
+ case 'wall_time':
return wallX();
- case "relative":
+ case 'relative':
return relativeX();
default:
- throw new Error("invalid xType: " + xType);
+ throw new Error('invalid xType: ' + xType);
}
}
})(TF || (TF = {}));
@@ -1437,11 +1442,10 @@ var TF;
selectedRuns: Array,
xType: String,
dataProvider: Function,
- tooltipUpdater: Function,
_initialized: Boolean,
},
observers: [
- "_makeChart(type, tag, dataProvider, tooltipUpdater, xType, colorScale, _initialized)",
+ "_makeChart(type, tag, dataProvider, xType, colorScale, _initialized)",
"_changeRuns(_chart, selectedRuns.*)"
],
_changeRuns: function(chart) {
@@ -1466,13 +1470,14 @@ var TF;
throw new Error("Unrecognized chart type");
}
},
- _makeChart: function(type, tag, dataProvider, tooltipUpdater, xType, colorScale, _initialized) {
+ _makeChart: function(type, tag, dataProvider, xType, colorScale, _initialized) {
if (!_initialized) {
return;
}
if (this._chart) this._chart.destroy();
var cns = this._constructor(type);
- var chart = new cns(tag, dataProvider, tooltipUpdater, xType, colorScale);
+ var tooltip = d3.select(this.$.tooltip);
+ var chart = new cns(tag, dataProvider, xType, colorScale, tooltip);
var svg = d3.select(this.$.chartsvg);
this.async(function() {
chart.renderTo(svg);
@@ -1918,14 +1923,14 @@ var TF;
</script>
<script>/* Copyright 2015 Google Inc. All Rights Reserved.
-Licensed under the Apache License, Version 2.0 (the "License");
+Licensed under the Apache License, Version 2.0 (the 'License');
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
+distributed under the License is distributed on an 'AS IS' BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -1939,19 +1944,20 @@ var TF;
(function (TF) {
var Backend;
(function (Backend) {
- /*
- * Manages many fetch requests. Launches up to nSimultaneousRequests
- * simultaneously, and maintains a LIFO queue of requests to process when
- * more urls are requested than can be handled at once. The queue can be cleared.
- *
- * When a request is made, a Promise is returned which resolves with the parsed
- * JSON result from the request.
- */
+ /**
+ * Manages many fetch requests. Launches up to nSimultaneousRequests
+ * simultaneously, and maintains a LIFO queue of requests to process when
+ * more urls are requested than can be handled at once. The queue can be
+ * cleared.
+ *
+ * When a request is made, a Promise is returned which resolves with the
+ * parsed JSON result from the request.
+ */
var RequestCancellationError = (function (_super) {
__extends(RequestCancellationError, _super);
function RequestCancellationError() {
_super.apply(this, arguments);
- this.name = "RequestCancellationError";
+ this.name = 'RequestCancellationError';
}
return RequestCancellationError;
}(Error));
@@ -1961,7 +1967,7 @@ var TF;
function RequestNetworkError(req, url) {
_super.call(this);
this.message = "RequestNetworkError: " + req.status + " at " + url;
- this.name = "RequestNetworkError";
+ this.name = 'RequestNetworkError';
this.req = req;
this.url = url;
}
@@ -1984,17 +1990,22 @@ var TF;
var resolver = { resolve: resolve, reject: reject };
_this._queue.push(resolver);
_this.launchRequests();
- }).then(function () {
+ })
+ .then(function () {
return _this.promiseWithRetries(url, _this._maxRetries);
- }).then(function (response) {
- // Success - Let's free space for another active reqest, and launch it
+ })
+ .then(function (response) {
+ // Success - Let's free space for another active
+ // reqest, and launch it
_this._nActiveRequests--;
_this.launchRequests();
return response;
}, function (rejection) {
- if (rejection.name === "RequestNetworkError") {
- // If we failed due to network error, we should decrement
- // _nActiveRequests because this request was active
+ if (rejection.name === 'RequestNetworkError') {
+ // If we failed due to network error, we should
+ // decrement
+ // _nActiveRequests because this request was
+ // active
_this._nActiveRequests--;
_this.launchRequests();
}
@@ -2004,7 +2015,7 @@ var TF;
};
RequestManager.prototype.clearQueue = function () {
while (this._queue.length > 0) {
- this._queue.pop().reject(new RequestCancellationError("Request cancelled by clearQueue"));
+ this._queue.pop().reject(new RequestCancellationError('Request cancelled by clearQueue'));
}
};
/* Return number of currently pending requests */
@@ -2016,7 +2027,8 @@ var TF;
return this._nActiveRequests + this._queue.length;
};
RequestManager.prototype.launchRequests = function () {
- while (this._nActiveRequests < this._nSimultaneousRequests && this._queue.length > 0) {
+ while (this._nActiveRequests < this._nSimultaneousRequests &&
+ this._queue.length > 0) {
this._nActiveRequests++;
this._queue.pop().resolve();
}
@@ -2025,7 +2037,7 @@ var TF;
* Try to request a given URL using overwritable _promiseFromUrl method.
* If the request fails for any reason, we will retry up to maxRetries
* times. In practice, this will help us paper over transient network issues
- * like "502 Bad Gateway".
+ * like '502 Bad Gateway'.
* By default, Chrome displays network errors in console, so
* the user will be able to tell when the requests are failing. I think this
* is a feature, if the request failures and retries are causing any
@@ -2048,7 +2060,7 @@ var TF;
RequestManager.prototype._promiseFromUrl = function (url) {
return new Promise(function (resolve, reject) {
var req = new XMLHttpRequest();
- req.open("GET", url);
+ req.open('GET', url);
req.onload = function () {
if (req.status === 200) {
resolve(JSON.parse(req.responseText));
@@ -2071,14 +2083,14 @@ var TF;
</script>
<script>/* Copyright 2015 Google Inc. All Rights Reserved.
-Licensed under the Apache License, Version 2.0 (the "License");
+Licensed under the Apache License, Version 2.0 (the 'License');
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
+distributed under the License is distributed on an 'AS IS' BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -2087,14 +2099,14 @@ var TF;
(function (TF) {
var Backend;
(function (Backend) {
- Backend.BAD_CHARACTERS = "#%&{}\\/<>*? $!'\":@+`|=() ";
+ Backend.BAD_CHARACTERS = '#%&{}\\/<>*? $!\'":@+`|=() ';
/** Cleanup a url so that it can be loaded from a filesystem. */
function demoify(s) {
// for consistency with python's urllib.urlencode
- s = s.replace(new RegExp("%20", "g"), "+");
+ s = s.replace(new RegExp('%20', 'g'), '+');
for (var i = 0; i < Backend.BAD_CHARACTERS.length; i++) {
var c = Backend.BAD_CHARACTERS[i];
- s = s.replace(new RegExp("\\" + c, "g"), "_");
+ s = s.replace(new RegExp('\\' + c, 'g'), '_');
}
return s;
}
@@ -2103,15 +2115,15 @@ var TF;
// It's important that the keys be sorted, so we always grab the right file
// if we are talking to the backend generated by serialze_tensorboard.py
if (params == null) {
- return "";
+ return '';
}
var components = _.keys(params)
.sort()
.filter(function (k) { return params[k] !== undefined; })
- .map(function (k) { return k + "=" + encodeURIComponent(params[k]); });
- var result = components.length ? "?" + components.join("&") : "";
+ .map(function (k) { return k + '=' + encodeURIComponent(params[k]); });
+ var result = components.length ? '?' + components.join('&') : '';
// Replace parens for consistency with urllib.urlencode
- return result.replace(/\(/g, "%28").replace(/\)/g, "%29");
+ return result.replace(/\(/g, '%28').replace(/\)/g, '%29');
}
Backend.queryEncoder = queryEncoder;
})(Backend = TF.Backend || (TF.Backend = {}));
@@ -2119,14 +2131,14 @@ var TF;
</script>
<script>/* Copyright 2015 Google Inc. All Rights Reserved.
-Licensed under the Apache License, Version 2.0 (the "License");
+Licensed under the Apache License, Version 2.0 (the 'License');
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
+distributed under the License is distributed on an 'AS IS' BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -2142,54 +2154,65 @@ var TF;
* @param demoMode {boolean} Whether to modify urls for filesystem demo usage.
*/
function router(dataDir, demoMode) {
- if (dataDir === void 0) { dataDir = "/data"; }
+ if (dataDir === void 0) { dataDir = '/data'; }
if (demoMode === void 0) { demoMode = false; }
var clean = demoMode ? Backend.demoify : function (x) { return x; };
- if (dataDir[dataDir.length - 1] === "/") {
+ if (dataDir[dataDir.length - 1] === '/') {
dataDir = dataDir.slice(0, dataDir.length - 1);
}
function standardRoute(route) {
return function (tag, run) {
- var url = dataDir + "/" + route + clean(Backend.queryEncoder({ tag: tag, run: run }));
+ var url = dataDir + '/' + route + clean(Backend.queryEncoder({ tag: tag, run: run }));
if (demoMode) {
- url += ".json";
+ url += '.json';
}
return url;
};
}
function individualImageUrl(query) {
- var url = dataDir + "/" + clean("individualImage?" + query);
+ var url = dataDir + '/' + clean('individualImage?' + query);
if (demoMode) {
- url += ".png";
+ url += '.png';
+ }
+ return url;
+ }
+ function individualAudioUrl(query) {
+ var url = dataDir + '/' + clean('individualAudio?' + query);
+ if (demoMode) {
+ url += '.wav';
}
return url;
}
function graphUrl(run, limit_attr_size, large_attrs_key) {
- var query_params = [["run", clean(run)]];
+ var query_params = [['run', clean(run)]];
if (limit_attr_size != null && !demoMode) {
- query_params.push(["limit_attr_size", String(limit_attr_size)]);
+ query_params.push(['limit_attr_size', String(limit_attr_size)]);
}
if (large_attrs_key != null && !demoMode) {
- query_params.push(["large_attrs_key", large_attrs_key]);
+ query_params.push(['large_attrs_key', large_attrs_key]);
}
- var query = query_params.map(function (param) {
- return param[0] + "=" + encodeURIComponent(param[1]);
- }).join("&");
- var url = dataDir + "/graph" + clean("?" + query);
+ var query = query_params
+ .map(function (param) {
+ return param[0] + '=' + encodeURIComponent(param[1]);
+ })
+ .join('&');
+ var url = dataDir + '/graph' + clean('?' + query);
if (demoMode) {
- url += ".pbtxt";
+ url += '.pbtxt';
}
return url;
}
return {
- runs: function () { return dataDir + "/runs" + (demoMode ? ".json" : ""); },
+ runs: function () { return dataDir + '/runs' + (demoMode ? '.json' : ''); },
individualImage: individualImageUrl,
+ individualAudio: individualAudioUrl,
graph: graphUrl,
- scalars: standardRoute("scalars"),
- histograms: standardRoute("histograms"),
- compressedHistograms: standardRoute("compressedHistograms"),
- images: standardRoute("images"),
- runMetadata: standardRoute("run_metadata"),
+ scalars: standardRoute('scalars'),
+ histograms: standardRoute('histograms'),
+ compressedHistograms: standardRoute('compressedHistograms'),
+ images: standardRoute('images'),
+ audio: standardRoute('audio'),
+ runMetadata: standardRoute('run_metadata'),
};
}
Backend.router = router;
@@ -2216,7 +2239,7 @@ var TF;
var Backend;
(function (Backend_1) {
Backend_1.TYPES = [
- 'scalar', 'histogram', 'compressedHistogram', 'graph', 'image',
+ 'scalar', 'histogram', 'compressedHistogram', 'graph', 'image', 'audio',
'runMetadata'
];
/**
@@ -2272,6 +2295,14 @@ var TF;
return this.runs().then(function (x) { return _.mapValues(x, 'images'); });
};
/**
+ * Return a promise showing the Run-to-Tag mapping for audio data.
+ * TODO(cassandrax): Replace this with the direct route, when
+ * available.
+ */
+ Backend.prototype.audioRuns = function () {
+ return this.runs().then(function (x) { return _.mapValues(x, 'audio'); });
+ };
+ /**
* Return a promise showing the Run-to-Tag mapping for compressedHistogram
* data.
* TODO(cassandrax): Replace this with the direct route, when
@@ -2339,6 +2370,15 @@ var TF;
return p.then(map(this.createImage.bind(this)));
};
/**
+ * Return a promise containing AudioDatums for given run and tag.
+ */
+ Backend.prototype.audio = function (tag, run) {
+ var url = this.router.audio(tag, run);
+ var p;
+ p = this.requestManager.request(url);
+ return p.then(map(this.createAudio.bind(this)));
+ };
+ /**
* Returns a promise to load the string RunMetadata for given run/tag.
*/
Backend.prototype.runMetadata = function (tag, run) {
@@ -2365,6 +2405,14 @@ var TF;
url: this.router.individualImage(x.query),
};
};
+ Backend.prototype.createAudio = function (x) {
+ return {
+ content_type: x.content_type,
+ wall_time: timeToDate(x.wall_time),
+ step: x.step,
+ url: this.router.individualAudio(x.query),
+ };
+ };
return Backend;
}());
Backend_1.Backend = Backend;
@@ -2430,8 +2478,9 @@ var TF;
* to visualize. When visualizing histograms, having the left edge and width
* makes things quite a bit easier.
*
- * @param {histogram} Histogram - A histogram from tensorboard backend.
- * @return {HistogramBin[]} - Each bin has an x (left edge), a dx (width), and a y (count).
+ * @param histogram A histogram from tensorboard backend.
+ * @return A histogram bin. Each bin has an x (left edge), a dx (width),
+ * and a y (count).
*
* If given rightedges are inclusive, then these left edges (x) are exclusive.
*/
@@ -2537,15 +2586,15 @@ var TF;
backendReload: function () {
var _this = this;
if (this.dataType == null) {
- throw new Error("TF.Backend.Behavior: Need a dataType to reload.");
+ throw new Error('TF.Backend.Behavior: Need a dataType to reload.');
}
if (this.backend == null) {
- throw new Error("TF.Backend.Behavior: Need a backend to reload.");
+ throw new Error('TF.Backend.Behavior: Need a backend to reload.');
}
- var runsRoute = this.backend[this.dataType + "Runs"].bind(this.backend);
- this._setLoadState("pending");
+ var runsRoute = this.backend[this.dataType + 'Runs'].bind(this.backend);
+ this._setLoadState('pending');
return runsRoute().then(function (x) {
- _this._setLoadState("loaded");
+ _this._setLoadState('loaded');
if (_.isEqual(x, _this.run2tag)) {
// If x and run2tag are equal, let's avoid updating everything
// since that can needlessly trigger run changes, reloads, etc
@@ -2558,7 +2607,7 @@ var TF;
_this._setRuns(TF.Backend.getRuns(x));
return x;
}, function (fail) {
- _this._setLoadState("failure");
+ _this._setLoadState('failure');
return fail;
});
},
@@ -2573,7 +2622,7 @@ var TF;
},
_throwErrorOnUnrecognizedType: function (dataType) {
if (TF.Backend.TYPES.indexOf(dataType) === -1) {
- throw new Error("TF.Backend.Behavior: Unknown dataType " + dataType);
+ throw new Error('TF.Backend.Behavior: Unknown dataType ' + dataType);
}
},
};
@@ -2585,9 +2634,6 @@ var TF;
<template>
<div id="plumbing">
<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>
@@ -2600,7 +2646,7 @@ var TF;
<tf-x-type-selector id="xTypeSelector" out-x-type="{{xType}}"></tf-x-type-selector>
</div>
<div class="sidebar-section">
- <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>
+ <tf-run-selector id="runSelector" runs="[[runs]]" class-scale="[[classScale]]" out-selected="{{selectedRuns}}"></tf-run-selector>
</div>
</div>
<div class="center">
@@ -2612,7 +2658,7 @@ var TF;
<div class="card">
<span class="card-title">[[tag]]</span>
<div class="card-content">
- <tf-chart tag="[[tag]]" data-provider="[[dataProvider]]" type="scalar" id="chart" selected-runs="[[validRuns(tag, selectedRuns.*, run2tag.*)]]" x-type="[[xType]]" color-scale="[[colorScale]]" on-keyup="toggleSelected" tabindex="2" tooltip-updater="[[tooltipUpdater]]"></tf-chart>
+ <tf-chart tag="[[tag]]" data-provider="[[dataProvider]]" type="scalar" id="chart" selected-runs="[[validRuns(tag, selectedRuns.*, run2tag.*)]]" x-type="[[xType]]" color-scale="[[colorScale]]" on-keyup="toggleSelected" tabindex="2"></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]]">
@@ -2697,8 +2743,6 @@ var TF;
<template>
<div id="plumbing">
<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>
@@ -2710,7 +2754,7 @@ var TF;
<tf-x-type-selector id="xTypeSelector" out-x-type="{{xType}}"></tf-x-type-selector>
</div>
<div class="sidebar-section">
- <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>
+ <tf-run-selector id="runSelector" runs="[[runs]]" class-scale="[[classScale]]" out-selected="{{selectedRuns}}"></tf-run-selector>
</div>
</div>
@@ -2725,7 +2769,7 @@ var TF;
<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-provider="[[dataProvider]]" color-scale="[[colorScale]]" on-keyup="toggleSelected" tabindex="2" tooltip-updater="[[tooltipUpdater]]"></tf-chart>
+ <tf-chart tag="[[tag]]" type="compressedHistogram" id="chart" selected-runs="[[_array(run)]]" x-type="[[xType]]" data-provider="[[dataProvider]]" color-scale="[[colorScale]]" on-keyup="toggleSelected" tabindex="2"></tf-chart>
<paper-icon-button class="expand-button" icon="fullscreen" on-tap="toggleSelected"></paper-icon-button>
</div>
</div>
@@ -3003,6 +3047,211 @@ var TF;
</script>
</dom-module>
+<dom-module id="tf-audio-loader" assetpath="../tf-audio-dashboard/">
+ <style>
+ :host {
+ display: block;
+ }
+ img {
+ width: 100%;
+ height: 100%;
+ image-rendering: pixelated;
+ }
+ </style>
+ <template>
+ <template is="dom-if" if="[[audioUrl]]">
+ <audio controls="" loop="">
+ <source src="[[audioUrl]]" type="[[audioContentType]]">
+ </audio>
+ </template>
+ </template>
+ <script>
+ Polymer({
+ is: "tf-audio-loader",
+ properties: {
+ run: String,
+ tag: String,
+ audioGenerator: Function,
+ audioUrl: String,
+ audioContentType: String
+ },
+ reload: function() {
+ var _this = this;
+ this.audioUrl = ""; // force reload
+ this.audioContentType = "";
+ this.audioGenerator(this.tag, this.run).then(function(metadatas) {
+ var last_metadata = _.last(metadatas);
+ _this.audioUrl = last_metadata.url;
+ _this.audioContentType = last_metadata.content_type;
+ })
+ },
+ ready: function() {
+ // Need to test so that it will not error if it is constructed w/o
+ // all properties (so that it's possible to use stub to mock it out)
+ if (this.run != null && this.tag != null && this.audioGenerator != null) {
+ this.reload();
+ }
+ },
+ });
+ </script>
+</dom-module>
+
+<dom-module id="tf-audio-grid" assetpath="../tf-audio-dashboard/">
+ <template>
+ <style include="scrollbar-style"></style>
+ <div id="fullContainer" class="container scrollbar">
+ <div id="topRow" class="container">
+ <div class="noshrink" id="paddingCell"></div>
+ <template is="dom-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]]" as="tag">
+ <div class="audio-row container noshrink">
+ <div class="tag-name-cell noshrink">
+ <span class="tag-name">[[tag]]</span>
+ </div>
+ <template is="dom-repeat" items="[[runs]]" as="run">
+ <div class="audio-cell noshrink">
+ <template is="dom-if" if="[[_exists(run, tag, runToAudio.*)]]">
+ <tf-audio-loader id="loader" run="[[run]]" tag="[[tag]]" audio-generator="[[audioGenerator]]">
+ </tf-audio-loader>
+ </template>
+ </div>
+ </template>
+ </div>
+ </template>
+ </div>
+ </div>
+ <style>
+ :host {
+ display: block;
+ height: 100%;
+ }
+ .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%;
+ }
+ .audio-row {
+ flex-direction: row;
+ padding-top: 5px;
+ }
+ .audio-cell {
+ width: 300px;
+ height: 30px;
+ border: 1px solid black;
+ }
+ .tag-name-cell {
+ width: 300px;
+ height: 30px;
+ 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-audio-grid",
+ properties: {
+ runToAudio: Object,
+ tags: Array,
+ runs: Array,
+ audioGenerator: Function,
+ },
+ _exists: function (run, tag) {
+ return this.runToAudio[run].indexOf(tag) !== -1;
+ },
+ });
+ </script>
+</dom-module>
+
+<dom-module id="tf-audio-dashboard" assetpath="../tf-audio-dashboard/">
+ <template>
+ <div class="center">
+ <tf-no-data-warning data-type="audio" show-warning="[[dataNotFound]]"></tf-no-data-warning>
+ <tf-audio-grid id="audioGrid" run-to-audio="[[run2tag]]" audio-generator="[[dataProvider]]" tags="[[tags]]" runs="[[runs]]"></tf-audio-grid>
+ </div>
+
+ <style>
+ .center {
+ 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>
+ </template>
+ <script>
+ Polymer({
+ is: "tf-audio-dashboard",
+ properties: {
+ dataType: {value: "audio"},
+ },
+ behaviors: [
+ TF.Dashboard.ReloadBehavior("tf-audio-loader"),
+ TF.Backend.Behavior
+ ],
+ attached: function() {
+ this.async(function() {
+ this.fire("rendered");
+ });
+ },
+ _hasAudio: function(runToAudioChange) {
+ return _.values(runToAudioChange.base).some(function(arr) {
+ return arr.length > 0;
+ });
+ },
+ });
+ </script>
+</dom-module>
+
<dom-module id="tf-graph-loader" assetpath="../tf-graph-loader/">
</dom-module>
@@ -3066,7 +3315,7 @@ Polymer({
value: 0,
msg: ''
});
- var tracker = tf.getTracker(this);
+ var tracker = tf.graph.util.getTracker(this);
tf.graph.parser.fetchAndParseMetadata(path, tracker)
.then(function(stats) {
this._setOutStats(stats);
@@ -3078,7 +3327,7 @@ Polymer({
value: 0,
msg: ''
});
- var tracker = tf.getTracker(this);
+ var tracker = tf.graph.util.getTracker(this);
var hierarchyParams = {
verifyTemplate: true,
// If a set of numbered op nodes has at least this number of nodes
@@ -3091,7 +3340,7 @@ Polymer({
seriesMap: {},
};
this._setOutHierarchyParams(hierarchyParams);
- var dataTracker = tf.getSubtaskTracker(tracker, 30, 'Data');
+ var dataTracker = tf.graph.util.getSubtaskTracker(tracker, 30, 'Data');
tf.graph.parser.fetchAndParseGraphData(path, pbTxtFile, dataTracker)
.then(function(graph) {
// Build the flat graph (consists only of Op nodes).
@@ -3119,12 +3368,12 @@ Polymer({
outEmbeddingTypes: ['^[a-zA-Z]+Summary$'],
refEdges: refEdges
};
- var graphTracker = tf.getSubtaskTracker(tracker, 20, 'Graph');
+ var graphTracker = tf.graph.util.getSubtaskTracker(tracker, 20, 'Graph');
return tf.graph.build(graph, buildParams, graphTracker);
})
.then(function(graph) {
this._setOutGraph(graph);
- var hierarchyTracker = tf.getSubtaskTracker(tracker, 50,
+ var hierarchyTracker = tf.graph.util.getSubtaskTracker(tracker, 50,
'Namespace hierarchy');
return tf.graph.hierarchy.build(graph, hierarchyParams, hierarchyTracker);
}.bind(this))
@@ -3161,14 +3410,14 @@ Polymer({
</script>
<script>/* Copyright 2015 Google Inc. All Rights Reserved.
-Licensed under the Apache License, Version 2.0 (the "License");
+Licensed under the Apache License, Version 2.0 (the 'License');
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
+distributed under the License is distributed on an 'AS IS' BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -3176,149 +3425,159 @@ limitations under the License.
var tf;
(function (tf) {
/**
- * Recommended delay (ms) when running an expensive task asynchronously
- * that gives enough time for the progress bar to update its UI.
- */
- var ASYNC_TASK_DELAY = 20;
- function time(msg, task) {
- var start = Date.now();
- var result = task();
- /* tslint:disable */
- console.log(msg, ":", Date.now() - start, "ms");
- /* tslint:enable */
- return result;
- }
- tf.time = time;
- /**
- * Creates a tracker that sets the progress property of the
- * provided polymer component. The provided component must have
- * a property called 'progress' that is not read-only. The progress
- * property is an object with a numerical 'value' property and a
- * string 'msg' property.
- */
- function getTracker(polymerComponent) {
- return {
- setMessage: function (msg) {
- polymerComponent.set("progress", {
- value: polymerComponent.progress.value,
- msg: msg
- });
- },
- updateProgress: function (value) {
- polymerComponent.set("progress", {
- value: polymerComponent.progress.value + value,
- msg: polymerComponent.progress.msg
- });
- },
- reportError: function (msg, err) {
- // Log the stack trace in the console.
- console.error(err.stack);
- // And send a user-friendly message to the UI.
- polymerComponent.set("progress", {
- value: polymerComponent.progress.value,
- msg: msg,
- error: true
- });
- },
- };
- }
- tf.getTracker = getTracker;
- /**
- * Creates a tracker for a subtask given the parent tracker, the total progress
- * of the subtask and the subtask message. The parent task should pass a
- * subtracker to its subtasks. The subtask reports its own progress which
- * becames relative to the main task.
- */
- function getSubtaskTracker(parentTracker, impactOnTotalProgress, subtaskMsg) {
- return {
- setMessage: function (progressMsg) {
- // The parent should show a concatenation of its message along with
- // its subtask tracker message.
- parentTracker.setMessage(subtaskMsg + ": " + progressMsg);
- },
- updateProgress: function (incrementValue) {
- // Update the parent progress relative to the child progress.
- // For example, if the sub-task progresses by 30%, and the impact on the
- // total progress is 50%, then the task progresses by 30% * 50% = 15%.
- parentTracker
- .updateProgress(incrementValue * impactOnTotalProgress / 100);
- },
- reportError: function (msg, err) {
- // The parent should show a concatenation of its message along with
- // its subtask error message.
- parentTracker.reportError(subtaskMsg + ": " + msg, err);
- }
- };
- }
- tf.getSubtaskTracker = getSubtaskTracker;
- /**
- * Runs an expensive task and return the result.
+ * Mapping from color palette name to color palette, which contains
+ * exact colors for multiple states of a single color palette.
*/
- function runTask(msg, incProgressValue, task, tracker) {
- // Update the progress message to say the current running task.
- tracker.setMessage(msg);
- // Run the expensive task with a delay that gives enough time for the
- // UI to update.
- try {
- var result = tf.time(msg, task);
- // Update the progress value.
- tracker.updateProgress(incProgressValue);
- // Return the result to be used by other tasks.
- return result;
- }
- catch (e) {
- // Errors that happen inside asynchronous tasks are
- // reported to the tracker using a user-friendly message.
- tracker.reportError("Failed " + msg, e);
+ tf.COLORS = [
+ {
+ 'name': 'Google Blue',
+ 'color': '#4184f3',
+ 'active': '#3a53c5',
+ 'disabled': '#cad8fc'
+ },
+ {
+ 'name': 'Google Red',
+ 'color': '#db4437',
+ 'active': '#8f2a0c',
+ 'disabled': '#e8c6c1'
+ },
+ {
+ 'name': 'Google Yellow',
+ 'color': '#f4b400',
+ 'active': '#db9200',
+ 'disabled': '#f7e8b0'
+ },
+ {
+ 'name': 'Google Green',
+ 'color': '#0f9d58',
+ 'active': '#488046',
+ 'disabled': '#c2e1cc'
+ },
+ {
+ 'name': 'Purple',
+ 'color': '#aa46bb',
+ 'active': '#5c1398',
+ 'disabled': '#d7bce6'
+ },
+ {
+ 'name': 'Teal',
+ 'color': '#00abc0',
+ 'active': '#47828e',
+ 'disabled': '#c2eaf2'
+ },
+ {
+ 'name': 'Deep Orange',
+ 'color': '#ff6f42',
+ 'active': '#ca4a06',
+ 'disabled': '#f2cbba'
+ },
+ {
+ 'name': 'Lime',
+ 'color': '#9d9c23',
+ 'active': '#7f771d',
+ 'disabled': '#f1f4c2'
+ },
+ {
+ 'name': 'Indigo',
+ 'color': '#5b6abf',
+ 'active': '#3e47a9',
+ 'disabled': '#c5c8e8'
+ },
+ {
+ 'name': 'Pink',
+ 'color': '#ef6191',
+ 'active': '#ca1c60',
+ 'disabled': '#e9b9ce'
+ },
+ {
+ 'name': 'Deep Teal',
+ 'color': '#00786a',
+ 'active': '#2b4f43',
+ 'disabled': '#bededa'
+ },
+ {
+ 'name': 'Deep Pink',
+ 'color': '#c1175a',
+ 'active': '#75084f',
+ 'disabled': '#de8cae'
+ },
+ {
+ 'name': 'Gray',
+ 'color': '#9E9E9E',
+ 'active': '#424242',
+ 'disabled': 'F5F5F5' // 100
}
- }
- tf.runTask = runTask;
- /**
- * Runs an expensive task asynchronously and returns a promise of the result.
- */
- function runAsyncTask(msg, incProgressValue, task, tracker) {
- return new Promise(function (resolve, reject) {
- // Update the progress message to say the current running task.
- tracker.setMessage(msg);
- // Run the expensive task with a delay that gives enough time for the
- // UI to update.
- setTimeout(function () {
- try {
- var result = tf.time(msg, task);
- // Update the progress value.
- tracker.updateProgress(incProgressValue);
- // Return the result to be used by other tasks.
- resolve(result);
- }
- catch (e) {
- // Errors that happen inside asynchronous tasks are
- // reported to the tracker using a user-friendly message.
- tracker.reportError("Failed " + msg, e);
- }
- }, ASYNC_TASK_DELAY);
- });
- }
- tf.runAsyncTask = runAsyncTask;
+ ].reduce(function (m, c) {
+ m[c.name] = c;
+ return m;
+ }, {});
/**
- * Returns a query selector with escaped special characters that are not
- * allowed in a query selector.
+ * Mapping from op category to color palette name
+ * e.g., OP_GROUP_COLORS['state_ops'] = 'Google Blue';
*/
- function escapeQuerySelector(querySelector) {
- return querySelector.replace(/([:.\[\],/\\\(\)])/g, "\\$1");
- }
- tf.escapeQuerySelector = escapeQuerySelector;
-})(tf || (tf = {})); // close module tf
+ tf.OP_GROUP_COLORS = [
+ {
+ color: 'Google Red',
+ groups: [
+ 'gen_legacy_ops', 'legacy_ops', 'legacy_flogs_input',
+ 'legacy_image_input', 'legacy_input_example_input',
+ 'legacy_sequence_input', 'legacy_seti_input_input'
+ ]
+ },
+ { color: 'Deep Orange', groups: ['constant_ops'] },
+ { color: 'Indigo', groups: ['state_ops'] },
+ { color: 'Purple', groups: ['nn_ops', 'nn'] },
+ { color: 'Google Green', groups: ['math_ops'] },
+ { color: 'Lime', groups: ['array_ops'] },
+ { color: 'Teal', groups: ['control_flow_ops', 'data_flow_ops'] },
+ { color: 'Pink', groups: ['summary_ops'] },
+ { color: 'Deep Pink', groups: ['io_ops'] }
+ ].reduce(function (m, c) {
+ c.groups.forEach(function (group) { m[group] = c.color; });
+ return m;
+ }, {});
+})(tf || (tf = {}));
</script>
<script>/* Copyright 2015 Google Inc. All Rights Reserved.
-Licensed under the Apache License, Version 2.0 (the "License");
+Licensed under the Apache License, Version 2.0 (the 'License');
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
+distributed under the License is distributed on an 'AS IS' BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+</script>
+<script>/* Copyright 2015 Google Inc. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the 'License');
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an 'AS IS' BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+</script>
+<script>/* Copyright 2015 Google Inc. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the 'License');
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an 'AS IS' BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -3328,17 +3587,17 @@ var tf;
var graph;
(function (graph_1) {
/** Delimiter used in node names to denote namespaces. */
- graph_1.NAMESPACE_DELIM = "/";
- graph_1.ROOT_NAME = "__root__";
+ graph_1.NAMESPACE_DELIM = '/';
+ graph_1.ROOT_NAME = '__root__';
/** Attribute key used for storing attributes that are too large. */
- graph_1.LARGE_ATTRS_KEY = "_too_large_attrs";
+ graph_1.LARGE_ATTRS_KEY = '_too_large_attrs';
/**
* Maximum allowed size in bytes, before the attribute is considered large
* and filtered out of the graph.
*/
graph_1.LIMIT_ATTR_SIZE = 1024;
// Separator between the source and the destination name of the edge.
- graph_1.EDGE_KEY_DELIM = "--";
+ graph_1.EDGE_KEY_DELIM = '--';
(function (GraphType) {
GraphType[GraphType["FULL"] = 0] = "FULL";
GraphType[GraphType["EMBEDDED"] = 1] = "EMBEDDED";
@@ -3376,7 +3635,7 @@ var tf;
var SeriesGroupingType = graph_1.SeriesGroupingType;
;
/** Attribute key reserved for the shapes of the output tensors. */
- var OUTPUT_SHAPES_KEY = "_output_shapes";
+ var OUTPUT_SHAPES_KEY = '_output_shapes';
/**
* A SlimGraph is inspired by graphlib.Graph, but having only the functionality
* that we need.
@@ -3406,7 +3665,7 @@ var tf;
}
EllipsisNodeImpl.prototype.setNumMoreNodes = function (numNodes) {
this.numMoreNodes = numNodes;
- this.name = "... " + numNodes + " more";
+ this.name = '... ' + numNodes + ' more';
};
return EllipsisNodeImpl;
}());
@@ -3461,9 +3720,9 @@ var tf;
// Lookup the node in the graph by its original name, e.g. A. If not
// found, lookup by the rewritten name A/(A) in case the name is both
// a namespace and a node name.
- var nodeName = nodeStats.node_name in graph.nodes ?
- nodeStats.node_name :
- nodeStats.node_name + graph_1.NAMESPACE_DELIM + "(" + nodeStats.node_name + ")";
+ var nodeName = nodeStats.node_name in graph.nodes ? nodeStats.node_name :
+ nodeStats.node_name +
+ graph_1.NAMESPACE_DELIM + '(' + nodeStats.node_name + ')';
// Couldn't find a matching node.
if (!(nodeName in graph.nodes)) {
return;
@@ -3473,10 +3732,26 @@ var tf;
if (nodeStats.memory) {
_.each(nodeStats.memory, function (alloc) {
if (alloc.total_bytes) {
- totalBytes += Number(alloc.total_bytes);
+ if (alloc.total_bytes > 0) {
+ totalBytes += Number(alloc.total_bytes);
+ }
+ else {
+ /* tslint:disable */
+ console.log('ignoring negative memory allocation for ' + nodeName);
+ }
}
});
}
+ var totalMicroSeconds = 0;
+ if (nodeStats.all_end_rel_micros) {
+ if (nodeStats.all_end_rel_micros > 0) {
+ totalMicroSeconds = Number(nodeStats.all_end_rel_micros);
+ }
+ else {
+ /* tslint:disable */
+ console.log('ignoring negative runtime for ' + nodeName);
+ }
+ }
var outputSize = null;
if (nodeStats.output) {
outputSize = _.map(nodeStats.output, function (output) {
@@ -3484,7 +3759,7 @@ var tf;
});
}
graph.nodes[nodeName].device = devStats.device;
- graph.nodes[nodeName].stats = new NodeStats(totalBytes, Number(nodeStats.all_end_rel_micros), outputSize);
+ graph.nodes[nodeName].stats = new NodeStats(totalBytes, totalMicroSeconds, outputSize);
});
});
}
@@ -3550,12 +3825,12 @@ var tf;
};
/**
* Returns the op node associated with the metanode.
- * For example, if the metanode is "sgd", the associated
+ * For example, if the metanode is 'sgd', the associated
* op node is sgd/(sgd).
*/
MetanodeImpl.prototype.getRootOp = function () {
- var nameSplit = this.name.split("/");
- var rootOpName = this.name + "/(" + nameSplit[nameSplit.length - 1] + ")";
+ var nameSplit = this.name.split('/');
+ var rootOpName = this.name + '/(' + nameSplit[nameSplit.length - 1] + ')';
return this.metagraph.node(rootOpName);
};
/**
@@ -3652,10 +3927,11 @@ var tf;
}
graph_1.createSeriesNode = createSeriesNode;
function getSeriesNodeName(prefix, suffix, parent, startId, endId) {
- var numRepresentation = (typeof startId !== "undefined" && typeof endId !== "undefined") ?
- "[" + startId + "-" + endId + "]" : "#";
+ var numRepresentation = (typeof startId !== 'undefined' && typeof endId !== 'undefined') ?
+ '[' + startId + '-' + endId + ']' :
+ '#';
var pattern = prefix + numRepresentation + suffix;
- return (parent ? parent + "/" : "") + pattern;
+ return (parent ? parent + '/' : '') + pattern;
}
graph_1.getSeriesNodeName = getSeriesNodeName;
var SeriesNodeImpl = (function () {
@@ -3733,8 +4009,8 @@ var tf;
*/
function normalizeInputs(inputs) {
return _.reduce(inputs, function (normalizedInputs, inputName) {
- var start = inputName[0] === "^";
- var colon = inputName.lastIndexOf(":");
+ var start = inputName[0] === '^';
+ var colon = inputName.lastIndexOf(':');
var end = colon !== -1 &&
inputName.length - colon > 1 &&
!(/\D/).test(inputName.substring(colon + 1)) ?
@@ -3758,7 +4034,7 @@ var tf;
}
// Check if this op type and input number corresponds to a
// reference edge using the refEdges dictionary in the params.
- var isRefEdge = params.refEdges[outputNode.op + " " + index] === true;
+ var isRefEdge = params.refEdges[outputNode.op + ' ' + index] === true;
graph.edges.push({
v: inputName,
w: outputNode.name,
@@ -3795,7 +4071,8 @@ var tf;
* even for very large networks that amounts to less than 10k spaces.
*/
var nodeNames = new Array(rawNodes.length);
- return tf.runAsyncTask("Normalizing names", 30, function () {
+ return tf.graph.util
+ .runAsyncTask('Normalizing names', 30, function () {
var opNodes = new Array(rawNodes.length);
var index = 0;
_.each(rawNodes, function (rawNode) {
@@ -3815,7 +4092,8 @@ var tf;
});
return;
}
- // The node is not an embedding, so add it to the names and nodes lists.
+ // The node is not an embedding, so add it to the names and nodes
+ // lists.
opNodes[index] = opNode;
nodeNames[index] = opNode.name;
index++;
@@ -3826,7 +4104,7 @@ var tf;
}, tracker)
.then(function (opNodes) {
// Create the graph data structure from the graphlib library.
- return tf.runAsyncTask("Building the data structure", 70, function () {
+ return tf.graph.util.runAsyncTask('Building the data structure', 70, function () {
var normalizedNameDict = mapStrictHierarchy(nodeNames, embeddingNodeNames);
var graph = new SlimGraph;
// Add the nodes to the graph.
@@ -3845,20 +4123,26 @@ var tf;
// 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.
+ // Visit each node's inputs to add the edges to the graph. If the
+ // input
+ // is an in-embedding, then add it to the node's in-embeddings
+ // instead.
_.each(opNodes, function (opNode) {
_.each(opNode.inputs, function (input, i) {
var inputName = input.name;
if (inputName in inEmbedding) {
var inEmbedNode = inEmbedding[inputName];
opNode.inEmbeddings.push(inEmbedNode);
- // Move the inputs of the in-embedding node into incoming edges of
- // the main node. E.g. the control dependency of a constant node
- // should be moved to the op node where the constant is embedded.
+ // Move the inputs of the in-embedding node into incoming
+ // edges of
+ // the main node. E.g. the control dependency of a constant
+ // node
+ // should be moved to the op node where the constant is
+ // embedded.
for (var _i = 0, _a = inEmbedNode.inputs; _i < _a.length; _i++) {
var embedInput = _a[_i];
- addEdgeToGraph(graph, normalizedNameDict[embedInput.name] || embedInput.name, opNode, embedInput.isControlDependency, params, i);
+ addEdgeToGraph(graph, normalizedNameDict[embedInput.name] ||
+ embedInput.name, opNode, embedInput.isControlDependency, params, i);
}
}
else if (inputName in outEmbedding) {
@@ -3867,7 +4151,8 @@ var tf;
var outEmbedNode = outEmbedding[inputName];
for (var _b = 0, _c = outEmbedNode.inputs; _b < _c.length; _b++) {
var embedInput = _c[_b];
- addEdgeToGraph(graph, normalizedNameDict[embedInput.name] || embedInput.name, opNode, input.isControlDependency, params, i);
+ addEdgeToGraph(graph, normalizedNameDict[embedInput.name] ||
+ embedInput.name, opNode, input.isControlDependency, params, i);
}
}
else {
@@ -3893,7 +4178,7 @@ var tf;
var graph = new graphlib.Graph(opt);
graph.setGraph({
name: name,
- rankdir: "BT",
+ rankdir: 'BT',
type: type
});
return graph;
@@ -3923,15 +4208,15 @@ var tf;
*/
function getStrictName(name) {
var parts = name.split(graph_1.NAMESPACE_DELIM);
- return name + graph_1.NAMESPACE_DELIM + "(" + parts[parts.length - 1] + ")";
+ return name + graph_1.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.
+ * 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.
@@ -3953,9 +4238,17 @@ var tf;
_.each(getHierarchicalPath(a).slice(0, -1), function (ns) {
namespaceSet[ns] = true;
});
- var b = nodeNames[i + 1];
- if (_.startsWith(b, a + graph_1.NAMESPACE_DELIM)) {
- newNameDictionary[a] = getStrictName(a);
+ for (var j = i + 1; j < nodeNames.length; ++j) {
+ var b = nodeNames[j];
+ if (_.startsWith(b, a)) {
+ if (b.length > a.length && b.charAt(a.length) === graph_1.NAMESPACE_DELIM) {
+ newNameDictionary[a] = getStrictName(a);
+ break;
+ }
+ }
+ else {
+ break;
+ }
}
}
// Go through all the embedding node names and rename them in case they
@@ -4028,10 +4321,10 @@ var tf;
*/
function getIncludeNodeButtonString(include) {
if (include === tf.graph.InclusionType.EXCLUDE) {
- return "Add to main graph";
+ return 'Add to main graph';
}
else {
- return "Remove from main graph";
+ return 'Remove from main graph';
}
}
graph_1.getIncludeNodeButtonString = getIncludeNodeButtonString;
@@ -4042,10 +4335,10 @@ var tf;
*/
function getGroupSeriesNodeButtonString(group) {
if (group === tf.graph.SeriesGroupingType.GROUP) {
- return "Ungroup this series of nodes";
+ return 'Ungroup this series of nodes';
}
else {
- return "Group this series of nodes";
+ return 'Group this series of nodes';
}
}
graph_1.getGroupSeriesNodeButtonString = getGroupSeriesNodeButtonString;
@@ -4069,275 +4362,14 @@ var tf;
</script>
<script>/* Copyright 2015 Google Inc. All Rights Reserved.
-Licensed under the Apache License, Version 2.0 (the "License");
+Licensed under the Apache License, Version 2.0 (the 'License');
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-==============================================================================*/
-var tf;
-(function (tf) {
- var graph;
- (function (graph) {
- var parser;
- (function (parser) {
- /**
- * Parses a native js value, which can be either a string, boolean or number.
- *
- * @param value The value to be parsed.
- */
- function parseValue(value) {
- if (value === "true") {
- return true;
- }
- if (value === "false") {
- return false;
- }
- var firstChar = value[0];
- if (firstChar === "\"") {
- return value.substring(1, value.length - 1);
- }
- var num = parseFloat(value);
- return isNaN(num) ? value : num;
- }
- /**
- * Fetches a text file and returns a promise of the result.
- */
- function fetchPbTxt(filepath) {
- return new Promise(function (resolve, reject) {
- d3.text(filepath, function (error, text) {
- if (error) {
- reject(error);
- return;
- }
- resolve(text);
- });
- });
- }
- parser.fetchPbTxt = fetchPbTxt;
- /**
- * Fetches the metadata file, parses it and returns a promise of the result.
- */
- function fetchAndParseMetadata(path, tracker) {
- return tf.runTask("Reading metadata pbtxt", 40, function () {
- if (path == null) {
- return Promise.resolve(null);
- }
- return fetchPbTxt(path).then(function (text) { return new Blob([text]); });
- }, tracker)
- .then(function (blob) {
- return tf.runTask("Parsing metadata.pbtxt", 60, function () {
- return blob != null ? parseStatsPbTxt(blob) : null;
- }, tracker);
- });
- }
- parser.fetchAndParseMetadata = fetchAndParseMetadata;
- /**
- * Fetches the graph file, parses it and returns a promise of the result.
- */
- function fetchAndParseGraphData(path, pbTxtFile, tracker) {
- return tf.runTask("Reading graph pbtxt", 40, function () {
- return pbTxtFile ?
- Promise.resolve(pbTxtFile) :
- fetchPbTxt(path).then(function (text) { return new Blob([text]); });
- }, tracker)
- .then(function (blob) {
- return tf.runTask("Parsing graph.pbtxt", 60, function () {
- return parseGraphPbTxt(blob);
- }, tracker);
- });
- }
- parser.fetchAndParseGraphData = fetchAndParseGraphData;
- /**
- * Parse a file object in a streaming fashion line by line (or custom delim).
- * Can handle very large files.
- * @param input The file object
- * @param callback The callback called on each line
- * @param chunkSize The size of each read chunk. (optional)
- * @param delim The delimiter used to split a line. (optional)
- * @returns A promise for when it is finished.
- */
- function streamParse(file, callback, chunkSize, delim) {
- if (chunkSize === void 0) { chunkSize = 1000000; }
- if (delim === void 0) { delim = "\n"; }
- return new Promise(function (resolve, reject) {
- var offset = 0;
- var fileSize = file.size - 1;
- var data = "";
- function readHandler(evt) {
- if (evt.target.error == null) {
- offset += evt.target.result.length;
- var str = evt.target.result;
- var parts = str.split(delim);
- var first = data + parts[0];
- if (parts.length === 1) {
- data = first;
- readChunk(offset, chunkSize);
- return;
- }
- data = parts[parts.length - 1];
- callback(first);
- for (var i = 1; i < parts.length - 1; i++) {
- callback(parts[i]);
- }
- }
- else {
- // read error
- reject(evt.target.error);
- return;
- }
- if (offset >= fileSize) {
- if (data) {
- callback(data);
- }
- resolve(true);
- return;
- }
- readChunk(offset, chunkSize);
- }
- function readChunk(offset, size) {
- var reader = new FileReader();
- var blob = file.slice(offset, offset + size);
- reader.onload = readHandler;
- reader.readAsText(blob);
- }
- readChunk(offset, chunkSize);
- });
- }
- parser.streamParse = streamParse;
- /**
- * Since proto-txt doesn't explicitly say whether an attribute is repeated
- * (an array) or not, we keep a hard-coded list of attributes that are known
- * to be repeated. This list is used in parsing time to convert repeated
- * attributes into arrays even when the attribute only shows up once in the
- * object.
- */
- var GRAPH_REPEATED_FIELDS = {
- "node": true,
- "node.input": true,
- "node.attr": true,
- "node.attr.value.list.type": true,
- "node.attr.value.shape.dim": true,
- "node.attr.value.tensor.string_val": true,
- "node.attr.value.tensor.tensor_shape.dim": true,
- "node.attr.value.list.shape": true,
- "node.attr.value.list.shape.dim": true,
- "node.attr.value.list.s": true
- };
- var METADATA_REPEATED_FIELDS = {
- "step_stats.dev_stats": true,
- "step_stats.dev_stats.node_stats": true,
- "step_stats.dev_stats.node_stats.output": true,
- "step_stats.dev_stats.node_stats.memory": true,
- "step_stats.dev_stats.node_stats.output.tensor_description.shape.dim": true
- };
- /**
- * Parses a blob of proto txt file into a raw Graph object.
- */
- function parseGraphPbTxt(input) {
- return parsePbtxtFile(input, GRAPH_REPEATED_FIELDS).then(function (obj) { return obj["node"]; });
- }
- parser.parseGraphPbTxt = parseGraphPbTxt;
- /**
- * Parses a blob of proto txt file into a StepStats object.
- */
- function parseStatsPbTxt(input) {
- return parsePbtxtFile(input, METADATA_REPEATED_FIELDS)
- .then(function (obj) { return obj["step_stats"]; });
- }
- /**
- * Parses a blob of proto txt file into javascript object.
- *
- * @param input The Blob or file object implementing slice.
- * @param repeatedFields Map (Set) of all the repeated fields, since you can't
- * tell directly from the pbtxt if a field is repeated or not.
- * @returns The parsed object.
- */
- function parsePbtxtFile(input, repeatedFields) {
- var output = {};
- var stack = [];
- var path = [];
- var current = output;
- function splitNameAndValueInAttribute(line) {
- var colonIndex = line.indexOf(":");
- var name = line.substring(0, colonIndex).trim();
- var value = parseValue(line.substring(colonIndex + 2).trim());
- return {
- name: name,
- value: value
- };
- }
- /**
- * Adds a value, given the attribute name and the host object. If the
- * attribute already exists, but is not an array, it will convert it to an
- * array of values.
- *
- * @param obj The host object that holds the attribute.
- * @param name The attribute name (key).
- * @param value The attribute value.
- * @param path A path that identifies the attribute. Used to check if
- * an attribute is an array or not.
- */
- function addAttribute(obj, name, value, path) {
- // We treat "node" specially since it is done so often.
- var existingValue = obj[name];
- if (existingValue == null) {
- obj[name] = path.join(".") in repeatedFields ? [value] : value;
- }
- else if (Array.isArray(existingValue)) {
- existingValue.push(value);
- }
- else {
- obj[name] = [existingValue, value];
- }
- }
- // Run through the file a line at a time.
- return streamParse(input, function (line) {
- if (!line) {
- return;
- }
- switch (line[line.length - 1]) {
- case "{":
- var name_1 = line.substring(0, line.length - 2).trim();
- var newValue = {};
- stack.push(current);
- path.push(name_1);
- addAttribute(current, name_1, newValue, path);
- current = newValue;
- break;
- case "}":
- current = stack.pop();
- path.pop();
- break;
- default:
- var x = splitNameAndValueInAttribute(line);
- addAttribute(current, x.name, x.value, path.concat(x.name));
- break;
- }
- }).then(function () {
- return output;
- });
- }
- })(parser = graph.parser || (graph.parser = {}));
- })(graph = tf.graph || (tf.graph = {}));
-})(tf || (tf = {})); // Close module tf.graph.parser.
-</script>
-<script>/* Copyright 2015 Google Inc. All Rights Reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
+distributed under the License is distributed on an 'AS IS' BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -4388,9 +4420,9 @@ var tf;
var _this = this;
var node = this.index[nodeName];
if (!node) {
- throw Error("Could not find node in hierarchy: " + nodeName);
+ throw Error('Could not find node in hierarchy: ' + nodeName);
}
- if (!("metagraph" in node)) {
+ if (!('metagraph' in node)) {
return null;
}
var groupNode = node;
@@ -4398,8 +4430,8 @@ var tf;
return groupNode.bridgegraph;
}
var bridgegraph = groupNode.bridgegraph =
- graph_1.createGraph("BRIDGEGRAPH", graph_1.GraphType.BRIDGE);
- if (!node.parentNode || !("metagraph" in node.parentNode)) {
+ graph_1.createGraph('BRIDGEGRAPH', graph_1.GraphType.BRIDGE);
+ if (!node.parentNode || !('metagraph' in node.parentNode)) {
return bridgegraph;
}
var parentNode = node.parentNode;
@@ -4419,7 +4451,7 @@ var tf;
// there's a Metaedge in the bridgegraph that covers it.
_.each(parentMetaedge.baseEdgeList, function (baseEdge) {
// Based on the direction, figure out which is the descendant node
- // and which is the "other" node (sibling of parent or ancestor).
+ // and which is the 'other' node (sibling of parent or ancestor).
var _a = inbound ?
[baseEdge.w, parentEdgeObj.v] :
[baseEdge.v, parentEdgeObj.w], descendantName = _a[0], otherName = _a[1];
@@ -4460,8 +4492,7 @@ var tf;
}
currentNode = currentNode.parentNode;
}
- throw Error("Could not find immediate child for descendant: " +
- descendantName);
+ throw Error('Could not find immediate child for descendant: ' + descendantName);
};
;
/**
@@ -4474,16 +4505,16 @@ var tf;
* 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: []};
+ * - 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
+ * 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
+ * we look at Z/Y's predecessors, the best we can say is ['A'] without getting
* into the details of which of Z/Y's descendant nodes have predecessors to
* which of A's descendants.
*
@@ -4493,7 +4524,7 @@ var tf;
HierarchyImpl.prototype.getPredecessors = function (nodeName) {
var node = this.index[nodeName];
if (!node) {
- throw Error("Could not find node with name: " + nodeName);
+ throw Error('Could not find node with name: ' + nodeName);
}
var predecessors = this.getOneWayEdges(node, true);
// Add embedded predecessors, such as constants.
@@ -4516,7 +4547,7 @@ var tf;
HierarchyImpl.prototype.getSuccessors = function (nodeName) {
var node = this.index[nodeName];
if (!node) {
- throw Error("Could not find node with name: " + nodeName);
+ throw Error('Could not find node with name: ' + nodeName);
}
var successors = this.getOneWayEdges(node, false);
// Add embedded successors, such as summaries.
@@ -4552,9 +4583,9 @@ var tf;
* 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
+ * - { '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
@@ -4570,7 +4601,7 @@ var tf;
HierarchyImpl.prototype.getTopologicalOrdering = function (nodeName) {
var node = this.index[nodeName];
if (!node) {
- throw Error("Could not find node with name: " + nodeName);
+ throw Error('Could not find node with name: ' + nodeName);
}
if (!node.isGroupNode) {
return null;
@@ -4660,7 +4691,8 @@ var tf;
function build(graph, params, tracker) {
var h = new HierarchyImpl();
var seriesNames = {};
- return tf.runAsyncTask("Adding nodes", 20, function () {
+ return tf.graph.util
+ .runAsyncTask('Adding nodes', 20, function () {
// Get all the possible device names.
var deviceNames = {};
_.each(graph.nodes, function (node, nodeName) {
@@ -4672,25 +4704,23 @@ var tf;
addNodes(h, graph);
}, tracker)
.then(function () {
- return tf.runAsyncTask("Detect series", 20, function () {
+ return tf.graph.util.runAsyncTask('Detect series', 20, function () {
if (params.seriesNodeMinSize > 0) {
groupSeries(h.root, h, seriesNames, params.seriesNodeMinSize, params.seriesMap);
}
}, tracker);
})
.then(function () {
- return tf.runAsyncTask("Adding edges", 30, function () {
+ return tf.graph.util.runAsyncTask('Adding edges', 30, function () {
addEdges(h, graph, seriesNames);
}, tracker);
})
.then(function () {
- return tf.runAsyncTask("Finding similar subgraphs", 30, function () {
+ return tf.graph.util.runAsyncTask('Finding similar subgraphs', 30, function () {
h.templates = graph_1.template.detect(h, params.verifyTemplate);
}, tracker);
})
- .then(function () {
- return h;
- });
+ .then(function () { return h; });
}
hierarchy_1.build = build;
;
@@ -4819,8 +4849,8 @@ var tf;
// 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.");
+ // metanodes and op nodes. E.g. 'A/B' becomes 'A/B/(B)'.
+ throw Error('No difference found between ancestor paths.');
}
}
var sharedAncestorNode = nodeIndex[sourcePath[sourceAncestorIndex + 1]];
@@ -4928,7 +4958,7 @@ var tf;
}
/**
* 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
+ * Infer series name using by trying to find pattern '<number>' in the node
* name.
*
* @param clusters Dictionary output from clusterNodes().
@@ -4950,14 +4980,14 @@ var tf;
// number at the end of the name after an underscore, which is allowed to
// vary.
_.each(members, function (name) {
- var isGroup = name.charAt(name.length - 1) === "*";
- var namepath = name.split("/");
+ var isGroup = name.charAt(name.length - 1) === '*';
+ var namepath = name.split('/');
var leaf = namepath[namepath.length - 1];
- var parent = namepath.slice(0, namepath.length - 1).join("/");
+ var parent = namepath.slice(0, namepath.length - 1).join('/');
var matches = leaf.match(/^(\D*)_(\d+)$/);
var prefix;
var id;
- var suffix = "";
+ var suffix = '';
if (matches) {
prefix = matches[1]; // the front non-numeric characters
id = matches[2]; // the digits
@@ -4965,7 +4995,7 @@ var tf;
else {
prefix = isGroup ? leaf.substr(0, leaf.length - 1) : leaf;
id = 0;
- suffix = isGroup ? "*" : "";
+ suffix = isGroup ? '*' : '';
}
var seriesName = graph_1.getSeriesNodeName(prefix, suffix, parent);
candidatesDict[seriesName] = candidatesDict[seriesName] || [];
@@ -5023,6 +5053,981 @@ var tf;
})(graph = tf.graph || (tf.graph = {}));
})(tf || (tf = {})); // close module tf.graph.hierarchy
</script>
+<script>/* Copyright 2015 Google Inc. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the 'License');
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an 'AS IS' BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+var tf;
+(function (tf) {
+ var graph;
+ (function (graph_1) {
+ var layout;
+ (function (layout) {
+ /** Set of parameters that define the look and feel of the graph. */
+ layout.PARAMS = {
+ animation: {
+ /** Default duration for graph animations in ms. */
+ duration: 250
+ },
+ graph: {
+ /** Graph parameter for metanode. */
+ meta: {
+ /**
+ * Dagre's nodesep param - number of pixels that
+ * separate nodes horizontally in the layout.
+ *
+ * See https://github.com/cpettitt/dagre/wiki#configuring-the-layout
+ */
+ nodeSep: 5,
+ /**
+ * Dagre's ranksep param - number of pixels
+ * between each rank in the layout.
+ *
+ * See https://github.com/cpettitt/dagre/wiki#configuring-the-layout
+ */
+ rankSep: 25,
+ /**
+ * Dagre's edgesep param - number of pixels that separate
+ * edges horizontally in the layout.
+ */
+ edgeSep: 5,
+ },
+ /** Graph parameter for metanode. */
+ series: {
+ /**
+ * Dagre's nodesep param - number of pixels that
+ * separate nodes horizontally in the layout.
+ *
+ * See https://github.com/cpettitt/dagre/wiki#configuring-the-layout
+ */
+ nodeSep: 5,
+ /**
+ * Dagre's ranksep param - number of pixels
+ * between each rank in the layout.
+ *
+ * See https://github.com/cpettitt/dagre/wiki#configuring-the-layout
+ */
+ rankSep: 25,
+ /**
+ * Dagre's edgesep param - number of pixels that separate
+ * edges horizontally in the layout.
+ */
+ edgeSep: 5
+ },
+ /**
+ * Padding is used to correctly position the graph SVG inside of its parent
+ * element. The padding amounts are applied using an SVG transform of X and
+ * Y coordinates.
+ */
+ padding: {
+ paddingTop: 40,
+ paddingLeft: 20
+ }
+ },
+ subscene: {
+ meta: {
+ paddingTop: 10,
+ paddingBottom: 10,
+ paddingLeft: 10,
+ paddingRight: 10,
+ /**
+ * Used to leave room for the label on top of the highest node in
+ * the core graph.
+ */
+ labelHeight: 20,
+ /** X-space between each extracted node and the core graph. */
+ extractXOffset: 15,
+ /** Y-space between each extracted node. */
+ extractYOffset: 20
+ },
+ series: {
+ paddingTop: 10,
+ paddingBottom: 10,
+ paddingLeft: 10,
+ paddingRight: 10,
+ labelHeight: 10
+ }
+ },
+ nodeSize: {
+ /** Size of meta nodes. */
+ meta: {
+ radius: 5,
+ width: 60,
+ /** A scale for the node's height based on number of nodes inside */
+ height: d3.scale.linear().domain([1, 200]).range([15, 60]).clamp(true),
+ /** The radius of the circle denoting the expand button. */
+ expandButtonRadius: 3
+ },
+ /** Size of op nodes. */
+ op: {
+ width: 15,
+ height: 6,
+ radius: 3,
+ labelOffset: -8
+ },
+ /** Size of series nodes. */
+ series: {
+ expanded: {
+ // For expanded series nodes, width and height will be
+ // computed to account for the subscene.
+ radius: 10,
+ labelOffset: 0,
+ },
+ vertical: {
+ // When unexpanded, series whose underlying metagraphs contain
+ // one or more non-control edges will show as a vertical stack
+ // of ellipses.
+ width: 16,
+ height: 13,
+ labelOffset: -13,
+ },
+ horizontal: {
+ // When unexpanded, series whose underlying metagraphs contain
+ // no non-control edges will show as a horizontal stack of
+ // ellipses.
+ width: 24,
+ height: 8,
+ radius: 10,
+ labelOffset: -10,
+ },
+ },
+ /** Size of bridge nodes. */
+ bridge: {
+ // NOTE: bridge nodes will normally be invisible, but they must
+ // take up some space so that the layout step leaves room for
+ // their edges.
+ width: 20,
+ height: 20,
+ radius: 2,
+ labelOffset: 0
+ }
+ },
+ shortcutSize: {
+ /** Size of shortcuts for op nodes */
+ op: {
+ width: 10,
+ height: 4
+ },
+ /** Size of shortcuts for meta nodes */
+ meta: {
+ width: 12,
+ height: 4,
+ radius: 1
+ },
+ /** Size of shortcuts for series nodes */
+ series: {
+ width: 14,
+ height: 4,
+ }
+ },
+ annotations: {
+ /** Maximum possible width of the bounding box for in annotations */
+ inboxWidth: 50,
+ /** Maximum possible width of the bounding box for out annotations */
+ outboxWidth: 50,
+ /** X-space between the shape and each annotation-node. */
+ xOffset: 10,
+ /** Y-space between each annotation-node. */
+ yOffset: 3,
+ /** X-space between each annotation-node and its label. */
+ labelOffset: 2,
+ /** 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. */
+ function layoutScene(renderNodeInfo) {
+ // Update layout, size, and annotations of its children nodes and edges.
+ if (renderNodeInfo.node.isGroupNode) {
+ layoutChildren(renderNodeInfo);
+ }
+ // Update position of its children nodes and edges
+ if (renderNodeInfo.node.type === graph_1.NodeType.META) {
+ layoutMetanode(renderNodeInfo);
+ }
+ else if (renderNodeInfo.node.type === graph_1.NodeType.SERIES) {
+ layoutSeriesNode(renderNodeInfo);
+ }
+ }
+ layout.layoutScene = layoutScene;
+ ;
+ /**
+ * Updates the total width of an unexpanded node which includes the size of its
+ * in and out annotations.
+ */
+ function updateTotalWidthOfNode(renderInfo) {
+ renderInfo.inboxWidth = renderInfo.inAnnotations.list.length > 0 ?
+ layout.PARAMS.annotations.inboxWidth : 0;
+ renderInfo.outboxWidth = renderInfo.outAnnotations.list.length > 0 ?
+ layout.PARAMS.annotations.outboxWidth : 0;
+ // Assign the width of the core box (the main shape of the node).
+ renderInfo.coreBox.width = renderInfo.width;
+ renderInfo.coreBox.height = renderInfo.height;
+ // TODO(jimbo): Account for font width rather than using a magic number.
+ var labelLength = renderInfo.node.name.length -
+ renderInfo.node.name.lastIndexOf(graph_1.NAMESPACE_DELIM) - 1;
+ var charWidth = 3; // 3 pixels per character.
+ // Compute the total width of the node.
+ renderInfo.width = Math.max(renderInfo.coreBox.width +
+ renderInfo.inboxWidth + renderInfo.outboxWidth, labelLength * charWidth);
+ }
+ /**
+ * Update layout, size, and annotations of its children nodes and edges.
+ */
+ function layoutChildren(renderNodeInfo) {
+ var children = renderNodeInfo.coreGraph.nodes().map(function (n) {
+ return renderNodeInfo.coreGraph.node(n);
+ }).concat(renderNodeInfo.isolatedInExtract, renderNodeInfo.isolatedOutExtract);
+ _.each(children, function (childNodeInfo) {
+ // Set size of each child
+ switch (childNodeInfo.node.type) {
+ case graph_1.NodeType.OP:
+ _.extend(childNodeInfo, layout.PARAMS.nodeSize.op);
+ break;
+ case graph_1.NodeType.BRIDGE:
+ _.extend(childNodeInfo, layout.PARAMS.nodeSize.bridge);
+ break;
+ case graph_1.NodeType.META:
+ if (!childNodeInfo.expanded) {
+ // Set fixed width and scalable height based on cardinality
+ _.extend(childNodeInfo, layout.PARAMS.nodeSize.meta);
+ childNodeInfo.height =
+ layout.PARAMS.nodeSize.meta.height(childNodeInfo.node.cardinality);
+ }
+ else {
+ var childGroupNodeInfo = childNodeInfo;
+ layoutScene(childGroupNodeInfo); // Recursively layout its subscene.
+ }
+ break;
+ case graph_1.NodeType.SERIES:
+ if (childNodeInfo.expanded) {
+ _.extend(childNodeInfo, layout.PARAMS.nodeSize.series.expanded);
+ var childGroupNodeInfo = childNodeInfo;
+ layoutScene(childGroupNodeInfo); // Recursively layout its subscene.
+ }
+ else {
+ var childGroupNodeInfo = childNodeInfo;
+ var seriesParams = childGroupNodeInfo.node.hasNonControlEdges ?
+ layout.PARAMS.nodeSize.series.vertical :
+ layout.PARAMS.nodeSize.series.horizontal;
+ _.extend(childNodeInfo, seriesParams);
+ }
+ break;
+ default:
+ throw Error('Unrecognized node type: ' + childNodeInfo.node.type);
+ }
+ // Compute total width of un-expanded nodes. Width of expanded nodes
+ // has already been computed.
+ if (!childNodeInfo.expanded) {
+ updateTotalWidthOfNode(childNodeInfo);
+ }
+ // Layout each child's annotations
+ layoutAnnotation(childNodeInfo);
+ });
+ }
+ /**
+ * Calculate layout for a graph using dagre
+ * @param graph the graph to be laid out
+ * @param params layout parameters
+ * @return width and height of the core graph
+ */
+ function dagreLayout(graph, params) {
+ _.extend(graph.graph(), {
+ nodesep: params.nodeSep,
+ ranksep: params.rankSep,
+ edgesep: params.edgeSep
+ });
+ var bridgeNodeNames = [];
+ var nonBridgeNodeNames = [];
+ // Split out nodes into bridge and non-bridge nodes, and calculate the total
+ // width we should use for bridge nodes.
+ _.each(graph.nodes(), function (nodeName) {
+ var nodeInfo = graph.node(nodeName);
+ if (nodeInfo.node.type === graph_1.NodeType.BRIDGE) {
+ bridgeNodeNames.push(nodeName);
+ }
+ else {
+ nonBridgeNodeNames.push(nodeName);
+ }
+ });
+ // If there are no non-bridge nodes, then the graph has zero size.
+ if (!nonBridgeNodeNames.length) {
+ return {
+ width: 0,
+ height: 0,
+ };
+ }
+ dagre.layout(graph);
+ // Calculate the true bounding box of the graph by iterating over nodes and
+ // edges rather than accepting dagre's word for it. In particular, we should
+ // ignore the extra-wide bridge nodes and bridge edges, and allow for
+ // annotation boxes and labels.
+ var minX = Infinity;
+ var minY = Infinity;
+ var maxX = -Infinity;
+ var maxY = -Infinity;
+ _.each(nonBridgeNodeNames, function (nodeName) {
+ var nodeInfo = graph.node(nodeName);
+ var w = 0.5 * nodeInfo.width;
+ var x1 = nodeInfo.x - w;
+ var x2 = nodeInfo.x + w;
+ minX = x1 < minX ? x1 : minX;
+ maxX = x2 > maxX ? x2 : maxX;
+ // TODO(jimbo): Account for the height of labels above op nodes here.
+ var h = 0.5 * nodeInfo.height;
+ var y1 = nodeInfo.y - h;
+ var y2 = nodeInfo.y + h;
+ minY = y1 < minY ? y1 : minY;
+ maxY = y2 > maxY ? y2 : maxY;
+ });
+ _.each(graph.edges(), function (edgeObj) {
+ var edgeInfo = graph.edge(edgeObj);
+ if (edgeInfo.structural) {
+ return; // Skip structural edges from min/max calculations.
+ }
+ // Since the node size passed to dagre includes the in and out
+ // annotations, the endpoints of the edge produced by dagre may not
+ // point to the actual node shape (rectangle, ellipse). We correct the
+ // end-points by finding the intersection of a line between the
+ // next-to-last (next-to-first) point and the destination (source)
+ // rectangle.
+ var sourceNode = graph.node(edgeInfo.metaedge.v);
+ var destNode = graph.node(edgeInfo.metaedge.w);
+ // Straight 3-points edges are special case, since they are curved after
+ // our default correction. To keep them straight, we remove the mid point
+ // and correct the first and the last point to be the center of the
+ // source and destination node respectively.
+ if (edgeInfo.points.length === 3 && isStraightLine(edgeInfo.points)) {
+ if (sourceNode != null) {
+ var cxSource = sourceNode.expanded ?
+ sourceNode.x : computeCXPositionOfNodeShape(sourceNode);
+ edgeInfo.points[0].x = cxSource;
+ }
+ if (destNode != null) {
+ var cxDest = destNode.expanded ?
+ destNode.x : computeCXPositionOfNodeShape(destNode);
+ edgeInfo.points[2].x = cxDest;
+ }
+ // Remove the middle point so the edge doesn't curve.
+ edgeInfo.points = [edgeInfo.points[0], edgeInfo.points[1]];
+ }
+ // Correct the destination endpoint of the edge.
+ var nextToLastPoint = edgeInfo.points[edgeInfo.points.length - 2];
+ // The destination node might be null if this is a bridge edge.
+ if (destNode != null) {
+ edgeInfo.points[edgeInfo.points.length - 1] =
+ intersectPointAndNode(nextToLastPoint, destNode);
+ }
+ // Correct the source endpoint of the edge.
+ var secondPoint = edgeInfo.points[1];
+ // The source might be null if this is a bridge edge.
+ if (sourceNode != null) {
+ edgeInfo.points[0] = intersectPointAndNode(secondPoint, sourceNode);
+ }
+ _.each(edgeInfo.points, function (point) {
+ minX = point.x < minX ? point.x : minX;
+ maxX = point.x > maxX ? point.x : maxX;
+ minY = point.y < minY ? point.y : minY;
+ maxY = point.y > maxY ? point.y : maxY;
+ });
+ });
+ // Shift all nodes and edge points to account for the left-padding amount,
+ // and the invisible bridge nodes.
+ _.each(graph.nodes(), function (nodeName) {
+ var nodeInfo = graph.node(nodeName);
+ nodeInfo.x -= minX;
+ nodeInfo.y -= minY;
+ });
+ _.each(graph.edges(), function (edgeObj) {
+ _.each(graph.edge(edgeObj).points, function (point) {
+ point.x -= minX;
+ point.y -= minY;
+ });
+ });
+ return {
+ width: maxX - minX,
+ height: maxY - minY
+ };
+ }
+ /** Layout a metanode. Only called for an expanded node. */
+ function layoutMetanode(renderNodeInfo) {
+ // First, copy params specific to meta nodes onto this render info object.
+ var params = layout.PARAMS.subscene.meta;
+ _.extend(renderNodeInfo, params);
+ // Invoke dagre.layout() on the core graph and record the bounding box
+ // dimensions.
+ _.extend(renderNodeInfo.coreBox, dagreLayout(renderNodeInfo.coreGraph, layout.PARAMS.graph.meta));
+ // Calculate the position of nodes in isolatedInExtract relative to the
+ // top-left corner of inExtractBox (the bounding box for all inExtract nodes)
+ // and calculate the size of the inExtractBox.
+ var maxInExtractWidth = _.max(renderNodeInfo.isolatedInExtract, function (renderNode) { return renderNode.width; }).width;
+ renderNodeInfo.inExtractBox.width = maxInExtractWidth != null ?
+ maxInExtractWidth : 0;
+ renderNodeInfo.inExtractBox.height =
+ _.reduce(renderNodeInfo.isolatedInExtract, function (height, child, i) {
+ var yOffset = i > 0 ? params.extractYOffset : 0;
+ // use width/height here to avoid overlaps between extracts
+ child.x = 0;
+ child.y = height + yOffset + child.height / 2;
+ return height + yOffset + child.height;
+ }, 0);
+ // Calculate the position of nodes in isolatedOutExtract relative to the
+ // top-left corner of outExtractBox (the bounding box for all outExtract
+ // nodes) and calculate the size of the outExtractBox.
+ var maxOutExtractWidth = _.max(renderNodeInfo.isolatedOutExtract, function (renderNode) { return renderNode.width; }).width;
+ renderNodeInfo.outExtractBox.width = maxOutExtractWidth != null ?
+ maxOutExtractWidth : 0;
+ renderNodeInfo.outExtractBox.height =
+ _.reduce(renderNodeInfo.isolatedOutExtract, function (height, child, i) {
+ var yOffset = i > 0 ? params.extractYOffset : 0;
+ // use width/height here to avoid overlaps between extracts
+ child.x = 0;
+ child.y = height + yOffset + child.height / 2;
+ return height + yOffset + child.height;
+ }, 0);
+ // Compute the total padding between the core graph, in-extract and
+ // out-extract boxes.
+ var numParts = 0;
+ if (renderNodeInfo.isolatedInExtract.length > 0) {
+ numParts++;
+ }
+ if (renderNodeInfo.isolatedOutExtract.length > 0) {
+ numParts++;
+ }
+ if (renderNodeInfo.coreGraph.nodeCount() > 0) {
+ numParts++;
+ }
+ var offset = layout.PARAMS.subscene.meta.extractXOffset;
+ var padding = numParts <= 1 ? 0 : (numParts <= 2 ? offset : 2 * offset);
+ // Add the in-extract and out-extract width to the core box width.
+ renderNodeInfo.coreBox.width += renderNodeInfo.inExtractBox.width +
+ renderNodeInfo.outExtractBox.width + padding;
+ renderNodeInfo.coreBox.height =
+ params.labelHeight +
+ Math.max(renderNodeInfo.inExtractBox.height, renderNodeInfo.coreBox.height, renderNodeInfo.outExtractBox.height);
+ // Determine the whole metanode's width (from left to right).
+ renderNodeInfo.width = renderNodeInfo.coreBox.width +
+ params.paddingLeft + params.paddingRight;
+ // Determine the whole metanode's height (from top to bottom).
+ renderNodeInfo.height =
+ renderNodeInfo.paddingTop +
+ renderNodeInfo.coreBox.height +
+ renderNodeInfo.paddingBottom;
+ }
+ /**
+ * Calculate layout for series node's core graph. Only called for an expanded
+ * series.
+ */
+ function layoutSeriesNode(node) {
+ var graph = node.coreGraph;
+ var params = layout.PARAMS.subscene.series;
+ _.extend(node, params);
+ // Layout the core.
+ _.extend(node.coreBox, dagreLayout(node.coreGraph, layout.PARAMS.graph.series));
+ _.each(graph.nodes(), function (nodeName) {
+ graph.node(nodeName).excluded = false;
+ });
+ // Series do not have in/outExtractBox so no need to include them here.
+ node.width = node.coreBox.width + params.paddingLeft + params.paddingRight;
+ node.height = node.coreBox.height + params.paddingTop + params.paddingBottom;
+ }
+ /**
+ * Calculate layout for annotations of a given node.
+ * This will modify positions of the given node and its annotations.
+ *
+ * @see tf.graph.render.Node and tf.graph.render.Annotation
+ * for description of each property of each render node.
+ *
+ */
+ function layoutAnnotation(renderNodeInfo) {
+ // If the render node is an expanded metanode, then its annotations will not
+ // be visible and we should skip the annotation calculations.
+ if (renderNodeInfo.expanded) {
+ return;
+ }
+ var inAnnotations = renderNodeInfo.inAnnotations.list;
+ var outAnnotations = renderNodeInfo.outAnnotations.list;
+ // Calculate size for in-annotations
+ _.each(inAnnotations, function (a) { return sizeAnnotation(a); });
+ // Calculate size for out-annotations
+ _.each(outAnnotations, function (a) { return sizeAnnotation(a); });
+ var params = layout.PARAMS.annotations;
+ // Calculate annotation node position (a.dx, a.dy)
+ // and total height for in-annotations
+ // After this chunk of code:
+ // inboxHeight = sum of annotation heights+ (annotation.length - 1 * yOffset)
+ var inboxHeight = _.reduce(inAnnotations, function (height, a, i) {
+ var yOffset = i > 0 ? params.yOffset : 0;
+ a.dx = -(renderNodeInfo.coreBox.width + a.width) / 2 - params.xOffset;
+ a.dy = height + yOffset + a.height / 2;
+ return height + yOffset + a.height;
+ }, 0);
+ _.each(inAnnotations, function (a) {
+ a.dy -= inboxHeight / 2;
+ a.labelOffset = params.labelOffset;
+ });
+ // Calculate annotation node position (a.dx, a.dy)
+ // and total height for out-annotations
+ // After this chunk of code:
+ // outboxHeight = sum of annotation heights +
+ // (annotation.length - 1 * yOffset)
+ var outboxHeight = _.reduce(outAnnotations, function (height, a, i) {
+ var yOffset = i > 0 ? params.yOffset : 0;
+ a.dx = (renderNodeInfo.coreBox.width + a.width) / 2 + params.xOffset;
+ a.dy = height + yOffset + a.height / 2;
+ return height + yOffset + a.height;
+ }, 0);
+ _.each(outAnnotations, function (a) {
+ // adjust by (half of ) the total height
+ // so dy is relative to the host node's center.
+ a.dy -= outboxHeight / 2;
+ a.labelOffset = params.labelOffset;
+ });
+ // Creating scales for touch point between the in-annotation edges
+ // and their hosts.
+ var inTouchHeight = Math.min(renderNodeInfo.height / 2 - renderNodeInfo.radius, inboxHeight / 2);
+ inTouchHeight = inTouchHeight < 0 ? 0 : inTouchHeight;
+ var inY = d3.scale.linear()
+ .domain([0, inAnnotations.length - 1])
+ .range([-inTouchHeight, inTouchHeight]);
+ // Calculate annotation edge position
+ _.each(inAnnotations, function (a, i) {
+ a.points = [
+ // The annotation node end
+ {
+ dx: a.dx + a.width / 2,
+ dy: a.dy
+ },
+ // The host node end
+ {
+ dx: -renderNodeInfo.coreBox.width / 2,
+ // only use scale if there are more than one,
+ // otherwise center it vertically
+ dy: inAnnotations.length > 1 ? inY(i) : 0
+ }
+ ];
+ });
+ // Creating scales for touch point between the out-annotation edges
+ // and their hosts.
+ var outTouchHeight = Math.min(renderNodeInfo.height / 2 - renderNodeInfo.radius, outboxHeight / 2);
+ outTouchHeight = outTouchHeight < 0 ? 0 : outTouchHeight;
+ var outY = d3.scale.linear()
+ .domain([0, outAnnotations.length - 1])
+ .range([-outTouchHeight, outTouchHeight]);
+ _.each(outAnnotations, function (a, i) {
+ // Add point from the border of the annotation node
+ a.points = [
+ // The host node end
+ {
+ dx: renderNodeInfo.coreBox.width / 2,
+ // only use scale if there are more than one,
+ // otherwise center it vertically
+ dy: outAnnotations.length > 1 ? outY(i) : 0
+ },
+ // The annotation node end
+ {
+ dx: a.dx - a.width / 2,
+ dy: a.dy
+ }
+ ];
+ });
+ renderNodeInfo.height =
+ Math.max(renderNodeInfo.height, inboxHeight, outboxHeight);
+ }
+ /**
+ * Set size of an annotation node.
+ */
+ function sizeAnnotation(a) {
+ switch (a.annotationType) {
+ case graph_1.render.AnnotationType.CONSTANT:
+ _.extend(a, layout.PARAMS.constant.size);
+ break;
+ case graph_1.render.AnnotationType.SHORTCUT:
+ if (a.node.type === graph_1.NodeType.OP) {
+ _.extend(a, layout.PARAMS.shortcutSize.op);
+ }
+ else if (a.node.type === graph_1.NodeType.META) {
+ _.extend(a, layout.PARAMS.shortcutSize.meta);
+ }
+ else if (a.node.type === graph_1.NodeType.SERIES) {
+ _.extend(a, layout.PARAMS.shortcutSize.series);
+ }
+ else {
+ throw Error('Invalid node type: ' + a.node.type);
+ }
+ break;
+ case graph_1.render.AnnotationType.SUMMARY:
+ _.extend(a, layout.PARAMS.constant.size);
+ break;
+ }
+ }
+ /**
+ * Determines the center position of the node's shape. The position depends
+ * on if the node has in and out-annotations.
+ */
+ function computeCXPositionOfNodeShape(renderInfo) {
+ if (renderInfo.expanded) {
+ return renderInfo.x;
+ }
+ var dx = renderInfo.inAnnotations.list.length ? renderInfo.inboxWidth : 0;
+ return renderInfo.x - renderInfo.width / 2 + dx +
+ renderInfo.coreBox.width / 2;
+ }
+ layout.computeCXPositionOfNodeShape = computeCXPositionOfNodeShape;
+ /** Returns the angle (in degrees) between two points. */
+ function angleBetweenTwoPoints(a, b) {
+ var dx = b.x - a.x;
+ var dy = b.y - a.y;
+ return 180 * Math.atan(dy / dx) / Math.PI;
+ }
+ /**
+ * Returns if a line going through the specified points is a straight line.
+ */
+ function isStraightLine(points) {
+ var angle = angleBetweenTwoPoints(points[0], points[1]);
+ for (var i = 1; i < points.length - 1; i++) {
+ var newAngle = angleBetweenTwoPoints(points[i], points[i + 1]);
+ // Have a tolerance of 1 degree.
+ if (Math.abs(newAngle - angle) > 1) {
+ return false;
+ }
+ angle = newAngle;
+ }
+ return true;
+ }
+ /**
+ * Returns the intersection of a line between the provided point
+ * and the provided rectangle.
+ */
+ function intersectPointAndNode(point, node) {
+ // cx and cy are the center of the rectangle.
+ var cx = node.expanded ?
+ node.x : computeCXPositionOfNodeShape(node);
+ var cy = node.y;
+ // Calculate the slope
+ var dx = point.x - cx;
+ var dy = point.y - cy;
+ var w = node.expanded ? node.width : node.coreBox.width;
+ var h = node.expanded ? node.height : node.coreBox.height;
+ var deltaX, deltaY;
+ if (Math.abs(dy) * w / 2 > Math.abs(dx) * h / 2) {
+ // The intersection is above or below the rectangle.
+ if (dy < 0) {
+ h = -h;
+ }
+ deltaX = dy === 0 ? 0 : h / 2 * dx / dy;
+ deltaY = h / 2;
+ }
+ else {
+ // The intersection is left or right of the rectangle.
+ if (dx < 0) {
+ w = -w;
+ }
+ deltaX = w / 2;
+ deltaY = dx === 0 ? 0 : w / 2 * dy / dx;
+ }
+ return { x: cx + deltaX, y: cy + deltaY };
+ }
+ })(layout = graph_1.layout || (graph_1.layout = {}));
+ })(graph = tf.graph || (tf.graph = {}));
+})(tf || (tf = {})); // close module
+</script>
+<script>/* Copyright 2015 Google Inc. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the 'License');
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an 'AS IS' BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+var tf;
+(function (tf) {
+ var graph;
+ (function (graph) {
+ var parser;
+ (function (parser) {
+ /**
+ * Parses a native js value, which can be either a string, boolean or number.
+ *
+ * @param value The value to be parsed.
+ */
+ function parseValue(value) {
+ if (value === 'true') {
+ return true;
+ }
+ if (value === 'false') {
+ return false;
+ }
+ var firstChar = value[0];
+ if (firstChar === '"') {
+ return value.substring(1, value.length - 1);
+ }
+ var num = parseFloat(value);
+ return isNaN(num) ? value : num;
+ }
+ /**
+ * Fetches a text file and returns a promise of the result.
+ */
+ function fetchPbTxt(filepath) {
+ return new Promise(function (resolve, reject) {
+ d3.text(filepath, function (error, text) {
+ if (error) {
+ reject(error);
+ return;
+ }
+ resolve(text);
+ });
+ });
+ }
+ parser.fetchPbTxt = fetchPbTxt;
+ /**
+ * Fetches the metadata file, parses it and returns a promise of the result.
+ */
+ function fetchAndParseMetadata(path, tracker) {
+ return tf.graph.util
+ .runTask('Reading metadata pbtxt', 40, function () {
+ if (path == null) {
+ return Promise.resolve(null);
+ }
+ return fetchPbTxt(path).then(function (text) { return new Blob([text]); });
+ }, tracker)
+ .then(function (blob) {
+ return tf.graph.util.runTask('Parsing metadata.pbtxt', 60, function () {
+ return blob != null ? parseStatsPbTxt(blob) : null;
+ }, tracker);
+ });
+ }
+ parser.fetchAndParseMetadata = fetchAndParseMetadata;
+ /**
+ * Fetches the graph file, parses it and returns a promise of the result.
+ */
+ function fetchAndParseGraphData(path, pbTxtFile, tracker) {
+ return tf.graph.util
+ .runTask('Reading graph pbtxt', 40, function () {
+ return pbTxtFile ? Promise.resolve(pbTxtFile) :
+ fetchPbTxt(path).then(function (text) { return new Blob([text]); });
+ }, tracker)
+ .then(function (blob) {
+ return tf.graph.util.runTask('Parsing graph.pbtxt', 60, function () {
+ return parseGraphPbTxt(blob);
+ }, tracker);
+ });
+ }
+ parser.fetchAndParseGraphData = fetchAndParseGraphData;
+ /**
+ * Parse a file object in a streaming fashion line by line (or custom delim).
+ * Can handle very large files.
+ * @param input The file object
+ * @param callback The callback called on each line
+ * @param chunkSize The size of each read chunk. (optional)
+ * @param delim The delimiter used to split a line. (optional)
+ * @returns A promise for when it is finished.
+ */
+ function streamParse(file, callback, chunkSize, delim) {
+ if (chunkSize === void 0) { chunkSize = 1000000; }
+ if (delim === void 0) { delim = '\n'; }
+ return new Promise(function (resolve, reject) {
+ var offset = 0;
+ var fileSize = file.size - 1;
+ var data = '';
+ function readHandler(evt) {
+ if (evt.target.error == null) {
+ offset += evt.target.result.length;
+ var str = evt.target.result;
+ var parts = str.split(delim);
+ var first = data + parts[0];
+ if (parts.length === 1) {
+ data = first;
+ readChunk(offset, chunkSize);
+ return;
+ }
+ data = parts[parts.length - 1];
+ callback(first);
+ for (var i = 1; i < parts.length - 1; i++) {
+ callback(parts[i]);
+ }
+ }
+ else {
+ // read error
+ reject(evt.target.error);
+ return;
+ }
+ if (offset >= fileSize) {
+ if (data) {
+ callback(data);
+ }
+ resolve(true);
+ return;
+ }
+ readChunk(offset, chunkSize);
+ }
+ function readChunk(offset, size) {
+ var reader = new FileReader();
+ var blob = file.slice(offset, offset + size);
+ reader.onload = readHandler;
+ reader.readAsText(blob);
+ }
+ readChunk(offset, chunkSize);
+ });
+ }
+ parser.streamParse = streamParse;
+ /**
+ * Since proto-txt doesn't explicitly say whether an attribute is repeated
+ * (an array) or not, we keep a hard-coded list of attributes that are known
+ * to be repeated. This list is used in parsing time to convert repeated
+ * attributes into arrays even when the attribute only shows up once in the
+ * object.
+ */
+ var GRAPH_REPEATED_FIELDS = {
+ 'node': true,
+ 'node.input': true,
+ 'node.attr': true,
+ 'node.attr.value.list.type': true,
+ 'node.attr.value.shape.dim': true,
+ 'node.attr.value.tensor.string_val': true,
+ 'node.attr.value.tensor.tensor_shape.dim': true,
+ 'node.attr.value.list.shape': true,
+ 'node.attr.value.list.shape.dim': true,
+ 'node.attr.value.list.s': true
+ };
+ var METADATA_REPEATED_FIELDS = {
+ 'step_stats.dev_stats': true,
+ 'step_stats.dev_stats.node_stats': true,
+ 'step_stats.dev_stats.node_stats.output': true,
+ 'step_stats.dev_stats.node_stats.memory': true,
+ 'step_stats.dev_stats.node_stats.output.tensor_description.shape.dim': true
+ };
+ /**
+ * Parses a blob of proto txt file into a raw Graph object.
+ */
+ function parseGraphPbTxt(input) {
+ return parsePbtxtFile(input, GRAPH_REPEATED_FIELDS).then(function (obj) { return obj['node']; });
+ }
+ parser.parseGraphPbTxt = parseGraphPbTxt;
+ /**
+ * Parses a blob of proto txt file into a StepStats object.
+ */
+ function parseStatsPbTxt(input) {
+ return parsePbtxtFile(input, METADATA_REPEATED_FIELDS)
+ .then(function (obj) { return obj['step_stats']; });
+ }
+ /**
+ * Parses a blob of proto txt file into javascript object.
+ *
+ * @param input The Blob or file object implementing slice.
+ * @param repeatedFields Map (Set) of all the repeated fields, since you can't
+ * tell directly from the pbtxt if a field is repeated or not.
+ * @returns The parsed object.
+ */
+ function parsePbtxtFile(input, repeatedFields) {
+ var output = {};
+ var stack = [];
+ var path = [];
+ var current = output;
+ function splitNameAndValueInAttribute(line) {
+ var colonIndex = line.indexOf(':');
+ var name = line.substring(0, colonIndex).trim();
+ var value = parseValue(line.substring(colonIndex + 2).trim());
+ return {
+ name: name,
+ value: value
+ };
+ }
+ /**
+ * Adds a value, given the attribute name and the host object. If the
+ * attribute already exists, but is not an array, it will convert it to an
+ * array of values.
+ *
+ * @param obj The host object that holds the attribute.
+ * @param name The attribute name (key).
+ * @param value The attribute value.
+ * @param path A path that identifies the attribute. Used to check if
+ * an attribute is an array or not.
+ */
+ function addAttribute(obj, name, value, path) {
+ // We treat 'node' specially since it is done so often.
+ var existingValue = obj[name];
+ if (existingValue == null) {
+ obj[name] = path.join('.') in repeatedFields ? [value] : value;
+ }
+ else if (Array.isArray(existingValue)) {
+ existingValue.push(value);
+ }
+ else {
+ obj[name] = [existingValue, value];
+ }
+ }
+ // Run through the file a line at a time.
+ return streamParse(input, function (line) {
+ if (!line) {
+ return;
+ }
+ switch (line[line.length - 1]) {
+ case '{':
+ var name_1 = line.substring(0, line.length - 2).trim();
+ var newValue = {};
+ stack.push(current);
+ path.push(name_1);
+ addAttribute(current, name_1, newValue, path);
+ current = newValue;
+ break;
+ case '}':
+ current = stack.pop();
+ path.pop();
+ break;
+ default:
+ var x = splitNameAndValueInAttribute(line);
+ addAttribute(current, x.name, x.value, path.concat(x.name));
+ break;
+ }
+ }).then(function () {
+ return output;
+ });
+ }
+ })(parser = graph.parser || (graph.parser = {}));
+ })(graph = tf.graph || (tf.graph = {}));
+})(tf || (tf = {})); // Close module tf.graph.parser.
+</script>
<script>var __extends = (this && this.__extends) || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
@@ -5030,14 +6035,14 @@ var tf;
};
/* Copyright 2015 Google Inc. All Rights Reserved.
-Licensed under the Apache License, Version 2.0 (the "License");
+Licensed under the Apache License, Version 2.0 (the 'License');
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
+distributed under the License is distributed on an 'AS IS' BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -5054,10 +6059,7 @@ var tf;
/**
* Color parameters for op nodes.
*/
- render.OpNodeColors = {
- DEFAULT_FILL: "white",
- DEFAULT_STROKE: "#b2b2b2"
- };
+ render.OpNodeColors = { DEFAULT_FILL: 'white', DEFAULT_STROKE: '#b2b2b2' };
/**
* Color parameters for node encoding.
* @type {Object}
@@ -5066,15 +6068,15 @@ var tf;
/**
* Default fill and stroke to use when no other information is available.
*/
- DEFAULT_FILL: "#d9d9d9",
- DEFAULT_STROKE: "#a6a6a6",
+ DEFAULT_FILL: '#d9d9d9',
+ DEFAULT_STROKE: '#a6a6a6',
SATURATION: 0.6,
LIGHTNESS: 0.85,
/**
* Neutral color to use when the node is expanded (used when coloring by
* compute time, memory and device).
*/
- EXPANDED_COLOR: "#f0f0f0",
+ EXPANDED_COLOR: '#f0f0f0',
/**
* Standard hue values for node color palette.
*/
@@ -5090,18 +6092,16 @@ var tf;
var light = lightened ? 95 : 80;
return d3.hsl(hue, .01 * sat, .01 * light).toString();
},
- DEVICE_PALETTE: function (index) {
- return render.MetanodeColors.STRUCTURE_PALETTE(index);
- },
- UNKNOWN: "#eee",
- GRADIENT_OUTLINE: "#888"
+ DEVICE_PALETTE: function (index) { return render.MetanodeColors.STRUCTURE_PALETTE(index); },
+ UNKNOWN: '#eee',
+ GRADIENT_OUTLINE: '#888'
};
/**
* Color parameters for op nodes.
*/
render.SeriesNodeColors = {
- DEFAULT_FILL: "white",
- DEFAULT_STROKE: "#b2b2b2"
+ DEFAULT_FILL: 'white',
+ DEFAULT_STROKE: '#b2b2b2'
};
/**
* Parameters that affect how the graph is rendered on the screen.
@@ -5131,15 +6131,13 @@ var tf;
* sink-like nodes that will be extracted from the main graph.
*/
outExtractTypes: [
- "NoOp" // NoOps are sink-like used for managing control dependencies.
+ 'NoOp' // NoOps are sink-like used for managing control dependencies.
],
/**
* Types patterns for predefined in-extract nodes, which are
* source-like nodes that will be extracted from the main graph.
*/
- inExtractTypes: [
- "Variable"
- ],
+ inExtractTypes: ['Variable'],
/**
* When removing edges from a high degree node, remove all of its edges if
* detachAllEdgesForHighDegree is true. Otherwise remove all in-edges if
@@ -5162,7 +6160,7 @@ var tf;
* 2 colors, for the minimum and maximum value respectively, whenever we
* have a gradient scale.
*/
- minMaxColors: ["#fff5f0", "#fb6a4a"],
+ minMaxColors: ['#fff5f0', '#fb6a4a'],
/**
* Maximum number of annotations to be displayed on a node before an
* ellipsis is used.
@@ -5386,7 +6384,7 @@ var tf;
for (var _i = 1; _i < arguments.length; _i++) {
rest[_i - 1] = arguments[_i];
}
- return rest.concat([inbound ? "IN" : "OUT"]).join("~~");
+ return rest.concat([inbound ? 'IN' : 'OUT']).join('~~');
};
// Build out the bridgegraph.
var bridgegraph = this.hierarchy.getBridgegraph(nodeName);
@@ -5473,7 +6471,7 @@ var tf;
canDrawBridgePath = !!adjoiningMetaedge;
}
// Although dataflow edges are acyclic, control dependency edges may
- // actually point "backwards" in the graph. If this bridgeMetaedge is
+ // 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.
//
@@ -5668,7 +6666,7 @@ var tf;
// set a metaedge between the terminal node and the container node, but
// in that case, something about the graph upsets dagre.layout()'s
// longestPath algorithm (was getting errors due to an undefined).
- var structuralNodeName = getBridgeNodeName(inbound, nodeName, "STRUCTURAL_TARGET");
+ var structuralNodeName = getBridgeNodeName(inbound, nodeName, 'STRUCTURAL_TARGET');
var structuralRenderInfo = coreGraph.node(structuralNodeName);
if (!structuralRenderInfo) {
var bridgeNode = {
@@ -6119,7 +7117,7 @@ var tf;
*
* 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.
+ * 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.
@@ -6207,691 +7205,14 @@ var tf;
</script>
<script>/* Copyright 2015 Google Inc. All Rights Reserved.
-Licensed under the Apache License, Version 2.0 (the "License");
+Licensed under the Apache License, Version 2.0 (the 'License');
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-==============================================================================*/
-var tf;
-(function (tf) {
- var graph;
- (function (graph_1) {
- var template;
- (function (template) {
- /**
- * Detect repeating patterns of subgraphs.
- * Assign templateId to each subgraph if it belongs to a template.
- * Returns clusters of similar subgraphs .
- *
- * @param graph
- * @param verifyTemplate whether to run the template verification algorithm
- * @return a dict (template id => Array of node names)
- */
- function detect(h, verifyTemplate) {
- // In any particular subgraph, there are either
- // - leaf nodes (which do not have subgraph)
- // - metanode nodes - some of them have only one member (singular metanode)
- // and some have multiple members (non-singular metanode)
- // First, generate a nearest neighbor hash of metanode nodes.
- var nnGroups = clusterSimilarSubgraphs(h);
- // For each metanode, compare its subgraph (starting from shallower groups)
- // and assign template id.
- var templates = groupTemplateAndAssignId(nnGroups, verifyTemplate);
- // Sort the templates by minimum level in the graph at which they appear,
- // as this leads to optimal setting of the colors of each template for
- // maximum differentiation.
- return _(templates).pairs()
- .sortBy(function (pair) {
- return pair[1].level;
- })
- .map(function (pair) {
- return [pair[0], pair[1].nodes];
- })
- .object().value();
- }
- template.detect = detect;
- ;
- /**
- * @return Unique string for a metanode based on depth, |V|, |E| and
- * op type histogram.
- */
- function getSignature(metanode) {
- // depth=<number> |V|=<number> |E|=<number>
- var props = _.map({
- "depth": metanode.depth,
- "|V|": metanode.metagraph.nodes().length,
- "|E|": metanode.metagraph.edges().length
- }, function (v, k) { return k + "=" + v; }).join(" ");
- // optype1=count1,optype2=count2
- var ops = _.map(metanode.opHistogram, function (count, op) {
- return op + "=" + count;
- }).join(",");
- return props + " [ops] " + ops;
- }
- /**
- * Generate a nearest neighbor hash of metanodes
- * based on depth, |V|, |E|, and opHistogram of their subgraph
- * (excluding leaf nodes and singular metanodes).
- * @param graph The graph
- * @return Array of pairs of [signature,
- * Object with min level of the template and an Array of tf.graph.Group]
- * sort by ascending order of minimum depth at which metanode appears.
- */
- function clusterSimilarSubgraphs(h) {
- /** a dict from metanode.signature() => Array of tf.graph.Groups */
- var hashDict = _(h.getNodeMap()).reduce(function (hash, node, name) {
- if (node.type !== graph_1.NodeType.META) {
- return hash;
- }
- var levelOfMetaNode = name.split("/").length - 1;
- var signature = getSignature(node);
- var templateInfo = hash[signature] ||
- { nodes: [], level: levelOfMetaNode };
- hash[signature] = templateInfo;
- templateInfo.nodes.push(node);
- if (templateInfo.level > levelOfMetaNode) {
- templateInfo.level = levelOfMetaNode;
- }
- return hash;
- }, {});
- return _(hashDict).pairs()
- .filter(function (pair) {
- return pair[1].nodes.length > 1;
- })
- .sortBy(function (pair) {
- // sort by depth
- // (all members in the same nnGroup has equal depth)
- return pair[1].nodes[0].depth;
- })
- .value();
- }
- function groupTemplateAndAssignId(nnGroups, verifyTemplate) {
- // For each metanode, compare its subgraph (starting from shallower groups)
- // and assign template id.
- var result = {};
- return _.reduce(nnGroups, function (templates, nnGroupPair) {
- var signature = nnGroupPair[0], nnGroup = nnGroupPair[1].nodes, clusters = [];
- nnGroup.forEach(function (metanode) {
- // check with each existing cluster
- for (var i = 0; i < clusters.length; i++) {
- var similar = !verifyTemplate ||
- isSimilarSubgraph(clusters[i].metanode.metagraph, metanode.metagraph);
- // if similar, just add this metanode to the cluster
- if (similar) {
- // get template from the first one
- metanode.templateId = clusters[i].metanode.templateId;
- clusters[i].members.push(metanode.name);
- return;
- }
- }
- // otherwise create a new cluster with id "signature [count] "
- metanode.templateId = signature + "[" + clusters.length + "]";
- clusters.push({
- metanode: metanode,
- members: [metanode.name]
- });
- });
- clusters.forEach(function (c) {
- templates[c.metanode.templateId] = {
- level: nnGroupPair[1].level,
- nodes: c.members
- };
- });
- return templates;
- }, result);
- }
- function sortNodes(names, graph, prefix) {
- return _.sortByAll(names, function (name) {
- var node = graph.node(name);
- return node.op;
- }, function (name) {
- var node = graph.node(name);
- return node.templateId;
- }, function (name) {
- return graph.neighbors(name).length;
- }, function (name) {
- return graph.predecessors(name).length;
- }, function (name) {
- return graph.successors(name).length;
- }, function (name) {
- return name.substr(prefix.length);
- });
- }
- function isSimilarSubgraph(g1, g2) {
- if (!tf.graph.hasSimilarDegreeSequence(g1, g2)) {
- return false;
- }
- // if we want to skip, just return true here.
- // return true;
- // Verify sequence by running DFS
- var g1prefix = g1.graph().name;
- var g2prefix = g2.graph().name;
- var visited1 = {};
- var visited2 = {};
- var stack = [];
- /**
- * push sources or successors into the stack
- * if the visiting pattern has been similar.
- */
- function stackPushIfNotDifferent(n1, n2) {
- var sub1 = n1.substr(g1prefix.length), sub2 = n2.substr(g2prefix.length);
- /* tslint:disable */
- if (visited1[sub1] ^ visited2[sub1]) {
- console.warn("different visit pattern", "[" + g1prefix + "]", sub1, "[" + g2prefix + "]", sub2);
- return true;
- }
- /* tslint:enable */
- if (!visited1[sub1]) {
- visited1[sub1] = visited2[sub2] = true;
- stack.push({ n1: n1, n2: n2 });
- }
- return false;
- }
- // check if have same # of sources then sort and push
- var sources1 = g1.sources();
- var sources2 = g2.sources();
- if (sources1.length !== sources2.length) {
- /* tslint:disable */
- console.log("different source length");
- /* tslint:enable */
- return false;
- }
- sources1 = sortNodes(sources1, g1, g1prefix);
- sources2 = sortNodes(sources2, g2, g2prefix);
- for (var i = 0; i < sources1.length; i++) {
- var different = stackPushIfNotDifferent(sources1[i], sources2[i]);
- if (different) {
- return false;
- }
- }
- while (stack.length > 0) {
- var cur = stack.pop();
- // check node
- var similar = isSimilarNode(g1.node(cur.n1), g2.node(cur.n2));
- if (!similar) {
- return false;
- }
- // check if have same # of successors then sort and push
- var succ1 = g1.successors(cur.n1), succ2 = g2.successors(cur.n2);
- if (succ1.length !== succ2.length) {
- /* tslint:disable */
- console.log("# of successors mismatch", succ1, succ2);
- /* tslint:enable */
- return false;
- }
- succ1 = sortNodes(succ1, g1, g1prefix);
- succ2 = sortNodes(succ2, g2, g2prefix);
- for (var j = 0; j < succ1.length; j++) {
- var different = stackPushIfNotDifferent(succ1[j], succ2[j]);
- if (different) {
- return false;
- }
- }
- }
- return true;
- }
- /**
- * Returns if two nodes have identical structure.
- */
- function isSimilarNode(n1, n2) {
- if (n1.type === graph_1.NodeType.META) {
- // compare metanode
- var metanode1 = n1;
- var metanode2 = n2;
- return metanode1.templateId && metanode2.templateId &&
- metanode1.templateId === metanode2.templateId;
- }
- else if (n1.type === graph_1.NodeType.OP && n2.type === graph_1.NodeType.OP) {
- // compare leaf node
- return n1.op === n2.op;
- }
- else if (n1.type === graph_1.NodeType.SERIES && n2.type === graph_1.NodeType.SERIES) {
- // compare series node sizes and operations
- // (only need to check one op as all op nodes are identical in series)
- var sn1 = n1;
- var sn2 = n2;
- var seriesnode1Count = sn1.metagraph.nodeCount();
- return (seriesnode1Count === sn2.metagraph.nodeCount() &&
- (seriesnode1Count === 0 ||
- (sn1.metagraph.node(sn1.metagraph.nodes()[0]).op ===
- sn2.metagraph.node(sn2.metagraph.nodes()[0]).op)));
- }
- return false;
- }
- })(template = graph_1.template || (graph_1.template = {}));
- })(graph = tf.graph || (tf.graph = {}));
-})(tf || (tf = {}));
-</script>
-<script>/* Copyright 2015 Google Inc. All Rights Reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-==============================================================================*/
-var tf;
-(function (tf) {
- var graph;
- (function (graph) {
- var scene;
- (function (scene) {
- /** Enums element class of objects in the scene */
- scene.Class = {
- Node: {
- // <g> element that contains nodes.
- CONTAINER: "nodes",
- // <g> element that contains detail about a node.
- GROUP: "node",
- // <g> element that contains visual elements (like rect, ellipse).
- SHAPE: "nodeshape",
- // <*> element(s) under SHAPE that should receive color updates.
- COLOR_TARGET: "nodecolortarget",
- // <text> element showing the node's label.
- LABEL: "nodelabel",
- // <g> element that contains all visuals for the expand/collapse
- // button for expandable group nodes.
- BUTTON_CONTAINER: "buttoncontainer",
- // <circle> element that surrounds expand/collapse buttons.
- BUTTON_CIRCLE: "buttoncircle",
- // <path> element of the expand button.
- EXPAND_BUTTON: "expandbutton",
- // <path> element of the collapse button.
- COLLAPSE_BUTTON: "collapsebutton"
- },
- Edge: {
- CONTAINER: "edges",
- GROUP: "edge",
- LINE: "edgeline",
- REF_LINE: "refline",
- STRUCTURAL: "structural"
- },
- Annotation: {
- OUTBOX: "out-annotations",
- INBOX: "in-annotations",
- GROUP: "annotation",
- NODE: "annotation-node",
- EDGE: "annotation-edge",
- CONTROL_EDGE: "annotation-control-edge",
- LABEL: "annotation-label",
- ELLIPSIS: "annotation-ellipsis"
- },
- Scene: {
- GROUP: "scene",
- CORE: "core",
- INEXTRACT: "in-extract",
- OUTEXTRACT: "out-extract"
- },
- Subscene: {
- GROUP: "subscene"
- },
- OPNODE: "op",
- METANODE: "meta",
- SERIESNODE: "series",
- BRIDGENODE: "bridge",
- ELLIPSISNODE: "ellipsis"
- };
- /**
- * Helper method for fitting the graph in the svg view.
- *
- * @param svg The main svg.
- * @param zoomG The svg group used for panning and zooming.
- * @param d3zoom The zoom behavior.
- * @param callback Called when the fitting is done.
- */
- function fit(svg, zoomG, d3zoom, callback) {
- var svgRect = svg.getBoundingClientRect();
- var sceneSize = zoomG.getBBox();
- var scale = 0.9 * Math.min(svgRect.width / sceneSize.width, svgRect.height / sceneSize.height, 2);
- var params = graph.layout.PARAMS.graph;
- var zoomEvent = d3zoom.scale(scale)
- .on("zoomend.fitted", function () {
- // Remove the listener for the zoomend event,
- // so we don't get called at the end of regular zoom events,
- // just those that fit the graph to screen.
- d3zoom.on("zoomend.fitted", null);
- callback();
- })
- .translate([params.padding.paddingLeft, params.padding.paddingTop])
- .event;
- d3.select(zoomG).transition().duration(500).call(zoomEvent);
- }
- scene.fit = fit;
- ;
- /**
- * Helper method for panning the graph to center on the provided node,
- * if the node is currently off-screen.
- *
- * @param nodeName The node to center the graph on
- * @param svg The root SVG element for the graph
- * @param zoomG The svg group used for panning and zooming.
- * @param d3zoom The zoom behavior.
- * @return True if the graph had to be panned to display the
- * provided node.
- */
- function panToNode(nodeName, svg, zoomG, d3zoom) {
- var node = d3.select("[data-name='" + nodeName + "']."
- + scene.Class.Node.GROUP).node();
- if (!node) {
- return false;
- }
- var translate = d3zoom.translate();
- // Check if the selected node is off-screen in either
- // X or Y dimension in either direction.
- var nodeBox = node.getBBox();
- var nodeCtm = node.getScreenCTM();
- var pointTL = svg.createSVGPoint();
- var pointBR = svg.createSVGPoint();
- pointTL.x = nodeBox.x;
- pointTL.y = nodeBox.y;
- pointBR.x = nodeBox.x + nodeBox.width;
- pointBR.y = nodeBox.y + nodeBox.height;
- pointTL = pointTL.matrixTransform(nodeCtm);
- pointBR = pointBR.matrixTransform(nodeCtm);
- var isOutsideOfBounds = function (start, end, bound) {
- return end < 0 || start > bound;
- };
- var svgRect = svg.getBoundingClientRect();
- if (isOutsideOfBounds(pointTL.x, pointBR.x, svgRect.width) ||
- isOutsideOfBounds(pointTL.y, pointBR.y, svgRect.height)) {
- // Determine the amount to transform the graph in both X and Y
- // dimensions in order to center the selected node. This takes into
- // acount the position of the node, the size of the svg scene, the
- // amount the scene has been scaled by through zooming, and any previous
- // transform already performed by this logic.
- var centerX = (pointTL.x + pointBR.x) / 2;
- var centerY = (pointTL.y + pointBR.y) / 2;
- var dx = ((svgRect.width / 2) - centerX);
- var dy = ((svgRect.height / 2) - centerY);
- var zoomEvent = d3zoom.translate([translate[0] + dx, translate[1] + dy])
- .event;
- d3.select(zoomG).transition().duration(500).call(zoomEvent);
- return true;
- }
- return false;
- }
- scene.panToNode = panToNode;
- ;
- /**
- * Given a container d3 selection, select a child svg element of a given tag
- * and class if exists or append / insert one otherwise. If multiple children
- * matches the tag and class name, returns only the first one.
- *
- * @param container
- * @param tagName tag name.
- * @param className (optional) Class name.
- * @param before (optional) reference DOM node for insertion.
- * @return selection of the element
- */
- function selectOrCreateChild(container, tagName, className, before) {
- var child = selectChild(container, tagName, className);
- if (!child.empty()) {
- return child;
- }
- var newElement = document.createElementNS("http://www.w3.org/2000/svg", tagName);
- if (className) {
- newElement.classList.add(className);
- }
- if (before) {
- container.node().insertBefore(newElement, before);
- }
- else {
- container.node().appendChild(newElement);
- }
- return d3.select(newElement)
- .datum(container.datum());
- }
- scene.selectOrCreateChild = selectOrCreateChild;
- ;
- /**
- * Given a container d3 selection, select a child element of a given tag and
- * class. If multiple children matches the tag and class name, returns only
- * the first one.
- *
- * @param container
- * @param tagName tag name.
- * @param className (optional) Class name.
- * @return selection of the element, or an empty selection
- */
- function selectChild(container, tagName, className) {
- var children = container.node().childNodes;
- for (var i = 0; i < children.length; i++) {
- var child = children[i];
- if (child.tagName === tagName &&
- (!className || child.classList.contains(className))) {
- return d3.select(child);
- }
- }
- return d3.select(null);
- }
- scene.selectChild = selectChild;
- ;
- /**
- * Select or create a sceneGroup and build/update its nodes and edges.
- *
- * Structure Pattern:
- *
- * <g class="scene">
- * <g class="core">
- * <g class="edges">
- * ... stuff from tf.graph.scene.edges.build ...
- * </g>
- * <g class="nodes">
- * ... stuff from tf.graph.scene.nodes.build ...
- * </g>
- * </g>
- * <g class="in-extract">
- * <g class="nodes">
- * ... stuff from tf.graph.scene.nodes.build ...
- * </g>
- * </g>
- * <g class="out-extract">
- * <g class="nodes">
- * ... stuff from tf.graph.scene.nodes.build ...
- * </g>
- * </g>
- * </g>
- *
- * @param container D3 selection of the parent.
- * @param renderNode render node of a metanode or series node.
- * @param sceneElement <tf-graph-scene> polymer element.
- * @param sceneClass class attribute of the scene (default="scene").
- */
- function buildGroup(container, renderNode, sceneElement, sceneClass) {
- sceneClass = sceneClass || scene.Class.Scene.GROUP;
- var isNewSceneGroup = selectChild(container, "g", sceneClass).empty();
- var sceneGroup = selectOrCreateChild(container, "g", sceneClass);
- // core
- var coreGroup = selectOrCreateChild(sceneGroup, "g", scene.Class.Scene.CORE);
- var coreNodes = _.reduce(renderNode.coreGraph.nodes(), function (nodes, name) {
- var node = renderNode.coreGraph.node(name);
- if (!node.excluded) {
- nodes.push(node);
- }
- return nodes;
- }, []);
- if (renderNode.node.type === graph.NodeType.SERIES) {
- // For series, we want the first item on top, so reverse the array so
- // the first item in the series becomes last item in the top, and thus
- // is rendered on the top.
- coreNodes.reverse();
- }
- // Create the layer of edges for this scene (paths).
- scene.edge.buildGroup(coreGroup, renderNode.coreGraph, sceneElement);
- // Create the layer of nodes for this scene (ellipses, rects etc).
- scene.node.buildGroup(coreGroup, coreNodes, sceneElement);
- // In-extract
- if (renderNode.isolatedInExtract.length > 0) {
- var inExtractGroup = selectOrCreateChild(sceneGroup, "g", scene.Class.Scene.INEXTRACT);
- scene.node.buildGroup(inExtractGroup, renderNode.isolatedInExtract, sceneElement);
- }
- else {
- selectChild(sceneGroup, "g", scene.Class.Scene.INEXTRACT).remove();
- }
- // Out-extract
- if (renderNode.isolatedOutExtract.length > 0) {
- var outExtractGroup = selectOrCreateChild(sceneGroup, "g", scene.Class.Scene.OUTEXTRACT);
- scene.node.buildGroup(outExtractGroup, renderNode.isolatedOutExtract, sceneElement);
- }
- else {
- selectChild(sceneGroup, "g", scene.Class.Scene.OUTEXTRACT).remove();
- }
- position(sceneGroup, renderNode);
- // Fade in the scene group if it didn't already exist.
- if (isNewSceneGroup) {
- sceneGroup.attr("opacity", 0).transition().attr("opacity", 1);
- }
- return sceneGroup;
- }
- scene.buildGroup = buildGroup;
- ;
- /**
- * Given a scene's svg group, set g.in-extract, g.coreGraph, g.out-extract svg
- * groups' position relative to the scene.
- *
- * @param sceneGroup
- * @param renderNode render node of a metanode or series node.
- */
- function position(sceneGroup, renderNode) {
- // Translate scenes down by the label height so that when showing graphs in
- // expanded metanodes, the graphs are below the labels. Do not shift them
- // down for series nodes as series nodes don't have labels inside of their
- // bounding boxes.
- var yTranslate = renderNode.node.type === graph.NodeType.SERIES ?
- 0 : graph.layout.PARAMS.subscene.meta.labelHeight;
- // core
- translate(selectChild(sceneGroup, "g", scene.Class.Scene.CORE), 0, yTranslate);
- // in-extract
- var hasInExtract = renderNode.isolatedInExtract.length > 0;
- var hasOutExtract = renderNode.isolatedOutExtract.length > 0;
- if (hasInExtract) {
- var offset = graph.layout.PARAMS.subscene.meta.extractXOffset;
- var inExtractX = renderNode.coreBox.width -
- renderNode.inExtractBox.width / 2 - renderNode.outExtractBox.width -
- (hasOutExtract ? offset : 0);
- translate(selectChild(sceneGroup, "g", scene.Class.Scene.INEXTRACT), inExtractX, yTranslate);
- }
- // out-extract
- if (hasOutExtract) {
- var outExtractX = renderNode.coreBox.width -
- renderNode.outExtractBox.width / 2;
- translate(selectChild(sceneGroup, "g", scene.Class.Scene.OUTEXTRACT), outExtractX, yTranslate);
- }
- }
- ;
- /** Adds a click listener to a group that fires a graph-select event */
- function addGraphClickListener(graphGroup, sceneElement) {
- d3.select(graphGroup).on("click", function () {
- sceneElement.fire("graph-select");
- });
- }
- scene.addGraphClickListener = addGraphClickListener;
- ;
- /** Helper for adding transform: translate(x0, y0) */
- function translate(selection, x0, y0) {
- // If it is already placed on the screen, make it a transition.
- if (selection.attr("transform") != null) {
- selection = selection.transition("position");
- }
- selection.attr("transform", "translate(" + x0 + "," + y0 + ")");
- }
- scene.translate = translate;
- ;
- /**
- * Helper for setting position of a svg rect
- * @param rect rect to set position of.
- * @param cx Center x.
- * @param cy Center x.
- * @param width Width to set.
- * @param height Height to set.
- */
- function positionRect(rect, cx, cy, width, height) {
- rect.transition().attr({
- x: cx - width / 2,
- y: cy - height / 2,
- width: width,
- height: height
- });
- }
- scene.positionRect = positionRect;
- ;
- /**
- * Helper for setting position of a svg expand/collapse button
- * @param button container group
- * @param renderNode the render node of the group node to position
- * the button on.
- */
- function positionButton(button, renderNode) {
- var cx = graph.layout.computeCXPositionOfNodeShape(renderNode);
- // Position the button in the top-right corner of the group node,
- // with space given the draw the button inside of the corner.
- var width = renderNode.expanded ?
- renderNode.width : renderNode.coreBox.width;
- var height = renderNode.expanded ?
- renderNode.height : renderNode.coreBox.height;
- var x = cx + width / 2 - 6;
- var y = renderNode.y - height / 2 + 6;
- // For unexpanded series nodes, the button has special placement due
- // to the unique visuals of this group node.
- if (renderNode.node.type === graph.NodeType.SERIES && !renderNode.expanded) {
- x += 10;
- y -= 2;
- }
- var translateStr = "translate(" + x + "," + y + ")";
- button.selectAll("path").transition().attr("transform", translateStr);
- button.select("circle").transition().attr({
- cx: x,
- cy: y,
- r: graph.layout.PARAMS.nodeSize.meta.expandButtonRadius
- });
- }
- scene.positionButton = positionButton;
- ;
- /**
- * Helper for setting position of a svg ellipse
- * @param ellipse ellipse to set position of.
- * @param cx Center x.
- * @param cy Center x.
- * @param width Width to set.
- * @param height Height to set.
- */
- function positionEllipse(ellipse, cx, cy, width, height) {
- ellipse.transition().attr({
- cx: cx,
- cy: cy,
- rx: width / 2,
- ry: height / 2
- });
- }
- scene.positionEllipse = positionEllipse;
- ;
- })(scene = graph.scene || (graph.scene = {}));
- })(graph = tf.graph || (tf.graph = {}));
-})(tf || (tf = {})); // close module
-</script>
-<script>/* Copyright 2015 Google Inc. All Rights Reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
+distributed under the License is distributed on an 'AS IS' BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -6907,12 +7228,12 @@ var tf;
/**
* Populate a given annotation container group
*
- * <g class="{in|out}-annotations"></g>
+ * <g class='{in|out}-annotations'></g>
*
* with annotation group of the following structure:
*
- * <g class="annotation">
- * <g class="annotation-node">
+ * <g class='annotation'>
+ * <g class='annotation-node'>
* \x3c!--
* Content here determined by Scene.node.buildGroup.
* --\x3e
@@ -6927,7 +7248,8 @@ var tf;
*/
function buildGroup(container, annotationData, d, sceneElement) {
// Select all children and join with data.
- var annotationGroups = container.selectAll(function () {
+ var annotationGroups = container
+ .selectAll(function () {
// using d3's selector function
// See https://github.com/mbostock/d3/releases/tag/v2.0.0
// (It's not listed in the d3 wiki.)
@@ -6935,8 +7257,8 @@ var tf;
})
.data(annotationData.list, function (d) { return d.node.name; });
annotationGroups.enter()
- .append("g")
- .attr("data-name", function (a) { return a.node.name; })
+ .append('g')
+ .attr('data-name', function (a) { return a.node.name; })
.each(function (a) {
var aGroup = d3.select(this);
// Add annotation to the index in the scene
@@ -6945,11 +7267,11 @@ var tf;
var edgeType = scene.Class.Annotation.EDGE;
var metaedge = a.renderMetaedgeInfo && a.renderMetaedgeInfo.metaedge;
if (metaedge && !metaedge.numRegularEdges) {
- edgeType += " " + scene.Class.Annotation.CONTROL_EDGE;
+ edgeType += ' ' + scene.Class.Annotation.CONTROL_EDGE;
}
// If any edges are reference edges, add the reference edge class.
if (metaedge && metaedge.numRefEdges) {
- edgeType += " " + scene.Class.Edge.REF_LINE;
+ edgeType += ' ' + scene.Class.Edge.REF_LINE;
}
scene.edge.appendEdge(aGroup, a, sceneElement, edgeType);
if (a.annotationType !== graph.render.AnnotationType.ELLIPSIS) {
@@ -6961,10 +7283,10 @@ var tf;
}
});
annotationGroups
- .attr("class", function (a) {
- return scene.Class.Annotation.GROUP + " " +
- annotationToClassName(a.annotationType) +
- " " + scene.node.nodeClass(a);
+ .attr('class', function (a) {
+ return scene.Class.Annotation.GROUP + ' ' +
+ annotationToClassName(a.annotationType) + ' ' +
+ scene.node.nodeClass(a);
})
.each(function (a) {
var aGroup = d3.select(this);
@@ -6988,76 +7310,67 @@ var tf;
* Maps an annotation enum to a class name used in css rules.
*/
function annotationToClassName(annotationType) {
- return (graph.render.AnnotationType[annotationType] || "")
- .toLowerCase() || null;
+ return (graph.render.AnnotationType[annotationType] || '').toLowerCase() || null;
}
function buildShape(aGroup, a) {
if (a.annotationType === graph.render.AnnotationType.SUMMARY) {
- var summary = scene.selectOrCreateChild(aGroup, "use");
+ var summary = scene.selectOrCreateChild(aGroup, 'use');
summary.attr({
- "class": "summary",
- "xlink:href": "#summary-icon",
- "cursor": "pointer"
+ 'class': 'summary',
+ 'xlink:href': '#summary-icon',
+ 'cursor': 'pointer'
});
}
else {
var shape = scene.node.buildShape(aGroup, a, scene.Class.Annotation.NODE);
// add title tag to get native tooltips
- scene.selectOrCreateChild(shape, "title").text(a.node.name);
+ scene.selectOrCreateChild(shape, 'title').text(a.node.name);
}
}
function addAnnotationLabelFromNode(aGroup, a) {
- var namePath = a.node.name.split("/");
+ var namePath = a.node.name.split('/');
var text = namePath[namePath.length - 1];
- var shortenedText = text.length > 8 ? text.substring(0, 8) + "..." : text;
+ var shortenedText = text.length > 8 ? text.substring(0, 8) + '...' : text;
return addAnnotationLabel(aGroup, shortenedText, a, null, text);
}
function addAnnotationLabel(aGroup, label, a, additionalClassNames, fullLabel) {
var classNames = scene.Class.Annotation.LABEL;
if (additionalClassNames) {
- classNames += " " + additionalClassNames;
+ classNames += ' ' + additionalClassNames;
}
var titleText = fullLabel ? fullLabel : label;
- return aGroup.append("text")
- .attr("class", classNames)
- .attr("dy", ".35em")
- .attr("text-anchor", a.isIn ? "end" : "start")
+ return aGroup.append('text')
+ .attr('class', classNames)
+ .attr('dy', '.35em')
+ .attr('text-anchor', a.isIn ? 'end' : 'start')
.text(label)
- .append("title").text(titleText);
+ .append('title')
+ .text(titleText);
}
function addInteraction(selection, d, annotation, sceneElement) {
selection
- .on("mouseover", function (a) {
- sceneElement.fire("annotation-highlight", {
- name: a.node.name,
- hostName: d.node.name
- });
+ .on('mouseover', function (a) {
+ sceneElement.fire('annotation-highlight', { name: a.node.name, hostName: d.node.name });
})
- .on("mouseout", function (a) {
- sceneElement.fire("annotation-unhighlight", {
- name: a.node.name,
- hostName: d.node.name
- });
+ .on('mouseout', function (a) {
+ sceneElement.fire('annotation-unhighlight', { name: a.node.name, hostName: d.node.name });
})
- .on("click", function (a) {
- // Stop this event"s propagation so that it isn't also considered a
+ .on('click', function (a) {
+ // Stop this event's propagation so that it isn't also considered a
// graph-select.
d3.event.stopPropagation();
- sceneElement.fire("annotation-select", {
- name: a.node.name,
- hostName: d.node.name
- });
+ sceneElement.fire('annotation-select', { name: a.node.name, hostName: d.node.name });
});
if (annotation.annotationType !== graph.render.AnnotationType.SUMMARY &&
annotation.annotationType !== graph.render.AnnotationType.CONSTANT) {
- selection.on("contextmenu", scene.contextmenu.getMenu(scene.node.getContextMenu(annotation.node, sceneElement)));
+ selection.on('contextmenu', scene.contextmenu.getMenu(scene.node.getContextMenu(annotation.node, sceneElement)));
}
}
;
/**
* Adjust annotation's position.
*
- * @param aGroup selection of a "g.annotation" element.
+ * @param aGroup selection of a 'g.annotation' element.
* @param d Host node data.
* @param a annotation node data.
* @param scene <tf-graph-scene> polymer element.
@@ -7066,7 +7379,7 @@ var tf;
var cx = graph.layout.computeCXPositionOfNodeShape(d);
// Annotations that point to embedded nodes (constants,summary)
// don't have a render information attached so we don't stylize these.
- // Also we don't stylize ellipsis annotations (the string "... and X more").
+ // Also we don't stylize ellipsis annotations (the string '... and X more').
if (a.renderNodeInfo &&
a.annotationType !== graph.render.AnnotationType.ELLIPSIS) {
scene.node.stylize(aGroup, a.renderNodeInfo, sceneElement, scene.Class.Annotation.NODE);
@@ -7076,7 +7389,7 @@ var tf;
a.width += 10;
}
// label position
- aGroup.select("text." + scene.Class.Annotation.LABEL).transition().attr({
+ aGroup.select('text.' + scene.Class.Annotation.LABEL).transition().attr({
x: cx + a.dx + (a.isIn ? -1 : 1) * (a.width / 2 + a.labelOffset),
y: d.y + a.dy
});
@@ -7085,20 +7398,18 @@ var tf;
// If there is an image, we adjust the location of the image to be vertically
// centered with the node and horizontally centered between the arrow and the
// text label.
- aGroup.select("use.summary").transition().attr({
+ aGroup.select('use.summary').transition().attr({
x: cx + a.dx - 3,
y: d.y + a.dy - 6
});
// Node position (only one of the shape selection will be non-empty.)
- scene.positionEllipse(aGroup.select("." + scene.Class.Annotation.NODE + " ellipse"), cx + a.dx, d.y + a.dy, a.width, a.height);
- scene.positionRect(aGroup.select("." + scene.Class.Annotation.NODE + " rect"), cx + a.dx, d.y + a.dy, a.width, a.height);
- scene.positionRect(aGroup.select("." + scene.Class.Annotation.NODE + " use"), cx + a.dx, d.y + a.dy, a.width, a.height);
+ scene.positionEllipse(aGroup.select('.' + scene.Class.Annotation.NODE + ' ellipse'), cx + a.dx, d.y + a.dy, a.width, a.height);
+ scene.positionRect(aGroup.select('.' + scene.Class.Annotation.NODE + ' rect'), cx + a.dx, d.y + a.dy, a.width, a.height);
+ scene.positionRect(aGroup.select('.' + scene.Class.Annotation.NODE + ' use'), cx + a.dx, d.y + a.dy, a.width, a.height);
// Edge position
- aGroup.select("path." + scene.Class.Annotation.EDGE).transition().attr("d", function (a) {
+ aGroup.select('path.' + scene.Class.Annotation.EDGE).transition().attr('d', function (a) {
// map relative position to absolute position
- var points = a.points.map(function (p) {
- return { x: p.dx + cx, y: p.dy + d.y };
- });
+ var points = a.points.map(function (p) { return { x: p.dx + cx, y: p.dy + d.y }; });
return scene.edge.interpolate(points);
});
}
@@ -7110,14 +7421,79 @@ var tf;
</script>
<script>/* Copyright 2015 Google Inc. All Rights Reserved.
-Licensed under the Apache License, Version 2.0 (the "License");
+Licensed under the Apache License, Version 2.0 (the 'License');
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
+distributed under the License is distributed on an 'AS IS' BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+var tf;
+(function (tf) {
+ var graph;
+ (function (graph) {
+ var scene;
+ (function (scene) {
+ var contextmenu;
+ (function (contextmenu) {
+ /**
+ * Returns the event listener, which can be used as an argument for the d3
+ * selection.on function. Renders the context menu that is to be displayed
+ * in response to the event.
+ */
+ function getMenu(menu) {
+ var menuSelection = d3.select('.context-menu');
+ // Close the menu when anything else is clicked.
+ d3.select('body').on('click.context', function () { menuSelection.style('display', 'none'); });
+ // Function called to populate the context menu.
+ return function (data, index) {
+ var _this = this;
+ // Position and display the menu.
+ var event = d3.event;
+ menuSelection.style({
+ 'display': 'block',
+ 'left': (event.layerX + 1) + 'px',
+ 'top': (event.layerY + 1) + 'px'
+ });
+ // Stop the event from propagating further.
+ event.preventDefault();
+ event.stopPropagation();
+ // Add provided items to the context menu.
+ menuSelection.html('');
+ var list = menuSelection.append('ul');
+ list.selectAll('li')
+ .data(menu)
+ .enter()
+ .append('li')
+ .html(function (d) { return d.title(data); })
+ .on('click', function (d, i) {
+ d.action(_this, data, index);
+ menuSelection.style('display', 'none');
+ });
+ };
+ }
+ contextmenu.getMenu = getMenu;
+ ;
+ })(contextmenu = scene.contextmenu || (scene.contextmenu = {}));
+ })(scene = graph.scene || (graph.scene = {}));
+ })(graph = tf.graph || (tf.graph = {}));
+})(tf || (tf = {})); // close module
+</script>
+<script>/* Copyright 2015 Google Inc. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the 'License');
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an 'AS IS' BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -7131,7 +7507,7 @@ var tf;
var edge;
(function (edge) {
/** Delimiter between dimensions when showing sizes of tensors. */
- var TENSOR_SHAPE_DELIM = "×";
+ var TENSOR_SHAPE_DELIM = '×';
/** The minimum stroke width of an edge. */
edge.MIN_EDGE_WIDTH = 0.75;
/** The maximum stroke width of an edge. */
@@ -7145,9 +7521,9 @@ var tf;
.domain(DOMAIN_EDGE_WIDTH_SCALE)
.range([edge.MIN_EDGE_WIDTH, edge.MAX_EDGE_WIDTH])
.clamp(true);
- var arrowheadMap = d3.scale.quantize()
- .domain([edge.MIN_EDGE_WIDTH, edge.MAX_EDGE_WIDTH])
- .range(["small", "medium", "large", "xlarge"]);
+ var arrowheadMap = d3.scale.quantize().domain([edge.MIN_EDGE_WIDTH, edge.MAX_EDGE_WIDTH]).range([
+ 'small', 'medium', 'large', 'xlarge'
+ ]);
/** Minimum stroke width to put edge labels in the middle of edges */
var CENTER_EDGE_LABEL_MIN_STROKE_WIDTH = 2.5;
function getEdgeKey(edgeObj) {
@@ -7155,14 +7531,14 @@ var tf;
}
edge.getEdgeKey = getEdgeKey;
/**
- * Select or Create a "g.edges" group to a given sceneGroup
- * and builds a number of "g.edge" groups inside the group.
+ * 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 class='edges'>
+ * <g class='edge'>
+ * <path class='edgeline'/>
* </g>
* ...
* </g>
@@ -7184,7 +7560,7 @@ var tf;
});
return edges;
}, edges);
- var container = scene.selectOrCreateChild(sceneGroup, "g", scene.Class.Edge.CONTAINER);
+ var container = scene.selectOrCreateChild(sceneGroup, 'g', scene.Class.Edge.CONTAINER);
// Select all children and join with data.
// (Note that all children of g.edges are g.edge)
var edgeGroups = container.selectAll(function () {
@@ -7195,9 +7571,9 @@ var tf;
}).data(edges, getEdgeKey);
// Make edges a group to support rendering multiple lines for metaedge
edgeGroups.enter()
- .append("g")
- .attr("class", scene.Class.Edge.GROUP)
- .attr("data-edge", getEdgeKey)
+ .append('g')
+ .attr('class', scene.Class.Edge.GROUP)
+ .attr('data-edge', getEdgeKey)
.each(function (d) {
var edgeGroup = d3.select(this);
d.label.edgeGroup = edgeGroup;
@@ -7231,11 +7607,10 @@ var tf;
return null;
}
if (shape.length === 0) {
- return "scalar";
+ return 'scalar';
}
- return shape.map(function (size) {
- return size === -1 ? "?" : size;
- }).join(TENSOR_SHAPE_DELIM);
+ return shape.map(function (size) { return size === -1 ? '?' : size; })
+ .join(TENSOR_SHAPE_DELIM);
}
edge.getShapeLabelFromNode = getShapeLabelFromNode;
/**
@@ -7246,7 +7621,7 @@ var tf;
function getLabelForEdge(metaedge, renderInfo) {
var isMultiEdge = metaedge.baseEdgeList.length > 1;
if (isMultiEdge) {
- return metaedge.baseEdgeList.length + " tensors";
+ return metaedge.baseEdgeList.length + ' tensors';
}
else {
var node_1 = renderInfo.getNodeByName(metaedge.baseEdgeList[0].v);
@@ -7268,11 +7643,12 @@ var tf;
var lineFunc = d3.svg.line()
.x(function (d) { return d.x; })
.y(function (d) { return d.y; });
- var path = d3.select(document.createElementNS("http://www.w3.org/2000/svg", "path")).attr("d", lineFunc(points));
- var markerWidth = +marker.attr("markerWidth");
- var viewBox = marker.attr("viewBox").split(" ").map(Number);
+ var path = d3.select(document.createElementNS('http://www.w3.org/2000/svg', 'path'))
+ .attr('d', lineFunc(points));
+ var markerWidth = +marker.attr('markerWidth');
+ var viewBox = marker.attr('viewBox').split(' ').map(Number);
var viewBoxWidth = viewBox[2] - viewBox[0];
- var refX = +marker.attr("refX");
+ var refX = +marker.attr('refX');
var pathNode = path.node();
if (isStart) {
var fractionStickingOut = refX / viewBoxWidth;
@@ -7315,23 +7691,22 @@ var tf;
}
edgeClass = edgeClass || scene.Class.Edge.LINE; // set default type
if (d.label && d.label.structural) {
- edgeClass += " " + scene.Class.Edge.STRUCTURAL;
+ edgeClass += ' ' + scene.Class.Edge.STRUCTURAL;
}
// Give the path a unique id, which will be used to link
// the textPath (edge label) to this path.
- var pathId = "path_" + getEdgeKey(d);
+ var pathId = 'path_' + getEdgeKey(d);
var strokeWidth = sceneElement.renderHierarchy.edgeWidthScale(size);
- var path = edgeGroup.append("path")
+ var path = edgeGroup.append('path')
.attr({
- "id": pathId,
- "class": edgeClass,
- }).style({
- "stroke-width": strokeWidth + "px"
- });
+ 'id': pathId,
+ 'class': edgeClass,
+ })
+ .style({ 'stroke-width': strokeWidth + 'px' });
// Check if there is a reference edge and add an arrowhead of the right size.
if (d.label && d.label.metaedge && d.label.metaedge.numRefEdges) {
var markerId = "ref-arrowhead-" + arrowheadMap(strokeWidth);
- path.style("marker-start", "url(#" + markerId + ")");
+ path.style('marker-start', "url(#" + markerId + ")");
d.label.startMarkerId = markerId;
}
if (d.label == null || d.label.metaedge == null) {
@@ -7346,18 +7721,22 @@ var tf;
}
// Put edge label in the middle of edge only if the edge is thick enough.
var baseline = strokeWidth > CENTER_EDGE_LABEL_MIN_STROKE_WIDTH ?
- "central" : "text-after-edge";
- edgeGroup.append("text").append("textPath").attr({
- "xlink:href": "#" + pathId,
- "startOffset": "50%",
- "text-anchor": "middle",
- "dominant-baseline": baseline
- }).text(labelForEdge);
+ 'central' :
+ 'text-after-edge';
+ edgeGroup.append('text')
+ .append('textPath')
+ .attr({
+ 'xlink:href': '#' + pathId,
+ 'startOffset': '50%',
+ 'text-anchor': 'middle',
+ 'dominant-baseline': 'central'
+ })
+ .text(labelForEdge);
}
edge.appendEdge = appendEdge;
;
edge.interpolate = d3.svg.line()
- .interpolate("basis")
+ .interpolate('basis')
.x(function (d) { return d.x; })
.y(function (d) { return d.y; });
/**
@@ -7370,10 +7749,10 @@ var tf;
// Adjust the path so that start/end markers point to the end
// of the path.
if (d.label.startMarkerId) {
- points = adjustPathPointsForMarker(points, d3.select("#" + d.label.startMarkerId), true);
+ points = adjustPathPointsForMarker(points, d3.select('#' + d.label.startMarkerId), true);
}
if (d.label.endMarkerId) {
- points = adjustPathPointsForMarker(points, d3.select("#" + d.label.endMarkerId), false);
+ points = adjustPathPointsForMarker(points, d3.select('#' + d.label.endMarkerId), false);
}
if (!adjoiningMetaedge) {
return d3.interpolate(a, edge.interpolate(points));
@@ -7401,9 +7780,10 @@ var tf;
};
}
function position(d) {
- d3.select(this).select("path." + scene.Class.Edge.LINE)
+ d3.select(this)
+ .select('path.' + scene.Class.Edge.LINE)
.transition()
- .attrTween("d", getEdgePathInterpolator);
+ .attrTween('d', getEdgePathInterpolator);
}
;
/**
@@ -7414,9 +7794,8 @@ var tf;
*/
function stylize(edgeGroup, d, stylize) {
var metaedge = d.label.metaedge;
- edgeGroup
- .select("path." + scene.Class.Edge.LINE)
- .classed("control-dep", metaedge && !metaedge.numRegularEdges);
+ edgeGroup.select('path.' + scene.Class.Edge.LINE)
+ .classed('control-dep', metaedge && !metaedge.numRegularEdges);
}
;
})(edge = scene.edge || (scene.edge = {}));
@@ -7426,14 +7805,14 @@ var tf;
</script>
<script>/* Copyright 2015 Google Inc. All Rights Reserved.
-Licensed under the Apache License, Version 2.0 (the "License");
+Licensed under the Apache License, Version 2.0 (the 'License');
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
+distributed under the License is distributed on an 'AS IS' BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -7447,29 +7826,30 @@ var tf;
var node;
(function (node_1) {
/**
- * Select or Create a "g.nodes" group to a given sceneGroup
- * and builds a number of "g.node" groups inside the group.
+ * 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 class='nodes'>
+ * <g class='node'>
+ * <g class='in-annotations'>
* ...
* </g>
- * <g class="out-annotations">
+ * <g class='out-annotations'>
* ...
* </g>
- * <g class="nodeshape">
+ * <g class='nodeshape'>
* \x3c!--
* Content of the node shape should be for the node itself. For example a
* Metanode would have a <rect> with rounded edges, an op would have an
- * <ellipse>. More complex nodes like series may contain multiple elements
- * which are conditionally visible based on whether the node is expanded.
+ * <ellipse>. More complex nodes like series may contain multiple
+ * elements which are conditionally visible based on whether the node is
+ * expanded.
* --\x3e
* </g>
- * <text class="label">node name</text>
- * <g class="subscene">
+ * <text class='label'>node name</text>
+ * <g class='subscene'>
* \x3c!--
* Content of the subscene (only for metanode and series node).
*
@@ -7491,10 +7871,11 @@ var tf;
* @return selection of the created nodeGroups
*/
function buildGroup(sceneGroup, nodeData, sceneElement) {
- var container = scene.selectOrCreateChild(sceneGroup, "g", scene.Class.Node.CONTAINER);
+ var container = scene.selectOrCreateChild(sceneGroup, 'g', scene.Class.Node.CONTAINER);
// Select all children and join with data.
// (Note that all children of g.nodes are g.node)
- var nodeGroups = container.selectAll(function () {
+ var nodeGroups = container
+ .selectAll(function () {
// using d3's selector function
// See https://github.com/mbostock/d3/releases/tag/v2.0.0
// (It's not listed in the d3 wiki.)
@@ -7502,12 +7883,12 @@ var tf;
})
.data(nodeData, function (d) {
// make sure that we don't have to swap shape type
- return d.node.name + ":" + d.node.type;
+ return d.node.name + ':' + d.node.type;
});
// ENTER
nodeGroups.enter()
- .append("g")
- .attr("data-name", function (d) { return d.node.name; })
+ .append('g')
+ .attr('data-name', function (d) { return d.node.name; })
.each(function (d) {
var nodeGroup = d3.select(this);
// index node group for quick stylizing
@@ -7515,16 +7896,16 @@ var tf;
});
// UPDATE
nodeGroups
- .attr("class", function (d) {
- return scene.Class.Node.GROUP + " " + nodeClass(d);
- })
+ .attr('class', function (d) { return scene.Class.Node.GROUP + ' ' + nodeClass(d); })
.each(function (d) {
var nodeGroup = d3.select(this);
- // Add g.in-annotations (always add -- to keep layer order consistent.)
- var inAnnotationBox = scene.selectOrCreateChild(nodeGroup, "g", scene.Class.Annotation.INBOX);
+ // Add g.in-annotations (always add -- to keep layer order
+ // consistent.)
+ var inAnnotationBox = scene.selectOrCreateChild(nodeGroup, 'g', scene.Class.Annotation.INBOX);
scene.annotation.buildGroup(inAnnotationBox, d.inAnnotations, d, sceneElement);
- // Add g.out-annotations (always add -- to keep layer order consistent.)
- var outAnnotationBox = scene.selectOrCreateChild(nodeGroup, "g", scene.Class.Annotation.OUTBOX);
+ // Add g.out-annotations (always add -- to keep layer order
+ // consistent.)
+ var outAnnotationBox = scene.selectOrCreateChild(nodeGroup, 'g', scene.Class.Annotation.OUTBOX);
scene.annotation.buildGroup(outAnnotationBox, d.outAnnotations, d, sceneElement);
// Build .shape first (background of the node).
var shape = buildShape(nodeGroup, d, scene.Class.Node.SHAPE);
@@ -7549,18 +7930,14 @@ var tf;
sceneElement.removeNodeGroup(d.node.name);
var nodeGroup = d3.select(this);
if (d.inAnnotations.list.length > 0) {
- nodeGroup.select("." + scene.Class.Annotation.INBOX)
- .selectAll("." + scene.Class.Annotation.GROUP)
- .each(function (a) {
- sceneElement.removeAnnotationGroup(a, d);
- });
+ nodeGroup.select('.' + scene.Class.Annotation.INBOX)
+ .selectAll('.' + scene.Class.Annotation.GROUP)
+ .each(function (a) { sceneElement.removeAnnotationGroup(a, d); });
}
if (d.outAnnotations.list.length > 0) {
- nodeGroup.select("." + scene.Class.Annotation.OUTBOX)
- .selectAll("." + scene.Class.Annotation.GROUP)
- .each(function (a) {
- sceneElement.removeAnnotationGroup(a, d);
- });
+ nodeGroup.select('.' + scene.Class.Annotation.OUTBOX)
+ .selectAll('.' + scene.Class.Annotation.GROUP)
+ .each(function (a) { sceneElement.removeAnnotationGroup(a, d); });
}
})
.remove();
@@ -7586,7 +7963,7 @@ var tf;
return scene.buildGroup(nodeGroup, renderNodeInfo, sceneElement, scene.Class.Subscene.GROUP);
}
// Clean out existing subscene if the node is not expanded.
- scene.selectChild(nodeGroup, "g", scene.Class.Subscene.GROUP).remove();
+ scene.selectChild(nodeGroup, 'g', scene.Class.Subscene.GROUP).remove();
}
return null;
}
@@ -7597,7 +7974,7 @@ var tf;
function subscenePosition(nodeGroup, d) {
var x0 = d.x - d.width / 2.0 + d.paddingLeft;
var y0 = d.y - d.height / 2.0 + d.paddingTop;
- var subscene = scene.selectChild(nodeGroup, "g", scene.Class.Subscene.GROUP);
+ var subscene = scene.selectChild(nodeGroup, 'g', scene.Class.Subscene.GROUP);
scene.translate(subscene, x0, y0);
}
;
@@ -7609,15 +7986,17 @@ var tf;
* @param sceneElement <tf-graph-scene> polymer element.
*/
function addButton(selection, d, sceneElement) {
- var group = scene.selectOrCreateChild(selection, "g", scene.Class.Node.BUTTON_CONTAINER);
- scene.selectOrCreateChild(group, "circle", scene.Class.Node.BUTTON_CIRCLE);
- scene.selectOrCreateChild(group, "path", scene.Class.Node.EXPAND_BUTTON).attr("d", "M0,-2.2 V2.2 M-2.2,0 H2.2");
- scene.selectOrCreateChild(group, "path", scene.Class.Node.COLLAPSE_BUTTON).attr("d", "M-2.2,0 H2.2");
- group.on("click", function (d) {
+ var group = scene.selectOrCreateChild(selection, 'g', scene.Class.Node.BUTTON_CONTAINER);
+ scene.selectOrCreateChild(group, 'circle', scene.Class.Node.BUTTON_CIRCLE);
+ scene.selectOrCreateChild(group, 'path', scene.Class.Node.EXPAND_BUTTON)
+ .attr('d', 'M0,-2.2 V2.2 M-2.2,0 H2.2');
+ scene.selectOrCreateChild(group, 'path', scene.Class.Node.COLLAPSE_BUTTON)
+ .attr('d', 'M-2.2,0 H2.2');
+ group.on('click', function (d) {
// Stop this event's propagation so that it isn't also considered a
// node-select.
d3.event.stopPropagation();
- sceneElement.fire("node-toggle-expand", { name: d.node.name });
+ sceneElement.fire('node-toggle-expand', { name: d.node.name });
});
scene.positionButton(group, d);
}
@@ -7632,37 +8011,38 @@ var tf;
*/
function addInteraction(selection, d, sceneElement, disableInteraction) {
if (disableInteraction) {
- selection.attr("pointer-events", "none");
+ selection.attr('pointer-events', 'none');
return;
}
var contextMenuFunction = scene.contextmenu.getMenu(getContextMenu(d.node, sceneElement));
- selection.on("dblclick", function (d) {
- sceneElement.fire("node-toggle-expand", { name: d.node.name });
+ selection
+ .on('dblclick', function (d) {
+ sceneElement.fire('node-toggle-expand', { name: d.node.name });
})
- .on("mouseover", function (d) {
+ .on('mouseover', function (d) {
// don't send mouseover over expanded group,
// otherwise it is causing too much glitches
if (sceneElement.isNodeExpanded(d)) {
return;
}
- sceneElement.fire("node-highlight", { name: d.node.name });
+ sceneElement.fire('node-highlight', { name: d.node.name });
})
- .on("mouseout", function (d) {
+ .on('mouseout', function (d) {
// don't send mouseover over expanded group,
// otherwise it is causing too much glitches
if (sceneElement.isNodeExpanded(d)) {
return;
}
- sceneElement.fire("node-unhighlight", { name: d.node.name });
+ sceneElement.fire('node-unhighlight', { name: d.node.name });
})
- .on("click", function (d) {
+ .on('click', function (d) {
// Stop this event's propagation so that it isn't also considered
// a graph-select.
d3.event.stopPropagation();
- sceneElement.fire("node-select", { name: d.node.name });
+ sceneElement.fire('node-select', { name: d.node.name });
})
- .on("contextmenu", function (d, i) {
- sceneElement.fire("node-select", { name: d.node.name });
+ .on('contextmenu', function (d, i) {
+ sceneElement.fire('node-select', { name: d.node.name });
contextMenuFunction.call(d, i);
});
}
@@ -7672,20 +8052,16 @@ var tf;
*/
function getContextMenu(node, sceneElement) {
var menu = [{
- title: function (d) {
- return graph.getIncludeNodeButtonString(node.include);
- },
+ title: function (d) { return graph.getIncludeNodeButtonString(node.include); },
action: function (elm, d, i) {
- sceneElement.fire("node-toggle-extract", { name: node.name });
+ sceneElement.fire('node-toggle-extract', { name: node.name });
}
}];
if (canBeInSeries(node)) {
menu.push({
- title: function (d) {
- return getGroupSettingLabel(node);
- },
+ title: function (d) { return getGroupSettingLabel(node); },
action: function (elm, d, i) {
- sceneElement.fire("node-toggle-seriesgroup", { name: getSeriesName(node) });
+ sceneElement.fire('node-toggle-seriesgroup', { name: getSeriesName(node) });
}
});
}
@@ -7749,23 +8125,22 @@ var tf;
* @param sceneElement <tf-graph-scene> polymer element.
*/
function labelBuild(nodeGroup, renderNodeInfo, sceneElement) {
- var namePath = renderNodeInfo.node.name.split("/");
+ var namePath = renderNodeInfo.node.name.split('/');
var text = namePath[namePath.length - 1];
// Truncate long labels for unexpanded Metanodes.
var useFontScale = renderNodeInfo.node.type === graph.NodeType.META &&
!renderNodeInfo.expanded;
- var label = scene.selectOrCreateChild(nodeGroup, "text", scene.Class.Node.LABEL);
+ var label = scene.selectOrCreateChild(nodeGroup, 'text', scene.Class.Node.LABEL);
// Make sure the label is visually on top among its siblings.
var labelNode = label.node();
labelNode.parentNode.appendChild(labelNode);
- label.attr("dy", ".35em")
- .attr("text-anchor", "middle");
+ label.attr('dy', '.35em').attr('text-anchor', 'middle');
if (useFontScale) {
if (text.length > sceneElement.maxMetanodeLabelLength) {
- text = text.substr(0, sceneElement.maxMetanodeLabelLength - 2) + "...";
+ text = text.substr(0, sceneElement.maxMetanodeLabelLength - 2) + '...';
}
var scale = getLabelFontScale(sceneElement);
- label.attr("font-size", scale(text.length) + "px");
+ label.attr('font-size', scale(text.length) + 'px');
}
label.text(text);
return label;
@@ -7790,9 +8165,10 @@ var tf;
* Set label position of a given node group
*/
function labelPosition(nodeGroup, cx, cy, yOffset) {
- scene.selectChild(nodeGroup, "text", scene.Class.Node.LABEL).transition()
- .attr("x", cx)
- .attr("y", cy + yOffset);
+ scene.selectChild(nodeGroup, 'text', scene.Class.Node.LABEL)
+ .transition()
+ .attr('x', cx)
+ .attr('y', cy + yOffset);
}
;
/**
@@ -7806,35 +8182,35 @@ var tf;
*/
function buildShape(nodeGroup, d, nodeClass) {
// Create a group to house the underlying visual elements.
- var shapeGroup = scene.selectOrCreateChild(nodeGroup, "g", nodeClass);
+ var shapeGroup = scene.selectOrCreateChild(nodeGroup, 'g', nodeClass);
// TODO(jimbo): DOM structure should be templated in HTML somewhere, not JS.
switch (d.node.type) {
case graph.NodeType.OP:
- scene.selectOrCreateChild(shapeGroup, "ellipse", scene.Class.Node.COLOR_TARGET);
+ scene.selectOrCreateChild(shapeGroup, 'ellipse', scene.Class.Node.COLOR_TARGET);
break;
case graph.NodeType.SERIES:
// Choose the correct stamp to use to represent this series.
- var stampType = "annotation";
+ var stampType = 'annotation';
var groupNodeInfo = d;
if (groupNodeInfo.coreGraph) {
- stampType = groupNodeInfo.node.hasNonControlEdges
- ? "vertical" : "horizontal";
+ stampType =
+ groupNodeInfo.node.hasNonControlEdges ? 'vertical' : 'horizontal';
}
- scene.selectOrCreateChild(shapeGroup, "use", scene.Class.Node.COLOR_TARGET)
- .attr("xlink:href", "#op-series-" + stampType + "-stamp");
- scene.selectOrCreateChild(shapeGroup, "rect", scene.Class.Node.COLOR_TARGET)
+ scene.selectOrCreateChild(shapeGroup, 'use', scene.Class.Node.COLOR_TARGET)
+ .attr('xlink:href', '#op-series-' + stampType + '-stamp');
+ scene.selectOrCreateChild(shapeGroup, 'rect', scene.Class.Node.COLOR_TARGET)
.attr({ rx: d.radius, ry: d.radius });
break;
case graph.NodeType.BRIDGE:
- scene.selectOrCreateChild(shapeGroup, "rect", scene.Class.Node.COLOR_TARGET)
+ scene.selectOrCreateChild(shapeGroup, 'rect', scene.Class.Node.COLOR_TARGET)
.attr({ rx: d.radius, ry: d.radius });
break;
case graph.NodeType.META:
- scene.selectOrCreateChild(shapeGroup, "rect", scene.Class.Node.COLOR_TARGET)
+ scene.selectOrCreateChild(shapeGroup, 'rect', scene.Class.Node.COLOR_TARGET)
.attr({ rx: d.radius, ry: d.radius });
break;
default:
- throw Error("Unrecognized node type: " + d.node.type);
+ throw Error('Unrecognized node type: ' + d.node.type);
}
return shapeGroup;
}
@@ -7854,25 +8230,25 @@ var tf;
return scene.Class.ELLIPSISNODE;
}
;
- throw Error("Unrecognized node type: " + d.node.type);
+ throw Error('Unrecognized node type: ' + d.node.type);
}
node_1.nodeClass = nodeClass;
;
/** Modify node and its subscene and its label's positional attributes */
function position(nodeGroup, d) {
- var shapeGroup = scene.selectChild(nodeGroup, "g", scene.Class.Node.SHAPE);
+ var shapeGroup = scene.selectChild(nodeGroup, 'g', scene.Class.Node.SHAPE);
var cx = graph.layout.computeCXPositionOfNodeShape(d);
switch (d.node.type) {
case graph.NodeType.OP: {
// position shape
- var shape = scene.selectChild(shapeGroup, "ellipse");
+ var shape = scene.selectChild(shapeGroup, 'ellipse');
scene.positionEllipse(shape, cx, d.y, d.coreBox.width, d.coreBox.height);
labelPosition(nodeGroup, cx, d.y, d.labelOffset);
break;
}
case graph.NodeType.META: {
// position shape
- var shape = scene.selectChild(shapeGroup, "rect");
+ var shape = scene.selectChild(shapeGroup, 'rect');
if (d.expanded) {
scene.positionRect(shape, d.x, d.y, d.width, d.height);
subscenePosition(nodeGroup, d);
@@ -7886,7 +8262,7 @@ var tf;
break;
}
case graph.NodeType.SERIES: {
- var shape = scene.selectChild(shapeGroup, "use");
+ var shape = scene.selectChild(shapeGroup, 'use');
if (d.expanded) {
scene.positionRect(shape, d.x, d.y, d.width, d.height);
subscenePosition(nodeGroup, d);
@@ -7903,12 +8279,12 @@ var tf;
// position shape
// NOTE: In reality, these will not be visible, but it helps to put them
// in the correct position for debugging purposes.
- var shape = scene.selectChild(shapeGroup, "rect");
+ var shape = scene.selectChild(shapeGroup, 'rect');
scene.positionRect(shape, d.x, d.y, d.width, d.height);
break;
}
default: {
- throw Error("Unrecognized node type: " + d.node.type);
+ throw Error('Unrecognized node type: ' + d.node.type);
}
}
}
@@ -7923,7 +8299,7 @@ var tf;
var ColorBy = node_1.ColorBy;
;
/**
- * Returns the fill color for the node given its state and the "color by"
+ * Returns the fill color for the node given its state and the 'color by'
* option.
*/
function getFillForNode(templateIndex, colorBy, renderInfo, isExpanded) {
@@ -7940,15 +8316,16 @@ var tf;
// If expanded, we're showing the background rect, which we want to
// appear gray. Otherwise we're showing a stack of ellipses which we
// want to show white.
- return isExpanded ? colorParams.EXPANDED_COLOR : "white";
+ return isExpanded ? colorParams.EXPANDED_COLOR : 'white';
}
else if (renderInfo.node.type === graph.NodeType.BRIDGE) {
- return renderInfo.structural ? "#f0e" :
- renderInfo.node.inbound ? "#0ef" : "#fe0";
+ return renderInfo.structural ?
+ '#f0e' :
+ renderInfo.node.inbound ? '#0ef' : '#fe0';
}
else {
// Op nodes are white.
- return "white";
+ return 'white';
}
case ColorBy.DEVICE:
if (renderInfo.deviceColors == null) {
@@ -7956,24 +8333,24 @@ var tf;
return colorParams.UNKNOWN;
}
var id = renderInfo.node.name;
- var escapedId = tf.escapeQuerySelector(id);
- var gradientDefs = d3.select("svg#svg defs #linearGradients");
- var linearGradient_1 = gradientDefs.select("linearGradient#" + escapedId);
+ var escapedId = tf.graph.util.escapeQuerySelector(id);
+ var gradientDefs = d3.select('svg#svg defs #linearGradients');
+ var linearGradient_1 = gradientDefs.select('linearGradient#' + escapedId);
// If the linear gradient is not there yet, create it.
if (linearGradient_1.size() === 0) {
- linearGradient_1 = gradientDefs.append("linearGradient").attr("id", id);
+ linearGradient_1 = gradientDefs.append('linearGradient').attr('id', id);
// Re-create the stops of the linear gradient.
- linearGradient_1.selectAll("*").remove();
+ linearGradient_1.selectAll('*').remove();
var cumulativeProportion_1 = 0;
// For each device, create a stop using the proportion of that device.
_.each(renderInfo.deviceColors, function (d) {
var color = d.color;
- linearGradient_1.append("stop")
- .attr("offset", cumulativeProportion_1)
- .attr("stop-color", color);
- linearGradient_1.append("stop")
- .attr("offset", cumulativeProportion_1 + d.proportion)
- .attr("stop-color", color);
+ linearGradient_1.append('stop')
+ .attr('offset', cumulativeProportion_1)
+ .attr('stop-color', color);
+ linearGradient_1.append('stop')
+ .attr('offset', cumulativeProportion_1 + d.proportion)
+ .attr('stop-color', color);
cumulativeProportion_1 += d.proportion;
});
}
@@ -7987,7 +8364,7 @@ var tf;
colorParams.EXPANDED_COLOR : renderInfo.memoryColor ||
colorParams.UNKNOWN;
default:
- throw new Error("Unknown case to color nodes by");
+ throw new Error('Unknown case to color nodes by');
}
}
node_1.getFillForNode = getFillForNode;
@@ -8001,18 +8378,18 @@ var tf;
var isSelected = sceneElement.isNodeSelected(renderInfo.node.name);
var isExtract = renderInfo.isInExtract || renderInfo.isOutExtract;
var isExpanded = renderInfo.expanded;
- nodeGroup.classed("highlighted", isHighlighted);
- nodeGroup.classed("selected", isSelected);
- nodeGroup.classed("extract", isExtract);
- nodeGroup.classed("expanded", isExpanded);
+ 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.
- var node = nodeGroup.select("." + nodeClass + " ." + scene.Class.Node.COLOR_TARGET);
+ var node = nodeGroup.select('.' + nodeClass + ' .' + scene.Class.Node.COLOR_TARGET);
var fillColor = getFillForNode(sceneElement.templateIndex, ColorBy[sceneElement.colorBy.toUpperCase()], renderInfo, isExpanded);
- node.style("fill", fillColor);
+ node.style('fill', fillColor);
// Choose outline to be darker version of node color if the node is a single
// color and is not selected.
- node.style("stroke", isSelected ? null : getStrokeForFill(fillColor));
+ node.style('stroke', isSelected ? null : getStrokeForFill(fillColor));
}
node_1.stylize = stylize;
;
@@ -8021,7 +8398,7 @@ var tf;
*/
function getStrokeForFill(fill) {
// If node is colored by a gradient, then use a dark gray outline.
- return fill.substring(0, 3) === "url" ?
+ return fill.substring(0, 3) === 'url' ?
graph.render.MetanodeColors.GRADIENT_OUTLINE :
d3.rgb(fill).darker().toString();
}
@@ -8033,14 +8410,14 @@ var tf;
</script>
<script>/* Copyright 2015 Google Inc. All Rights Reserved.
-Licensed under the Apache License, Version 2.0 (the "License");
+Licensed under the Apache License, Version 2.0 (the 'License');
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
+distributed under the License is distributed on an 'AS IS' BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -8051,908 +8428,833 @@ var tf;
(function (graph) {
var scene;
(function (scene) {
- var contextmenu;
- (function (contextmenu) {
- /**
- * Returns the event listener, which can be used as an argument for the d3
- * selection.on function. Renders the context menu that is to be displayed
- * in response to the event.
- */
- function getMenu(menu) {
- var menuSelection = d3.select(".context-menu");
- // Close the menu when anything else is clicked.
- d3.select("body").on("click.context", function () {
- menuSelection.style("display", "none");
- });
- // Function called to populate the context menu.
- return function (data, index) {
- var _this = this;
- // Position and display the menu.
- var event = d3.event;
- menuSelection.style({
- "display": "block",
- "left": (event.layerX + 1) + "px",
- "top": (event.layerY + 1) + "px"
- });
- // Stop the event from propagating further.
- event.preventDefault();
- event.stopPropagation();
- // Add provided items to the context menu.
- menuSelection.html("");
- var list = menuSelection.append("ul");
- list.selectAll("li").data(menu).enter()
- .append("li")
- .html(function (d) {
- return d.title(data);
- })
- .on("click", function (d, i) {
- d.action(_this, data, index);
- menuSelection.style("display", "none");
- });
- };
- }
- contextmenu.getMenu = getMenu;
- ;
- })(contextmenu = scene.contextmenu || (scene.contextmenu = {}));
- })(scene = graph.scene || (graph.scene = {}));
- })(graph = tf.graph || (tf.graph = {}));
-})(tf || (tf = {})); // close module
-</script>
-<script>/* Copyright 2015 Google Inc. All Rights Reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-==============================================================================*/
-var tf;
-(function (tf) {
- var graph;
- (function (graph_1) {
- var layout;
- (function (layout) {
- /** Set of parameters that define the look and feel of the graph. */
- layout.PARAMS = {
- animation: {
- /** Default duration for graph animations in ms. */
- duration: 250
- },
- graph: {
- /** Graph parameter for metanode. */
- meta: {
- /**
- * Dagre's nodesep param - number of pixels that
- * separate nodes horizontally in the layout.
- *
- * See https://github.com/cpettitt/dagre/wiki#configuring-the-layout
- */
- nodeSep: 5,
- /**
- * Dagre's ranksep param - number of pixels
- * between each rank in the layout.
- *
- * See https://github.com/cpettitt/dagre/wiki#configuring-the-layout
- */
- rankSep: 25,
- /**
- * Dagre's edgesep param - number of pixels that separate
- * edges horizontally in the layout.
- */
- edgeSep: 5,
- },
- /** Graph parameter for metanode. */
- series: {
- /**
- * Dagre's nodesep param - number of pixels that
- * separate nodes horizontally in the layout.
- *
- * See https://github.com/cpettitt/dagre/wiki#configuring-the-layout
- */
- nodeSep: 5,
- /**
- * Dagre's ranksep param - number of pixels
- * between each rank in the layout.
- *
- * See https://github.com/cpettitt/dagre/wiki#configuring-the-layout
- */
- rankSep: 25,
- /**
- * Dagre's edgesep param - number of pixels that separate
- * edges horizontally in the layout.
- */
- edgeSep: 5
- },
- /**
- * Padding is used to correctly position the graph SVG inside of its parent
- * element. The padding amounts are applied using an SVG transform of X and
- * Y coordinates.
- */
- padding: {
- paddingTop: 40,
- paddingLeft: 20
- }
- },
- subscene: {
- meta: {
- paddingTop: 10,
- paddingBottom: 10,
- paddingLeft: 10,
- paddingRight: 10,
- /**
- * Used to leave room for the label on top of the highest node in
- * the core graph.
- */
- labelHeight: 20,
- /** X-space between each extracted node and the core graph. */
- extractXOffset: 15,
- /** Y-space between each extracted node. */
- extractYOffset: 20
- },
- series: {
- paddingTop: 10,
- paddingBottom: 10,
- paddingLeft: 10,
- paddingRight: 10,
- labelHeight: 10
- }
+ /** Enums element class of objects in the scene */
+ scene.Class = {
+ Node: {
+ // <g> element that contains nodes.
+ CONTAINER: 'nodes',
+ // <g> element that contains detail about a node.
+ GROUP: 'node',
+ // <g> element that contains visual elements (like rect, ellipse).
+ SHAPE: 'nodeshape',
+ // <*> element(s) under SHAPE that should receive color updates.
+ COLOR_TARGET: 'nodecolortarget',
+ // <text> element showing the node's label.
+ LABEL: 'nodelabel',
+ // <g> element that contains all visuals for the expand/collapse
+ // button for expandable group nodes.
+ BUTTON_CONTAINER: 'buttoncontainer',
+ // <circle> element that surrounds expand/collapse buttons.
+ BUTTON_CIRCLE: 'buttoncircle',
+ // <path> element of the expand button.
+ EXPAND_BUTTON: 'expandbutton',
+ // <path> element of the collapse button.
+ COLLAPSE_BUTTON: 'collapsebutton'
},
- 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,
- 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,
- 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
- }
+ Edge: {
+ CONTAINER: 'edges',
+ GROUP: 'edge',
+ LINE: 'edgeline',
+ REF_LINE: 'refline',
+ STRUCTURAL: 'structural'
},
- 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,
- }
+ 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'
},
- annotations: {
- /** Maximum possible width of the bounding box for in annotations */
- inboxWidth: 50,
- /** Maximum possible width of the bounding box for out annotations */
- outboxWidth: 50,
- /** X-space between the shape and each annotation-node. */
- xOffset: 10,
- /** Y-space between each annotation-node. */
- yOffset: 3,
- /** X-space between each annotation-node and its label. */
- labelOffset: 2,
- /** Estimate max width for annotation label */
- labelWidth: 35
+ Scene: {
+ GROUP: 'scene',
+ CORE: 'core',
+ INEXTRACT: 'in-extract',
+ OUTEXTRACT: 'out-extract'
},
- constant: {
- size: {
- width: 4,
- height: 4
+ 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.
+ */
+ function fit(svg, zoomG, d3zoom, callback) {
+ var svgRect = svg.getBoundingClientRect();
+ var sceneSize = null;
+ try {
+ sceneSize = zoomG.getBBox();
+ if (sceneSize.width === 0) {
+ // There is no scene anymore. We have been detached from the dom.
+ return;
}
- },
- series: {
- /** Maximum number of repeated item for unexpanded series node. */
- maxStackCount: 3,
- /**
- * Positioning offset ratio for collapsed stack
- * of parallel series (series without edges between its members).
- */
- parallelStackOffsetRatio: 0.2,
- /**
- * Positioning offset ratio for collapsed stack
- * of tower series (series with edges between its members).
- */
- towerStackOffsetRatio: 0.5
- },
- minimap: {
- /** The maximum width/height the minimap can have. */
- size: 150
}
- };
- /** Calculate layout for a scene of a group node. */
- function layoutScene(renderNodeInfo) {
- // Update layout, size, and annotations of its children nodes and edges.
- if (renderNodeInfo.node.isGroupNode) {
- layoutChildren(renderNodeInfo);
+ catch (e) {
+ // Firefox produced NS_ERROR_FAILURE if we have been
+ // detached from the dom.
+ return;
}
- // Update position of its children nodes and edges
- if (renderNodeInfo.node.type === graph_1.NodeType.META) {
- layoutMetanode(renderNodeInfo);
+ var scale = 0.9 * Math.min(svgRect.width / sceneSize.width, svgRect.height / sceneSize.height, 2);
+ var params = graph.layout.PARAMS.graph;
+ var zoomEvent = d3zoom.scale(scale)
+ .on('zoomend.fitted', function () {
+ // Remove the listener for the zoomend event,
+ // so we don't get called at the end of regular zoom events,
+ // just those that fit the graph to screen.
+ d3zoom.on('zoomend.fitted', null);
+ callback();
+ })
+ .translate([params.padding.paddingLeft, params.padding.paddingTop])
+ .event;
+ d3.select(zoomG).transition().duration(500).call(zoomEvent);
+ }
+ scene.fit = fit;
+ ;
+ /**
+ * Helper method for panning the graph to center on the provided node,
+ * if the node is currently off-screen.
+ *
+ * @param nodeName The node to center the graph on
+ * @param svg The root SVG element for the graph
+ * @param zoomG The svg group used for panning and zooming.
+ * @param d3zoom The zoom behavior.
+ * @return True if the graph had to be panned to display the
+ * provided node.
+ */
+ function panToNode(nodeName, svg, zoomG, d3zoom) {
+ var node = d3
+ .select('[data-name="' + nodeName + '"].' + scene.Class.Node.GROUP)
+ .node();
+ if (!node) {
+ return false;
}
- else if (renderNodeInfo.node.type === graph_1.NodeType.SERIES) {
- layoutSeriesNode(renderNodeInfo);
+ var translate = d3zoom.translate();
+ // Check if the selected node is off-screen in either
+ // X or Y dimension in either direction.
+ var nodeBox = node.getBBox();
+ var nodeCtm = node.getScreenCTM();
+ var pointTL = svg.createSVGPoint();
+ var pointBR = svg.createSVGPoint();
+ pointTL.x = nodeBox.x;
+ pointTL.y = nodeBox.y;
+ pointBR.x = nodeBox.x + nodeBox.width;
+ pointBR.y = nodeBox.y + nodeBox.height;
+ pointTL = pointTL.matrixTransform(nodeCtm);
+ pointBR = pointBR.matrixTransform(nodeCtm);
+ var isOutsideOfBounds = function (start, end, bound) {
+ return end < 0 || start > bound;
+ };
+ var svgRect = svg.getBoundingClientRect();
+ if (isOutsideOfBounds(pointTL.x, pointBR.x, svgRect.width) ||
+ isOutsideOfBounds(pointTL.y, pointBR.y, svgRect.height)) {
+ // Determine the amount to transform the graph in both X and Y
+ // dimensions in order to center the selected node. This takes into
+ // acount the position of the node, the size of the svg scene, the
+ // amount the scene has been scaled by through zooming, and any previous
+ // transform already performed by this logic.
+ var centerX = (pointTL.x + pointBR.x) / 2;
+ var centerY = (pointTL.y + pointBR.y) / 2;
+ var dx = ((svgRect.width / 2) - centerX);
+ var dy = ((svgRect.height / 2) - centerY);
+ var zoomEvent = d3zoom.translate([translate[0] + dx, translate[1] + dy])
+ .event;
+ d3.select(zoomG).transition().duration(500).call(zoomEvent);
+ return true;
}
+ return false;
}
- layout.layoutScene = layoutScene;
+ scene.panToNode = panToNode;
;
/**
- * Updates the total width of an unexpanded node which includes the size of its
- * in and out annotations.
+ * 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
*/
- function updateTotalWidthOfNode(renderInfo) {
- renderInfo.inboxWidth = renderInfo.inAnnotations.list.length > 0 ?
- layout.PARAMS.annotations.inboxWidth : 0;
- renderInfo.outboxWidth = renderInfo.outAnnotations.list.length > 0 ?
- layout.PARAMS.annotations.outboxWidth : 0;
- // Assign the width of the core box (the main shape of the node).
- renderInfo.coreBox.width = renderInfo.width;
- renderInfo.coreBox.height = renderInfo.height;
- // TODO(jimbo): Account for font width rather than using a magic number.
- var labelLength = renderInfo.node.name.length -
- renderInfo.node.name.lastIndexOf(graph_1.NAMESPACE_DELIM) - 1;
- var charWidth = 3; // 3 pixels per character.
- // Compute the total width of the node.
- renderInfo.width = Math.max(renderInfo.coreBox.width +
- renderInfo.inboxWidth + renderInfo.outboxWidth, labelLength * charWidth);
+ function selectOrCreateChild(container, tagName, className, before) {
+ var child = selectChild(container, tagName, className);
+ if (!child.empty()) {
+ return child;
+ }
+ var newElement = document.createElementNS('http://www.w3.org/2000/svg', tagName);
+ if (className) {
+ newElement.classList.add(className);
+ }
+ if (before) {
+ container.node().insertBefore(newElement, before);
+ }
+ else {
+ container.node().appendChild(newElement);
+ }
+ return d3.select(newElement)
+ .datum(container.datum());
}
+ scene.selectOrCreateChild = selectOrCreateChild;
+ ;
/**
- * Update layout, size, and annotations of its children nodes and edges.
+ * 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
*/
- function layoutChildren(renderNodeInfo) {
- var children = renderNodeInfo.coreGraph.nodes().map(function (n) {
- return renderNodeInfo.coreGraph.node(n);
- }).concat(renderNodeInfo.isolatedInExtract, renderNodeInfo.isolatedOutExtract);
- _.each(children, function (childNodeInfo) {
- // Set size of each child
- switch (childNodeInfo.node.type) {
- case graph_1.NodeType.OP:
- _.extend(childNodeInfo, layout.PARAMS.nodeSize.op);
- break;
- case graph_1.NodeType.BRIDGE:
- _.extend(childNodeInfo, layout.PARAMS.nodeSize.bridge);
- break;
- case graph_1.NodeType.META:
- if (!childNodeInfo.expanded) {
- // Set fixed width and scalable height based on cardinality
- _.extend(childNodeInfo, layout.PARAMS.nodeSize.meta);
- childNodeInfo.height =
- layout.PARAMS.nodeSize.meta.height(childNodeInfo.node.cardinality);
- }
- else {
- var childGroupNodeInfo = childNodeInfo;
- layoutScene(childGroupNodeInfo); // Recursively layout its subscene.
- }
- break;
- case graph_1.NodeType.SERIES:
- if (childNodeInfo.expanded) {
- _.extend(childNodeInfo, layout.PARAMS.nodeSize.series.expanded);
- var childGroupNodeInfo = childNodeInfo;
- layoutScene(childGroupNodeInfo); // Recursively layout its subscene.
- }
- else {
- var childGroupNodeInfo = childNodeInfo;
- var seriesParams = childGroupNodeInfo.node.hasNonControlEdges ?
- layout.PARAMS.nodeSize.series.vertical :
- layout.PARAMS.nodeSize.series.horizontal;
- _.extend(childNodeInfo, seriesParams);
- }
- break;
- default:
- throw Error("Unrecognized node type: " + childNodeInfo.node.type);
- }
- // Compute total width of un-expanded nodes. Width of expanded nodes
- // has already been computed.
- if (!childNodeInfo.expanded) {
- updateTotalWidthOfNode(childNodeInfo);
+ function selectChild(container, tagName, className) {
+ var children = container.node().childNodes;
+ for (var i = 0; i < children.length; i++) {
+ var child = children[i];
+ if (child.tagName === tagName &&
+ (!className || child.classList.contains(className))) {
+ return d3.select(child);
}
- // Layout each child's annotations
- layoutAnnotation(childNodeInfo);
- });
+ }
+ return d3.select(null);
}
+ scene.selectChild = selectChild;
+ ;
/**
- * 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
+ * Select or create a sceneGroup and build/update its nodes and edges.
+ *
+ * Structure Pattern:
+ *
+ * <g class='scene'>
+ * <g class='core'>
+ * <g class='edges'>
+ * ... stuff from tf.graph.scene.edges.build ...
+ * </g>
+ * <g class='nodes'>
+ * ... stuff from tf.graph.scene.nodes.build ...
+ * </g>
+ * </g>
+ * <g class='in-extract'>
+ * <g class='nodes'>
+ * ... stuff from tf.graph.scene.nodes.build ...
+ * </g>
+ * </g>
+ * <g class='out-extract'>
+ * <g class='nodes'>
+ * ... stuff from tf.graph.scene.nodes.build ...
+ * </g>
+ * </g>
+ * </g>
+ *
+ * @param container D3 selection of the parent.
+ * @param renderNode render node of a metanode or series node.
+ * @param sceneElement <tf-graph-scene> polymer element.
+ * @param sceneClass class attribute of the scene (default='scene').
*/
- function dagreLayout(graph, params) {
- _.extend(graph.graph(), {
- nodesep: params.nodeSep,
- ranksep: params.rankSep,
- edgesep: params.edgeSep
- });
- var bridgeNodeNames = [];
- var nonBridgeNodeNames = [];
- // Split out nodes into bridge and non-bridge nodes, and calculate the total
- // width we should use for bridge nodes.
- _.each(graph.nodes(), function (nodeName) {
- var nodeInfo = graph.node(nodeName);
- if (nodeInfo.node.type === graph_1.NodeType.BRIDGE) {
- bridgeNodeNames.push(nodeName);
- }
- else {
- nonBridgeNodeNames.push(nodeName);
+ function buildGroup(container, renderNode, sceneElement, sceneClass) {
+ sceneClass = sceneClass || scene.Class.Scene.GROUP;
+ var isNewSceneGroup = selectChild(container, 'g', sceneClass).empty();
+ var sceneGroup = selectOrCreateChild(container, 'g', sceneClass);
+ // core
+ var coreGroup = selectOrCreateChild(sceneGroup, 'g', scene.Class.Scene.CORE);
+ var coreNodes = _.reduce(renderNode.coreGraph.nodes(), function (nodes, name) {
+ var node = renderNode.coreGraph.node(name);
+ if (!node.excluded) {
+ nodes.push(node);
}
- });
- // If there are no non-bridge nodes, then the graph has zero size.
- if (!nonBridgeNodeNames.length) {
- return {
- width: 0,
- height: 0,
- };
+ return nodes;
+ }, []);
+ if (renderNode.node.type === graph.NodeType.SERIES) {
+ // For series, we want the first item on top, so reverse the array so
+ // the first item in the series becomes last item in the top, and thus
+ // is rendered on the top.
+ coreNodes.reverse();
}
- dagre.layout(graph);
- // Calculate the true bounding box of the graph by iterating over nodes and
- // edges rather than accepting dagre's word for it. In particular, we should
- // ignore the extra-wide bridge nodes and bridge edges, and allow for
- // annotation boxes and labels.
- var minX = Infinity;
- var minY = Infinity;
- var maxX = -Infinity;
- var maxY = -Infinity;
- _.each(nonBridgeNodeNames, function (nodeName) {
- var nodeInfo = graph.node(nodeName);
- var w = 0.5 * nodeInfo.width;
- var x1 = nodeInfo.x - w;
- var x2 = nodeInfo.x + w;
- minX = x1 < minX ? x1 : minX;
- maxX = x2 > maxX ? x2 : maxX;
- // TODO(jimbo): Account for the height of labels above op nodes here.
- var h = 0.5 * nodeInfo.height;
- var y1 = nodeInfo.y - h;
- var y2 = nodeInfo.y + h;
- minY = y1 < minY ? y1 : minY;
- maxY = y2 > maxY ? y2 : maxY;
- });
- _.each(graph.edges(), function (edgeObj) {
- var edgeInfo = graph.edge(edgeObj);
- if (edgeInfo.structural) {
- return; // Skip structural edges from min/max calculations.
- }
- // Since the node size passed to dagre includes the in and out
- // annotations, the endpoints of the edge produced by dagre may not
- // point to the actual node shape (rectangle, ellipse). We correct the
- // end-points by finding the intersection of a line between the
- // next-to-last (next-to-first) point and the destination (source)
- // rectangle.
- var sourceNode = graph.node(edgeInfo.metaedge.v);
- var destNode = graph.node(edgeInfo.metaedge.w);
- // Straight 3-points edges are special case, since they are curved after
- // our default correction. To keep them straight, we remove the mid point
- // and correct the first and the last point to be the center of the
- // source and destination node respectively.
- if (edgeInfo.points.length === 3 && isStraightLine(edgeInfo.points)) {
- if (sourceNode != null) {
- var cxSource = sourceNode.expanded ?
- sourceNode.x : computeCXPositionOfNodeShape(sourceNode);
- edgeInfo.points[0].x = cxSource;
- }
- if (destNode != null) {
- var cxDest = destNode.expanded ?
- destNode.x : computeCXPositionOfNodeShape(destNode);
- edgeInfo.points[2].x = cxDest;
- }
- // Remove the middle point so the edge doesn't curve.
- edgeInfo.points = [edgeInfo.points[0], edgeInfo.points[1]];
- }
- // Correct the destination endpoint of the edge.
- var nextToLastPoint = edgeInfo.points[edgeInfo.points.length - 2];
- // The destination node might be null if this is a bridge edge.
- if (destNode != null) {
- edgeInfo.points[edgeInfo.points.length - 1] =
- intersectPointAndNode(nextToLastPoint, destNode);
- }
- // Correct the source endpoint of the edge.
- var secondPoint = edgeInfo.points[1];
- // The source might be null if this is a bridge edge.
- if (sourceNode != null) {
- edgeInfo.points[0] = intersectPointAndNode(secondPoint, sourceNode);
- }
- _.each(edgeInfo.points, function (point) {
- minX = point.x < minX ? point.x : minX;
- maxX = point.x > maxX ? point.x : maxX;
- minY = point.y < minY ? point.y : minY;
- maxY = point.y > maxY ? point.y : maxY;
- });
- });
- // Shift all nodes and edge points to account for the left-padding amount,
- // and the invisible bridge nodes.
- _.each(graph.nodes(), function (nodeName) {
- var nodeInfo = graph.node(nodeName);
- nodeInfo.x -= minX;
- nodeInfo.y -= minY;
- });
- _.each(graph.edges(), function (edgeObj) {
- _.each(graph.edge(edgeObj).points, function (point) {
- point.x -= minX;
- point.y -= minY;
- });
- });
- return {
- width: maxX - minX,
- height: maxY - minY
- };
- }
- /** Layout a metanode. Only called for an expanded node. */
- function layoutMetanode(renderNodeInfo) {
- // First, copy params specific to meta nodes onto this render info object.
- var params = layout.PARAMS.subscene.meta;
- _.extend(renderNodeInfo, params);
- // Invoke dagre.layout() on the core graph and record the bounding box
- // dimensions.
- _.extend(renderNodeInfo.coreBox, dagreLayout(renderNodeInfo.coreGraph, layout.PARAMS.graph.meta));
- // Calculate the position of nodes in isolatedInExtract relative to the
- // top-left corner of inExtractBox (the bounding box for all inExtract nodes)
- // and calculate the size of the inExtractBox.
- var maxInExtractWidth = _.max(renderNodeInfo.isolatedInExtract, function (renderNode) { return renderNode.width; }).width;
- renderNodeInfo.inExtractBox.width = maxInExtractWidth != null ?
- maxInExtractWidth : 0;
- renderNodeInfo.inExtractBox.height =
- _.reduce(renderNodeInfo.isolatedInExtract, function (height, child, i) {
- var yOffset = i > 0 ? params.extractYOffset : 0;
- // use width/height here to avoid overlaps between extracts
- child.x = 0;
- child.y = height + yOffset + child.height / 2;
- return height + yOffset + child.height;
- }, 0);
- // Calculate the position of nodes in isolatedOutExtract relative to the
- // top-left corner of outExtractBox (the bounding box for all outExtract
- // nodes) and calculate the size of the outExtractBox.
- var maxOutExtractWidth = _.max(renderNodeInfo.isolatedOutExtract, function (renderNode) { return renderNode.width; }).width;
- renderNodeInfo.outExtractBox.width = maxOutExtractWidth != null ?
- maxOutExtractWidth : 0;
- renderNodeInfo.outExtractBox.height =
- _.reduce(renderNodeInfo.isolatedOutExtract, function (height, child, i) {
- var yOffset = i > 0 ? params.extractYOffset : 0;
- // use width/height here to avoid overlaps between extracts
- child.x = 0;
- child.y = height + yOffset + child.height / 2;
- return height + yOffset + child.height;
- }, 0);
- // Compute the total padding between the core graph, in-extract and
- // out-extract boxes.
- var numParts = 0;
- if (renderNodeInfo.isolatedInExtract.length > 0) {
- numParts++;
+ // Create the layer of edges for this scene (paths).
+ scene.edge.buildGroup(coreGroup, renderNode.coreGraph, sceneElement);
+ // Create the layer of nodes for this scene (ellipses, rects etc).
+ scene.node.buildGroup(coreGroup, coreNodes, sceneElement);
+ // In-extract
+ if (renderNode.isolatedInExtract.length > 0) {
+ var inExtractGroup = selectOrCreateChild(sceneGroup, 'g', scene.Class.Scene.INEXTRACT);
+ scene.node.buildGroup(inExtractGroup, renderNode.isolatedInExtract, sceneElement);
}
- if (renderNodeInfo.isolatedOutExtract.length > 0) {
- numParts++;
+ else {
+ selectChild(sceneGroup, 'g', scene.Class.Scene.INEXTRACT).remove();
}
- if (renderNodeInfo.coreGraph.nodeCount() > 0) {
- numParts++;
+ // Out-extract
+ if (renderNode.isolatedOutExtract.length > 0) {
+ var outExtractGroup = selectOrCreateChild(sceneGroup, 'g', scene.Class.Scene.OUTEXTRACT);
+ scene.node.buildGroup(outExtractGroup, renderNode.isolatedOutExtract, sceneElement);
}
- var offset = layout.PARAMS.subscene.meta.extractXOffset;
- var padding = numParts <= 1 ? 0 : (numParts <= 2 ? offset : 2 * offset);
- // Add the in-extract and out-extract width to the core box width.
- renderNodeInfo.coreBox.width += renderNodeInfo.inExtractBox.width +
- renderNodeInfo.outExtractBox.width + padding;
- renderNodeInfo.coreBox.height =
- params.labelHeight +
- Math.max(renderNodeInfo.inExtractBox.height, renderNodeInfo.coreBox.height, renderNodeInfo.outExtractBox.height);
- // Determine the whole metanode's width (from left to right).
- renderNodeInfo.width = renderNodeInfo.coreBox.width +
- params.paddingLeft + params.paddingRight;
- // Determine the whole metanode's height (from top to bottom).
- renderNodeInfo.height =
- renderNodeInfo.paddingTop +
- renderNodeInfo.coreBox.height +
- renderNodeInfo.paddingBottom;
+ else {
+ selectChild(sceneGroup, 'g', scene.Class.Scene.OUTEXTRACT).remove();
+ }
+ position(sceneGroup, renderNode);
+ // Fade in the scene group if it didn't already exist.
+ if (isNewSceneGroup) {
+ sceneGroup.attr('opacity', 0).transition().attr('opacity', 1);
+ }
+ return sceneGroup;
}
+ scene.buildGroup = buildGroup;
+ ;
/**
- * Calculate layout for series node's core graph. Only called for an expanded
- * series.
+ * 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 layoutSeriesNode(node) {
- var graph = node.coreGraph;
- var params = layout.PARAMS.subscene.series;
- _.extend(node, params);
- // Layout the core.
- _.extend(node.coreBox, dagreLayout(node.coreGraph, layout.PARAMS.graph.series));
- _.each(graph.nodes(), function (nodeName) {
- graph.node(nodeName).excluded = false;
+ function position(sceneGroup, renderNode) {
+ // Translate scenes down by the label height so that when showing graphs in
+ // expanded metanodes, the graphs are below the labels. Do not shift them
+ // down for series nodes as series nodes don't have labels inside of their
+ // bounding boxes.
+ var yTranslate = renderNode.node.type === graph.NodeType.SERIES ?
+ 0 : graph.layout.PARAMS.subscene.meta.labelHeight;
+ // core
+ translate(selectChild(sceneGroup, 'g', scene.Class.Scene.CORE), 0, yTranslate);
+ // in-extract
+ var hasInExtract = renderNode.isolatedInExtract.length > 0;
+ var hasOutExtract = renderNode.isolatedOutExtract.length > 0;
+ if (hasInExtract) {
+ var offset = graph.layout.PARAMS.subscene.meta.extractXOffset;
+ var inExtractX = renderNode.coreBox.width -
+ renderNode.inExtractBox.width / 2 - renderNode.outExtractBox.width -
+ (hasOutExtract ? offset : 0);
+ translate(selectChild(sceneGroup, 'g', scene.Class.Scene.INEXTRACT), inExtractX, yTranslate);
+ }
+ // out-extract
+ if (hasOutExtract) {
+ var outExtractX = renderNode.coreBox.width -
+ renderNode.outExtractBox.width / 2;
+ translate(selectChild(sceneGroup, 'g', scene.Class.Scene.OUTEXTRACT), outExtractX, yTranslate);
+ }
+ }
+ ;
+ /** Adds a click listener to a group that fires a graph-select event */
+ function addGraphClickListener(graphGroup, sceneElement) {
+ d3.select(graphGroup).on('click', function () {
+ sceneElement.fire('graph-select');
});
- // 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;
}
+ scene.addGraphClickListener = addGraphClickListener;
+ ;
+ /** Helper for adding transform: translate(x0, y0) */
+ function translate(selection, x0, y0) {
+ // If it is already placed on the screen, make it a transition.
+ if (selection.attr('transform') != null) {
+ selection = selection.transition('position');
+ }
+ selection.attr('transform', 'translate(' + x0 + ',' + y0 + ')');
+ }
+ scene.translate = translate;
+ ;
/**
- * Calculate layout for annotations of a given node.
- * This will modify positions of the given node and its annotations.
- *
- * @see tf.graph.render.Node and tf.graph.render.Annotation
- * for description of each property of each render node.
- *
+ * Helper for setting position of a svg rect
+ * @param rect rect to set position of.
+ * @param cx Center x.
+ * @param cy Center x.
+ * @param width Width to set.
+ * @param height Height to set.
*/
- function layoutAnnotation(renderNodeInfo) {
- // If the render node is an expanded metanode, then its annotations will not
- // be visible and we should skip the annotation calculations.
- if (renderNodeInfo.expanded) {
- return;
- }
- var inAnnotations = renderNodeInfo.inAnnotations.list;
- var outAnnotations = renderNodeInfo.outAnnotations.list;
- // Calculate size for in-annotations
- _.each(inAnnotations, function (a) { return sizeAnnotation(a); });
- // Calculate size for out-annotations
- _.each(outAnnotations, function (a) { return sizeAnnotation(a); });
- var params = layout.PARAMS.annotations;
- // Calculate annotation node position (a.dx, a.dy)
- // and total height for in-annotations
- // After this chunk of code:
- // inboxHeight = sum of annotation heights+ (annotation.length - 1 * yOffset)
- var inboxHeight = _.reduce(inAnnotations, function (height, a, i) {
- var yOffset = i > 0 ? params.yOffset : 0;
- a.dx = -(renderNodeInfo.coreBox.width + a.width) / 2 - params.xOffset;
- a.dy = height + yOffset + a.height / 2;
- return height + yOffset + a.height;
- }, 0);
- _.each(inAnnotations, function (a) {
- a.dy -= inboxHeight / 2;
- a.labelOffset = params.labelOffset;
- });
- // Calculate annotation node position (a.dx, a.dy)
- // and total height for out-annotations
- // After this chunk of code:
- // outboxHeight = sum of annotation heights +
- // (annotation.length - 1 * yOffset)
- var outboxHeight = _.reduce(outAnnotations, function (height, a, i) {
- var yOffset = i > 0 ? params.yOffset : 0;
- a.dx = (renderNodeInfo.coreBox.width + a.width) / 2 + params.xOffset;
- a.dy = height + yOffset + a.height / 2;
- return height + yOffset + a.height;
- }, 0);
- _.each(outAnnotations, function (a) {
- // adjust by (half of ) the total height
- // so dy is relative to the host node's center.
- a.dy -= outboxHeight / 2;
- a.labelOffset = params.labelOffset;
- });
- // Creating scales for touch point between the in-annotation edges
- // and their hosts.
- var inTouchHeight = Math.min(renderNodeInfo.height / 2 - renderNodeInfo.radius, inboxHeight / 2);
- inTouchHeight = inTouchHeight < 0 ? 0 : inTouchHeight;
- var inY = d3.scale.linear()
- .domain([0, inAnnotations.length - 1])
- .range([-inTouchHeight, inTouchHeight]);
- // Calculate annotation edge position
- _.each(inAnnotations, function (a, i) {
- a.points = [
- // The annotation node end
- {
- dx: a.dx + a.width / 2,
- dy: a.dy
- },
- // The host node end
- {
- dx: -renderNodeInfo.coreBox.width / 2,
- // only use scale if there are more than one,
- // otherwise center it vertically
- dy: inAnnotations.length > 1 ? inY(i) : 0
- }
- ];
- });
- // Creating scales for touch point between the out-annotation edges
- // and their hosts.
- var outTouchHeight = Math.min(renderNodeInfo.height / 2 - renderNodeInfo.radius, outboxHeight / 2);
- outTouchHeight = outTouchHeight < 0 ? 0 : outTouchHeight;
- var outY = d3.scale.linear()
- .domain([0, outAnnotations.length - 1])
- .range([-outTouchHeight, outTouchHeight]);
- _.each(outAnnotations, function (a, i) {
- // Add point from the border of the annotation node
- a.points = [
- // The host node end
- {
- dx: renderNodeInfo.coreBox.width / 2,
- // only use scale if there are more than one,
- // otherwise center it vertically
- dy: outAnnotations.length > 1 ? outY(i) : 0
- },
- // The annotation node end
- {
- dx: a.dx - a.width / 2,
- dy: a.dy
- }
- ];
+ function positionRect(rect, cx, cy, width, height) {
+ rect.transition().attr({
+ x: cx - width / 2,
+ y: cy - height / 2,
+ width: width,
+ height: height
});
- renderNodeInfo.height =
- Math.max(renderNodeInfo.height, inboxHeight, outboxHeight);
}
+ scene.positionRect = positionRect;
+ ;
/**
- * Set size of an annotation node.
+ * Helper for setting position of a svg expand/collapse button
+ * @param button container group
+ * @param renderNode the render node of the group node to position
+ * the button on.
*/
- function sizeAnnotation(a) {
- switch (a.annotationType) {
- case graph_1.render.AnnotationType.CONSTANT:
- _.extend(a, layout.PARAMS.constant.size);
- break;
- case graph_1.render.AnnotationType.SHORTCUT:
- if (a.node.type === graph_1.NodeType.OP) {
- _.extend(a, layout.PARAMS.shortcutSize.op);
- }
- else if (a.node.type === graph_1.NodeType.META) {
- _.extend(a, layout.PARAMS.shortcutSize.meta);
- }
- else if (a.node.type === graph_1.NodeType.SERIES) {
- _.extend(a, layout.PARAMS.shortcutSize.series);
- }
- else {
- throw Error("Invalid node type: " + a.node.type);
- }
- break;
- case graph_1.render.AnnotationType.SUMMARY:
- _.extend(a, layout.PARAMS.constant.size);
- break;
+ function positionButton(button, renderNode) {
+ var cx = graph.layout.computeCXPositionOfNodeShape(renderNode);
+ // Position the button in the top-right corner of the group node,
+ // with space given the draw the button inside of the corner.
+ var width = renderNode.expanded ?
+ renderNode.width : renderNode.coreBox.width;
+ var height = renderNode.expanded ?
+ renderNode.height : renderNode.coreBox.height;
+ var x = cx + width / 2 - 6;
+ var y = renderNode.y - height / 2 + 6;
+ // For unexpanded series nodes, the button has special placement due
+ // to the unique visuals of this group node.
+ if (renderNode.node.type === graph.NodeType.SERIES && !renderNode.expanded) {
+ x += 10;
+ y -= 2;
}
+ var translateStr = 'translate(' + x + ',' + y + ')';
+ button.selectAll('path').transition().attr('transform', translateStr);
+ button.select('circle').transition().attr({ cx: x, cy: y, r: graph.layout.PARAMS.nodeSize.meta.expandButtonRadius });
}
+ scene.positionButton = positionButton;
+ ;
/**
- * Determines the center position of the node's shape. The position depends
- * on if the node has in and out-annotations.
+ * Helper for setting position of a svg ellipse
+ * @param ellipse ellipse to set position of.
+ * @param cx Center x.
+ * @param cy Center x.
+ * @param width Width to set.
+ * @param height Height to set.
*/
- function computeCXPositionOfNodeShape(renderInfo) {
- if (renderInfo.expanded) {
- return renderInfo.x;
- }
- var dx = renderInfo.inAnnotations.list.length ? renderInfo.inboxWidth : 0;
- return renderInfo.x - renderInfo.width / 2 + dx +
- renderInfo.coreBox.width / 2;
+ function positionEllipse(ellipse, cx, cy, width, height) {
+ ellipse.transition().attr({
+ cx: cx,
+ cy: cy,
+ rx: width / 2,
+ ry: height / 2
+ });
}
- layout.computeCXPositionOfNodeShape = computeCXPositionOfNodeShape;
- /** Returns the angle (in degrees) between two points. */
- function angleBetweenTwoPoints(a, b) {
- var dx = b.x - a.x;
- var dy = b.y - a.y;
- return 180 * Math.atan(dy / dx) / Math.PI;
+ scene.positionEllipse = positionEllipse;
+ ;
+ })(scene = graph.scene || (graph.scene = {}));
+ })(graph = tf.graph || (tf.graph = {}));
+})(tf || (tf = {})); // close module
+</script>
+<script>/* Copyright 2015 Google Inc. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the 'License');
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an 'AS IS' BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+var tf;
+(function (tf) {
+ var graph;
+ (function (graph_1) {
+ var template;
+ (function (template) {
+ /**
+ * Detect repeating patterns of subgraphs.
+ * Assign templateId to each subgraph if it belongs to a template.
+ * Returns clusters of similar subgraphs .
+ *
+ * @param graph
+ * @param verifyTemplate whether to run the template verification algorithm
+ * @return a dict (template id => Array of node names)
+ */
+ function detect(h, verifyTemplate) {
+ // In any particular subgraph, there are either
+ // - leaf nodes (which do not have subgraph)
+ // - metanode nodes - some of them have only one member (singular metanode)
+ // and some have multiple members (non-singular metanode)
+ // First, generate a nearest neighbor hash of metanode nodes.
+ var nnGroups = clusterSimilarSubgraphs(h);
+ // For each metanode, compare its subgraph (starting from shallower groups)
+ // and assign template id.
+ var templates = groupTemplateAndAssignId(nnGroups, verifyTemplate);
+ // Sort the templates by minimum level in the graph at which they appear,
+ // as this leads to optimal setting of the colors of each template for
+ // maximum differentiation.
+ return _(templates).pairs()
+ .sortBy(function (pair) {
+ return pair[1].level;
+ })
+ .map(function (pair) {
+ return [pair[0], pair[1].nodes];
+ })
+ .object().value();
}
+ template.detect = detect;
+ ;
/**
- * Returns if a line going through the specified points is a straight line.
+ * @return Unique string for a metanode based on depth, |V|, |E| and
+ * op type histogram.
*/
- function isStraightLine(points) {
- var angle = angleBetweenTwoPoints(points[0], points[1]);
- for (var i = 1; i < points.length - 1; i++) {
- var newAngle = angleBetweenTwoPoints(points[i], points[i + 1]);
- // Have a tolerance of 1 degree.
- if (Math.abs(newAngle - angle) > 1) {
+ function getSignature(metanode) {
+ // depth=<number> |V|=<number> |E|=<number>
+ var props = _.map({
+ 'depth': metanode.depth,
+ '|V|': metanode.metagraph.nodes().length,
+ '|E|': metanode.metagraph.edges().length
+ }, function (v, k) { return k + '=' + v; })
+ .join(' ');
+ // optype1=count1,optype2=count2
+ var ops = _.map(metanode.opHistogram, function (count, op) {
+ return op + '=' + count;
+ }).join(',');
+ return props + ' [ops] ' + ops;
+ }
+ /**
+ * Generate a nearest neighbor hash of metanodes
+ * based on depth, |V|, |E|, and opHistogram of their subgraph
+ * (excluding leaf nodes and singular metanodes).
+ * @param graph The graph
+ * @return Array of pairs of [signature,
+ * Object with min level of the template and an Array of tf.graph.Group]
+ * sort by ascending order of minimum depth at which metanode appears.
+ */
+ function clusterSimilarSubgraphs(h) {
+ /** a dict from metanode.signature() => Array of tf.graph.Groups */
+ var hashDict = _(h.getNodeMap()).reduce(function (hash, node, name) {
+ if (node.type !== graph_1.NodeType.META) {
+ return hash;
+ }
+ var levelOfMetaNode = name.split('/').length - 1;
+ var signature = getSignature(node);
+ var templateInfo = hash[signature] ||
+ { nodes: [], level: levelOfMetaNode };
+ hash[signature] = templateInfo;
+ templateInfo.nodes.push(node);
+ if (templateInfo.level > levelOfMetaNode) {
+ templateInfo.level = levelOfMetaNode;
+ }
+ return hash;
+ }, {});
+ return _(hashDict).pairs()
+ .filter(function (pair) {
+ return pair[1].nodes.length > 1;
+ })
+ .sortBy(function (pair) {
+ // sort by depth
+ // (all members in the same nnGroup has equal depth)
+ return pair[1].nodes[0].depth;
+ })
+ .value();
+ }
+ function groupTemplateAndAssignId(nnGroups, verifyTemplate) {
+ // For each metanode, compare its subgraph (starting from shallower groups)
+ // and assign template id.
+ var result = {};
+ return _.reduce(nnGroups, function (templates, nnGroupPair) {
+ var signature = nnGroupPair[0], nnGroup = nnGroupPair[1].nodes, clusters = [];
+ nnGroup.forEach(function (metanode) {
+ // check with each existing cluster
+ for (var i = 0; i < clusters.length; i++) {
+ var similar = !verifyTemplate ||
+ isSimilarSubgraph(clusters[i].metanode.metagraph, metanode.metagraph);
+ // if similar, just add this metanode to the cluster
+ if (similar) {
+ // get template from the first one
+ metanode.templateId = clusters[i].metanode.templateId;
+ clusters[i].members.push(metanode.name);
+ return;
+ }
+ }
+ // otherwise create a new cluster with id 'signature [count] '
+ metanode.templateId = signature + '[' + clusters.length + ']';
+ clusters.push({
+ metanode: metanode,
+ members: [metanode.name]
+ });
+ });
+ clusters.forEach(function (c) {
+ templates[c.metanode.templateId] = {
+ level: nnGroupPair[1].level,
+ nodes: c.members
+ };
+ });
+ return templates;
+ }, result);
+ }
+ function sortNodes(names, graph, prefix) {
+ return _.sortByAll(names, function (name) {
+ var node = graph.node(name);
+ return node.op;
+ }, function (name) {
+ var node = graph.node(name);
+ return node.templateId;
+ }, function (name) {
+ return graph.neighbors(name).length;
+ }, function (name) {
+ return graph.predecessors(name).length;
+ }, function (name) {
+ return graph.successors(name).length;
+ }, function (name) {
+ return name.substr(prefix.length);
+ });
+ }
+ function isSimilarSubgraph(g1, g2) {
+ if (!tf.graph.hasSimilarDegreeSequence(g1, g2)) {
+ return false;
+ }
+ // if we want to skip, just return true here.
+ // return true;
+ // Verify sequence by running DFS
+ var g1prefix = g1.graph().name;
+ var g2prefix = g2.graph().name;
+ var visited1 = {};
+ var visited2 = {};
+ var stack = [];
+ /**
+ * push sources or successors into the stack
+ * if the visiting pattern has been similar.
+ */
+ function stackPushIfNotDifferent(n1, n2) {
+ var sub1 = n1.substr(g1prefix.length), sub2 = n2.substr(g2prefix.length);
+ /* tslint:disable */
+ if (visited1[sub1] ^ visited2[sub1]) {
+ console.warn('different visit pattern', '[' + g1prefix + ']', sub1, '[' + g2prefix + ']', sub2);
+ return true;
+ }
+ /* tslint:enable */
+ if (!visited1[sub1]) {
+ visited1[sub1] = visited2[sub2] = true;
+ stack.push({ n1: n1, n2: n2 });
+ }
+ return false;
+ }
+ // check if have same # of sources then sort and push
+ var sources1 = g1.sources();
+ var sources2 = g2.sources();
+ if (sources1.length !== sources2.length) {
+ /* tslint:disable */
+ console.log('different source length');
+ /* tslint:enable */
+ return false;
+ }
+ sources1 = sortNodes(sources1, g1, g1prefix);
+ sources2 = sortNodes(sources2, g2, g2prefix);
+ for (var i = 0; i < sources1.length; i++) {
+ var different = stackPushIfNotDifferent(sources1[i], sources2[i]);
+ if (different) {
return false;
}
- angle = newAngle;
+ }
+ while (stack.length > 0) {
+ var cur = stack.pop();
+ // check node
+ var similar = isSimilarNode(g1.node(cur.n1), g2.node(cur.n2));
+ if (!similar) {
+ return false;
+ }
+ // check if have same # of successors then sort and push
+ var succ1 = g1.successors(cur.n1), succ2 = g2.successors(cur.n2);
+ if (succ1.length !== succ2.length) {
+ /* tslint:disable */
+ console.log('# of successors mismatch', succ1, succ2);
+ /* tslint:enable */
+ return false;
+ }
+ succ1 = sortNodes(succ1, g1, g1prefix);
+ succ2 = sortNodes(succ2, g2, g2prefix);
+ for (var j = 0; j < succ1.length; j++) {
+ var different = stackPushIfNotDifferent(succ1[j], succ2[j]);
+ if (different) {
+ return false;
+ }
+ }
}
return true;
}
/**
- * Returns the intersection of a line between the provided point
- * and the provided rectangle.
+ * Returns if two nodes have identical structure.
*/
- function intersectPointAndNode(point, node) {
- // cx and cy are the center of the rectangle.
- var cx = node.expanded ?
- node.x : computeCXPositionOfNodeShape(node);
- var cy = node.y;
- // Calculate the slope
- var dx = point.x - cx;
- var dy = point.y - cy;
- var w = node.expanded ? node.width : node.coreBox.width;
- var h = node.expanded ? node.height : node.coreBox.height;
- var deltaX, deltaY;
- if (Math.abs(dy) * w / 2 > Math.abs(dx) * h / 2) {
- // The intersection is above or below the rectangle.
- if (dy < 0) {
- h = -h;
- }
- deltaX = dy === 0 ? 0 : h / 2 * dx / dy;
- deltaY = h / 2;
+ function isSimilarNode(n1, n2) {
+ if (n1.type === graph_1.NodeType.META) {
+ // compare metanode
+ var metanode1 = n1;
+ var metanode2 = n2;
+ return metanode1.templateId && metanode2.templateId &&
+ metanode1.templateId === metanode2.templateId;
}
- else {
- // The intersection is left or right of the rectangle.
- if (dx < 0) {
- w = -w;
- }
- deltaX = w / 2;
- deltaY = dx === 0 ? 0 : w / 2 * dy / dx;
+ else if (n1.type === graph_1.NodeType.OP && n2.type === graph_1.NodeType.OP) {
+ // compare leaf node
+ return n1.op === n2.op;
}
- return { x: cx + deltaX, y: cy + deltaY };
+ else if (n1.type === graph_1.NodeType.SERIES && n2.type === graph_1.NodeType.SERIES) {
+ // compare series node sizes and operations
+ // (only need to check one op as all op nodes are identical in series)
+ var sn1 = n1;
+ var sn2 = n2;
+ var seriesnode1Count = sn1.metagraph.nodeCount();
+ return (seriesnode1Count === sn2.metagraph.nodeCount() &&
+ (seriesnode1Count === 0 ||
+ (sn1.metagraph.node(sn1.metagraph.nodes()[0]).op ===
+ sn2.metagraph.node(sn2.metagraph.nodes()[0]).op)));
+ }
+ return false;
}
- })(layout = graph_1.layout || (graph_1.layout = {}));
+ })(template = graph_1.template || (graph_1.template = {}));
})(graph = tf.graph || (tf.graph = {}));
-})(tf || (tf = {})); // close module
+})(tf || (tf = {}));
</script>
<script>/* Copyright 2015 Google Inc. All Rights Reserved.
-Licensed under the Apache License, Version 2.0 (the "License");
+Licensed under the Apache License, Version 2.0 (the 'License');
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
+distributed under the License is distributed on an 'AS IS' BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
+/**
+ * @fileoverview Utility functions for the tensorflow graph visualizer.
+ */
var tf;
(function (tf) {
- /**
- * Mapping from color palette name to color palette, which contains
- * exact colors for multiple states of a single color palette.
- */
- tf.COLORS = [
- {
- "name": "Google Blue",
- "color": "#4184f3",
- "active": "#3a53c5",
- "disabled": "#cad8fc"
- },
- {
- "name": "Google Red",
- "color": "#db4437",
- "active": "#8f2a0c",
- "disabled": "#e8c6c1"
- },
- {
- "name": "Google Yellow",
- "color": "#f4b400",
- "active": "#db9200",
- "disabled": "#f7e8b0"
- },
- {
- "name": "Google Green",
- "color": "#0f9d58",
- "active": "#488046",
- "disabled": "#c2e1cc"
- },
- {
- "name": "Purple",
- "color": "#aa46bb",
- "active": "#5c1398",
- "disabled": "#d7bce6"
- },
- {
- "name": "Teal",
- "color": "#00abc0",
- "active": "#47828e",
- "disabled": "#c2eaf2"
- },
- {
- "name": "Deep Orange",
- "color": "#ff6f42",
- "active": "#ca4a06",
- "disabled": "#f2cbba"
- },
- {
- "name": "Lime",
- "color": "#9d9c23",
- "active": "#7f771d",
- "disabled": "#f1f4c2"
- },
- {
- "name": "Indigo",
- "color": "#5b6abf",
- "active": "#3e47a9",
- "disabled": "#c5c8e8"
- },
- {
- "name": "Pink",
- "color": "#ef6191",
- "active": "#ca1c60",
- "disabled": "#e9b9ce"
- },
- {
- "name": "Deep Teal",
- "color": "#00786a",
- "active": "#2b4f43",
- "disabled": "#bededa"
- },
- {
- "name": "Deep Pink",
- "color": "#c1175a",
- "active": "#75084f",
- "disabled": "#de8cae"
- },
- {
- "name": "Gray",
- "color": "#9E9E9E",
- "active": "#424242",
- "disabled": "F5F5F5" // 100
- }
- ]
- .reduce(function (m, c) {
- m[c.name] = c;
- return m;
- }, {});
- /**
- * Mapping from op category to color palette name
- * e.g., OP_GROUP_COLORS["state_ops"] = "Google Blue";
- */
- tf.OP_GROUP_COLORS = [
- {
- color: "Google Red",
- groups: [
- "gen_legacy_ops", "legacy_ops", "legacy_flogs_input",
- "legacy_image_input", "legacy_input_example_input",
- "legacy_sequence_input", "legacy_seti_input_input"
- ]
- },
- { color: "Deep Orange", groups: ["constant_ops"] },
- { color: "Indigo", groups: ["state_ops"] },
- { color: "Purple", groups: ["nn_ops", "nn"] },
- { color: "Google Green", groups: ["math_ops"] },
- { color: "Lime", groups: ["array_ops"] },
- { color: "Teal", groups: ["control_flow_ops", "data_flow_ops"] },
- { color: "Pink", groups: ["summary_ops"] },
- { color: "Deep Pink", groups: ["io_ops"] }
- ]
- .reduce(function (m, c) {
- c.groups.forEach(function (group) { m[group] = c.color; });
- return m;
- }, {});
+ var graph;
+ (function (graph) {
+ var util;
+ (function (util) {
+ /**
+ * Recommended delay (ms) when running an expensive task asynchronously
+ * that gives enough time for the progress bar to update its UI.
+ */
+ var ASYNC_TASK_DELAY = 20;
+ function time(msg, task) {
+ var start = Date.now();
+ var result = task();
+ /* tslint:disable */
+ console.log(msg, ':', Date.now() - start, 'ms');
+ /* tslint:enable */
+ return result;
+ }
+ util.time = time;
+ /**
+ * Creates a tracker that sets the progress property of the
+ * provided polymer component. The provided component must have
+ * a property called 'progress' that is not read-only. The progress
+ * property is an object with a numerical 'value' property and a
+ * string 'msg' property.
+ */
+ function getTracker(polymerComponent) {
+ return {
+ setMessage: function (msg) {
+ polymerComponent.set('progress', { value: polymerComponent.progress.value, msg: msg });
+ },
+ updateProgress: function (value) {
+ polymerComponent.set('progress', {
+ value: polymerComponent.progress.value + value,
+ msg: polymerComponent.progress.msg
+ });
+ },
+ reportError: function (msg, err) {
+ // Log the stack trace in the console.
+ console.error(err.stack);
+ // And send a user-friendly message to the UI.
+ polymerComponent.set('progress', { value: polymerComponent.progress.value, msg: msg, error: true });
+ },
+ };
+ }
+ util.getTracker = getTracker;
+ /**
+ * Creates a tracker for a subtask given the parent tracker, the total
+ * progress
+ * of the subtask and the subtask message. The parent task should pass a
+ * subtracker to its subtasks. The subtask reports its own progress which
+ * becames relative to the main task.
+ */
+ function getSubtaskTracker(parentTracker, impactOnTotalProgress, subtaskMsg) {
+ return {
+ setMessage: function (progressMsg) {
+ // The parent should show a concatenation of its message along with
+ // its subtask tracker message.
+ parentTracker.setMessage(subtaskMsg + ': ' + progressMsg);
+ },
+ updateProgress: function (incrementValue) {
+ // Update the parent progress relative to the child progress.
+ // For example, if the sub-task progresses by 30%, and the impact on the
+ // total progress is 50%, then the task progresses by 30% * 50% = 15%.
+ parentTracker.updateProgress(incrementValue * impactOnTotalProgress / 100);
+ },
+ reportError: function (msg, err) {
+ // The parent should show a concatenation of its message along with
+ // its subtask error message.
+ parentTracker.reportError(subtaskMsg + ': ' + msg, err);
+ }
+ };
+ }
+ util.getSubtaskTracker = getSubtaskTracker;
+ /**
+ * Runs an expensive task and return the result.
+ */
+ function runTask(msg, incProgressValue, task, tracker) {
+ // Update the progress message to say the current running task.
+ tracker.setMessage(msg);
+ // Run the expensive task with a delay that gives enough time for the
+ // UI to update.
+ try {
+ var result = tf.graph.util.time(msg, task);
+ // Update the progress value.
+ tracker.updateProgress(incProgressValue);
+ // Return the result to be used by other tasks.
+ return result;
+ }
+ catch (e) {
+ // Errors that happen inside asynchronous tasks are
+ // reported to the tracker using a user-friendly message.
+ tracker.reportError('Failed ' + msg, e);
+ }
+ }
+ util.runTask = runTask;
+ /**
+ * Runs an expensive task asynchronously and returns a promise of the result.
+ */
+ function runAsyncTask(msg, incProgressValue, task, tracker) {
+ return new Promise(function (resolve, reject) {
+ // Update the progress message to say the current running task.
+ tracker.setMessage(msg);
+ // Run the expensive task with a delay that gives enough time for the
+ // UI to update.
+ setTimeout(function () {
+ try {
+ var result = tf.graph.util.time(msg, task);
+ // Update the progress value.
+ tracker.updateProgress(incProgressValue);
+ // Return the result to be used by other tasks.
+ resolve(result);
+ }
+ catch (e) {
+ // Errors that happen inside asynchronous tasks are
+ // reported to the tracker using a user-friendly message.
+ tracker.reportError('Failed ' + msg, e);
+ }
+ }, ASYNC_TASK_DELAY);
+ });
+ }
+ util.runAsyncTask = runAsyncTask;
+ /**
+ * Returns a query selector with escaped special characters that are not
+ * allowed in a query selector.
+ */
+ function escapeQuerySelector(querySelector) {
+ return querySelector.replace(/([:.\[\],/\\\(\)])/g, '\\$1');
+ }
+ util.escapeQuerySelector = escapeQuerySelector;
+ })(util = graph.util || (graph.util = {}));
+ })(graph = tf.graph || (tf.graph = {}));
})(tf || (tf = {}));
</script>
<script>/* Copyright 2015 Google Inc. All Rights Reserved.
-Licensed under the Apache License, Version 2.0 (the "License");
+Licensed under the Apache License, Version 2.0 (the 'License');
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
+distributed under the License is distributed on an 'AS IS' BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -8984,26 +9286,26 @@ var tf;
var $minimap = d3.select(minimap);
// The minimap will have 2 main components: the canvas showing the content
// and an svg showing a rectangle of the currently zoomed/panned viewpoint.
- var $minimapSvg = $minimap.select("svg");
+ var $minimapSvg = $minimap.select('svg');
// Make the viewpoint rectangle draggable.
- var $viewpoint = $minimapSvg.select("rect");
+ var $viewpoint = $minimapSvg.select('rect');
var dragmove = function (d) {
_this.viewpointCoord.x = d3.event.x;
_this.viewpointCoord.y = d3.event.y;
_this.updateViewpoint();
};
this.viewpointCoord = { x: 0, y: 0 };
- var drag = d3.behavior.drag().origin(Object).on("drag", dragmove);
+ var drag = d3.behavior.drag().origin(Object).on('drag', dragmove);
$viewpoint.datum(this.viewpointCoord).call(drag);
// Make the minimap clickable.
- $minimapSvg.on("click", function () {
+ $minimapSvg.on('click', function () {
if (d3.event.defaultPrevented) {
// This click was part of a drag event, so suppress it.
return;
}
// Update the coordinates of the viewpoint.
- var width = Number($viewpoint.attr("width"));
- var height = Number($viewpoint.attr("height"));
+ var width = Number($viewpoint.attr('width'));
+ var height = Number($viewpoint.attr('height'));
var clickCoords = d3.mouse($minimapSvg.node());
_this.viewpointCoord.x = clickCoords[0] - width / 2;
_this.viewpointCoord.y = clickCoords[1] - height / 2;
@@ -9012,12 +9314,13 @@ var tf;
this.viewpoint = $viewpoint.node();
this.minimapSvg = $minimapSvg.node();
this.minimap = minimap;
- this.canvas = $minimap.select("canvas.first").node();
+ this.canvas = $minimap.select('canvas.first').node();
this.canvasBuffer =
- $minimap.select("canvas.second").node();
+ $minimap.select('canvas.second').node();
this.downloadCanvas =
- $minimap.select("canvas.download").node();
- d3.select(this.downloadCanvas).style("display", "none");
+ $minimap.select('canvas.download').node();
+ d3.select(this.downloadCanvas).style('display', 'none');
+ this.update();
}
/**
* Updates the position and the size of the viewpoint rectangle.
@@ -9026,8 +9329,8 @@ var tf;
Minimap.prototype.updateViewpoint = function () {
// Update the coordinates of the viewpoint rectangle.
d3.select(this.viewpoint)
- .attr("x", this.viewpointCoord.x)
- .attr("y", this.viewpointCoord.y);
+ .attr('x', this.viewpointCoord.x)
+ .attr('y', this.viewpointCoord.y);
// Update the translation vector of the main svg to reflect the
// new viewpoint.
var mainX = -this.viewpointCoord.x * this.scaleMain / this.scaleMinimap;
@@ -9041,20 +9344,30 @@ var tf;
*/
Minimap.prototype.update = function () {
var _this = this;
- // The origin hasn't rendered yet. Ignore making an update.
- if (this.zoomG.childElementCount === 0) {
+ var sceneSize = null;
+ try {
+ // Get the size of the entire scene.
+ sceneSize = this.zoomG.getBBox();
+ if (sceneSize.width === 0) {
+ // There is no scene anymore. We have been detached from the dom.
+ return;
+ }
+ }
+ catch (e) {
+ // Firefox produced NS_ERROR_FAILURE if we have been
+ // detached from the dom.
return;
}
- var $download = d3.select("#graphdownload");
+ var $download = d3.select('#graphdownload');
this.download = $download.node();
- $download.on("click", function (d) {
- _this.download.href = _this.downloadCanvas.toDataURL("image/png");
+ $download.on('click', function (d) {
+ _this.download.href = _this.downloadCanvas.toDataURL('image/png');
});
var $svg = d3.select(this.svg);
// Read all the style rules in the document and embed them into the svg.
// The svg needs to be self contained, i.e. all the style rules need to be
// embedded so the canvas output matches the origin.
- var stylesText = "";
+ var stylesText = '';
for (var k = 0; k < document.styleSheets.length; k++) {
try {
var cssRules = document.styleSheets[k].cssRules ||
@@ -9064,26 +9377,24 @@ var tf;
}
for (var i = 0; i < cssRules.length; i++) {
// Remove tf-* selectors from the styles.
- stylesText += cssRules[i].cssText.replace(/ ?tf-[\w-]+ ?/g, "") +
- "\n";
+ stylesText +=
+ cssRules[i].cssText.replace(/ ?tf-[\w-]+ ?/g, '') + '\n';
}
}
catch (e) {
- if (e.name !== "SecurityError") {
+ if (e.name !== 'SecurityError') {
throw e;
}
}
}
// Temporarily add the css rules to the main svg.
- var svgStyle = $svg.append("style");
+ var svgStyle = $svg.append('style');
svgStyle.text(stylesText);
// Temporarily remove the zoom/pan transform from the main svg since we
// want the minimap to show a zoomed-out and centered view.
var $zoomG = d3.select(this.zoomG);
- var zoomTransform = $zoomG.attr("transform");
- $zoomG.attr("transform", null);
- // Get the size of the entire scene.
- var sceneSize = this.zoomG.getBBox();
+ var zoomTransform = $zoomG.attr('transform');
+ $zoomG.attr('transform', null);
// Since we add padding, account for that here.
sceneSize.height += this.labelPadding * 2;
sceneSize.width += this.labelPadding * 2;
@@ -9128,26 +9439,26 @@ var tf;
width: null,
height: null
});
- $zoomG.attr("transform", zoomTransform);
+ $zoomG.attr('transform', zoomTransform);
var image = new Image();
image.onload = function () {
// Draw the svg content onto the buffer canvas.
- var context = _this.canvasBuffer.getContext("2d");
+ var context = _this.canvasBuffer.getContext('2d');
context.clearRect(0, 0, _this.canvasBuffer.width, _this.canvasBuffer.height);
context.drawImage(image, 0, 0, _this.minimapSize.width, _this.minimapSize.height);
requestAnimationFrame(function () {
// Hide the old canvas and show the new buffer canvas.
- d3.select(_this.canvasBuffer).style("display", null);
- d3.select(_this.canvas).style("display", "none");
+ d3.select(_this.canvasBuffer).style('display', null);
+ d3.select(_this.canvas).style('display', 'none');
// Swap the two canvases.
_a = [_this.canvasBuffer, _this.canvas], _this.canvas = _a[0], _this.canvasBuffer = _a[1];
var _a;
});
- var downloadContext = _this.downloadCanvas.getContext("2d");
+ var downloadContext = _this.downloadCanvas.getContext('2d');
downloadContext.clearRect(0, 0, _this.downloadCanvas.width, _this.downloadCanvas.height);
downloadContext.drawImage(image, 0, 0, _this.downloadCanvas.width, _this.downloadCanvas.height);
};
- var blob = new Blob([svgXml], { type: "image/svg+xml;charset=utf-8" });
+ var blob = new Blob([svgXml], { type: 'image/svg+xml;charset=utf-8' });
image.src = URL.createObjectURL(blob);
};
/**
@@ -9159,6 +9470,10 @@ var tf;
* @param scale The scaling factor, or none to use the last used one.
*/
Minimap.prototype.zoom = function (translate, scale) {
+ if (this.scaleMinimap == null) {
+ // Scene is not ready yet.
+ return;
+ }
// Update the new translate and scale params, only if specified.
this.translate = translate || this.translate;
this.scaleMain = scale || this.scaleMain;
@@ -9189,10 +9504,10 @@ var tf;
Math.min(Math.max(0, y), mapHeight);
var fracIntersect = (w * h) / (mapWidth * mapHeight);
if (fracIntersect < FRAC_VIEWPOINT_AREA) {
- this.minimap.classList.remove("hidden");
+ this.minimap.classList.remove('hidden');
}
else {
- this.minimap.classList.add("hidden");
+ this.minimap.classList.add('hidden');
}
};
return Minimap;
@@ -9822,12 +10137,12 @@ Polymer({
/** Main method for building the scene */
_build: function(renderHierarchy) {
this.templateIndex = renderHierarchy.hierarchy.getTemplateIndex();
- tf.time('tf-graph-scene (layout):', function() {
+ tf.graph.util.time('tf-graph-scene (layout):', function() {
// layout the scene for this meta / series node
tf.graph.layout.layoutScene(renderHierarchy.root, this);
}.bind(this));
- tf.time('tf-graph-scene (build scene):', function() {
+ tf.graph.util.time('tf-graph-scene (build scene):', function() {
tf.graph.scene.buildGroup(d3.select(this.$.root), renderHierarchy.root, this);
tf.graph.scene.addGraphClickListener(this.$.svg, this);
}.bind(this));
@@ -10165,7 +10480,7 @@ Polymer({
}
},
_buildRenderHierarchy: function(graphHierarchy) {
- tf.time('new tf.graph.render.Hierarchy', function() {
+ tf.graph.util.time('new tf.graph.render.Hierarchy', function() {
if (graphHierarchy.root.type !== tf.graph.NodeType.META) {
// root must be metanode but sometimes Polymer's dom-if has not
// remove tf-graph element yet in <tf-node-info>
@@ -10317,8 +10632,8 @@ Polymer({
value: 0,
msg: ''
});
- var tracker = tf.getTracker(this);
- var hierarchyTracker = tf.getSubtaskTracker(tracker, 100,
+ var tracker = tf.graph.util.getTracker(this);
+ var hierarchyTracker = tf.graph.util.getSubtaskTracker(tracker, 100,
'Namespace hierarchy');
tf.graph.hierarchy.build(this.basicGraph, this.hierarchyParams, hierarchyTracker)
.then(function(graphHierarchy) {
@@ -11977,6 +12292,8 @@ Polymer({
</paper-toolbar>
<div id="content" class="fit">
+ <content id="injected-overview"></content>
+
<template is="dom-if" if="[[_modeIsEvents(mode)]]">
<tf-event-dashboard id="events" backend="[[_backend]]" router="[[router]]"></tf-event-dashboard>
</template>
@@ -11985,6 +12302,10 @@ Polymer({
<tf-image-dashboard id="images" backend="[[_backend]]"></tf-image-dashboard>
</template>
+ <template is="dom-if" if="[[_modeIsAudio(mode)]]">
+ <tf-audio-dashboard id="audio" backend="[[_backend]]"></tf-audio-dashboard>
+ </template>
+
<template is="dom-if" if="[[_modeIsGraphs(mode)]]">
<tf-graph-dashboard id="graphs" backend="[[_backend]]" router="[[router]]"></tf-graph-dashboard>
</template>
@@ -12003,7 +12324,7 @@ Polymer({
}
#toolbar {
- background-color: var(--tb-orange-strong);
+ background-color: var(--tb-toolbar-background-color, --tb-orange-strong);
-webkit-font-smoothing: antialiased;
}
@@ -12014,6 +12335,7 @@ Polymer({
letter-spacing: -0.025em;
font-weight: 500;
flex-grow: 2;
+ display: var(--tb-toolbar-title-display, block);
}
.tabs {
@@ -12050,6 +12372,7 @@ Polymer({
#content {
height: 100%;
}
+
[disabled] {
opacity: 0.2;
color: white;
@@ -12104,6 +12427,9 @@ Polymer({
_modeIsImages: function(mode) {
return mode === "images";
},
+ _modeIsAudio: function(mode) {
+ return mode === "audio";
+ },
_modeIsGraphs: function(mode) {
return mode === "graphs";
},