aboutsummaryrefslogtreecommitdiffhomepage
path: root/tensorflow/tensorboard/dist/tf-tensorboard.html
diff options
context:
space:
mode:
authorGravatar A. Unique TensorFlower <nobody@tensorflow.org>2016-02-01 19:37:09 -0800
committerGravatar Manjunath Kudlur <keveman@gmail.com>2016-02-02 08:35:23 -0800
commit0db132c4690a07c2d77d50b43b42c730af78ce3c (patch)
tree36691a1e9a250af34aa14c3e288005b92850d381 /tensorflow/tensorboard/dist/tf-tensorboard.html
parent0ea345362f1b7ffa413427157a17f5d0f4da04f2 (diff)
Autogenerated Change: Release TensorBoard at TAG: 11
Change: 113594352
Diffstat (limited to 'tensorflow/tensorboard/dist/tf-tensorboard.html')
-rw-r--r--tensorflow/tensorboard/dist/tf-tensorboard.html5901
1 files changed, 2943 insertions, 2958 deletions
diff --git a/tensorflow/tensorboard/dist/tf-tensorboard.html b/tensorflow/tensorboard/dist/tf-tensorboard.html
index 78f9177d11..78e975886e 100644
--- a/tensorflow/tensorboard/dist/tf-tensorboard.html
+++ b/tensorflow/tensorboard/dist/tf-tensorboard.html
@@ -1,10 +1,552 @@
// AUTOGENERATED FILE - DO NOT MODIFY
<html><head><meta charset="UTF-8">
+</head><body><div hidden="" by-vulcanize="">
+<dom-module id="tf-data-coordinator" assetpath="../tf-event-dashboard/">
+ <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.
+==============================================================================*/
+/// <reference path="../../typings/tsd.d.ts" />
+/// <reference path="../plottable/plottable.d.ts" />
+var TF;
+(function (TF) {
+ /* The DataCoordinator generates TF.Datasets for each run/tag combination,
+ * and is responsible for communicating with the backend to load data into them.
+ * A key fact about this design is that when Datasets modify their data, they
+ * automatically notify all dependent Plottable charts.
+ */
+ var DataCoordinator = (function () {
+ function DataCoordinator(urlGenerator, runToTag) {
+ this.datasets = {};
+ this.urlGenerator = urlGenerator;
+ this.runToTag = runToTag;
+ }
+ /* Create or return an array of Datasets for the given
+ * tag and runs. It filters which runs it uses by checking
+ * that data exists for each tag-run combination.
+ * Calling this triggers a load on the dataset.
+ */
+ DataCoordinator.prototype.getDatasets = function (tag, runs) {
+ var _this = this;
+ var usableRuns = runs.filter(function (r) {
+ var tags = _this.runToTag[r];
+ return tags.indexOf(tag) !== -1;
+ });
+ return usableRuns.map(function (r) { return _this.getDataset(tag, r); });
+ };
+ /* Create or return a Dataset for given tag and run.
+ * Calling this triggers a load on the dataset.
+ */
+ DataCoordinator.prototype.getDataset = function (tag, run) {
+ var dataset = this._getDataset(tag, run);
+ dataset.load();
+ return dataset;
+ };
+ DataCoordinator.prototype._getDataset = function (tag, run) {
+ var key = [tag, run].toString();
+ var dataset;
+ if (this.datasets[key] != null) {
+ dataset = this.datasets[key];
+ }
+ else {
+ dataset = new TF.Dataset(tag, run, this.urlGenerator);
+ this.datasets[key] = dataset;
+ }
+ return dataset;
+ };
+ return DataCoordinator;
+ })();
+ TF.DataCoordinator = DataCoordinator;
+})(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 __extends = (this && this.__extends) || function (d, b) {
+ for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
+ function __() { this.constructor = d; }
+ d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+};
+/// <reference path="../../typings/tsd.d.ts" />
+/// <reference path="../plottable/plottable.d.ts" />
+var TF;
+(function (TF) {
+ /* An extension of Plottable.Dataset that knows how to load data from a backend.
+ */
+ var Dataset = (function (_super) {
+ __extends(Dataset, _super);
+ function Dataset(tag, run, urlGenerator) {
+ _super.call(this, [], { tag: tag, run: run });
+ this.load = _.debounce(this._load, 10);
+ this.tag = tag;
+ this.run = run;
+ this.urlGenerator = urlGenerator;
+ }
+ Dataset.prototype._load = function () {
+ var _this = this;
+ var url = this.urlGenerator(this.tag, this.run);
+ if (this.lastRequest != null) {
+ this.lastRequest.abort();
+ }
+ this.lastRequest = d3.json(url, function (error, json) {
+ _this.lastRequest = null;
+ if (error) {
+ /* tslint:disable */
+ console.log(error);
+ /* tslint:enable */
+ throw new Error("Failure loading JSON at url: \"" + url + "\"");
+ }
+ else {
+ _this.data(json);
+ }
+ });
+ };
+ return Dataset;
+ })(Plottable.Dataset);
+ TF.Dataset = Dataset;
+})(TF || (TF = {}));
+</script>
+ <script>
+ Polymer({
+ is: "tf-data-coordinator",
+ properties: {
+ urlGenerator: Object,
+ outDataCoordinator: {
+ type: Object,
+ computed: "getCoordinator(urlGenerator, runToTag)",
+ notify: true,
+ },
+ },
+ getCoordinator: function(generator, runToTag) {
+ return new TF.DataCoordinator(generator, runToTag);
+ }
+ });
+ </script>
+</dom-module>
+<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>
+ .scrollbar::-webkit-scrollbar-track
+ {
+ visibility: hidden;
+ }
+
+ .scrollbar::-webkit-scrollbar
+ {
+ width: 10px;
+ }
+
+ .scrollbar::-webkit-scrollbar-thumb
+ {
+ border-radius: 10px;
+ -webkit-box-shadow: inset 0 0 2px rgba(0,0,0,.3);
+ background-color: var(--paper-grey-500);
+ color: var(--paper-grey-900);
+ }
+ .scrollbar {
+ box-sizing: border-box;
+ }
+ </style>
+ </template>
+</dom-module>
+<dom-module id="run-color-style" assetpath="../tf-dashboard-common/">
+ <template>
+ <style>
+ [color-class="light-blue"] paper-checkbox {
+ --paper-checkbox-checked-color: var(--paper-light-blue-500);
+ --paper-checkbox-checked-ink-color: var(--paper-light-blue-500);
+ --paper-checkbox-unchecked-color: var(--paper-light-blue-900);
+ --paper-checkbox-unchecked-ink-color: var(--paper-light-blue-900);
+ }
+ [color-class="red"] paper-checkbox {
+ --paper-checkbox-checked-color: var(--paper-red-500);
+ --paper-checkbox-checked-ink-color: var(--paper-red-500);
+ --paper-checkbox-unchecked-color: var(--paper-red-900);
+ --paper-checkbox-unchecked-ink-color: var(--paper-red-900);
+ }
+ [color-class="green"] paper-checkbox {
+ --paper-checkbox-checked-color: var(--paper-green-500);
+ --paper-checkbox-checked-ink-color: var(--paper-green-500);
+ --paper-checkbox-unchecked-color: var(--paper-green-900);
+ --paper-checkbox-unchecked-ink-color: var(--paper-green-900);
+ }
+ [color-class="purple"] paper-checkbox {
+ --paper-checkbox-checked-color: var(--paper-purple-500);
+ --paper-checkbox-checked-ink-color: var(--paper-purple-500);
+ --paper-checkbox-unchecked-color: var(--paper-purple-900);
+ --paper-checkbox-unchecked-ink-color: var(--paper-purple-900);
+ }
+ [color-class="teal"] paper-checkbox {
+ --paper-checkbox-checked-color: var(--paper-teal-500);
+ --paper-checkbox-checked-ink-color: var(--paper-teal-500);
+ --paper-checkbox-unchecked-color: var(--paper-teal-900);
+ --paper-checkbox-unchecked-ink-color: var(--paper-teal-900);
+ }
+ [color-class="pink"] paper-checkbox {
+ --paper-checkbox-checked-color: var(--paper-pink-500);
+ --paper-checkbox-checked-ink-color: var(--paper-pink-500);
+ --paper-checkbox-unchecked-color: var(--paper-pink-900);
+ --paper-checkbox-unchecked-ink-color: var(--paper-pink-900);
+ }
+ [color-class="orange"] paper-checkbox {
+ --paper-checkbox-checked-color: var(--paper-orange-500);
+ --paper-checkbox-checked-ink-color: var(--paper-orange-500);
+ --paper-checkbox-unchecked-color: var(--paper-orange-900);
+ --paper-checkbox-unchecked-ink-color: var(--paper-orange-900);
+ }
+ [color-class="brown"] paper-checkbox {
+ --paper-checkbox-checked-color: var(--paper-brown-500);
+ --paper-checkbox-checked-ink-color: var(--paper-brown-500);
+ --paper-checkbox-unchecked-color: var(--paper-brown-900);
+ --paper-checkbox-unchecked-ink-color: var(--paper-brown-900);
+ }
+ [color-class="indigo"] paper-checkbox {
+ --paper-checkbox-checked-color: var(--paper-indigo-500);
+ --paper-checkbox-checked-ink-color: var(--paper-indigo-500);
+ --paper-checkbox-unchecked-color: var(--paper-indigo-900);
+ --paper-checkbox-unchecked-ink-color: var(--paper-indigo-900);
+ }
+ </style>
+ </template>
+</dom-module>
+
+
+<dom-module id="tf-multi-checkbox" assetpath="../tf-multi-checkbox/">
+ <style include="scrollbar-style"></style>
+ <style include="run-color-style"></style>
+
+ <template>
+ <div id="outer-container" class="scrollbar">
+ <template is="dom-repeat" items="[[names]]" sort="[[_tooltipComparator(tooltips, tooltipOrderer)]]">
+ <div class="run-row" color-class$="[[_applyColorClass(item, classScale)]]" null-tooltip$="[[_isNullTooltip(item, tooltips)]]" highlight$="[[_isHighlighted(item, highlights.*)]]">
+ <div class="checkbox-container vertical-align-container">
+ <paper-checkbox class="checkbox vertical-align-center" name="[[item]]" checked$="[[_isChecked(item,outSelected.*)]]" on-change="_checkboxChange"></paper-checkbox>
+ </div>
+ <div class="item-label-container">
+ <span>[[item]]</span>
+ </div>
+ <div class="tooltip-value-container vertical-align-container">
+ <span class="vertical-align-top">[[_lookupTooltip(item,tooltips)]]</span>
+ </div>
+ </div>
+ </template>
+ </div>
+ <style>
+ :host {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ }
+ #outer-container {
+ overflow-y: scroll;
+ overflow-x: hidden;
+ width: 100%;
+ flex-grow: 1;
+ flex-shrink: 1;
+ word-wrap: break-word;
+ }
+ .run-row {
+ padding-top: 5px;
+ padding-bottom: 5px;
+ display: flex;
+ flex-direction: row;
+ font-size: 13px;
+ }
+ .checkbox-container {
+ flex-grow: 0;
+ flex-shrink: 0;
+ }
+ .checkbox {
+ padding-left: 2px;
+ width: 32px;
+ }
+ .item-label-container {
+ flex-grow: 1;
+ flex-shrink: 1;
+ width: 0px; /* hack to get the flex-grow to work properly */
+ }
+ .tooltip-value-container {
+ display: flex;
+ justify-content: center;
+ flex-grow: 0;
+ flex-shrink: 0;
+ text-align:right;
+ padding-left: 2px;
+ }
+ .vertical-align-container {
+ display: flex;
+ justify-content: center;
+ }
+ .vertical-align-container .vertical-align-center {
+ align-self: center;
+ }
+ .vertical-align-container .vertical-align-top {
+ align-self: start;
+ }
+ [null-tooltip] {
+ display: none;
+ }
+ [highlight] {
+ font-weight: bold;
+ }
+ </style>
+ </template>
+
+ <script>
+ Polymer({
+ is: "tf-multi-checkbox",
+ properties: {
+ names: Array,
+ tooltipOrderer: {
+ /* Used to compute how to order the tooltips based on the tooltip value.
+ * By default, it parses the tooltip strings as numbers.
+ * If set to a falsey value, tooltips are always ordered lexicographically.
+ */
+ type: Function,
+ value: function() {
+ return function(x) {return +x;}
+ },
+ },
+ tooltips: Object,
+ highlights: Array,
+ outSelected: {
+ type: Array,
+ notify: true,
+ value: function() {
+ return [];
+ },
+ },
+ hideMissingTooltips: {
+ // If we have tooltips, but some names are missing, do we hide them?
+ type: Boolean,
+ value: true,
+ },
+ classScale: Function, // map from run name to css class
+ },
+ observers: [
+ "_initializeOutSelected(names.*)",
+ ],
+ _lookupTooltip: function(item, tooltips) {
+ return tooltips != null ? tooltips[item] : null;
+ },
+ _isNullTooltip: function(item, tooltips) {
+ if (!this.hideMissingTooltips) {
+ return true;
+ }
+ if (tooltips == null) {
+ return false;
+ }
+ return tooltips[item] == null;
+ },
+ _initializeOutSelected: function(change) {
+ this.outSelected = change.base.slice();
+ },
+ _tooltipComparator: function(tooltips, tooltipOrderer) {
+ return function(a, b) {
+ if (!tooltips || !tooltipOrderer) {
+ // if we're missing tooltips or orderer, do lexicogrpahic sort
+ return a.localeCompare(b);
+ }
+ function getValue(x) {
+ var value = tooltipOrderer(tooltips[x]);
+ return value == null || _.isNaN(value) ? -Infinity : value;
+ }
+ var aValue = getValue(a);
+ var bValue = getValue(b);
+ return aValue === bValue ? a.localeCompare(b) : bValue - aValue;
+ }
+ },
+ _checkboxChange: function(e) {
+ var name = e.srcElement.name;
+ var idx = this.outSelected.indexOf(name);
+ var checked = e.srcElement.checked;
+ if (checked && idx === -1) {
+ this.push("outSelected", name);
+ } else if (!checked && idx !== -1) {
+ this.splice("outSelected", idx, 1);
+ }
+ },
+ _isChecked: function(item, outSelectedChange) {
+ var outSelected = outSelectedChange.base;
+ return outSelected.indexOf(item) !== -1;
+ },
+ _initializeRuns: function(change) {
+ this.outSelected = change.base.slice();
+ },
+ _applyColorClass: function(item, classScale) {
+ // TODO: Update style just on the element that changes
+ // and apply at microtask timing
+ this.debounce("restyle", function (){
+ this.updateStyles();
+ }, 16);
+ return classScale(item);
+ },
+ _isHighlighted: function(item, highlights) {
+ return highlights.base.indexOf(item) !== -1;
+ },
+ });
+ </script>
+</dom-module>
+<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>
+ </div>
+ <tf-multi-checkbox names="[[runs]]" tooltips="[[tooltips]]" highlights="[[_arrayify(closestRun)]]" out-selected="{{outSelected}}" class-scale="[[classScale]]" hide-missing-tooltips=""></tf-multi-checkbox>
+ <paper-button class="x-button" id="toggle-all" on-tap="_toggleAll">
+ Toggle All Runs
+ </paper-button>
+ <style>
+ :host {
+ display: flex;
+ flex-direction: column;
+ padding-bottom: 10px;
+ box-sizing: border-box;
+ }
+ #top-text {
+ width: 100%;
+ flex-grow: 0;
+ flex-shrink: 0;
+ padding-right: 16px;
+ padding-bottom: 6px;
+ box-sizing: border-box;
+ color: var(--paper-grey-800);
+ }
+ tf-multi-checkbox {
+ display: flex;
+ flex-grow: 1;
+ flex-shrink: 1;
+ height: 0px; /* hackhack So the flex-grow takes over and gives it space */
+ }
+ .x-button {
+ font-size: 13px;
+ background-color: var(--tb-ui-light-accent);
+ 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;
+ font-weight: normal;
+ font-size: 14px;
+ margin-bottom: 5px;
+ }
+ paper-button {
+ margin-left: 0;
+ }
+ </style>
+ </template>
+ <script>
+ Polymer({
+ is: "tf-run-selector",
+ properties: {
+ outSelected: {type: Array, notify: true},
+ // runs: an array of strings, representing the run names that may be chosen
+ runs: Array,
+ tooltips: {type: Object, value: null}, // {[run: string]: string}
+ xValue: {type: String, value: null}, // the string representing run's x val
+ xType: String, // string: relative, stpe, wall_time
+ classScale: Object, // map from run name to color class (css)
+ closestRun: {type: String, value: null}, // which run has a value closest to mouse coordinate
+ },
+ _toggleAll: function() {
+ if (this.outSelected.length > 0) {
+ this.outSelected = [];
+ } else {
+ this.outSelected = this.runs.slice();
+ }
+ },
+ _arrayify: function(item) {
+ return [item];
+ },
+ });
+ </script>
+</dom-module>
<style is="custom-style">
:root {
@@ -18,23 +560,2124 @@
</style>
+<dom-module id="tf-x-type-selector" assetpath="../tf-event-dashboard/">
+ <template>
+ <div id="buttons">
+ <h3>Horizontal Axis</h3>
+ <paper-button class="x-button selected" id="step" on-tap="_select">
+ step
+ </paper-button>
+ <paper-button class="x-button" id="relative" on-tap="_select">
+ relative
+ </paper-button>
+ <paper-button class="x-button" id="wall_time" on-tap="_select">
+ wall
+ </paper-button>
+ </div>
+ <style>
+ .x-button {
+ width: 30%;
+ font-size: 13px;
+ background: none;
+ margin-top: 10px;
+ color: var(--tb-ui-dark-accent);
+ }
+ .x-button:first-of-type {
+ margin-left: 0;
+ }
+ .x-button.selected {
+ background-color: var(--tb-ui-dark-accent);
+ color: white!important;
+ }
+ #buttons h3 {
+ color: var(--paper-grey-800);
+ margin: 0;
+ font-weight: normal;
+ font-size: 14px;
+ margin-bottom: 5px;
+ }
+ </style>
+ </template>
+ <script>
+ Polymer({
+ is: "tf-x-type-selector",
+ properties: {
+ outXType: {type: String, notify: true, readOnly: true, value: "step"},
+ },
+ _select: function(e) {
+ var _this = this;
+ ["step", "wall_time", "relative"].forEach(function(id) {
+ _this.$[id].classList.remove("selected");
+ });
+ this._setOutXType(e.currentTarget.id);
+ e.currentTarget.classList.add("selected");
+ },
+ });
+ </script>
+</dom-module>
+<dom-module id="tf-run-generator" assetpath="../tf-dashboard-common/">
+ <template>
+ <iron-ajax id="ajax" auto="" url="[[url]]" handle-as="json" debounce="300" on-response="_setResponse" verbose="true">
+ </iron-ajax>
+ </template>
+ <script>
+ Polymer({
+ is: "tf-run-generator",
+ properties: {
+ url: String,
+ _runToTag: {
+ type: Object,
+ readOnly: true,
+ },
+ outRunToScalars: {
+ // {[runName: string]: string[]}
+ // the names of scalar tags.
+ type: Object,
+ computed: "_scalars(_runToTag.*)",
+ notify: true,
+ },
+ outRunToHistograms: {
+ // {[runName: string]: string[]}
+ // the names of histogram tags.
+ type: Object,
+ computed: "_histograms(_runToTag.*)",
+ notify: true,
+ },
+ outRunToCompressedHistograms: {
+ // {[runName: string]: string[]}
+ // the names of histogram tags.
+ type: Object,
+ computed: "_compressedHistograms(_runToTag.*)",
+ notify: true,
+ },
+ outRunToImages: {
+ // {[runName: string]: string[]}
+ // the names of image tags.
+ type: Object,
+ computed: "_images(_runToTag.*)",
+ notify: true,
+ },
+ outRunsWithGraph: {
+ // ["run1", "run2", ...]
+ // array of run names that have an associated graph definition.
+ type: Array,
+ computed: "_graphs(_runToTag.*)",
+ notify: true
+ }
+ },
+ _scalars: function(_runToTag) {
+ return _.mapValues(_runToTag.base, "scalars");
+ },
+ _histograms: function(_runToTag) {
+ return _.mapValues(_runToTag.base, "histograms");
+ },
+ _compressedHistograms: function(_runToTag) {
+ return _.mapValues(_runToTag.base, "compressedHistograms");
+ },
+ _images: function(_runToTag) {
+ return _.mapValues(_runToTag.base, "images");
+ },
+ _graphs: function(_runToTag) {
+ var runsWithGraph = [];
+ _.each(_runToTag.base, function(runInfo, runName) {
+ if (runInfo.graph === true) {
+ runsWithGraph.push(runName);
+ }
+ });
+ return runsWithGraph;
+ },
+ _setResponse: function(event) {
+ this._set_runToTag(event.detail.response);
+ }
+ });
+ </script>
+</dom-module>
+<dom-module id="tf-color-scale" assetpath="../tf-event-dashboard/">
+ <script>
+ (function() {
+ // TODO(danmane) - get Plottable team to make an API point for this
+ Plottable.Scales.Color._LOOP_LIGHTEN_FACTOR = 0;
+ var classColorPairs = [
+ ["light-blue", "#03A9F4"],
+ ["red" , "#f44366"],
+ ["green" , "#4CAF50"],
+ ["purple" , "#9c27b0"],
+ ["teal" , "#009688"],
+ ["pink" , "#e91e63"],
+ ["orange" , "#ff9800"],
+ ["brown" , "#795548"],
+ ["indigo" , "#3f51b5"],
+ ];
+ var classes = _.pluck(classColorPairs, 0);
+ var colors = _.pluck(classColorPairs, 1);
+ Polymer({
+ is: "tf-color-scale",
+ properties: {
+ runs: Array,
+ outClassScale: {
+ type: Object,
+ notify: true,
+ readOnly: true,
+ value: function() {
+ return new d3.scale.ordinal().range(classes);
+ },
+ // TODO(danmane): the class scale will not update if the domain changes.
+ // this behavior is inconsistent with the ColorScale.
+ // in practice we don't change runs after initial load so it's not currently an issue
+ },
+ outColorScale: {
+ type: Object,
+ notify: true,
+ readOnly: true,
+ value: function() {
+ var scale = new Plottable.Scales.Color().range(colors);
+ scale.onUpdate(this._notifyColorScaleDomainChange.bind(this));
+ return scale;
+ },
+ },
+ },
+ observers: ["_changeRuns(runs.*)"],
+ _changeRuns: function(runs) {
+ this.outClassScale.domain(this.runs);
+ this.outColorScale.domain(this.runs);
+ },
+ _notifyColorScaleDomainChange: function() {
+ this.notifyPath("outColorScale.domain_path", this.outColorScale.domain());
+ this.outColorScale.domain_path = null;
+ },
+ });
+ })();
+ </script>
+</dom-module>
+<dom-module id="tf-url-generator" assetpath="../tf-dashboard-common/">
+ <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.
+==============================================================================*/
+/// <reference path="../../typings/tsd.d.ts" />
+/// <reference path="../plottable/plottable.d.ts" />
+var TF;
+(function (TF) {
+ var Urls;
+ (function (Urls) {
+ ;
+ Urls.routes = ["runs", "scalars", "histograms",
+ "compressedHistograms", "images",
+ "individualImage", "graph"];
+ function productionRouter() {
+ /* The standard router for communicating with the TensorBoard backend */
+ function standardRoute(route) {
+ return function (tag, run) {
+ return "/data/" + route + "?tag=" + encodeURIComponent(tag)
+ + "&run=" + encodeURIComponent(run);
+ };
+ }
+ function individualImageUrl(query) {
+ return "/data/individualImage?" + query;
+ }
+ function graphUrl(run) {
+ return "/data/graph?run=" + encodeURIComponent(run);
+ }
+ return {
+ runs: function () { return "/data/runs"; },
+ individualImage: individualImageUrl,
+ graph: graphUrl,
+ scalars: standardRoute("scalars"),
+ histograms: standardRoute("histograms"),
+ compressedHistograms: standardRoute("compressedHistograms"),
+ images: standardRoute("images"),
+ };
+ }
+ Urls.productionRouter = productionRouter;
+ ;
+ function demoRouter(dataDir) {
+ /* Retrieves static .json data generated by demo_from_server.py */
+ function demoRoute(route) {
+ return function (tag, run) {
+ run = run.replace(/[ \)\(]/g, "_");
+ tag = tag.replace(/[ \)\(]/g, "_");
+ return dataDir + "/" + route + "/" + run + "/" + tag + ".json";
+ };
+ }
+ ;
+ function individualImageUrl(query) {
+ return dataDir + "/individualImage/" + query + ".png";
+ }
+ ;
+ function graphUrl(run) {
+ run = run.replace(/ /g, "_");
+ return dataDir + "/graph/" + run + ".pbtxt";
+ }
+ ;
+ return {
+ runs: function () { return dataDir + "/runs.json"; },
+ individualImage: individualImageUrl,
+ graph: graphUrl,
+ scalars: demoRoute("scalars"),
+ histograms: demoRoute("histograms"),
+ compressedHistograms: demoRoute("compressedHistograms"),
+ images: demoRoute("images"),
+ };
+ }
+ Urls.demoRouter = demoRouter;
+ })(Urls = TF.Urls || (TF.Urls = {}));
+})(TF || (TF = {}));
+</script>
+ <script>
+ var polymerObject = {
+ is: "tf-url-generator",
+ _computeRuns: function(router) {
+ return router.runs();
+ },
+ properties: {
+ router: {
+ type: Object,
+ },
+ outRunsUrl: {
+ type: String,
+ computed: "_computeRuns(router)",
+ readOnly: true,
+ notify: true,
+ },
+ },
+ };
+ TF.Urls.routes.forEach(function(route) {
+ /* for each route (other than runs, handled seperately):
+ * out`RouteName`: {
+ * type: Function,
+ * readOnly: true,
+ * notify: true,
+ * value: function() {
+ * return TF.Urls.`routeName`Url;
+ * }
+ */
+ if (route === "runs") {
+ return;
+ }
+ var computeFnName = "_compute" + route;
+ polymerObject[computeFnName] = function(router) {
+ return router[route];
+ };
+ var urlName = route + "Url";
+ var propertyName = Polymer.CaseMap.dashToCamelCase("out-" + urlName + "Generator");
+ polymerObject.properties[propertyName] = {
+ type: Function,
+ computed: computeFnName + "(router)",
+ notify: true,
+ }
+ });
+ Polymer(polymerObject);
+ </script>
+</dom-module>
+<dom-module id="tf-dashboard-layout" assetpath="../tf-dashboard-common/">
+ <template>
+ <div id="sidebar">
+ <content select=".sidebar"></content>
+ </div>
+ <div id="center" class="scrollbar">
+ <content select=".center"></content>
+ </div>
+ <style include="scrollbar-style"></style>
+ <style>
+ #sidebar {
+ width: inherit;
+ height: 100%;
+ overflow: ellipsis;
+ flex-grow: 0;
+ flex-shrink: 0;
+ }
+ #center {
+ height: 100%;
+ overflow-y: scroll;
+ flex-grow: 1;
+ flex-shrink: 1;
+ }
+ .tf-graph-dashboard #center {
+ background: white;
+ }
+ :host {
+ display: flex;
+ flex-direction: row;
+ height: 100%;
+ }
+ </style>
+ </template>
+ <script>
+ Polymer({
+ is: "tf-dashboard-layout",
+ });
+ </script>
+</dom-module>
+<dom-module id="dashboard-style" assetpath="../tf-dashboard-common/">
+ <template>
+ <style>
+ .card {
+ height: 200px;
+ width: 300px;
+ display: flex;
+ flex-direction: column;
+ margin: 5px;
+ padding: 0 30px 30px 0;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ position: relative;
+ }
+ .card .card-title {
+ flex-grow: 0;
+ flex-shrink: 0;
+ margin-bottom: 10px;
+ font-size: 14px;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ }
+
+ .card .card-content {
+ flex-grow: 1;
+ flex-shrink: 1;
+ display: flex;
+ }
+ .card .card-bottom-row {
+ flex-grow: 0;
+ flex-shrink: 0;
+ padding-left: 10px;
+ padding-right: 10px;
+ }
+
+ .card.selected {
+ height: 400px;
+ width: 100%;
+ }
+ [shift] {
+ bottom: 20px !important;
+ }
+ .expand-button {
+ position: absolute;
+ left: 0px;
+ bottom: 20px;
+ color: #2196F3;
+ display: block;
+ }
+
+ #content-container{
+ display: block;
+ }
+
+ .sidebar {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ margin-right: 20px;
+ }
+
+ #categorizer {
+ flex-shrink: 0;
+ }
+
+ #xTypeSelector {
+ flex-shrink: 0;
+ margin: 20px 0;
+ }
+
+ #runSelector {
+ flex-shrink: 1;
+ flex-grow: 1;
+ }
+
+ .sidebar-section {
+ border-top: solid 1px rgba(0, 0, 0, 0.12);
+ padding: 20px 0px 20px 30px;
+ }
+
+ .sidebar-section:first-child {
+ border: none;
+ }
+
+ .sidebar-section:last-child {
+ flex-grow: 1;
+ display: flex;
+ }
+
+ paper-checkbox {
+ --paper-checkbox-checked-color: var(--tb-ui-dark-accent);
+ --paper-checkbox-unchecked-color: var(--tb-ui-dark-accent);
+ font-size: 14px;
+ }
+
+ </style>
+ </template>
+</dom-module>
+
+<dom-module id="tf-downloader" assetpath="../tf-dashboard-common/">
+ <template>
+ <paper-dropdown-menu no-label-float="true" label="run to download" selected-item-label="{{_run}}">
+ <paper-menu class="dropdown-content">
+ <template is="dom-repeat" items="[[_runs]]">
+ <paper-item no-label-float="true">[[item]]</paper-item>
+ </template>
+ </paper-menu>
+ </paper-dropdown-menu>
+ <a download="[[_csvName(_run)]]" href="[[_csvUrl(_run, urlFn)]]">CSV</a>
+ <a download="[[_jsonName(_run)]]" href="[[_jsonUrl(_run, urlFn)]]">JSON</a>
+ <style>
+ :host {
+ display: block;
+ }
+ paper-dropdown-menu {
+ width: 220px;
+ --paper-input-container-label: {
+ font-size: 10px;
+ }
+ --paper-input-container-input: {
+ font-size: 10px;
+ }
+ }
+ a {
+ font-size: 10px;
+ border-radius: 3px;
+ border: 1px solid #EEE;
+ }
+ paper-input {
+ font-size: 22px;
+ }
+ </style>
+ </template>
+ <script>
+ Polymer({
+ is: "tf-downloader",
+ properties: {
+ _run: String,
+ _runs: {
+ type: Array,
+ computed: "_computeRuns(runToTag.*, selectedRuns.*)",
+ },
+ selectedRuns: Array,
+ runToTag: Object,
+ tag: String,
+ urlFn: Function,
+ },
+ _computeRuns: function(runToTagChange, selectedRunsChange) {
+ var runToTag = this.runToTag;
+ var tag = this.tag;
+ return this.selectedRuns.filter(function(x) {
+ return runToTag[x].indexOf(tag) !== -1;
+ })
+ },
+ _csvUrl: function(_run, urlFn) {
+ return urlFn(this.tag, _run) + "&format=csv";
+ },
+ _jsonUrl: function(_run, urlFn) {
+ return urlFn(this.tag, _run);
+ },
+ _csvName: function(_run) {
+ return "run_" + _run + ",tag_" + this.tag + ".csv";
+ },
+ _jsonName: function(_run) {
+ return "run-" + _run + "-tag-" + this.tag + ".json";
+ },
+ });
+ </script>
+</dom-module>
+
+
+<dom-module id="tf-regex-group" assetpath="../tf-regex-group/">
+ <template>
+ <div class="regex-list">
+ <template is="dom-repeat" items="{{rawRegexes}}">
+ <div class="regex-line">
+ <paper-checkbox class="active-button" checked="{{item.active}}" disabled="[[!item.valid]]"></paper-checkbox>
+ <paper-input id="text-input" class="regex-input" label="Regex filter" no-label-float="" bind-value="{{item.regex}}" invalid="[[!item.valid]]" on-keyup="moveFocus"></paper-input>
+ <paper-icon-button icon="close" class="delete-button" aria-label="Delete Regex" tabindex="0" on-tap="deleteRegex"></paper-icon-button>
+ </div>
+ <style>
+ .regex-input {
+ width: 230px;
+ display: inline-block;
+ margin-left: -3px;
+ }
+
+ paper-checkbox {
+ --paper-checkbox-checked-color: var(--tb-ui-dark-accent);
+ --paper-checkbox-unchecked-color: var(--tb-ui-dark-accent);
+ }
+
+ .delete-button {
+ color: var(--paper-grey-700);
+ width: 40px;
+ height: 40px;
+ margin-right: -10px;
+ }
+
+ .regex-list {
+ margin-bottom: 10px;
+ }
+
+ paper-input {
+ --paper-input-container-focus-color: var(--tb-orange-strong);
+ --paper-input-container-input: {
+ font-size: 14px;
+ };
+ --paper-input-container-label: {
+ font-size: 14px;
+ };
+ }
+ </style>
+ </template>
+ </div>
+ </template>
+ <script>
+ Polymer({
+ is: "tf-regex-group",
+ properties: {
+ rawRegexes: {
+ type: Array,
+ value: function() {
+ return [{regex: "", active: true, valid: true}];
+ }
+ },
+ regexes: {type: Array, computed: "usableRegexes(rawRegexes.*)", notify: true},
+ },
+ observers: [
+ "addNewRegexIfNeeded(rawRegexes.*)",
+ "checkValidity(rawRegexes.*)",
+ ],
+ checkValidity: function(x) {
+ var match = x.path.match(/rawRegexes\.(\d+)\.regex/);
+ if (match) {
+ var idx = match[1];
+ this.set("rawRegexes." + idx + ".valid", this.isValid(x.value));
+ }
+ },
+ isValid: function(s) {
+ try {
+ new RegExp(s);
+ return true;
+ } catch (e) {
+ return false;
+ }
+ },
+ usableRegexes: function(regexes) {
+ var isValid = this.isValid;
+ return regexes.base.filter(function (r) {
+ // Checking validity here (rather than using the data property)
+ // is necessary because otherwise we might send invalid regexes due
+ // to the fact that this function can call before the observer does
+ return r.regex !== "" && r.active && isValid(r.regex);
+ }).map(function(r) {
+ return r.regex;
+ });
+ },
+ addNewRegexIfNeeded: function() {
+ var last = this.rawRegexes[this.rawRegexes.length - 1];
+ if (last.regex !== "") {
+ this.push("rawRegexes", {regex: "", active: true, valid: true});
+ }
+ },
+ deleteRegex: function(e) {
+ if (this.rawRegexes.length > 1) {
+ this.splice("rawRegexes", e.model.index, 1);
+ }
+ },
+ moveFocus: function(e) {
+ if (e.keyCode === 13) {
+ var idx = e.model.index;
+ var inputs = Polymer.dom(this.root).querySelectorAll(".regex-input");
+ if (idx < this.rawRegexes.length - 1) {
+ inputs[idx+1].$.input.focus();
+ } else {
+ document.activeElement.blur();
+ }
+ }
+ }
+ });
+ </script>
+</dom-module>
+
+
+<dom-module id="tf-categorizer" assetpath="../tf-categorizer/">
+ <template>
+ <div class="inputs">
+ <tf-regex-group id="regex-group" regexes="{{regexes}}"></tf-regex-group>
+ </div>
+ <div id="underscore-categorization">
+ <paper-checkbox checked="{{splitOnUnderscore}}">Split on underscores</paper-checkbox>
+ </div>
+ <style>
+ :host {
+ display: block;
+ padding-bottom: 15px;
+ }
+ paper-checkbox {
+ --paper-checkbox-checked-color: var(--paper-grey-600);
+ --paper-checkbox-unchecked-color: var(--paper-grey-600);
+ font-size: 14px;
+ }
+ #underscore-categorization {
+ color: var(--paper-grey-700);
+ }
+ </style>
+ </template>
+ <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.
+==============================================================================*/
+/// <reference path="../../typings/tsd.d.ts" />
+var Categorizer;
+(function (Categorizer) {
+ /* Canonical TensorFlow ops are namespaced using forward slashes.
+ * This fallback categorizer categorizes by the top-level namespace.
+ */
+ Categorizer.topLevelNamespaceCategorizer = splitCategorizer(/\//);
+ // Try to produce good categorizations on legacy graphs, which often
+ // are namespaced like l1_foo/bar or l2_baz/bam.
+ // If there is no leading underscore before the first forward slash,
+ // then it behaves the same as topLevelNamespaceCategorizer
+ Categorizer.legacyUnderscoreCategorizer = splitCategorizer(/[\/_]/);
+ function fallbackCategorizer(s) {
+ switch (s) {
+ case "TopLevelNamespaceCategorizer":
+ return Categorizer.topLevelNamespaceCategorizer;
+ case "LegacyUnderscoreCategorizer":
+ return Categorizer.legacyUnderscoreCategorizer;
+ default:
+ throw new Error("Unrecognized categorization strategy: " + s);
+ }
+ }
+ Categorizer.fallbackCategorizer = fallbackCategorizer;
+ /* An "extractor" is a function that takes a tag name, and "extracts" a category name.
+ * This function takes an extractor, and produces a categorizer.
+ * Currently, it is just used for the fallbackCategorizer, but we may want to
+ * refactor the general categorization logic to use the concept of extractors.
+ */
+ function extractorToCategorizer(extractor) {
+ return function (tags) {
+ if (tags.length === 0) {
+ return [];
+ }
+ var sortedTags = tags.slice().sort();
+ var categories = [];
+ var currentCategory = {
+ name: extractor(sortedTags[0]),
+ tags: [],
+ };
+ sortedTags.forEach(function (t) {
+ var topLevel = extractor(t);
+ if (currentCategory.name !== topLevel) {
+ categories.push(currentCategory);
+ currentCategory = {
+ name: topLevel,
+ tags: [],
+ };
+ }
+ currentCategory.tags.push(t);
+ });
+ categories.push(currentCategory);
+ return categories;
+ };
+ }
+ function splitCategorizer(r) {
+ var extractor = function (t) {
+ return t.split(r)[0];
+ };
+ return extractorToCategorizer(extractor);
+ }
+ function defineCategory(ruledef) {
+ var r = new RegExp(ruledef);
+ var f = function (tag) {
+ return r.test(tag);
+ };
+ return { name: ruledef, matches: f };
+ }
+ Categorizer.defineCategory = defineCategory;
+ function _categorizer(rules, fallback) {
+ return function (tags) {
+ var remaining = d3.set(tags);
+ var userSpecified = rules.map(function (def) {
+ var tags = [];
+ remaining.forEach(function (t) {
+ if (def.matches(t)) {
+ tags.push(t);
+ }
+ });
+ var cat = { name: def.name, tags: tags.sort() };
+ return cat;
+ });
+ var defaultCategories = fallback(remaining.values());
+ return userSpecified.concat(defaultCategories);
+ };
+ }
+ Categorizer._categorizer = _categorizer;
+ function categorizer(s) {
+ var rules = s.categoryDefinitions.map(defineCategory);
+ var fallback = fallbackCategorizer(s.fallbackCategorizer);
+ return _categorizer(rules, fallback);
+ }
+ Categorizer.categorizer = categorizer;
+ ;
+})(Categorizer || (Categorizer = {}));
+</script>
+ <script>
+ Polymer({
+ is: "tf-categorizer",
+ properties: {
+ regexes: {type: Array},
+ tags: {type: Array},
+ categoriesAreExclusive: {type: Boolean, value: true},
+ fallbackCategorizer: {
+ type: String,
+ computed: "chooseFallbackCategorizer(splitOnUnderscore)"
+ },
+ splitOnUnderscore: {
+ type: Boolean,
+ value: false,
+ },
+ categorizer: {
+ type: Object,
+ computed: "computeCategorization(regexes.*, categoriesAreExclusive, fallbackCategorizer)",
+ },
+ categories: {type: Array, value: function() {return [];}, notify: true, readOnly: true},
+ },
+ observers: ['recategorize(tags.*, categorizer)'],
+ computeCategorization: function(regexes, categoriesAreExclusive, fallbackCategorizer) {
+ var categorizationStrategy = {
+ categoryDefinitions: regexes.base,
+ categoriesAreExclusive: categoriesAreExclusive,
+ fallbackCategorizer: fallbackCategorizer,
+ };
+ return Categorizer.categorizer(categorizationStrategy);
+ },
+ recategorize: function() {
+ this.debounce("tf-categorizer-recategorize", function (){
+ var categories = this.categorizer(this.tags);
+ this._setCategories(categories);
+ })
+ },
+ chooseFallbackCategorizer: function(splitOnUnderscore) {
+ if (splitOnUnderscore) {
+ return "LegacyUnderscoreCategorizer";
+ } else {
+ return "TopLevelNamespaceCategorizer";
+ }
+ },
+ });
+ </script>
+</dom-module>
+
+<dom-module id="tf-chart" assetpath="../tf-event-dashboard/">
+ <template>
+ <svg id="chartsvg"></svg>
+ <style>
+ :host {
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ display: flex;
+ flex-direction: column;
+ flex-grow: 1;
+ flex-shrink: 1;
+ }
+ svg {
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ flex-grow: 1;
+ flex-shrink: 1;
+ }
+ .plottable .crosshairs line.guide-line {
+ stroke: #777;
+ }
+ </style>
+ </template>
+ <script>/* 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 __extends = (this && this.__extends) || function (d, b) {
+ for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
+ function __() { this.constructor = d; }
+ d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+};
+var Plottable;
+(function (Plottable) {
+ var DragZoomLayer = (function (_super) {
+ __extends(DragZoomLayer, _super);
+ /* Constructs a SelectionBoxLayer with an attached DragInteraction and ClickInteraction.
+ * On drag, it triggers an animated zoom into the box that was dragged.
+ * On double click, it zooms back out to the original view, before any zooming.
+ * The zoom animation uses an easing function (default d3.ease("cubic-in-out")) and is customizable.
+ * Usage: Construct the selection box layer and attach x and y scales, and then add the layer
+ * over the plot you are zooming on using a Component Group.
+ * TODO(danmane) - merge this into Plottable
+ */
+ function DragZoomLayer(xScale, yScale) {
+ _super.call(this);
+ this.isZoomed = false;
+ this.easeFn = d3.ease("cubic-in-out");
+ this._animationTime = 750;
+ this.xScale(xScale);
+ this.yScale(yScale);
+ this._dragInteraction = new Plottable.Interactions.Drag();
+ this._dragInteraction.attachTo(this);
+ this._doubleClickInteraction = new Plottable.Interactions.DoubleClick();
+ this._doubleClickInteraction.attachTo(this);
+ this.setupCallbacks();
+ }
+ DragZoomLayer.prototype.setupCallbacks = function () {
+ var _this = this;
+ var dragging = false;
+ this._dragInteraction.onDragStart(function (startPoint) {
+ _this.bounds({
+ topLeft: startPoint,
+ bottomRight: startPoint,
+ });
+ });
+ this._dragInteraction.onDrag(function (startPoint, endPoint) {
+ _this.bounds({ topLeft: startPoint, bottomRight: endPoint });
+ _this.boxVisible(true);
+ dragging = true;
+ });
+ this._dragInteraction.onDragEnd(function (startPoint, endPoint) {
+ _this.boxVisible(false);
+ _this.bounds({ topLeft: startPoint, bottomRight: endPoint });
+ if (dragging) {
+ _this.zoom();
+ }
+ dragging = false;
+ });
+ this._doubleClickInteraction.onDoubleClick(this.unzoom.bind(this));
+ };
+ DragZoomLayer.prototype.animationTime = function (animationTime) {
+ if (animationTime == null) {
+ return this._animationTime;
+ }
+ if (animationTime < 0) {
+ throw new Error("animationTime cannot be negative");
+ }
+ this._animationTime = animationTime;
+ return this;
+ };
+ /* Set the easing function, which determines how the zoom interpolates over time. */
+ DragZoomLayer.prototype.ease = function (fn) {
+ if (typeof (fn) !== "function") {
+ throw new Error("ease function must be a function");
+ }
+ if (fn(0) !== 0 || fn(1) !== 1) {
+ Plottable.Utils.Window.warn("Easing function does not maintain invariant f(0)==0 && f(1)==1. Bad behavior may result.");
+ }
+ this.easeFn = fn;
+ return this;
+ };
+ // Zoom into extent of the selection box bounds
+ DragZoomLayer.prototype.zoom = function () {
+ var x0 = this.xExtent()[0].valueOf();
+ var x1 = this.xExtent()[1].valueOf();
+ var y0 = this.yExtent()[1].valueOf();
+ var y1 = this.yExtent()[0].valueOf();
+ if (x0 === x1 || y0 === y1) {
+ return;
+ }
+ if (!this.isZoomed) {
+ this.isZoomed = true;
+ this.xDomainToRestore = this.xScale().domain();
+ this.yDomainToRestore = this.yScale().domain();
+ }
+ this.interpolateZoom(x0, x1, y0, y1);
+ };
+ // Restore the scales to their state before any zoom
+ DragZoomLayer.prototype.unzoom = function () {
+ if (!this.isZoomed) {
+ return;
+ }
+ this.isZoomed = false;
+ this.interpolateZoom(this.xDomainToRestore[0], this.xDomainToRestore[1], this.yDomainToRestore[0], this.yDomainToRestore[1]);
+ };
+ // If we are zooming, disable interactions, to avoid contention
+ DragZoomLayer.prototype.isZooming = function (isZooming) {
+ this._dragInteraction.enabled(!isZooming);
+ this._doubleClickInteraction.enabled(!isZooming);
+ };
+ DragZoomLayer.prototype.interpolateZoom = function (x0f, x1f, y0f, y1f) {
+ var _this = this;
+ var x0s = this.xScale().domain()[0].valueOf();
+ var x1s = this.xScale().domain()[1].valueOf();
+ var y0s = this.yScale().domain()[0].valueOf();
+ var y1s = this.yScale().domain()[1].valueOf();
+ // Copy a ref to the ease fn, so that changing ease wont affect zooms in progress
+ var ease = this.easeFn;
+ var interpolator = function (a, b, p) { return d3.interpolateNumber(a, b)(ease(p)); };
+ this.isZooming(true);
+ var start = Date.now();
+ var draw = function () {
+ var now = Date.now();
+ var passed = now - start;
+ var p = _this._animationTime === 0 ? 1 : Math.min(1, passed / _this._animationTime);
+ var x0 = interpolator(x0s, x0f, p);
+ var x1 = interpolator(x1s, x1f, p);
+ var y0 = interpolator(y0s, y0f, p);
+ var y1 = interpolator(y1s, y1f, p);
+ _this.xScale().domain([x0, x1]);
+ _this.yScale().domain([y0, y1]);
+ if (p < 1) {
+ Plottable.Utils.DOM.requestAnimationFramePolyfill(draw);
+ }
+ else {
+ _this.isZooming(false);
+ }
+ };
+ draw();
+ };
+ return DragZoomLayer;
+ })(Plottable.Components.SelectionBoxLayer);
+ Plottable.DragZoomLayer = DragZoomLayer;
+})(Plottable || (Plottable = {}));
+</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 __extends = (this && this.__extends) || function (d, b) {
+ for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
+ function __() { this.constructor = d; }
+ d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+};
+/// <reference path="../../typings/tsd.d.ts" />
+/// <reference path="../plottable/plottable.d.ts" />
+var TF;
+(function (TF) {
+ var Y_TOOLTIP_FORMATTER_PRECISION = 4;
+ var STEP_AXIS_FORMATTER_PRECISION = 4;
+ var Y_AXIS_FORMATTER_PRECISION = 3;
+ var BaseChart = (function () {
+ function BaseChart(tag, dataCoordinator, tooltipUpdater, xType, colorScale) {
+ this.dataCoordinator = dataCoordinator;
+ this.tag = tag;
+ this.colorScale = colorScale;
+ this.tooltipUpdater = tooltipUpdater;
+ this.buildChart(xType);
+ }
+ BaseChart.prototype.changeRuns = function (runs) {
+ throw new Error("Abstract method not implemented");
+ };
+ 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();
+ }
+ var xComponents = getXComponents(xType);
+ this.xAccessor = xComponents.accessor;
+ this.xScale = xComponents.scale;
+ this.xAxis = xComponents.axis;
+ this.xAxis.margin(0).tickLabelPadding(3);
+ this.xTooltipFormatter = xComponents.tooltipFormatter;
+ this.yScale = new Plottable.Scales.Linear();
+ this.yAxis = new Plottable.Axes.Numeric(this.yScale, "left");
+ var yFormatter = multiscaleFormatter(Y_AXIS_FORMATTER_PRECISION);
+ this.yAxis.margin(0).tickLabelPadding(5).formatter(yFormatter);
+ this.yAxis.usesTextWidthApproximation(true);
+ var center = this.buildPlot(this.xAccessor, this.xScale, this.yScale);
+ this.gridlines = new Plottable.Components.Gridlines(this.xScale, this.yScale);
+ var dzl = new Plottable.DragZoomLayer(this.xScale, this.yScale);
+ this.center = new Plottable.Components.Group([center, this.gridlines, dzl]);
+ this.outer = new Plottable.Components.Table([
+ [this.yAxis, this.center],
+ [null, this.xAxis]
+ ]);
+ };
+ BaseChart.prototype.buildPlot = function (xAccessor, xScale, yScale) {
+ throw new Error("Abstract method not implemented.");
+ };
+ BaseChart.prototype.renderTo = function (target) {
+ this.outer.renderTo(target);
+ };
+ BaseChart.prototype.redraw = function () {
+ this.outer.redraw();
+ };
+ BaseChart.prototype.destroy = function () {
+ this.outer.destroy();
+ };
+ return BaseChart;
+ })();
+ TF.BaseChart = BaseChart;
+ var LineChart = (function (_super) {
+ __extends(LineChart, _super);
+ function LineChart() {
+ _super.apply(this, arguments);
+ }
+ LineChart.prototype.buildPlot = function (xAccessor, xScale, yScale) {
+ var yAccessor = accessorize("2");
+ var plot = new Plottable.Plots.Line();
+ plot.x(xAccessor, xScale);
+ plot.y(yAccessor, yScale);
+ plot.attr("stroke", function (d, i, m) { return m.run; }, this.colorScale);
+ this.plot = plot;
+ var group = this.addCrosshairs(plot, yAccessor);
+ return group;
+ };
+ LineChart.prototype.changeRuns = function (runs) {
+ var datasets = this.dataCoordinator.getDatasets(this.tag, runs);
+ this.plot.datasets(datasets);
+ };
+ return LineChart;
+ })(BaseChart);
+ TF.LineChart = LineChart;
+ var HistogramChart = (function (_super) {
+ __extends(HistogramChart, _super);
+ function HistogramChart() {
+ _super.apply(this, arguments);
+ }
+ HistogramChart.prototype.changeRuns = function (runs) {
+ var datasets = this.dataCoordinator.getDatasets(this.tag, runs);
+ this.plots.forEach(function (p) { return p.datasets(datasets); });
+ };
+ 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 accessors = percents.map(function (p, i) { return function (datum) { return datum[2][i][1]; }; });
+ var median = 4;
+ var medianAccessor = accessors[median];
+ var plots = _.range(accessors.length - 1).map(function (i) {
+ var p = new Plottable.Plots.Area();
+ p.x(xAccessor, xScale);
+ var y0 = i > median ? accessors[i] : accessors[i + 1];
+ var y = i > median ? accessors[i + 1] : accessors[i];
+ p.y(y, yScale);
+ p.y0(y0);
+ p.attr("fill", function (d, i, m) { return m.run; }, _this.colorScale);
+ p.attr("stroke", function (d, i, m) { return m.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]; });
+ 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);
+ this.plots = plots;
+ var group = this.addCrosshairs(medianPlot, medianAccessor);
+ return new Plottable.Components.Group([new Plottable.Components.Group(plots), group]);
+ };
+ return HistogramChart;
+ })(BaseChart);
+ TF.HistogramChart = HistogramChart;
+ /* Create a formatter function that will switch between exponential and
+ * regular display depending on the scale of the number being formatted,
+ * and show `digits` significant digits.
+ */
+ function multiscaleFormatter(digits) {
+ return function (v) {
+ var absv = Math.abs(v);
+ if (absv < 1E-15) {
+ // Sometimes zero-like values get an annoying representation
+ absv = 0;
+ }
+ var f;
+ if (absv >= 1E4) {
+ f = d3.format("." + digits + "e");
+ }
+ else if (absv > 0 && absv < 0.01) {
+ f = d3.format("." + digits + "e");
+ }
+ else {
+ f = d3.format("." + digits + "g");
+ }
+ return f(v);
+ };
+ }
+ function accessorize(key) {
+ return function (d, index, dataset) { return d[key]; };
+ }
+ function stepX() {
+ var scale = new Plottable.Scales.Linear();
+ var axis = new Plottable.Axes.Numeric(scale, "bottom");
+ var formatter = Plottable.Formatters.siSuffix(STEP_AXIS_FORMATTER_PRECISION);
+ axis.formatter(formatter);
+ return {
+ scale: scale,
+ axis: axis,
+ accessor: accessorize("1"),
+ tooltipFormatter: formatter,
+ };
+ }
+ function wallX() {
+ var scale = new Plottable.Scales.Time();
+ var formatter = Plottable.Formatters.time("%a %b %e, %H:%M:%S");
+ return {
+ scale: scale,
+ axis: new Plottable.Axes.Time(scale, "bottom"),
+ accessor: function (d, index, dataset) {
+ return d[0] * 1000; // convert seconds to ms
+ },
+ tooltipFormatter: function (d) { return formatter(new Date(d)); },
+ };
+ }
+ function relativeX() {
+ var scale = new Plottable.Scales.Linear();
+ var formatter = function (n) {
+ var days = Math.floor(n / 24);
+ n -= (days * 24);
+ var hours = Math.floor(n);
+ n -= hours;
+ n *= 60;
+ var minutes = Math.floor(n);
+ n -= minutes;
+ n *= 60;
+ var seconds = Math.floor(n);
+ return days + "d " + hours + "h " + minutes + "m " + seconds + "s";
+ };
+ return {
+ scale: scale,
+ axis: new Plottable.Axes.Numeric(scale, "bottom"),
+ accessor: function (d, index, dataset) {
+ var data = dataset && dataset.data();
+ // I can't imagine how this function would be called when the data is empty
+ // (after all, it iterates over the data), but lets guard just to be safe.
+ var first = data.length > 0 ? data[0][0] : 0;
+ return (d[0] - first) / (60 * 60); // convert seconds to hours
+ },
+ tooltipFormatter: formatter,
+ };
+ }
+ function getXComponents(xType) {
+ switch (xType) {
+ case "step":
+ return stepX();
+ case "wall_time":
+ return wallX();
+ case "relative":
+ return relativeX();
+ default:
+ throw new Error("invalid xType: " + xType);
+ }
+ }
+})(TF || (TF = {}));
+</script>
+ <script>
+ Polymer({
+ is: "tf-chart",
+ properties: {
+ type: String, // "scalar" or "compressedHistogram"
+ _chart: Object,
+ colorScale: Object,
+ tag: String,
+ selectedRuns: Array,
+ xType: String,
+ dataCoordinator: Object,
+ tooltipUpdater: Function,
+ _initialized: Boolean,
+ },
+ observers: [
+ "_makeChart(type, tag, dataCoordinator, tooltipUpdater, xType, colorScale, _initialized)",
+ "_changeRuns(_chart, selectedRuns.*)"
+ ],
+ _changeRuns: function(chart, change) {
+ this._chart.changeRuns(this.selectedRuns);
+ this.redraw();
+ },
+ redraw: function() {
+ this._chart.redraw();
+ },
+ _constructor: function(type) {
+ if (type === "scalar") {
+ return TF.LineChart;
+ } else if (type === "compressedHistogram") {
+ return TF.HistogramChart;
+ } else {
+ throw new Error("Unrecognized chart type");
+ }
+ },
+ _makeChart: function(type, tag, dataCoordinator, tooltipUpdater, xType, colorScale, _initialized) {
+ if (!_initialized) {
+ return;
+ }
+ if (this._chart) this._chart.destroy();
+ var cns = this._constructor(type);
+ var chart = new cns(tag, dataCoordinator, tooltipUpdater, xType, colorScale);
+ var svg = d3.select(this.$.chartsvg);
+ this.async(function() {
+ chart.renderTo(svg);
+ this._chart = chart;
+ }, 350);
+ },
+ attached: function() {
+ this._initialized = true;
+ },
+ detached: function() {
+ this._initialized = false;
+ }
+ });
+ </script>
+</dom-module>
+
+<dom-module id="tf-collapsable-pane" assetpath="../tf-collapsable-pane/">
+ <template>
+ <button class="heading" on-tap="togglePane" open-button$="[[opened]]">
+ <span class="name">[[name]]</span>
+ <span class="hackpadding"></span>
+ <span class="count">
+ <span>[[count]]</span>
+ </span>
+ </button>
+ <iron-collapse opened="[[opened]]">
+ <div class="content">
+ <template is="dom-if" if="[[opened]]" restamp="[[restamp]]">
+ <content></content>
+ </template>
+ </div>
+ </iron-collapse>
+ <style>
+ :host {
+ display: block;
+ margin: 0 5px 1px 10px;
+ }
+
+ :host:first-of-type {
+ margin-top: 20px;
+ }
+
+ :host:last-of-type {
+ margin-bottom: 20px;
+ }
+
+ .heading {
+ background-color: white;
+ border-radius: 2px;
+ border: none;
+ cursor: pointer;
+ -webkit-tap-highlight-color: rgba(0,0,0,0);
+ width: 100%;
+ box-sizing: border-box;
+ font-size: 15px;
+ display: inline-flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: space-between;
+ line-height: 1;
+ box-shadow: 0 1px 5px rgba(0,0,0,0.2);
+ padding: 10px 15px;
+ }
+
+ .content {
+ padding: 15px;
+ border: 1px solid #dedede;
+ border-top: none;
+ border-bottom-left-radius: 2px;
+ border-bottom-right-radius: 2px;
+ background: white;
+ }
+
+ [open-button] {
+ border-bottom-left-radius: 0px !important;
+ border-bottom-right-radius: 0px !important;
+ }
+
+ .name {
+ flex-grow: 0;
+ }
+
+ .count {
+ flex-grow: 0;
+ float: right;
+ margin-right: 5px;
+ font-size: 12px;
+ color: var(--paper-grey-500);
+ }
+
+ .hackpadding {
+ /* An obnoxious hack, but I can't get justify-content: space-between to work */
+ flex-grow: 1;
+ }
+ </style>
+ </template>
+ <script>
+ Polymer({
+ is: "tf-collapsable-pane",
+ properties: {
+ opened: {type: Boolean, value: false},
+ restamp: {type: Boolean, value: true},
+ name: {type: String, observer: "hide"},
+ count: {type: Number},
+ },
+ hide: function() {
+ this.opened = false;
+ },
+ togglePane: function() {
+ this.opened = !this.opened;
+ }
+ });
+ </script>
+
+</dom-module>
+<dom-module id="warning-style" assetpath="../tf-dashboard-common/">
+ <template>
+ <style>
+ .warning {
+ max-width: 540px;
+ margin: 80px auto 0 auto;
+ }
+ </style>
+ </template>
+</dom-module>
+
+<dom-module id="tf-event-dashboard" assetpath="../tf-event-dashboard/">
+ <template>
+ <div id="plumbing">
+ <tf-url-generator router="[[router]]" out-runs-url="{{runsUrl}}" out-scalars-url-generator="{{scalarsUrlGen}}" id="urlGenerator"></tf-url-generator>
+
+ <tf-data-coordinator id="dataCoordinator" url-generator="[[scalarsUrlGen]]" run-to-tag="[[runToScalars]]" color-scale="[[colorScale]]" out-data-coordinator="{{dataCoordinator}}"></tf-data-coordinator>
+
+ <tf-run-generator id="runGenerator" url="[[runsUrl]]" out-run-to-scalars="{{runToScalars}}"></tf-run-generator>
+
+ <tf-color-scale id="colorScale" runs="[[_runs]]" out-color-scale="{{colorScale}}" out-class-scale="{{classScale}}"></tf-color-scale>
+
+ <tf-tooltip-coordinator id="tooltipCoordinator" out-tooltip-updater="{{tooltipUpdater}}" out-tooltip-map="{{tooltipMap}}" out-x-value="{{tooltipXValue}}" out-closest-run="{{closestRun}}"></tf-tooltip-coordinator>
+
+ </div>
+
+ <tf-dashboard-layout>
+ <div class="sidebar">
+ <div class="sidebar-section">
+ <tf-categorizer id="categorizer" tags="[[_visibleTags]]" categories="{{categories}}"></tf-categorizer>
+ <paper-checkbox id="download-option" checked="{{_show_download_links}}">Data download links</paper-checkbox>
+ </div>
+ <div class="sidebar-section">
+ <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>
+ </div>
+ </div>
+ <div class="center">
+ <template is="dom-if" if="[[!categories.length]]">
+ <div class="warning">
+ <p>
+ No scalar summary tags were found.
+ </p>
+ <p>
+ Maybe data hasn't loaded yet, or maybe you need
+ to add some <code>tf.scalar_summary</code> ops to your graph, and
+ serialize them using the <code>tf.training.summary_io.SummaryWriter</code>.
+ </p>
+ </div>
+ </template>
+ <template is="dom-repeat" items="[[categories]]">
+ <tf-collapsable-pane name="[[item.name]]" count="[[item.tags.length]]">
+ <div class="layout horizontal wrap">
+ <template is="dom-repeat" items="[[item.tags]]" as="tag">
+ <div class="card">
+ <span class="card-title">[[tag]]</span>
+ <div class="card-content">
+ <tf-chart tag="[[tag]]" type="scalar" id="chart" selected-runs="[[selectedRuns]]" x-type="[[xType]]" data-coordinator="[[dataCoordinator]]" color-scale="[[colorScale]]" on-keyup="toggleSelected" tabindex="2" tooltip-updater="[[tooltipUpdater]]"></tf-chart>
+ <paper-icon-button class="expand-button" shift$="[[_show_download_links]]" icon="fullscreen" on-tap="toggleSelected"></paper-icon-button>
+ </div>
+ <template is="dom-if" if="[[_show_download_links]]">
+ <div class="card-bottom-row">
+ <tf-downloader selected-runs="[[selectedRuns]]" tag="[[tag]]" url-fn="[[scalarsUrlGen]]" run-to-tag="[[runToScalars]]">
+ </tf-downloader>
+ </div>
+ </template>
+ </div>
+ </template>
+ </div>
+ </tf-collapsable-pane>
+ </template>
+ </div>
+ </tf-dashboard-layout>
+
+ <style include="dashboard-style"></style>
+ <style include="warning-style"></style>
+
+ </template>
+
+ <script>
+ Polymer({
+ is: "tf-event-dashboard",
+ properties: {
+ _runs: {
+ type: Array,
+ computed: "_getRuns(runToScalars)",
+ },
+ _visibleTags: {
+ type: Array,
+ computed: "_getVisibleTags(selectedRuns.*, runToScalars.*)"
+ },
+ _show_download_links: Boolean,
+ router: {
+ type: Object,
+ value: TF.Urls.productionRouter(),
+ },
+ },
+ observers: ['redraw(_show_download_links)'],
+ redraw: function(_show_download_links) {
+ var els = this.getElementsByTagName("tf-chart");
+ for (var i=0; i<els.length; i++) {
+ els[i].redraw();
+ }
+ },
+ _getRuns: function(runToScalars) {
+ return _.keys(runToScalars);
+ },
+ _getVisibleTags: function(selectedRunsChange, runsToScalarsChange) {
+ var keys = selectedRunsChange.base;
+ var dict = runsToScalarsChange.base;
+ return _.union.apply(null, keys.map(function(k) {return dict[k]}));
+ },
+ toggleSelected: function(e) {
+ var currentTarget = Polymer.dom(e.currentTarget);
+ var parentDiv = currentTarget.parentNode.parentNode;
+ parentDiv.classList.toggle("selected");
+ var chart = currentTarget.previousElementSibling;
+ if (chart) {
+ chart.redraw();
+ }
+ },
+ });
+ </script>
+</dom-module>
+
+<dom-module id="tf-histogram-dashboard" assetpath="../tf-histogram-dashboard/">
+ <template>
+ <div id="plumbing">
+ <tf-url-generator router="[[router]]" out-runs-url="{{runsUrl}}" out-compressed-histograms-url-generator="{{compressedHistogramsUrlGen}}" id="urlGenerator"></tf-url-generator>
+
+ <tf-data-coordinator id="dataCoordinator" url-generator="[[compressedHistogramsUrlGen]]" run-to-tag="[[runToCompressedHistograms]]" color-scale="[[colorScale]]" out-data-coordinator="{{dataCoordinator}}"></tf-data-coordinator>
+
+ <tf-run-generator id="runGenerator" url="[[runsUrl]]" out-run-to-compressed-histograms="{{runToCompressedHistograms}}"></tf-run-generator>
+
+ <tf-color-scale id="colorScale" runs="[[_runs]]" out-color-scale="{{colorScale}}" out-class-scale="{{classScale}}"></tf-color-scale>
+
+ <tf-tooltip-coordinator id="tooltipCoordinator" out-tooltip-updater="{{tooltipUpdater}}" out-tooltip-map="{{tooltipMap}}" out-x-value="{{tooltipXValue}}" out-closest-run="{{closestRun}}"></tf-tooltip-coordinator>
+ </div>
+
+ <tf-dashboard-layout>
+ <div class="sidebar">
+ <div class="sidebar-section">
+ <tf-categorizer id="categorizer" tags="[[_visibleTags]]" categories="{{categories}}"></tf-categorizer>
+ </div>
+ <div class="sidebar-section">
+ <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>
+ </div>
+ </div>
+
+ <div class="center">
+ <template is="dom-if" if="[[!categories.length]]">
+ <div class="warning">
+ <p>
+ No histogram tags were found.
+ </p>
+ <p>
+ Maybe data hasn't loaded yet, or maybe you need
+ to add some <code>tf.histogram_summary</code> ops to your graph, and
+ serialize them using the <code>tf.training.summary_io.SummaryWriter</code>.
+ </p>
+ </div>
+ </template>
+ <template is="dom-repeat" items="[[categories]]">
+ <tf-collapsable-pane name="[[item.name]]" count="[[_count(item.tags, selectedRuns.*, runToCompressedHistograms.*)]]">
+ <div class="layout horizontal wrap">
+ <template is="dom-repeat" items="[[item.tags]]" as="tag">
+ <template is="dom-repeat" items="[[selectedRuns]]" as="run">
+ <template is="dom-if" if="[[_exists(run, tag, runToCompressedHistograms.*)]]">
+ <div class="card">
+ <span class="card-title">[[tag]]</span>
+ <div class="card-content">
+ <tf-chart tag="[[tag]]" type="compressedHistogram" id="chart" selected-runs="[[_array(run)]]" x-type="[[xType]]" data-coordinator="[[dataCoordinator]]" color-scale="[[colorScale]]" on-keyup="toggleSelected" tabindex="2" tooltip-updater="[[tooltipUpdater]]"></tf-chart>
+ <paper-icon-button class="expand-button" icon="fullscreen" on-tap="toggleSelected"></paper-icon-button>
+ </div>
+ </div>
+ </template>
+ </template>
+ </template>
+ </div>
+ </tf-collapsable-pane>
+ </template>
+ </div>
+ </tf-dashboard-layout>
+
+ <style include="dashboard-style"></style>
+ <style include="warning-style"></style>
+ </template>
+
+ <script>
+ Polymer({
+ is: "tf-histogram-dashboard",
+ properties: {
+ _runs: {
+ type: Array,
+ computed: "_getRuns(runToCompressedHistograms)",
+ },
+ _visibleTags: {
+ type: Array,
+ computed: "_getVisibleTags(selectedRuns.*, runToCompressedHistograms.*)"
+ },
+ router: {
+ type: Object,
+ value: TF.Urls.productionRouter(),
+ },
+ },
+ _exists: function(run, tag, runToCompressedHistogramsChange) {
+ var runToCompressedHistograms = runToCompressedHistogramsChange.base;
+ return runToCompressedHistograms[run].indexOf(tag) !== -1;
+ },
+ _array: function(x) {
+ return [x];
+ },
+ _count: function(tags, selectedRunsChange, runToCompressedHistogramsChange) {
+ var selectedRuns = selectedRunsChange.base;
+ var runToCompressedHistograms = runToCompressedHistogramsChange.base;
+ var targetTags = {};
+ tags.forEach(function(t) {
+ targetTags[t] = true;
+ });
+ var count = 0;
+ selectedRuns.forEach(function(r) {
+ runToCompressedHistograms[r].forEach(function(t) {
+ if (targetTags[t]) {
+ count++;
+ }
+ });
+ });
+ return count;
+ },
+ _getRuns: function(runToCompressedHistograms) {
+ return _.keys(runToCompressedHistograms);
+ },
+ _getVisibleTags: function(selectedRunsChange, runToCompressedHistogramsChange) {
+ var keys = selectedRunsChange.base;
+ var dict = runToCompressedHistogramsChange.base;
+ return _.union.apply(null, keys.map(function(k) {return dict[k]}));
+ },
+ toggleSelected: function(e) {
+ var currentTarget = Polymer.dom(e.currentTarget);
+ var parentDiv = currentTarget.parentNode.parentNode;
+ parentDiv.classList.toggle("selected");
+ var chart = currentTarget.previousElementSibling;
+ if (chart) {
+ chart.redraw();
+ }
+ },
+ });
+ </script>
+</dom-module>
+
+<dom-module id="tf-image-loader" assetpath="../tf-image-dashboard/">
+ <style>
+ :host {
+ display: block;
+ }
+ img {
+ width: 100%;
+ height: 100%;
+ image-rendering: pixelated;
+ }
+ </style>
+ <template>
+ <iron-ajax id="ajax" auto="" url="[[metadataUrl]]" handle-as="json" debounce="50" last-response="{{imageMetadata}}" verbose="true"></iron-ajax>
+ <template is="dom-if" if="[[imageUrl]]">
+ <img src="[[imageUrl]]">
+ </template>
+ </template>
+ <script>
+ Polymer({
+ is: "tf-image-loader",
+ properties: {
+ run: String,
+ tag: String,
+ imagesGenerator: Function,
+ individualImageGenerator: Function,
+ imageMetadata: Array,
+ metadataUrl: {
+ type: String,
+ computed: "apply(imagesGenerator, tag, run)",
+ },
+ imageUrl: {
+ type: String,
+ computed: "getLastImage(imageMetadata, individualImageGenerator)",
+ },
+ },
+ apply: function(imagesGenerator, run, tag) {
+ return imagesGenerator(run, tag);
+ },
+ getLastImage: function(imageMetadata, individualImageGenerator) {
+ if (imageMetadata == null) {
+ return null;
+ }
+ var query = _.last(imageMetadata).query;
+ return individualImageGenerator(query);
+ },
+ });
+ </script>
+</dom-module>
+
+<dom-module id="tf-image-grid" assetpath="../tf-image-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]]" sort="_sort" as="tag">
+ <div class="image-row container noshrink">
+ <div class="tag-name-cell noshrink">
+ <span class="tag-name">[[tag]]</span>
+ </div>
+ <template is="dom-repeat" items="[[_runs]]" as="run">
+ <div class="image-cell noshrink">
+ <template is="dom-if" if="[[_exists(run, tag, runToImages.*)]]">
+ <tf-image-loader id="loader" run="[[run]]" tag="[[tag]]" images-generator="[[imagesGenerator]]" individual-image-generator="[[individualImageGenerator]]">
+ </tf-image-loader>
+ </template>
+ </div>
+ </template>
+ </div>
+ </template>
+ </div>
+ </div>
+ <style>
+ :host {
+ display: block;
+ height: 100%;
+ }
+ .container {
+ display: flex;
+ flex-wrap: nowrap;
+ }
+ #fullContainer {
+ width: 100%;
+ height: 100%;
+ flex-direction: column;
+ padding-top: 20px;
+ overflow: scroll;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ }
+ #topRow {
+ flex-direction: row;
+ }
+ #bottomContainer {
+ flex-direction: column;
+ height: 100%;
+ width: 100%;
+ }
+ .image-row {
+ flex-direction: row;
+ }
+ .image-cell {
+ width: 300px;
+ height: 300px;
+ border: 1px solid black;
+ }
+ .tag-name-cell {
+ height: 300px;
+ width: 300px;
+ display:flex;
+ flex-direction: column;
+ justify-content: center;
+ }
+ .tag-name {
+ word-wrap: break-word;
+ text-align: center;
+ white-space: nowrap;
+ }
+ .run-name-cell {
+ width: 300px;
+ height: 30px;
+ text-align: center;
+ }
+ .noshrink {
+ flex-shrink: 0;
+ }
+ #paddingCell {
+ width: 300px;
+ height: 30px;
+ }
+ </style>
+ </template>
+ <script>
+ Polymer({
+ is: "tf-image-grid",
+ properties: {
+ runToImages: Object,
+ _tags: {type: Array, computed: "_getTags(runToImages.*)"},
+ _runs: {type: Array, computed: "_getRuns(runToImages.*)"},
+ imagesGenerator: Function,
+ individualImageGenerator: Function,
+ },
+ _getTags: function(runToImages) {
+ return _.chain(runToImages.base).values().flatten().union().value();
+ },
+ _getRuns: function(runToImages) {
+ var r2i = runToImages.base;
+ return _.keys(r2i).filter(function(x) {return r2i[x].length > 0;});
+ },
+ _exists: function (run, tag, runToImages) {
+ runToImages = runToImages.base;
+ return runToImages[run].indexOf(tag) !== -1;
+ },
+ _sort: function(a, b) {
+ return a > b;
+ },
+ });
+ </script>
+</dom-module>
+
+<dom-module id="tf-image-dashboard" assetpath="../tf-image-dashboard/">
+ <template>
+ <div id="plumbing">
+ <tf-url-generator router="[[router]]" out-runs-url="{{runsUrl}}" out-images-url-generator="{{imagesUrlGen}}" out-individual-image-url-generator="{{individualImageUrlGen}}" id="urlGenerator"></tf-url-generator>
+
+ <tf-run-generator id="runGenerator" url="[[runsUrl]]" out-run-to-images="{{runToImages}}"></tf-run-generator>
+ </div>
+
+ <div class="center">
+ <template is="dom-if" if="[[!_hasImages(runToImages.*)]]">
+ <div class="warning">
+ <p>
+ No image tags were found.
+ </p>
+ <p>
+ Maybe data hasn't loaded yet, or maybe you need
+ to add some <code>tf.image_summary</code> ops to your graph, and
+ serialize them using the <code>tf.training.summary_io.SummaryWriter</code>.
+ </p>
+ </div>
+ </template>
+ <tf-image-grid id="imageGrid" run-to-images="[[runToImages]]" images-generator="[[imagesUrlGen]]" individual-image-generator="[[individualImageUrlGen]]"></tf-image-grid>
+ </div>
+
+ <style>
+ .center {
+ padding-left: 10px;
+ padding-right: 10px;
+ height: 100%;
+ width: 100%;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ }
+ :host {
+ height: 100%;
+ display: block;
+ }
+
+ </style>
+ <style include="warning-style"></style>
+ </template>
+ <script>
+ Polymer({
+ is: "tf-image-dashboard",
+ properties: {
+ runToImages: Object,
+ imagesUrlGen: Function,
+ individualImageUrlGen: Function,
+ router: {
+ type: Object,
+ value: TF.Urls.productionRouter(),
+ },
+ },
+ _hasImages: function(runToImagesChange) {
+ return _.values(runToImagesChange.base).some(function(arr) {
+ return arr.length > 0;
+ });
+ },
+ });
+ </script>
+</dom-module>
+
+<dom-module id="tf-graph-loader" assetpath="../tf-graph-loader/">
+</dom-module>
+
+<script>
+Polymer({
+
+ is: 'tf-graph-loader',
+
+ properties: {
+ /**
+ * @type {value: number, msg: string}
+ *
+ * A number between 0 and 100 denoting the % of progress
+ * for the progress bar and the displayed message.
+ */
+ progress: {
+ type: Object,
+ notify: true,
+ readOnly: true // Produces, does not consume.
+ },
+ datasets: Array,
+ hasStats: {
+ type: Boolean,
+ readOnly: true, // This property produces data.
+ notify: true
+ },
+ selectedDataset: Number,
+ selectedFile: {
+ type: Object,
+ observer: '_selectedFileChanged'
+ },
+ outGraphHierarchy: {
+ type: Object,
+ readOnly: true, //readonly so outsider can't change this via binding
+ notify: true
+ },
+ outGraph: {
+ type: Object,
+ readOnly: true, //readonly so outsider can't change this via binding
+ notify: true
+ },
+ outGraphName: {
+ type: String,
+ readOnly: true,
+ notify: true
+ }
+ },
+ observers: [
+ '_selectedDatasetChanged(selectedDataset, datasets)'
+ ],
+ _parseAndConstructHierarchicalGraph: function(dataset, pbTxtContent) {
+ var self = this;
+ // Reset the progress bar to 0.
+ self._setProgress({
+ value: 0,
+ msg: ''
+ });
+ var tracker = {
+ setMessage: function(msg) {
+ self._setProgress({
+ value: self.progress.value,
+ msg: msg
+ });
+ },
+ updateProgress: function(value) {
+ self._setProgress({
+ value: self.progress.value + value,
+ msg: self.progress.msg
+ });
+ },
+ reportError: function(msg) {
+ self._setProgress({
+ value: self.progress.value,
+ msg: msg,
+ error: true
+ });
+ },
+ };
+ var statsJson;
+ var dataTracker = tf.getSubtaskTracker(tracker, 30, 'Data');
+ tf.graph.parser.readAndParseData(dataset, pbTxtContent, dataTracker)
+ .then(function(result) {
+ // Build the flat graph (consists only of Op nodes).
+ var nodes = result.nodes;
+ statsJson = result.statsJson;
+
+ // This is the whitelist of inputs on op types that are considered
+ // reference edges. "Assign 0" indicates that the first input to
+ // an OpNode with operation type "Assign" is a reference edge.
+ var refEdges = {};
+ refEdges["Assign 0"] = true;
+ refEdges["AssignAdd 0"] = true;
+ refEdges["AssignSub 0"] = true;
+ refEdges["assign 0"] = true;
+ refEdges["assign_add 0"] = true;
+ refEdges["assign_sub 0"] = true;
+ refEdges["count_up_to 0"] = true;
+ refEdges["ScatterAdd 0"] = true;
+ refEdges["ScatterSub 0"] = true;
+ refEdges["ScatterUpdate 0"] = true;
+ refEdges["scatter_add 0"] = true;
+ refEdges["scatter_sub 0"] = true;
+ refEdges["scatter_update 0"] = true;
+ var buildParams = {
+ enableEmbedding: true,
+ inEmbeddingTypes: ['Const'],
+ outEmbeddingTypes: ['^[a-zA-Z]+Summary$'],
+ refEdges: refEdges
+ };
+ var graphTracker = tf.getSubtaskTracker(tracker, 20,
+ 'Graph');
+ return tf.graph.build(nodes, buildParams, graphTracker);
+ })
+ .then(function(graph) {
+ this._setOutGraph(graph);
+ if (statsJson) {
+ // If there are associated stats, join them with the graph.
+ tf.time('Joining stats info with graph...', function() {
+ tf.graph.joinStatsInfoWithGraph(graph, statsJson);
+ });
+ }
+ var hierarchyParams = {
+ verifyTemplate: true,
+ // If a set of numbered op nodes has at least this number of nodes
+ // then group them into a series node.
+ seriesNodeMinSize: 5,
+ };
+ var hierarchyTracker = tf.getSubtaskTracker(tracker, 50,
+ 'Namespace hierarchy');
+ return tf.graph.hierarchy.build(graph, hierarchyParams, hierarchyTracker);
+ }.bind(this))
+ .then(function(graphHierarchy) {
+ // Update the properties which notify the parent with the
+ // graph hierarchy and whether the data has live stats or not.
+ this._setHasStats(statsJson != null);
+ this._setOutGraphHierarchy(graphHierarchy);
+ }.bind(this))
+ .catch(function(reason) {
+ tracker.reportError("Graph visualization failed: " + reason);
+ });
+ },
+ _selectedDatasetChanged: function(datasetIndex, datasets) {
+ var dataset = datasets[datasetIndex];
+ this._parseAndConstructHierarchicalGraph(dataset);
+ this._setOutGraphName(dataset.name);
+ },
+ _selectedFileChanged: function(e) {
+ if (!e) {
+ return;
+ }
+ var file = e.target.files[0];
+ if (!file) {
+ return;
+ }
+
+ // Clear out the value of the file chooser. This ensures that if the user
+ // selects the same file, we'll re-read it.
+ e.target.value = '';
+
+ var reader = new FileReader();
+
+ reader.onload = function(e) {
+ this._parseAndConstructHierarchicalGraph(null, e.target.result);
+ }.bind(this);
+
+ reader.readAsText(file);
+ }
+});
+</script>
<script>/* Copyright 2015 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
@@ -300,7 +2943,7 @@ var tf;
}
/**
* Combines the specified stats with the current stats.
- * Modifies the current object. This methos is used to
+ * Modifies the current object. This method is used to
* compute aggregate stats for group nodes.
*/
NodeStats.prototype.combine = function (stats) {
@@ -1110,7 +3753,7 @@ var tf;
};
;
/**
- * Given the name of a node, return the names of its predecssors.
+ * Given the name of a node, return the names of its predecessors.
* For an OpNode, this will contain the targets from the underlying BaseEdges.
* For a GroupNode, this will contain the targets truncated to siblings of
* the shared ancestor.
@@ -1132,7 +3775,7 @@ var tf;
* into the details of which of of Z/Y's descendant nodes have predecessors to
* which of A's descendants.
*
- * On the other hand, for an OpNode it's clear what the final predecssors
+ * On the other hand, for an OpNode it's clear what the final predecessors
* ought to be. There is no ambiguity.
*/
HierarchyImpl.prototype.getPredecessors = function (nodeName) {
@@ -1172,7 +3815,7 @@ var tf;
}
return successors;
};
- /** Helper method for getPredeccessors and getSuccessors */
+ /** Helper method for getPredecessors and getSuccessors */
HierarchyImpl.prototype.getOneWayEdges = function (node, inEdges) {
var edges = { control: [], regular: [] };
// A node with no parent cannot have any edges.
@@ -1638,8 +4281,7 @@ limitations under the License.
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; }
- __.prototype = b.prototype;
- d.prototype = new __();
+ d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
/// <reference path="graph.ts" />
/// <reference path="hierarchy.ts" />
@@ -2001,7 +4643,7 @@ var tf;
// +----------------+
// | A |
// | +-----+ | +------+
- // | | B |>----->|>------->| Z |
+ // | | B |>-----\x3e|>-------\x3e| Z |
// | | | | | |
// | | | * | | |
// | | |<=====<|<=======<| |
@@ -2115,7 +4757,7 @@ var tf;
//
// +----------------------+
// | A +---+ |
- // | +------>| C | |
+ // | +------\x3e| C | |
// | | +---+ |
// | | ^ |
// | | | |
@@ -2142,7 +4784,7 @@ var tf;
//
// +----------------------+
// | A +---+ |
- // | +--->| C | |
+ // | +---\x3e| C | |
// | | +---+ |
// | +---+ ^ |
// | | B | | |
@@ -3441,9 +6083,9 @@ var tf;
*
* <g class="annotation">
* <g class="annotation-node">
- * <!--
+ * \x3c!--
* Content here determined by Scene.node.buildGroup.
- * -->
+ * --\x3e
* </g>
* </g>
*
@@ -3857,16 +6499,16 @@ var tf;
* ...
* </g>
* <g class="nodeshape">
- * <!--
+ * \x3c!--
* Content of the node shape should be for the node itself. For example a
* Metanode would have a <rect> with rounded edges, an op would have an
* <ellipse>. More complex nodes like series may contain multiple elements
* which are conditionally visible based on whether the node is expanded.
- * -->
+ * --\x3e
* </g>
* <text class="label">node name</text>
* <g class="subscene">
- * <!--
+ * \x3c!--
* Content of the subscene (only for metanode and series node).
*
* Subscene is a svg group that contains content of the
@@ -3874,7 +6516,7 @@ var tf;
*
* When the graph is expanded multiple times, a subscene can contain
* nested subscenes inside.
- * -->
+ * --\x3e
* </g>
* </g>
* ...
@@ -4845,7 +7487,7 @@ var tf;
});
});
// Shift all nodes and edge points to account for the left-padding amount,
- // and the invisble bridge nodes.
+ // and the invisible bridge nodes.
_.each(graph.nodes(), function (nodeName) {
var nodeInfo = graph.node(nodeName);
nodeInfo.x -= minX;
@@ -5148,8 +7790,8 @@ limitations under the License.
var tf;
(function (tf) {
/**
- * Mapping from color palette name to color pallette, which contains
- * exact colors for multiple states of a single color pallette.
+ * Mapping from color palette name to color palette, which contains
+ * exact colors for multiple states of a single color palette.
*/
tf.COLORS = [
{
@@ -5230,7 +7872,8 @@ var tf;
"active": "#424242",
"disabled": "F5F5F5" // 100
}
- ].reduce(function (m, c) {
+ ]
+ .reduce(function (m, c) {
m[c.name] = c;
return m;
}, {});
@@ -5241,2949 +7884,27 @@ var tf;
tf.OP_GROUP_COLORS = [
{
color: "Google Red",
- groups: ["gen_legacy_ops", "legacy_ops", "legacy_flogs_input",
+ 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;
- });
+ "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");
-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.
-==============================================================================*/
-/// <reference path="../../../../typings/tsd.d.ts" />
-/// <reference path="../common.ts" />
-var tf;
-(function (tf) {
- var scene;
- (function (scene) {
- /** Show minimap when the viewpoint area is less than X% of the whole area. */
- var FRAC_VIEWPOINT_AREA = 0.8;
- var Minimap = (function () {
- /**
- * Constructs a new minimap.
- *
- * @param svg The main svg element.
- * @param zoomG The svg group used for panning and zooming the main svg.
- * @param mainZoom The main zoom behavior.
- * @param minimap The minimap container.
- * @param maxWandH The maximum width/height for the minimap.
- * @param labelPadding Padding in pixels due to the main graph labels.
- */
- function Minimap(svg, zoomG, mainZoom, minimap, maxWandH, labelPadding) {
- var _this = this;
- this.svg = svg;
- this.labelPadding = labelPadding;
- this.zoomG = zoomG;
- this.mainZoom = mainZoom;
- this.maxWandH = maxWandH;
- var $minimap = d3.select(minimap);
- // The minimap will have 2 main components: the canvas showing the content
- // and an svg showing a rectangle of the currently zoomed/panned viewpoint.
- var $minimapSvg = $minimap.select("svg");
- // Make the viewpoint rectangle draggable.
- var $viewpoint = $minimapSvg.select("rect");
- var dragmove = function (d) {
- _this.viewpointCoord.x = d3.event.x;
- _this.viewpointCoord.y = d3.event.y;
- _this.updateViewpoint();
- };
- this.viewpointCoord = { x: 0, y: 0 };
- var drag = d3.behavior.drag().origin(Object).on("drag", dragmove);
- $viewpoint.datum(this.viewpointCoord).call(drag);
- // Make the minimap clickable.
- $minimapSvg.on("click", function () {
- if (d3.event.defaultPrevented) {
- // This click was part of a drag event, so suppress it.
- return;
- }
- // Update the coordinates of the viewpoint.
- var width = Number($viewpoint.attr("width"));
- var height = Number($viewpoint.attr("height"));
- var clickCoords = d3.mouse($minimapSvg.node());
- _this.viewpointCoord.x = clickCoords[0] - width / 2;
- _this.viewpointCoord.y = clickCoords[1] - height / 2;
- _this.updateViewpoint();
- });
- this.viewpoint = $viewpoint.node();
- this.minimapSvg = $minimapSvg.node();
- this.minimap = minimap;
- this.canvas = $minimap.select("canvas.first").node();
- this.canvasBuffer =
- $minimap.select("canvas.second").node();
- this.downloadCanvas =
- $minimap.select("canvas.download").node();
- d3.select(this.downloadCanvas).style("display", "none");
- }
- /**
- * Updates the position and the size of the viewpoint rectangle.
- * It also notifies the main svg about the new panned position.
- */
- Minimap.prototype.updateViewpoint = function () {
- // Update the coordinates of the viewpoint rectangle.
- d3.select(this.viewpoint)
- .attr("x", this.viewpointCoord.x)
- .attr("y", this.viewpointCoord.y);
- // Update the translation vector of the main svg to reflect the
- // new viewpoint.
- var mainX = -this.viewpointCoord.x * this.scaleMain / this.scaleMinimap;
- var mainY = -this.viewpointCoord.y * this.scaleMain / this.scaleMinimap;
- var zoomEvent = this.mainZoom.translate([mainX, mainY]).event;
- d3.select(this.zoomG).call(zoomEvent);
- };
- /**
- * Redraws the minimap. Should be called whenever the main svg
- * was updated (e.g. when a node was expanded).
- */
- Minimap.prototype.update = function () {
- var _this = this;
- // The origin hasn't rendered yet. Ignore making an update.
- if (this.zoomG.childElementCount === 0) {
- return;
- }
- var $download = d3.select("#graphdownload");
- this.download = $download.node();
- $download.on("click", function (d) {
- _this.download.href = _this.downloadCanvas.toDataURL("image/png");
- });
- var $svg = d3.select(this.svg);
- // Read all the style rules in the document and embed them into the svg.
- // The svg needs to be self contained, i.e. all the style rules need to be
- // embedded so the canvas output matches the origin.
- var stylesText = "";
- for (var k = 0; k < document.styleSheets.length; k++) {
- try {
- var cssRules = document.styleSheets[k].cssRules ||
- document.styleSheets[k].rules;
- if (cssRules == null) {
- continue;
- }
- for (var i = 0; i < cssRules.length; i++) {
- stylesText += cssRules[i].cssText + "\n";
- }
- }
- catch (e) {
- if (e.name !== "SecurityError") {
- throw e;
- }
- }
- }
- // Temporarily add the css rules to the main svg.
- var svgStyle = $svg.append("style");
- svgStyle.text(stylesText);
- // Temporarily remove the zoom/pan transform from the main svg since we
- // want the minimap to show a zoomed-out and centered view.
- var $zoomG = d3.select(this.zoomG);
- var zoomTransform = $zoomG.attr("transform");
- $zoomG.attr("transform", null);
- // Get the size of the entire scene.
- var sceneSize = this.zoomG.getBBox();
- // Since we add padding, account for that here.
- sceneSize.height += this.labelPadding * 2;
- sceneSize.width += this.labelPadding * 2;
- // Temporarily assign an explicit width/height to the main svg, since
- // it doesn't have one (uses flex-box), but we need it for the canvas
- // to work.
- $svg.attr({
- width: sceneSize.width,
- height: sceneSize.height,
- });
- // Since the content inside the svg changed (e.g. a node was expanded),
- // the aspect ratio have also changed. Thus, we need to update the scale
- // factor of the minimap. The scale factor is determined such that both
- // the width and height of the minimap are <= maximum specified w/h.
- this.scaleMinimap =
- this.maxWandH / Math.max(sceneSize.width, sceneSize.height);
- this.minimapSize = {
- width: sceneSize.width * this.scaleMinimap,
- height: sceneSize.height * this.scaleMinimap
- };
- // Update the size of the minimap's svg, the buffer canvas and the
- // viewpoint rect.
- d3.select(this.minimapSvg).attr(this.minimapSize);
- d3.select(this.canvasBuffer).attr(this.minimapSize);
- // Download canvas width and height are multiples of the style width and
- // height in order to increase pixel density of the PNG for clarity.
- d3.select(this.downloadCanvas).style({ width: sceneSize.width, height: sceneSize.height });
- d3.select(this.downloadCanvas).attr({ width: sceneSize.width * 3, height: sceneSize.height * 3 });
- if (this.translate != null && this.zoom != null) {
- // Update the viewpoint rectangle shape since the aspect ratio of the
- // map has changed.
- requestAnimationFrame(function () { return _this.zoom(); });
- }
- // Serialize the main svg to a string which will be used as the rendering
- // content for the canvas.
- var svgXml = (new XMLSerializer()).serializeToString(this.svg);
- // Now that the svg is serialized for rendering, remove the temporarily
- // assigned styles, explicit width and height and bring back the pan/zoom
- // transform.
- svgStyle.remove();
- $svg.attr({
- width: null,
- height: null
- });
- $zoomG.attr("transform", zoomTransform);
- var image = new Image();
- image.onload = function () {
- // Draw the svg content onto the buffer canvas.
- var context = _this.canvasBuffer.getContext("2d");
- context.clearRect(0, 0, _this.canvasBuffer.width, _this.canvasBuffer.height);
- context.drawImage(image, 0, 0, _this.minimapSize.width, _this.minimapSize.height);
- requestAnimationFrame(function () {
- // Hide the old canvas and show the new buffer canvas.
- d3.select(_this.canvasBuffer).style("display", null);
- d3.select(_this.canvas).style("display", "none");
- // Swap the two canvases.
- _a = [_this.canvasBuffer, _this.canvas], _this.canvas = _a[0], _this.canvasBuffer = _a[1];
- var _a;
- });
- var downloadContext = _this.downloadCanvas.getContext("2d");
- downloadContext.clearRect(0, 0, _this.downloadCanvas.width, _this.downloadCanvas.height);
- downloadContext.drawImage(image, 0, 0, _this.downloadCanvas.width, _this.downloadCanvas.height);
- };
- image.src = "data:image/svg+xml;base64," + btoa(svgXml);
- };
- /**
- * Handles changes in zooming/panning. Should be called from the main svg
- * to notify that a zoom/pan was performed and this minimap will update it's
- * viewpoint rectangle.
- *
- * @param translate The translate vector, or none to use the last used one.
- * @param scale The scaling factor, or none to use the last used one.
- */
- Minimap.prototype.zoom = function (translate, scale) {
- // Update the new translate and scale params, only if specified.
- this.translate = translate || this.translate;
- this.scaleMain = scale || this.scaleMain;
- // Update the location of the viewpoint rectangle.
- var svgRect = this.svg.getBoundingClientRect();
- var $viewpoint = d3.select(this.viewpoint);
- this.viewpointCoord.x = -this.translate[0] * this.scaleMinimap /
- this.scaleMain;
- this.viewpointCoord.y = -this.translate[1] * this.scaleMinimap /
- this.scaleMain;
- var viewpointWidth = svgRect.width * this.scaleMinimap / this.scaleMain;
- var viewpointHeight = svgRect.height * this.scaleMinimap / this.scaleMain;
- $viewpoint.attr({
- x: this.viewpointCoord.x,
- y: this.viewpointCoord.y,
- width: viewpointWidth,
- height: viewpointHeight
- });
- // Show/hide the minimap depending on the viewpoint area as fraction of the
- // whole minimap.
- var mapWidth = this.minimapSize.width;
- var mapHeight = this.minimapSize.height;
- var x = this.viewpointCoord.x;
- var y = this.viewpointCoord.y;
- var w = Math.min(Math.max(0, x + viewpointWidth), mapWidth) -
- Math.min(Math.max(0, x), mapWidth);
- var h = Math.min(Math.max(0, y + viewpointHeight), mapHeight) -
- Math.min(Math.max(0, y), mapHeight);
- var fracIntersect = (w * h) / (mapWidth * mapHeight);
- if (fracIntersect < FRAC_VIEWPOINT_AREA) {
- this.minimap.classList.remove("hidden");
- }
- else {
- this.minimap.classList.add("hidden");
- }
- };
- return Minimap;
- })();
- scene.Minimap = Minimap;
- })(scene = tf.scene || (tf.scene = {}));
-})(tf || (tf = {})); // close module tf.scene
-</script>
-
-
-
-
-
-
-
-</head><body><div hidden="" by-vulcanize=""><dom-module id="tf-data-coordinator" assetpath="../tf-event-dashboard/">
- <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.
-==============================================================================*/
-/// <reference path="../../typings/tsd.d.ts" />
-/// <reference path="../plottable/plottable.d.ts" />
-var TF;
-(function (TF) {
- /* The DataCoordinator generates TF.Datasets for each run/tag combination,
- * and is responsible for communicating with the backend to load data into them.
- * A key fact about this design is that when Datasets modify their data, they
- * automatically notify all dependent Plottable charts.
- */
- var DataCoordinator = (function () {
- function DataCoordinator(urlGenerator, runToTag) {
- this.datasets = {};
- this.urlGenerator = urlGenerator;
- this.runToTag = runToTag;
- }
- /* Create or return an array of Datasets for the given
- * tag and runs. It filters which runs it uses by checking
- * that data exists for each tag-run combination.
- * Calling this triggers a load on the dataset.
- */
- DataCoordinator.prototype.getDatasets = function (tag, runs) {
- var _this = this;
- var usableRuns = runs.filter(function (r) {
- var tags = _this.runToTag[r];
- return tags.indexOf(tag) !== -1;
- });
- return usableRuns.map(function (r) { return _this.getDataset(tag, r); });
- };
- /* Create or return a Dataset for given tag and run.
- * Calling this triggers a load on the dataset.
- */
- DataCoordinator.prototype.getDataset = function (tag, run) {
- var dataset = this._getDataset(tag, run);
- dataset.load();
- return dataset;
- };
- DataCoordinator.prototype._getDataset = function (tag, run) {
- var key = [tag, run].toString();
- var dataset;
- if (this.datasets[key] != null) {
- dataset = this.datasets[key];
- }
- else {
- dataset = new TF.Dataset(tag, run, this.urlGenerator);
- this.datasets[key] = dataset;
- }
- return dataset;
- };
- return DataCoordinator;
- })();
- TF.DataCoordinator = DataCoordinator;
-})(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 __extends = (this && this.__extends) || function (d, b) {
- for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
- function __() { this.constructor = d; }
- __.prototype = b.prototype;
- d.prototype = new __();
-};
-/// <reference path="../../typings/tsd.d.ts" />
-/// <reference path="../plottable/plottable.d.ts" />
-var TF;
-(function (TF) {
- /* An extension of Plottable.Dataset that knows how to load data from a backend.
- */
- var Dataset = (function (_super) {
- __extends(Dataset, _super);
- function Dataset(tag, run, urlGenerator) {
- _super.call(this, [], { tag: tag, run: run });
- this.load = _.debounce(this._load, 10);
- this.tag = tag;
- this.run = run;
- this.urlGenerator = urlGenerator;
- }
- Dataset.prototype._load = function () {
- var _this = this;
- var url = this.urlGenerator(this.tag, this.run);
- if (this.lastRequest != null) {
- this.lastRequest.abort();
- }
- this.lastRequest = d3.json(url, function (error, json) {
- _this.lastRequest = null;
- if (error) {
- /* tslint:disable */
- console.log(error);
- /* tslint:enable */
- throw new Error("Failure loading JSON at url: \"" + url + "\"");
- }
- else {
- _this.data(json);
- }
- });
- };
- return Dataset;
- })(Plottable.Dataset);
- TF.Dataset = Dataset;
-})(TF || (TF = {}));
-</script>
- <script>
- Polymer({
- is: "tf-data-coordinator",
- properties: {
- urlGenerator: Object,
- outDataCoordinator: {
- type: Object,
- computed: "getCoordinator(urlGenerator, runToTag)",
- notify: true,
- },
- },
- getCoordinator: function(generator, runToTag) {
- return new TF.DataCoordinator(generator, runToTag);
- }
- });
- </script>
-</dom-module>
-<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>
- .scrollbar::-webkit-scrollbar-track
- {
- visibility: hidden;
- }
-
- .scrollbar::-webkit-scrollbar
- {
- width: 10px;
- }
-
- .scrollbar::-webkit-scrollbar-thumb
- {
- border-radius: 10px;
- -webkit-box-shadow: inset 0 0 2px rgba(0,0,0,.3);
- background-color: var(--paper-grey-500);
- color: var(--paper-grey-900);
- }
- .scrollbar {
- box-sizing: border-box;
- }
- </style>
- </template>
-</dom-module>
-<dom-module id="run-color-style" assetpath="../tf-dashboard-common/">
- <template>
- <style>
- [color-class="light-blue"] paper-checkbox {
- --paper-checkbox-checked-color: var(--paper-light-blue-500);
- --paper-checkbox-checked-ink-color: var(--paper-light-blue-500);
- --paper-checkbox-unchecked-color: var(--paper-light-blue-900);
- --paper-checkbox-unchecked-ink-color: var(--paper-light-blue-900);
- }
- [color-class="red"] paper-checkbox {
- --paper-checkbox-checked-color: var(--paper-red-500);
- --paper-checkbox-checked-ink-color: var(--paper-red-500);
- --paper-checkbox-unchecked-color: var(--paper-red-900);
- --paper-checkbox-unchecked-ink-color: var(--paper-red-900);
- }
- [color-class="green"] paper-checkbox {
- --paper-checkbox-checked-color: var(--paper-green-500);
- --paper-checkbox-checked-ink-color: var(--paper-green-500);
- --paper-checkbox-unchecked-color: var(--paper-green-900);
- --paper-checkbox-unchecked-ink-color: var(--paper-green-900);
- }
- [color-class="purple"] paper-checkbox {
- --paper-checkbox-checked-color: var(--paper-purple-500);
- --paper-checkbox-checked-ink-color: var(--paper-purple-500);
- --paper-checkbox-unchecked-color: var(--paper-purple-900);
- --paper-checkbox-unchecked-ink-color: var(--paper-purple-900);
- }
- [color-class="teal"] paper-checkbox {
- --paper-checkbox-checked-color: var(--paper-teal-500);
- --paper-checkbox-checked-ink-color: var(--paper-teal-500);
- --paper-checkbox-unchecked-color: var(--paper-teal-900);
- --paper-checkbox-unchecked-ink-color: var(--paper-teal-900);
- }
- [color-class="pink"] paper-checkbox {
- --paper-checkbox-checked-color: var(--paper-pink-500);
- --paper-checkbox-checked-ink-color: var(--paper-pink-500);
- --paper-checkbox-unchecked-color: var(--paper-pink-900);
- --paper-checkbox-unchecked-ink-color: var(--paper-pink-900);
- }
- [color-class="orange"] paper-checkbox {
- --paper-checkbox-checked-color: var(--paper-orange-500);
- --paper-checkbox-checked-ink-color: var(--paper-orange-500);
- --paper-checkbox-unchecked-color: var(--paper-orange-900);
- --paper-checkbox-unchecked-ink-color: var(--paper-orange-900);
- }
- [color-class="brown"] paper-checkbox {
- --paper-checkbox-checked-color: var(--paper-brown-500);
- --paper-checkbox-checked-ink-color: var(--paper-brown-500);
- --paper-checkbox-unchecked-color: var(--paper-brown-900);
- --paper-checkbox-unchecked-ink-color: var(--paper-brown-900);
- }
- [color-class="indigo"] paper-checkbox {
- --paper-checkbox-checked-color: var(--paper-indigo-500);
- --paper-checkbox-checked-ink-color: var(--paper-indigo-500);
- --paper-checkbox-unchecked-color: var(--paper-indigo-900);
- --paper-checkbox-unchecked-ink-color: var(--paper-indigo-900);
- }
- </style>
- </template>
-</dom-module>
-<dom-module id="tf-multi-checkbox" assetpath="../tf-multi-checkbox/">
- <style include="scrollbar-style"></style>
- <style include="run-color-style"></style>
-
- <template>
- <div id="outer-container" class="scrollbar">
- <template is="dom-repeat" items="[[names]]" sort="[[_tooltipComparator(tooltips, tooltipOrderer)]]">
- <div class="run-row" color-class$="[[_applyColorClass(item, classScale)]]" null-tooltip$="[[_isNullTooltip(item, tooltips)]]" highlight$="[[_isHighlighted(item, highlights.*)]]">
- <div class="checkbox-container vertical-align-container">
- <paper-checkbox class="checkbox vertical-align-center" name="[[item]]" checked$="[[_isChecked(item,outSelected.*)]]" on-change="_checkboxChange"></paper-checkbox>
- </div>
- <div class="item-label-container">
- <span>[[item]]</span>
- </div>
- <div class="tooltip-value-container vertical-align-container">
- <span class="vertical-align-top">[[_lookupTooltip(item,tooltips)]]</span>
- </div>
- </div>
- </template>
- </div>
- <style>
- :host {
- display: flex;
- flex-direction: column;
- height: 100%;
- }
- #outer-container {
- overflow-y: scroll;
- overflow-x: hidden;
- width: 100%;
- flex-grow: 1;
- flex-shrink: 1;
- word-wrap: break-word;
- }
- .run-row {
- padding-top: 5px;
- padding-bottom: 5px;
- display: flex;
- flex-direction: row;
- font-size: 13px;
- }
- .checkbox-container {
- flex-grow: 0;
- flex-shrink: 0;
- }
- .checkbox {
- padding-left: 2px;
- width: 32px;
- }
- .item-label-container {
- flex-grow: 1;
- flex-shrink: 1;
- width: 0px; /* hack to get the flex-grow to work properly */
- }
- .tooltip-value-container {
- display: flex;
- justify-content: center;
- flex-grow: 0;
- flex-shrink: 0;
- text-align:right;
- padding-left: 2px;
- }
- .vertical-align-container {
- display: flex;
- justify-content: center;
- }
- .vertical-align-container .vertical-align-center {
- align-self: center;
- }
- .vertical-align-container .vertical-align-top {
- align-self: start;
- }
- [null-tooltip] {
- display: none;
- }
- [highlight] {
- font-weight: bold;
- }
- </style>
- </template>
-
- <script>
- Polymer({
- is: "tf-multi-checkbox",
- properties: {
- names: Array,
- tooltipOrderer: {
- /* Used to compute how to order the tooltips based on the tooltip value.
- * By default, it parses the tooltip strings as numbers.
- * If set to a falsey value, tooltips are always ordered lexicographically.
- */
- type: Function,
- value: function() {
- return function(x) {return +x;}
- },
- },
- tooltips: Object,
- highlights: Array,
- outSelected: {
- type: Array,
- notify: true,
- value: function() {
- return [];
- },
- },
- hideMissingTooltips: {
- // If we have tooltips, but some names are missing, do we hide them?
- type: Boolean,
- value: true,
- },
- classScale: Function, // map from run name to css class
- },
- observers: [
- "_initializeOutSelected(names.*)",
- ],
- _lookupTooltip: function(item, tooltips) {
- return tooltips != null ? tooltips[item] : null;
- },
- _isNullTooltip: function(item, tooltips) {
- if (!this.hideMissingTooltips) {
- return true;
- }
- if (tooltips == null) {
- return false;
- }
- return tooltips[item] == null;
- },
- _initializeOutSelected: function(change) {
- this.outSelected = change.base.slice();
- },
- _tooltipComparator: function(tooltips, tooltipOrderer) {
- return function(a, b) {
- if (!tooltips || !tooltipOrderer) {
- // if we're missing tooltips or orderer, do lexicogrpahic sort
- return a.localeCompare(b);
- }
- function getValue(x) {
- var value = tooltipOrderer(tooltips[x]);
- return value == null || _.isNaN(value) ? -Infinity : value;
- }
- var aValue = getValue(a);
- var bValue = getValue(b);
- return aValue === bValue ? a.localeCompare(b) : bValue - aValue;
- }
- },
- _checkboxChange: function(e) {
- var name = e.srcElement.name;
- var idx = this.outSelected.indexOf(name);
- var checked = e.srcElement.checked;
- if (checked && idx === -1) {
- this.push("outSelected", name);
- } else if (!checked && idx !== -1) {
- this.splice("outSelected", idx, 1);
- }
- },
- _isChecked: function(item, outSelectedChange) {
- var outSelected = outSelectedChange.base;
- return outSelected.indexOf(item) !== -1;
- },
- _initializeRuns: function(change) {
- this.outSelected = change.base.slice();
- },
- _applyColorClass: function(item, classScale) {
- // TODO: Update style just on the element that changes
- // and apply at microtask timing
- this.debounce("restyle", function (){
- this.updateStyles();
- }, 16);
- return classScale(item);
- },
- _isHighlighted: function(item, highlights) {
- return highlights.base.indexOf(item) !== -1;
- },
- });
- </script>
-
-</dom-module>
-<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>
- </div>
- <tf-multi-checkbox names="[[runs]]" tooltips="[[tooltips]]" highlights="[[_arrayify(closestRun)]]" out-selected="{{outSelected}}" class-scale="[[classScale]]" hide-missing-tooltips=""></tf-multi-checkbox>
- <paper-button class="x-button" id="toggle-all" on-tap="_toggleAll">
- Toggle All Runs
- </paper-button>
- <style>
- :host {
- display: flex;
- flex-direction: column;
- padding-bottom: 10px;
- box-sizing: border-box;
- }
- #top-text {
- width: 100%;
- flex-grow: 0;
- flex-shrink: 0;
- padding-right: 16px;
- padding-bottom: 6px;
- box-sizing: border-box;
- color: var(--paper-grey-800);
- }
- tf-multi-checkbox {
- display: flex;
- flex-grow: 1;
- flex-shrink: 1;
- height: 0px; /* hackhack So the flex-grow takes over and gives it space */
- }
- .x-button {
- font-size: 13px;
- background-color: var(--tb-ui-light-accent);
- 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;
- font-weight: normal;
- font-size: 14px;
- margin-bottom: 5px;
- }
- paper-button {
- margin-left: 0;
- }
- </style>
- </template>
- <script>
- Polymer({
- is: "tf-run-selector",
- properties: {
- outSelected: {type: Array, notify: true},
- // runs: an array of strings, representing the run names that may be chosen
- runs: Array,
- tooltips: {type: Object, value: null}, // {[run: string]: string}
- xValue: {type: String, value: null}, // the string representing run's x val
- xType: String, // string: relative, stpe, wall_time
- classScale: Object, // map from run name to color class (css)
- closestRun: {type: String, value: null}, // which run has a value closest to mouse coordinate
- },
- _toggleAll: function() {
- if (this.outSelected.length > 0) {
- this.outSelected = [];
- } else {
- this.outSelected = this.runs.slice();
- }
- },
- _arrayify: function(item) {
- return [item];
- },
- });
- </script>
-</dom-module>
-<dom-module id="tf-x-type-selector" assetpath="../tf-event-dashboard/">
- <template>
- <div id="buttons">
- <h3>Horizontal Axis</h3>
- <paper-button class="x-button selected" id="step" on-tap="_select">
- step
- </paper-button>
- <paper-button class="x-button" id="relative" on-tap="_select">
- relative
- </paper-button>
- <paper-button class="x-button" id="wall_time" on-tap="_select">
- wall
- </paper-button>
- </div>
- <style>
- .x-button {
- width: 30%;
- font-size: 13px;
- background: none;
- margin-top: 10px;
- color: var(--tb-ui-dark-accent);
- }
-
- .x-button:first-of-type {
- margin-left: 0;
- }
-
- .x-button.selected {
- background-color: var(--tb-ui-dark-accent);
- color: white!important;
- }
-
- #buttons h3 {
- color: var(--paper-grey-800);
- margin: 0;
- font-weight: normal;
- font-size: 14px;
- margin-bottom: 5px;
- }
- </style>
- </template>
- <script>
- Polymer({
- is: "tf-x-type-selector",
- properties: {
- outXType: {type: String, notify: true, readOnly: true, value: "step"},
- },
- _select: function(e) {
- var _this = this;
- ["step", "wall_time", "relative"].forEach(function(id) {
- _this.$[id].classList.remove("selected");
- });
- this._setOutXType(e.currentTarget.id);
- e.currentTarget.classList.add("selected");
- },
- });
- </script>
-</dom-module>
-<dom-module id="tf-run-generator" assetpath="../tf-dashboard-common/">
- <template>
- <iron-ajax id="ajax" auto="" url="[[url]]" handle-as="json" debounce="300" on-response="_setResponse" verbose="true">
- </iron-ajax>
- </template>
- <script>
- Polymer({
- is: "tf-run-generator",
- properties: {
- url: String,
- _runToTag: {
- type: Object,
- readOnly: true,
- },
- outRunToScalars: {
- // {[runName: string]: string[]}
- // the names of scalar tags.
- type: Object,
- computed: "_scalars(_runToTag.*)",
- notify: true,
- },
- outRunToHistograms: {
- // {[runName: string]: string[]}
- // the names of histogram tags.
- type: Object,
- computed: "_histograms(_runToTag.*)",
- notify: true,
- },
- outRunToCompressedHistograms: {
- // {[runName: string]: string[]}
- // the names of histogram tags.
- type: Object,
- computed: "_compressedHistograms(_runToTag.*)",
- notify: true,
- },
- outRunToImages: {
- // {[runName: string]: string[]}
- // the names of image tags.
- type: Object,
- computed: "_images(_runToTag.*)",
- notify: true,
- },
- outRunsWithGraph: {
- // ["run1", "run2", ...]
- // array of run names that have an associated graph definition.
- type: Array,
- computed: "_graphs(_runToTag.*)",
- notify: true
- }
- },
- _scalars: function(_runToTag) {
- return _.mapValues(_runToTag.base, "scalars");
- },
- _histograms: function(_runToTag) {
- return _.mapValues(_runToTag.base, "histograms");
- },
- _compressedHistograms: function(_runToTag) {
- return _.mapValues(_runToTag.base, "compressedHistograms");
- },
- _images: function(_runToTag) {
- return _.mapValues(_runToTag.base, "images");
- },
- _graphs: function(_runToTag) {
- var runsWithGraph = [];
- _.each(_runToTag.base, function(runInfo, runName) {
- if (runInfo.graph === true) {
- runsWithGraph.push(runName);
- }
- });
- return runsWithGraph;
- },
- _setResponse: function(event) {
- this._set_runToTag(event.detail.response);
- }
- });
- </script>
-</dom-module>
-<dom-module id="tf-color-scale" assetpath="../tf-event-dashboard/">
- <script>
- (function() {
- // TODO(danmane) - get Plottable team to make an API point for this
- Plottable.Scales.Color._LOOP_LIGHTEN_FACTOR = 0;
- var classColorPairs = [
- ["light-blue", "#03A9F4"],
- ["red" , "#f44366"],
- ["green" , "#4CAF50"],
- ["purple" , "#9c27b0"],
- ["teal" , "#009688"],
- ["pink" , "#e91e63"],
- ["orange" , "#ff9800"],
- ["brown" , "#795548"],
- ["indigo" , "#3f51b5"],
- ];
- var classes = _.pluck(classColorPairs, 0);
- var colors = _.pluck(classColorPairs, 1);
- Polymer({
- is: "tf-color-scale",
- properties: {
- runs: Array,
- outClassScale: {
- type: Object,
- notify: true,
- readOnly: true,
- value: function() {
- return new d3.scale.ordinal().range(classes);
- },
- // TODO(danmane): the class scale will not update if the domain changes.
- // this behavior is inconsistent with the ColorScale.
- // in practice we don't change runs after initial load so it's not currently an issue
- },
- outColorScale: {
- type: Object,
- notify: true,
- readOnly: true,
- value: function() {
- var scale = new Plottable.Scales.Color().range(colors);
- scale.onUpdate(this._notifyColorScaleDomainChange.bind(this));
- return scale;
- },
- },
- },
- observers: ["_changeRuns(runs.*)"],
- _changeRuns: function(runs) {
- this.outClassScale.domain(this.runs);
- this.outColorScale.domain(this.runs);
- },
- _notifyColorScaleDomainChange: function() {
- this.notifyPath("outColorScale.domain_path", this.outColorScale.domain());
- this.outColorScale.domain_path = null;
- },
- });
- })();
- </script>
-</dom-module>
-<dom-module id="tf-url-generator" assetpath="../tf-dashboard-common/">
- <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.
-==============================================================================*/
-/// <reference path="../../typings/tsd.d.ts" />
-/// <reference path="../plottable/plottable.d.ts" />
-var TF;
-(function (TF) {
- var Urls;
- (function (Urls) {
- ;
- Urls.routes = ["runs", "scalars", "histograms",
- "compressedHistograms", "images",
- "individualImage", "graph"];
- function productionRouter() {
- /* The standard router for communicating with the TensorBoard backend */
- function standardRoute(route) {
- return function (tag, run) {
- return "/data/" + route + "?tag=" + encodeURIComponent(tag)
- + "&run=" + encodeURIComponent(run);
- };
- }
- function individualImageUrl(query) {
- return "/data/individualImage?" + query;
- }
- function graphUrl(run) {
- return "/data/graph?run=" + encodeURIComponent(run);
- }
- return {
- runs: function () { return "/data/runs"; },
- individualImage: individualImageUrl,
- graph: graphUrl,
- scalars: standardRoute("scalars"),
- histograms: standardRoute("histograms"),
- compressedHistograms: standardRoute("compressedHistograms"),
- images: standardRoute("images"),
- };
- }
- Urls.productionRouter = productionRouter;
- ;
- function demoRouter(dataDir) {
- /* Retrieves static .json data generated by demo_from_server.py */
- function demoRoute(route) {
- return function (tag, run) {
- run = run.replace(/[ \)\(]/g, "_");
- tag = tag.replace(/[ \)\(]/g, "_");
- return dataDir + "/" + route + "/" + run + "/" + tag + ".json";
- };
- }
- ;
- function individualImageUrl(query) {
- return dataDir + "/individualImage/" + query + ".png";
- }
- ;
- function graphUrl(run) {
- run = run.replace(/ /g, "_");
- return dataDir + "/graph/" + run + ".pbtxt";
- }
- ;
- return {
- runs: function () { return dataDir + "/runs.json"; },
- individualImage: individualImageUrl,
- graph: graphUrl,
- scalars: demoRoute("scalars"),
- histograms: demoRoute("histograms"),
- compressedHistograms: demoRoute("compressedHistograms"),
- images: demoRoute("images"),
- };
- }
- Urls.demoRouter = demoRouter;
- })(Urls = TF.Urls || (TF.Urls = {}));
-})(TF || (TF = {}));
-</script>
- <script>
- var polymerObject = {
- is: "tf-url-generator",
- _computeRuns: function(router) {
- return router.runs();
- },
- properties: {
- router: {
- type: Object,
- },
- outRunsUrl: {
- type: String,
- computed: "_computeRuns(router)",
- readOnly: true,
- notify: true,
- },
- },
- };
- TF.Urls.routes.forEach(function(route) {
- /* for each route (other than runs, handled seperately):
- * out`RouteName`: {
- * type: Function,
- * readOnly: true,
- * notify: true,
- * value: function() {
- * return TF.Urls.`routeName`Url;
- * }
- */
- if (route === "runs") {
- return;
- }
- var computeFnName = "_compute" + route;
- polymerObject[computeFnName] = function(router) {
- return router[route];
- };
- var urlName = route + "Url";
- var propertyName = Polymer.CaseMap.dashToCamelCase("out-" + urlName + "Generator");
- polymerObject.properties[propertyName] = {
- type: Function,
- computed: computeFnName + "(router)",
- notify: true,
- }
- });
- Polymer(polymerObject);
- </script>
-</dom-module>
-<dom-module id="tf-dashboard-layout" assetpath="../tf-dashboard-common/">
- <template>
- <div id="sidebar">
- <content select=".sidebar"></content>
- </div>
-
- <div id="center" class="scrollbar">
- <content select=".center"></content>
- </div>
- <style include="scrollbar-style"></style>
- <style>
- #sidebar {
- width: inherit;
- height: 100%;
- overflow: ellipsis;
- flex-grow: 0;
- flex-shrink: 0;
- }
-
- #center {
- height: 100%;
- overflow-y: scroll;
- flex-grow: 1;
- flex-shrink: 1;
- }
-
- .tf-graph-dashboard #center {
- background: white;
- }
-
- :host {
- display: flex;
- flex-direction: row;
- height: 100%;
- }
- </style>
- </template>
- <script>
- Polymer({
- is: "tf-dashboard-layout",
- });
- </script>
-</dom-module>
-<dom-module id="dashboard-style" assetpath="../tf-dashboard-common/">
- <template>
- <style>
- .card {
- height: 200px;
- width: 300px;
- display: flex;
- flex-direction: column;
- margin: 5px;
- padding: 0 30px 30px 0;
- -webkit-user-select: none;
- -moz-user-select: none;
- position: relative;
- }
-
- .card .card-title {
- flex-grow: 0;
- flex-shrink: 0;
- margin-bottom: 10px;
- font-size: 14px;
- text-overflow: ellipsis;
- overflow: hidden;
- }
-
- .card .card-content {
- flex-grow: 1;
- flex-shrink: 1;
- display: flex;
- }
- .card .card-bottom-row {
- flex-grow: 0;
- flex-shrink: 0;
- padding-left: 10px;
- padding-right: 10px;
- }
-
- .card.selected {
- height: 400px;
- width: 100%;
- }
-
- [shift] {
- bottom: 20px !important;
- }
-
- .expand-button {
- position: absolute;
- left: 0px;
- bottom: 20px;
- color: #2196F3;
- display: block;
- }
-
- #content-container{
- display: block;
- }
-
- .sidebar {
- display: flex;
- flex-direction: column;
- height: 100%;
- margin-right: 20px;
- }
-
- #categorizer {
- flex-shrink: 0;
- }
-
- #xTypeSelector {
- flex-shrink: 0;
- margin: 20px 0;
- }
-
- #runSelector {
- flex-shrink: 1;
- flex-grow: 1;
- }
-
- .sidebar-section {
- border-top: solid 1px rgba(0, 0, 0, 0.12);
- padding: 20px 0px 20px 30px;
- }
-
- .sidebar-section:first-child {
- border: none;
- }
-
- .sidebar-section:last-child {
- flex-grow: 1;
- display: flex;
- }
-
- paper-checkbox {
- --paper-checkbox-checked-color: var(--tb-ui-dark-accent);
- --paper-checkbox-unchecked-color: var(--tb-ui-dark-accent);
- font-size: 14px;
- }
-
- </style>
- </template>
-</dom-module>
-<dom-module id="tf-downloader" assetpath="../tf-dashboard-common/">
- <template>
- <paper-dropdown-menu no-label-float="true" label="run to download" selected-item-label="{{_run}}">
- <paper-menu class="dropdown-content">
- <template is="dom-repeat" items="[[_runs]]">
- <paper-item no-label-float="true">[[item]]</paper-item>
- </template>
- </paper-menu>
- </paper-dropdown-menu>
- <a download="[[_csvName(_run)]]" href="[[_csvUrl(_run, urlFn)]]">CSV</a>
- <a download="[[_jsonName(_run)]]" href="[[_jsonUrl(_run, urlFn)]]">JSON</a>
- <style>
- :host {
- display: block;
- }
- paper-dropdown-menu {
- width: 220px;
- --paper-input-container-label: {
- font-size: 10px;
- }
- --paper-input-container-input: {
- font-size: 10px;
- }
- }
- a {
- font-size: 10px;
- border-radius: 3px;
- border: 1px solid #EEE;
- }
- paper-input {
- font-size: 22px;
- }
- </style>
- </template>
- <script>
- Polymer({
- is: "tf-downloader",
- properties: {
- _run: String,
- _runs: {
- type: Array,
- computed: "_computeRuns(runToTag.*, selectedRuns.*)",
- },
- selectedRuns: Array,
- runToTag: Object,
- tag: String,
- urlFn: Function,
- },
- _computeRuns: function(runToTagChange, selectedRunsChange) {
- var runToTag = this.runToTag;
- var tag = this.tag;
- return this.selectedRuns.filter(function(x) {
- return runToTag[x].indexOf(tag) !== -1;
- })
- },
- _csvUrl: function(_run, urlFn) {
- return urlFn(this.tag, _run) + "&format=csv";
- },
- _jsonUrl: function(_run, urlFn) {
- return urlFn(this.tag, _run);
- },
- _csvName: function(_run) {
- return "run_" + _run + ",tag_" + this.tag + ".csv";
- },
- _jsonName: function(_run) {
- return "run-" + _run + "-tag-" + this.tag + ".json";
- },
- });
- </script>
-</dom-module>
-<dom-module id="tf-regex-group" assetpath="../tf-regex-group/">
- <template>
- <div class="regex-list">
- <template is="dom-repeat" items="{{rawRegexes}}">
- <div class="regex-line">
- <paper-checkbox class="active-button" checked="{{item.active}}" disabled="[[!item.valid]]"></paper-checkbox>
- <paper-input id="text-input" class="regex-input" label="Regex filter" no-label-float="" bind-value="{{item.regex}}" invalid="[[!item.valid]]" on-keyup="moveFocus"></paper-input>
- <paper-icon-button icon="close" class="delete-button" aria-label="Delete Regex" tabindex="0" on-tap="deleteRegex"></paper-icon-button>
- </div>
- <style>
- .regex-input {
- width: 230px;
- display: inline-block;
- margin-left: -3px;
- }
-
- paper-checkbox {
- --paper-checkbox-checked-color: var(--tb-ui-dark-accent);
- --paper-checkbox-unchecked-color: var(--tb-ui-dark-accent);
- }
-
- .delete-button {
- color: var(--paper-grey-700);
- width: 40px;
- height: 40px;
- margin-right: -10px;
- }
-
- .regex-list {
- margin-bottom: 10px;
- }
-
- paper-input {
- --paper-input-container-focus-color: var(--tb-orange-strong);
- --paper-input-container-input: {
- font-size: 14px;
- };
- --paper-input-container-label: {
- font-size: 14px;
- };
- }
- </style>
- </template>
- </div>
- </template>
- <script>
- Polymer({
- is: "tf-regex-group",
- properties: {
- rawRegexes: {
- type: Array,
- value: function() {
- return [{regex: "", active: true, valid: true}];
- }
- },
- regexes: {type: Array, computed: "usableRegexes(rawRegexes.*)", notify: true},
- },
- observers: [
- "addNewRegexIfNeeded(rawRegexes.*)",
- "checkValidity(rawRegexes.*)",
- ],
- checkValidity: function(x) {
- var match = x.path.match(/rawRegexes\.(\d+)\.regex/);
- if (match) {
- var idx = match[1];
- this.set("rawRegexes." + idx + ".valid", this.isValid(x.value));
- }
- },
- isValid: function(s) {
- try {
- new RegExp(s);
- return true;
- } catch (e) {
- return false;
- }
- },
- usableRegexes: function(regexes) {
- var isValid = this.isValid;
- return regexes.base.filter(function (r) {
- // Checking validity here (rather than using the data property)
- // is necessary because otherwise we might send invalid regexes due
- // to the fact that this function can call before the observer does
- return r.regex !== "" && r.active && isValid(r.regex);
- }).map(function(r) {
- return r.regex;
- });
- },
- addNewRegexIfNeeded: function() {
- var last = this.rawRegexes[this.rawRegexes.length - 1];
- if (last.regex !== "") {
- this.push("rawRegexes", {regex: "", active: true, valid: true});
- }
- },
- deleteRegex: function(e) {
- if (this.rawRegexes.length > 1) {
- this.splice("rawRegexes", e.model.index, 1);
- }
- },
- moveFocus: function(e) {
- if (e.keyCode === 13) {
- var idx = e.model.index;
- var inputs = Polymer.dom(this.root).querySelectorAll(".regex-input");
- if (idx < this.rawRegexes.length - 1) {
- inputs[idx+1].$.input.focus();
- } else {
- document.activeElement.blur();
- }
- }
- }
- });
- </script>
-</dom-module>
-<dom-module id="tf-categorizer" assetpath="../tf-categorizer/">
- <template>
- <div class="inputs">
- <tf-regex-group id="regex-group" regexes="{{regexes}}"></tf-regex-group>
- </div>
- <div id="underscore-categorization">
- <paper-checkbox checked="{{splitOnUnderscore}}">Split on underscores</paper-checkbox>
- </div>
- <style>
- :host {
- display: block;
- padding-bottom: 15px;
- }
- paper-checkbox {
- --paper-checkbox-checked-color: var(--paper-grey-600);
- --paper-checkbox-unchecked-color: var(--paper-grey-600);
- font-size: 14px;
- }
- #underscore-categorization {
- color: var(--paper-grey-700);
- }
- </style>
- </template>
- <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.
-==============================================================================*/
-/// <reference path="../../typings/tsd.d.ts" />
-var Categorizer;
-(function (Categorizer) {
- /* Canonical TensorFlow ops are namespaced using forward slashes.
- * This fallback categorizer categorizes by the top-level namespace.
- */
- Categorizer.topLevelNamespaceCategorizer = splitCategorizer(/\//);
- // Try to produce good categorizations on legacy graphs, which often
- // are namespaced like l1_foo/bar or l2_baz/bam.
- // If there is no leading underscore before the first forward slash,
- // then it behaves the same as topLevelNamespaceCategorizer
- Categorizer.legacyUnderscoreCategorizer = splitCategorizer(/[\/_]/);
- function fallbackCategorizer(s) {
- switch (s) {
- case "TopLevelNamespaceCategorizer":
- return Categorizer.topLevelNamespaceCategorizer;
- case "LegacyUnderscoreCategorizer":
- return Categorizer.legacyUnderscoreCategorizer;
- default:
- throw new Error("Unrecognized categorization strategy: " + s);
- }
- }
- Categorizer.fallbackCategorizer = fallbackCategorizer;
- /* An "extractor" is a function that takes a tag name, and "extracts" a category name.
- * This function takes an extractor, and produces a categorizer.
- * Currently, it is just used for the fallbackCategorizer, but we may want to
- * refactor the general categorization logic to use the concept of extractors.
- */
- function extractorToCategorizer(extractor) {
- return function (tags) {
- if (tags.length === 0) {
- return [];
- }
- var sortedTags = tags.slice().sort();
- var categories = [];
- var currentCategory = {
- name: extractor(sortedTags[0]),
- tags: [],
- };
- sortedTags.forEach(function (t) {
- var topLevel = extractor(t);
- if (currentCategory.name !== topLevel) {
- categories.push(currentCategory);
- currentCategory = {
- name: topLevel,
- tags: [],
- };
- }
- currentCategory.tags.push(t);
- });
- categories.push(currentCategory);
- return categories;
- };
- }
- function splitCategorizer(r) {
- var extractor = function (t) {
- return t.split(r)[0];
- };
- return extractorToCategorizer(extractor);
- }
- function defineCategory(ruledef) {
- var r = new RegExp(ruledef);
- var f = function (tag) {
- return r.test(tag);
- };
- return { name: ruledef, matches: f };
- }
- Categorizer.defineCategory = defineCategory;
- function _categorizer(rules, fallback) {
- return function (tags) {
- var remaining = d3.set(tags);
- var userSpecified = rules.map(function (def) {
- var tags = [];
- remaining.forEach(function (t) {
- if (def.matches(t)) {
- tags.push(t);
- }
- });
- var cat = { name: def.name, tags: tags.sort() };
- return cat;
- });
- var defaultCategories = fallback(remaining.values());
- return userSpecified.concat(defaultCategories);
- };
- }
- Categorizer._categorizer = _categorizer;
- function categorizer(s) {
- var rules = s.categoryDefinitions.map(defineCategory);
- var fallback = fallbackCategorizer(s.fallbackCategorizer);
- return _categorizer(rules, fallback);
- }
- Categorizer.categorizer = categorizer;
- ;
-})(Categorizer || (Categorizer = {}));
-</script>
- <script>
- Polymer({
- is: "tf-categorizer",
- properties: {
- regexes: {type: Array},
- tags: {type: Array},
- categoriesAreExclusive: {type: Boolean, value: true},
- fallbackCategorizer: {
- type: String,
- computed: "chooseFallbackCategorizer(splitOnUnderscore)"
- },
- splitOnUnderscore: {
- type: Boolean,
- value: false,
- },
- categorizer: {
- type: Object,
- computed: "computeCategorization(regexes.*, categoriesAreExclusive, fallbackCategorizer)",
- },
- categories: {type: Array, value: function() {return [];}, notify: true, readOnly: true},
- },
- observers: ['recategorize(tags.*, categorizer)'],
- computeCategorization: function(regexes, categoriesAreExclusive, fallbackCategorizer) {
- var categorizationStrategy = {
- categoryDefinitions: regexes.base,
- categoriesAreExclusive: categoriesAreExclusive,
- fallbackCategorizer: fallbackCategorizer,
- };
- return Categorizer.categorizer(categorizationStrategy);
- },
- recategorize: function() {
- this.debounce("tf-categorizer-recategorize", function (){
- var categories = this.categorizer(this.tags);
- this._setCategories(categories);
- })
- },
- chooseFallbackCategorizer: function(splitOnUnderscore) {
- if (splitOnUnderscore) {
- return "LegacyUnderscoreCategorizer";
- } else {
- return "TopLevelNamespaceCategorizer";
- }
- },
- });
- </script>
-</dom-module>
-<dom-module id="tf-chart" assetpath="../tf-event-dashboard/">
- <template>
- <svg id="chartsvg"></svg>
- <style>
- :host {
- -webkit-user-select: none;
- -moz-user-select: none;
- display: flex;
- flex-direction: column;
- flex-grow: 1;
- flex-shrink: 1;
- }
- svg {
- -webkit-user-select: none;
- -moz-user-select: none;
- flex-grow: 1;
- flex-shrink: 1;
- }
- .plottable .crosshairs line.guide-line {
- stroke: #777;
- }
- </style>
- </template>
- <script>/* 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 __extends = (this && this.__extends) || function (d, b) {
- for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
- function __() { this.constructor = d; }
- __.prototype = b.prototype;
- d.prototype = new __();
-};
-var Plottable;
-(function (Plottable) {
- var DragZoomLayer = (function (_super) {
- __extends(DragZoomLayer, _super);
- /* Constructs a SelectionBoxLayer with an attached DragInteraction and ClickInteraction.
- * On drag, it triggers an animated zoom into the box that was dragged.
- * On double click, it zooms back out to the original view, before any zooming.
- * The zoom animation uses an easing function (default d3.ease("cubic-in-out")) and is customizable.
- * Usage: Construct the selection box layer and attach x and y scales, and then add the layer
- * over the plot you are zooming on using a Component Group.
- * TODO(danmane) - merge this into Plottable
- */
- function DragZoomLayer(xScale, yScale) {
- _super.call(this);
- this.isZoomed = false;
- this.easeFn = d3.ease("cubic-in-out");
- this._animationTime = 750;
- this.xScale(xScale);
- this.yScale(yScale);
- this._dragInteraction = new Plottable.Interactions.Drag();
- this._dragInteraction.attachTo(this);
- this._doubleClickInteraction = new Plottable.Interactions.DoubleClick();
- this._doubleClickInteraction.attachTo(this);
- this.setupCallbacks();
- }
- DragZoomLayer.prototype.setupCallbacks = function () {
- var _this = this;
- var dragging = false;
- this._dragInteraction.onDragStart(function (startPoint) {
- _this.bounds({
- topLeft: startPoint,
- bottomRight: startPoint,
- });
- });
- this._dragInteraction.onDrag(function (startPoint, endPoint) {
- _this.bounds({ topLeft: startPoint, bottomRight: endPoint });
- _this.boxVisible(true);
- dragging = true;
- });
- this._dragInteraction.onDragEnd(function (startPoint, endPoint) {
- _this.boxVisible(false);
- _this.bounds({ topLeft: startPoint, bottomRight: endPoint });
- if (dragging) {
- _this.zoom();
- }
- dragging = false;
- });
- this._doubleClickInteraction.onDoubleClick(this.unzoom.bind(this));
- };
- DragZoomLayer.prototype.animationTime = function (animationTime) {
- if (animationTime == null) {
- return this._animationTime;
- }
- if (animationTime < 0) {
- throw new Error("animationTime cannot be negative");
- }
- this._animationTime = animationTime;
- return this;
- };
- /* Set the easing function, which determines how the zoom interpolates over time. */
- DragZoomLayer.prototype.ease = function (fn) {
- if (typeof (fn) !== "function") {
- throw new Error("ease function must be a function");
- }
- if (fn(0) !== 0 || fn(1) !== 1) {
- Plottable.Utils.Window.warn("Easing function does not maintain invariant f(0)==0 && f(1)==1. Bad behavior may result.");
- }
- this.easeFn = fn;
- return this;
- };
- // Zoom into extent of the selection box bounds
- DragZoomLayer.prototype.zoom = function () {
- var x0 = this.xExtent()[0].valueOf();
- var x1 = this.xExtent()[1].valueOf();
- var y0 = this.yExtent()[1].valueOf();
- var y1 = this.yExtent()[0].valueOf();
- if (x0 === x1 || y0 === y1) {
- return;
- }
- if (!this.isZoomed) {
- this.isZoomed = true;
- this.xDomainToRestore = this.xScale().domain();
- this.yDomainToRestore = this.yScale().domain();
- }
- this.interpolateZoom(x0, x1, y0, y1);
- };
- // Restore the scales to their state before any zoom
- DragZoomLayer.prototype.unzoom = function () {
- if (!this.isZoomed) {
- return;
- }
- this.isZoomed = false;
- this.interpolateZoom(this.xDomainToRestore[0], this.xDomainToRestore[1], this.yDomainToRestore[0], this.yDomainToRestore[1]);
- };
- // If we are zooming, disable interactions, to avoid contention
- DragZoomLayer.prototype.isZooming = function (isZooming) {
- this._dragInteraction.enabled(!isZooming);
- this._doubleClickInteraction.enabled(!isZooming);
- };
- DragZoomLayer.prototype.interpolateZoom = function (x0f, x1f, y0f, y1f) {
- var _this = this;
- var x0s = this.xScale().domain()[0].valueOf();
- var x1s = this.xScale().domain()[1].valueOf();
- var y0s = this.yScale().domain()[0].valueOf();
- var y1s = this.yScale().domain()[1].valueOf();
- // Copy a ref to the ease fn, so that changing ease wont affect zooms in progress
- var ease = this.easeFn;
- var interpolator = function (a, b, p) { return d3.interpolateNumber(a, b)(ease(p)); };
- this.isZooming(true);
- var start = Date.now();
- var draw = function () {
- var now = Date.now();
- var passed = now - start;
- var p = _this._animationTime === 0 ? 1 : Math.min(1, passed / _this._animationTime);
- var x0 = interpolator(x0s, x0f, p);
- var x1 = interpolator(x1s, x1f, p);
- var y0 = interpolator(y0s, y0f, p);
- var y1 = interpolator(y1s, y1f, p);
- _this.xScale().domain([x0, x1]);
- _this.yScale().domain([y0, y1]);
- if (p < 1) {
- Plottable.Utils.DOM.requestAnimationFramePolyfill(draw);
- }
- else {
- _this.isZooming(false);
- }
- };
- draw();
- };
- return DragZoomLayer;
- })(Plottable.Components.SelectionBoxLayer);
- Plottable.DragZoomLayer = DragZoomLayer;
-})(Plottable || (Plottable = {}));
-</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 __extends = (this && this.__extends) || function (d, b) {
- for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
- function __() { this.constructor = d; }
- __.prototype = b.prototype;
- d.prototype = new __();
-};
-/// <reference path="../../typings/tsd.d.ts" />
-/// <reference path="../plottable/plottable.d.ts" />
-var TF;
-(function (TF) {
- var Y_TOOLTIP_FORMATTER_PRECISION = 4;
- var STEP_AXIS_FORMATTER_PRECISION = 4;
- var Y_AXIS_FORMATTER_PRECISION = 3;
- var BaseChart = (function () {
- function BaseChart(tag, dataCoordinator, tooltipUpdater, xType, colorScale) {
- this.dataCoordinator = dataCoordinator;
- this.tag = tag;
- this.colorScale = colorScale;
- this.tooltipUpdater = tooltipUpdater;
- this.buildChart(xType);
- }
- BaseChart.prototype.changeRuns = function (runs) {
- throw new Error("Abstract method not implemented");
- };
- 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, dont 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();
- }
- var xComponents = getXComponents(xType);
- this.xAccessor = xComponents.accessor;
- this.xScale = xComponents.scale;
- this.xAxis = xComponents.axis;
- this.xAxis.margin(0).tickLabelPadding(3);
- this.xTooltipFormatter = xComponents.tooltipFormatter;
- this.yScale = new Plottable.Scales.Linear();
- this.yAxis = new Plottable.Axes.Numeric(this.yScale, "left");
- var yFormatter = multiscaleFormatter(Y_AXIS_FORMATTER_PRECISION);
- this.yAxis.margin(0).tickLabelPadding(5).formatter(yFormatter);
- this.yAxis.usesTextWidthApproximation(true);
- var center = this.buildPlot(this.xAccessor, this.xScale, this.yScale);
- this.gridlines = new Plottable.Components.Gridlines(this.xScale, this.yScale);
- var dzl = new Plottable.DragZoomLayer(this.xScale, this.yScale);
- this.center = new Plottable.Components.Group([center, this.gridlines, dzl]);
- this.outer = new Plottable.Components.Table([
- [this.yAxis, this.center],
- [null, this.xAxis]
- ]);
- };
- BaseChart.prototype.buildPlot = function (xAccessor, xScale, yScale) {
- throw new Error("Abstract method not implemented.");
- };
- BaseChart.prototype.renderTo = function (target) {
- this.outer.renderTo(target);
- };
- BaseChart.prototype.redraw = function () {
- this.outer.redraw();
- };
- BaseChart.prototype.destroy = function () {
- this.outer.destroy();
- };
- return BaseChart;
- })();
- TF.BaseChart = BaseChart;
- var LineChart = (function (_super) {
- __extends(LineChart, _super);
- function LineChart() {
- _super.apply(this, arguments);
- }
- LineChart.prototype.buildPlot = function (xAccessor, xScale, yScale) {
- var yAccessor = accessorize("2");
- var plot = new Plottable.Plots.Line();
- plot.x(xAccessor, xScale);
- plot.y(yAccessor, yScale);
- plot.attr("stroke", function (d, i, m) { return m.run; }, this.colorScale);
- this.plot = plot;
- var group = this.addCrosshairs(plot, yAccessor);
- return group;
- };
- LineChart.prototype.changeRuns = function (runs) {
- var datasets = this.dataCoordinator.getDatasets(this.tag, runs);
- this.plot.datasets(datasets);
- };
- return LineChart;
- })(BaseChart);
- TF.LineChart = LineChart;
- var HistogramChart = (function (_super) {
- __extends(HistogramChart, _super);
- function HistogramChart() {
- _super.apply(this, arguments);
- }
- HistogramChart.prototype.changeRuns = function (runs) {
- var datasets = this.dataCoordinator.getDatasets(this.tag, runs);
- this.plots.forEach(function (p) { return p.datasets(datasets); });
- };
- 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 accessors = percents.map(function (p, i) { return function (datum) { return datum[2][i][1]; }; });
- var median = 4;
- var medianAccessor = accessors[median];
- var plots = _.range(accessors.length - 1).map(function (i) {
- var p = new Plottable.Plots.Area();
- p.x(xAccessor, xScale);
- var y0 = i > median ? accessors[i] : accessors[i + 1];
- var y = i > median ? accessors[i + 1] : accessors[i];
- p.y(y, yScale);
- p.y0(y0);
- p.attr("fill", function (d, i, m) { return m.run; }, _this.colorScale);
- p.attr("stroke", function (d, i, m) { return m.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]; });
- 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);
- this.plots = plots;
- var group = this.addCrosshairs(medianPlot, medianAccessor);
- return new Plottable.Components.Group([new Plottable.Components.Group(plots), group]);
- };
- return HistogramChart;
- })(BaseChart);
- TF.HistogramChart = HistogramChart;
- /* Create a formatter function that will switch between exponential and
- * regular display depending on the scale of the number being formatted,
- * and show `digits` significant digits.
- */
- function multiscaleFormatter(digits) {
- return function (v) {
- var absv = Math.abs(v);
- if (absv < 1E-15) {
- // Sometimes zero-like values get an annoying representation
- absv = 0;
- }
- var f;
- if (absv >= 1E4) {
- f = d3.format("." + digits + "e");
- }
- else if (absv > 0 && absv < 0.01) {
- f = d3.format("." + digits + "e");
- }
- else {
- f = d3.format("." + digits + "g");
- }
- return f(v);
- };
- }
- function accessorize(key) {
- return function (d, index, dataset) { return d[key]; };
- }
- function stepX() {
- var scale = new Plottable.Scales.Linear();
- var axis = new Plottable.Axes.Numeric(scale, "bottom");
- var formatter = Plottable.Formatters.siSuffix(STEP_AXIS_FORMATTER_PRECISION);
- axis.formatter(formatter);
- return {
- scale: scale,
- axis: axis,
- accessor: accessorize("1"),
- tooltipFormatter: formatter,
- };
- }
- function wallX() {
- var scale = new Plottable.Scales.Time();
- var formatter = Plottable.Formatters.time("%a %b %e, %H:%M:%S");
- return {
- scale: scale,
- axis: new Plottable.Axes.Time(scale, "bottom"),
- accessor: function (d, index, dataset) {
- return d[0] * 1000; // convert seconds to ms
- },
- tooltipFormatter: function (d) { return formatter(new Date(d)); },
- };
- }
- function relativeX() {
- var scale = new Plottable.Scales.Linear();
- var formatter = function (n) {
- var days = Math.floor(n / 24);
- n -= (days * 24);
- var hours = Math.floor(n);
- n -= hours;
- n *= 60;
- var minutes = Math.floor(n);
- n -= minutes;
- n *= 60;
- var seconds = Math.floor(n);
- return days + "d " + hours + "h " + minutes + "m " + seconds + "s";
- };
- return {
- scale: scale,
- axis: new Plottable.Axes.Numeric(scale, "bottom"),
- accessor: function (d, index, dataset) {
- var data = dataset && dataset.data();
- // I can't imagine how this function would be called when the data is empty
- // (after all, it iterates over the data), but lets guard just to be safe.
- var first = data.length > 0 ? data[0][0] : 0;
- return (d[0] - first) / (60 * 60); // convert seconds to hours
- },
- tooltipFormatter: formatter,
- };
- }
- function getXComponents(xType) {
- switch (xType) {
- case "step":
- return stepX();
- case "wall_time":
- return wallX();
- case "relative":
- return relativeX();
- default:
- throw new Error("invalid xType: " + xType);
- }
- }
-})(TF || (TF = {}));
-</script>
- <script>
- Polymer({
- is: "tf-chart",
- properties: {
- type: String, // "scalar" or "compressedHistogram"
- _chart: Object,
- colorScale: Object,
- tag: String,
- selectedRuns: Array,
- xType: String,
- dataCoordinator: Object,
- tooltipUpdater: Function,
- _initialized: Boolean,
- },
- observers: [
- "_makeChart(type, tag, dataCoordinator, tooltipUpdater, xType, colorScale, _initialized)",
- "_changeRuns(_chart, selectedRuns.*)"
- ],
- _changeRuns: function(chart, change) {
- this._chart.changeRuns(this.selectedRuns);
- this.redraw();
- },
- redraw: function() {
- this._chart.redraw();
- },
- _constructor: function(type) {
- if (type === "scalar") {
- return TF.LineChart;
- } else if (type === "compressedHistogram") {
- return TF.HistogramChart;
- } else {
- throw new Error("Unrecognized chart type");
- }
- },
- _makeChart: function(type, tag, dataCoordinator, tooltipUpdater, xType, colorScale, _initialized) {
- if (!_initialized) {
- return;
- }
- if (this._chart) this._chart.destroy();
- var cns = this._constructor(type);
- var chart = new cns(tag, dataCoordinator, tooltipUpdater, xType, colorScale);
- var svg = d3.select(this.$.chartsvg);
- this.async(function() {
- chart.renderTo(svg);
- this._chart = chart;
- }, 350);
- },
- attached: function() {
- this._initialized = true;
- },
- detached: function() {
- this._initialized = false;
- }
- });
- </script>
-</dom-module>
-<dom-module id="tf-collapsable-pane" assetpath="../tf-collapsable-pane/">
- <template>
- <button class="heading" on-tap="togglePane" open-button$="[[opened]]">
- <span class="name">[[name]]</span>
- <span class="hackpadding"></span>
- <span class="count">
- <span>[[count]]</span>
- </span>
- </button>
- <iron-collapse opened="[[opened]]">
- <div class="content">
- <template is="dom-if" if="[[opened]]" restamp="[[restamp]]">
- <content></content>
- </template>
- </div>
- </iron-collapse>
- <style>
- :host {
- display: block;
- margin: 0 5px 1px 10px;
- }
-
- :host:first-of-type {
- margin-top: 20px;
- }
-
- :host:last-of-type {
- margin-bottom: 20px;
- }
-
- .heading {
- background-color: white;
- border-radius: 2px;
- border: none;
- cursor: pointer;
- -webkit-tap-highlight-color: rgba(0,0,0,0);
- width: 100%;
- box-sizing: border-box;
- font-size: 15px;
- display: inline-flex;
- flex-direction: row;
- align-items: center;
- justify-content: space-between;
- line-height: 1;
- box-shadow: 0 1px 5px rgba(0,0,0,0.2);
- padding: 10px 15px;
- }
-
- .content {
- padding: 15px;
- border: 1px solid #dedede;
- border-top: none;
- border-bottom-left-radius: 2px;
- border-bottom-right-radius: 2px;
- background: white;
- }
-
- [open-button] {
- border-bottom-left-radius: 0px !important;
- border-bottom-right-radius: 0px !important;
- }
-
- .name {
- flex-grow: 0;
- }
-
- .count {
- flex-grow: 0;
- float: right;
- margin-right: 5px;
- font-size: 12px;
- color: var(--paper-grey-500);
- }
-
- .hackpadding {
- /* An obnoxious hack, but I can't get justify-content: space-between to work */
- flex-grow: 1;
- }
- </style>
- </template>
- <script>
- Polymer({
- is: "tf-collapsable-pane",
- properties: {
- opened: {type: Boolean, value: false},
- restamp: {type: Boolean, value: true},
- name: {type: String, observer: "hide"},
- count: {type: Number},
- },
- hide: function() {
- this.opened = false;
- },
- togglePane: function() {
- this.opened = !this.opened;
- }
- });
- </script>
-
-</dom-module>
-<dom-module id="warning-style" assetpath="../tf-dashboard-common/">
- <template>
- <style>
- .warning {
- max-width: 540px;
- margin: 80px auto 0 auto;
- }
- </style>
- </template>
-</dom-module>
-<dom-module id="tf-event-dashboard" assetpath="../tf-event-dashboard/">
- <template>
- <div id="plumbing">
- <tf-url-generator router="[[router]]" out-runs-url="{{runsUrl}}" out-scalars-url-generator="{{scalarsUrlGen}}" id="urlGenerator"></tf-url-generator>
-
- <tf-data-coordinator id="dataCoordinator" url-generator="[[scalarsUrlGen]]" run-to-tag="[[runToScalars]]" color-scale="[[colorScale]]" out-data-coordinator="{{dataCoordinator}}"></tf-data-coordinator>
-
- <tf-run-generator id="runGenerator" url="[[runsUrl]]" out-run-to-scalars="{{runToScalars}}"></tf-run-generator>
-
- <tf-color-scale id="colorScale" runs="[[_runs]]" out-color-scale="{{colorScale}}" out-class-scale="{{classScale}}"></tf-color-scale>
-
- <tf-tooltip-coordinator id="tooltipCoordinator" out-tooltip-updater="{{tooltipUpdater}}" out-tooltip-map="{{tooltipMap}}" out-x-value="{{tooltipXValue}}" out-closest-run="{{closestRun}}"></tf-tooltip-coordinator>
-
- </div>
-
- <tf-dashboard-layout>
- <div class="sidebar">
- <div class="sidebar-section">
- <tf-categorizer id="categorizer" tags="[[_visibleTags]]" categories="{{categories}}"></tf-categorizer>
- <paper-checkbox id="download-option" checked="{{_show_download_links}}">Data download links</paper-checkbox>
- </div>
- <div class="sidebar-section">
- <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>
- </div>
- </div>
- <div class="center">
- <template is="dom-if" if="[[!categories.length]]">
- <div class="warning">
- <p>
- No scalar summary tags were found.
- </p>
- <p>
- Maybe data hasn't loaded yet, or maybe you need
- to add some <code>tf.scalar_summary</code> ops to your graph, and
- serialize them using the <code>tf.training.summary_io.SummaryWriter</code>.
- </p>
- </div>
- </template>
- <template is="dom-repeat" items="[[categories]]">
- <tf-collapsable-pane name="[[item.name]]" count="[[item.tags.length]]">
- <div class="layout horizontal wrap">
- <template is="dom-repeat" items="[[item.tags]]" as="tag">
- <div class="card">
- <span class="card-title">[[tag]]</span>
- <div class="card-content">
- <tf-chart tag="[[tag]]" type="scalar" id="chart" selected-runs="[[selectedRuns]]" x-type="[[xType]]" data-coordinator="[[dataCoordinator]]" color-scale="[[colorScale]]" on-keyup="toggleSelected" tabindex="2" tooltip-updater="[[tooltipUpdater]]"></tf-chart>
- <paper-icon-button class="expand-button" shift$="[[_show_download_links]]" icon="fullscreen" on-tap="toggleSelected"></paper-icon-button>
- </div>
- <template is="dom-if" if="[[_show_download_links]]">
- <div class="card-bottom-row">
- <tf-downloader selected-runs="[[selectedRuns]]" tag="[[tag]]" url-fn="[[scalarsUrlGen]]" run-to-tag="[[runToScalars]]">
- </tf-downloader>
- </div>
- </template>
- </div>
- </template>
- </div>
- </tf-collapsable-pane>
- </template>
- </div>
- </tf-dashboard-layout>
-
- <style include="dashboard-style"></style>
- <style include="warning-style"></style>
-
- </template>
-
- <script>
- Polymer({
- is: "tf-event-dashboard",
- properties: {
- _runs: {
- type: Array,
- computed: "_getRuns(runToScalars)",
- },
- _visibleTags: {
- type: Array,
- computed: "_getVisibleTags(selectedRuns.*, runToScalars.*)"
- },
- _show_download_links: Boolean,
- router: {
- type: Object,
- value: TF.Urls.productionRouter(),
- },
- },
- observers: ['redraw(_show_download_links)'],
- redraw: function(_show_download_links) {
- var els = this.getElementsByTagName("tf-chart");
- for (var i=0; i<els.length; i++) {
- els[i].redraw();
- }
- },
- _getRuns: function(runToScalars) {
- return _.keys(runToScalars);
- },
- _getVisibleTags: function(selectedRunsChange, runsToScalarsChange) {
- var keys = selectedRunsChange.base;
- var dict = runsToScalarsChange.base;
- return _.union.apply(null, keys.map(function(k) {return dict[k]}));
- },
- toggleSelected: function(e) {
- var currentTarget = Polymer.dom(e.currentTarget);
- var parentDiv = currentTarget.parentNode.parentNode;
- parentDiv.classList.toggle("selected");
- var chart = currentTarget.previousElementSibling;
- if (chart) {
- chart.redraw();
- }
- },
- });
- </script>
-</dom-module>
-<dom-module id="tf-histogram-dashboard" assetpath="../tf-histogram-dashboard/">
- <template>
- <div id="plumbing">
- <tf-url-generator router="[[router]]" out-runs-url="{{runsUrl}}" out-compressed-histograms-url-generator="{{compressedHistogramsUrlGen}}" id="urlGenerator"></tf-url-generator>
-
- <tf-data-coordinator id="dataCoordinator" url-generator="[[compressedHistogramsUrlGen]]" run-to-tag="[[runToCompressedHistograms]]" color-scale="[[colorScale]]" out-data-coordinator="{{dataCoordinator}}"></tf-data-coordinator>
-
- <tf-run-generator id="runGenerator" url="[[runsUrl]]" out-run-to-compressed-histograms="{{runToCompressedHistograms}}"></tf-run-generator>
-
- <tf-color-scale id="colorScale" runs="[[_runs]]" out-color-scale="{{colorScale}}" out-class-scale="{{classScale}}"></tf-color-scale>
-
- <tf-tooltip-coordinator id="tooltipCoordinator" out-tooltip-updater="{{tooltipUpdater}}" out-tooltip-map="{{tooltipMap}}" out-x-value="{{tooltipXValue}}" out-closest-run="{{closestRun}}"></tf-tooltip-coordinator>
- </div>
-
- <tf-dashboard-layout>
- <div class="sidebar">
- <div class="sidebar-section">
- <tf-categorizer id="categorizer" tags="[[_visibleTags]]" categories="{{categories}}"></tf-categorizer>
- </div>
- <div class="sidebar-section">
- <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>
- </div>
- </div>
-
- <div class="center">
- <template is="dom-if" if="[[!categories.length]]">
- <div class="warning">
- <p>
- No histogram tags were found.
- </p>
- <p>
- Maybe data hasn't loaded yet, or maybe you need
- to add some <code>tf.histogram_summary</code> ops to your graph, and
- serialize them using the <code>tf.training.summary_io.SummaryWriter</code>.
- </p>
- </div>
- </template>
- <template is="dom-repeat" items="[[categories]]">
- <tf-collapsable-pane name="[[item.name]]" count="[[_count(item.tags, selectedRuns.*, runToCompressedHistograms.*)]]">
- <div class="layout horizontal wrap">
- <template is="dom-repeat" items="[[item.tags]]" as="tag">
- <template is="dom-repeat" items="[[selectedRuns]]" as="run">
- <template is="dom-if" if="[[_exists(run, tag, runToCompressedHistograms.*)]]">
- <div class="card">
- <span class="card-title">[[tag]]</span>
- <div class="card-content">
- <tf-chart tag="[[tag]]" type="compressedHistogram" id="chart" selected-runs="[[_array(run)]]" x-type="[[xType]]" data-coordinator="[[dataCoordinator]]" color-scale="[[colorScale]]" on-keyup="toggleSelected" tabindex="2" tooltip-updater="[[tooltipUpdater]]"></tf-chart>
- <paper-icon-button class="expand-button" icon="fullscreen" on-tap="toggleSelected"></paper-icon-button>
- </div>
- </div>
- </template>
- </template>
- </template>
- </div>
- </tf-collapsable-pane>
- </template>
- </div>
- </tf-dashboard-layout>
-
- <style include="dashboard-style"></style>
- <style include="warning-style"></style>
- </template>
-
- <script>
- Polymer({
- is: "tf-histogram-dashboard",
- properties: {
- _runs: {
- type: Array,
- computed: "_getRuns(runToCompressedHistograms)",
- },
- _visibleTags: {
- type: Array,
- computed: "_getVisibleTags(selectedRuns.*, runToCompressedHistograms.*)"
- },
- router: {
- type: Object,
- value: TF.Urls.productionRouter(),
- },
- },
- _exists: function(run, tag, runToCompressedHistogramsChange) {
- var runToCompressedHistograms = runToCompressedHistogramsChange.base;
- return runToCompressedHistograms[run].indexOf(tag) !== -1;
- },
- _array: function(x) {
- return [x];
- },
- _count: function(tags, selectedRunsChange, runToCompressedHistogramsChange) {
- var selectedRuns = selectedRunsChange.base;
- var runToCompressedHistograms = runToCompressedHistogramsChange.base;
- var targetTags = {};
- tags.forEach(function(t) {
- targetTags[t] = true;
- });
- var count = 0;
- selectedRuns.forEach(function(r) {
- runToCompressedHistograms[r].forEach(function(t) {
- if (targetTags[t]) {
- count++;
- }
- });
- });
- return count;
- },
- _getRuns: function(runToCompressedHistograms) {
- return _.keys(runToCompressedHistograms);
- },
- _getVisibleTags: function(selectedRunsChange, runToCompressedHistogramsChange) {
- var keys = selectedRunsChange.base;
- var dict = runToCompressedHistogramsChange.base;
- return _.union.apply(null, keys.map(function(k) {return dict[k]}));
- },
- toggleSelected: function(e) {
- var currentTarget = Polymer.dom(e.currentTarget);
- var parentDiv = currentTarget.parentNode.parentNode;
- parentDiv.classList.toggle("selected");
- var chart = currentTarget.previousElementSibling;
- if (chart) {
- chart.redraw();
- }
- },
- });
- </script>
-</dom-module>
-<dom-module id="tf-image-loader" assetpath="../tf-image-dashboard/">
- <style>
- :host {
- display: block;
- }
- img {
- width: 100%;
- height: 100%;
- }
- </style>
- <template>
- <iron-ajax id="ajax" auto="" url="[[metadataUrl]]" handle-as="json" debounce="50" last-response="{{imageMetadata}}" verbose="true"></iron-ajax>
- <template is="dom-if" if="[[imageUrl]]">
- <img src="[[imageUrl]]">
- </template>
- </template>
- <script>
- Polymer({
- is: "tf-image-loader",
- properties: {
- run: String,
- tag: String,
- imagesGenerator: Function,
- individualImageGenerator: Function,
- imageMetadata: Array,
- metadataUrl: {
- type: String,
- computed: "apply(imagesGenerator, tag, run)",
- },
- imageUrl: {
- type: String,
- computed: "getLastImage(imageMetadata, individualImageGenerator)",
- },
- },
- apply: function(imagesGenerator, run, tag) {
- return imagesGenerator(run, tag);
- },
- getLastImage: function(imageMetadata, individualImageGenerator) {
- if (imageMetadata == null) {
- return null;
- }
- var query = _.last(imageMetadata).query;
- return individualImageGenerator(query);
- },
- });
- </script>
-</dom-module>
-<dom-module id="tf-image-grid" assetpath="../tf-image-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]]" sort="_sort" as="tag">
- <div class="image-row container noshrink">
- <div class="tag-name-cell noshrink">
- <span class="tag-name">[[tag]]</span>
- </div>
- <template is="dom-repeat" items="[[_runs]]" as="run">
- <div class="image-cell noshrink">
- <template is="dom-if" if="[[_exists(run, tag, runToImages.*)]]">
- <tf-image-loader id="loader" run="[[run]]" tag="[[tag]]" images-generator="[[imagesGenerator]]" individual-image-generator="[[individualImageGenerator]]">
- </tf-image-loader>
- </template>
- </div>
- </template>
- </div>
- </template>
- </div>
- </div>
- <style>
- :host {
- display: block;
- height: 100%;
- }
- .container {
- display: flex;
- flex-wrap: nowrap;
- }
- #fullContainer {
- width: 100%;
- height: 100%;
- flex-direction: column;
- padding-top: 20px;
- overflow: scroll;
- -webkit-box-sizing: border-box;
- -moz-box-sizing: border-box;
- box-sizing: border-box;
- }
- #topRow {
- flex-direction: row;
- }
- #bottomContainer {
- flex-direction: column;
- height: 100%;
- width: 100%;
- }
- .image-row {
- flex-direction: row;
- }
- .image-cell {
- width: 300px;
- height: 300px;
- border: 1px solid black;
- }
- .tag-name-cell {
- height: 300px;
- width: 300px;
- display:flex;
- flex-direction: column;
- justify-content: center;
- }
- .tag-name {
- word-wrap: break-word;
- text-align: center;
- white-space: nowrap;
- }
- .run-name-cell {
- width: 300px;
- height: 30px;
- text-align: center;
- }
- .noshrink {
- flex-shrink: 0;
- }
- #paddingCell {
- width: 300px;
- height: 30px;
- }
- </style>
- </template>
- <script>
- Polymer({
- is: "tf-image-grid",
- properties: {
- runToImages: Object,
- _tags: {type: Array, computed: "_getTags(runToImages.*)"},
- _runs: {type: Array, computed: "_getRuns(runToImages.*)"},
- imagesGenerator: Function,
- individualImageGenerator: Function,
- },
- _getTags: function(runToImages) {
- return _.chain(runToImages.base).values().flatten().union().value();
- },
- _getRuns: function(runToImages) {
- var r2i = runToImages.base;
- return _.keys(r2i).filter(function(x) {return r2i[x].length > 0;});
- },
- _exists: function (run, tag, runToImages) {
- runToImages = runToImages.base;
- return runToImages[run].indexOf(tag) !== -1;
- },
- _sort: function(a, b) {
- return a > b;
- },
- });
- </script>
-</dom-module>
-<dom-module id="tf-image-dashboard" assetpath="../tf-image-dashboard/">
- <template>
- <div id="plumbing">
- <tf-url-generator router="[[router]]" out-runs-url="{{runsUrl}}" out-images-url-generator="{{imagesUrlGen}}" out-individual-image-url-generator="{{individualImageUrlGen}}" id="urlGenerator"></tf-url-generator>
-
- <tf-run-generator id="runGenerator" url="[[runsUrl]]" out-run-to-images="{{runToImages}}"></tf-run-generator>
- </div>
-
- <div class="center">
- <template is="dom-if" if="[[!_hasImages(runToImages.*)]]">
- <div class="warning">
- <p>
- No image tags were found.
- </p>
- <p>
- Maybe data hasn't loaded yet, or maybe you need
- to add some <code>tf.image_summary</code> ops to your graph, and
- serialize them using the <code>tf.training.summary_io.SummaryWriter</code>.
- </p>
- </div>
- </template>
- <tf-image-grid id="imageGrid" run-to-images="[[runToImages]]" images-generator="[[imagesUrlGen]]" individual-image-generator="[[individualImageUrlGen]]"></tf-image-grid>
- </div>
-
- <style>
- .center {
- padding-left: 10px;
- padding-right: 10px;
- height: 100%;
- width: 100%;
- -webkit-box-sizing: border-box;
- -moz-box-sizing: border-box;
- box-sizing: border-box;
- }
- :host {
- height: 100%;
- display: block;
- }
-
- </style>
- <style include="warning-style"></style>
- </template>
- <script>
- Polymer({
- is: "tf-image-dashboard",
- properties: {
- runToImages: Object,
- imagesUrlGen: Function,
- individualImageUrlGen: Function,
- router: {
- type: Object,
- value: TF.Urls.productionRouter(),
- },
- },
- _hasImages: function(runToImagesChange) {
- return _.values(runToImagesChange.base).some(function(arr) {
- return arr.length > 0;
- });
- },
- });
- </script>
-</dom-module>
-<dom-module id="tf-graph-loader" assetpath="../tf-graph-loader/">
-</dom-module>
-
-<script>
-Polymer({
-
- is: 'tf-graph-loader',
-
- properties: {
- /**
- * @type {value: number, msg: string}
- *
- * A number between 0 and 100 denoting the % of progress
- * for the progress bar and the displayed message.
- */
- progress: {
- type: Object,
- notify: true,
- readOnly: true // Produces, does not consume.
- },
- datasets: Array,
- hasStats: {
- type: Boolean,
- readOnly: true, // This property produces data.
- notify: true
- },
- selectedDataset: Number,
- selectedFile: {
- type: Object,
- observer: '_selectedFileChanged'
- },
- outGraphHierarchy: {
- type: Object,
- readOnly: true, //readonly so outsider can't change this via binding
- notify: true
- },
- outGraph: {
- type: Object,
- readOnly: true, //readonly so outsider can't change this via binding
- notify: true
- },
- outGraphName: {
- type: String,
- readOnly: true,
- notify: true
- }
- },
- observers: [
- '_selectedDatasetChanged(selectedDataset, datasets)'
- ],
- _parseAndConstructHierarchicalGraph: function(dataset, pbTxtContent) {
- var self = this;
- // Reset the progress bar to 0.
- self._setProgress({
- value: 0,
- msg: ''
- });
- var tracker = {
- setMessage: function(msg) {
- self._setProgress({
- value: self.progress.value,
- msg: msg
- });
- },
- updateProgress: function(value) {
- self._setProgress({
- value: self.progress.value + value,
- msg: self.progress.msg
- });
- },
- reportError: function(msg) {
- self._setProgress({
- value: self.progress.value,
- msg: msg,
- error: true
- });
- },
- };
- var statsJson;
- var dataTracker = tf.getSubtaskTracker(tracker, 30, 'Data');
- tf.graph.parser.readAndParseData(dataset, pbTxtContent, dataTracker)
- .then(function(result) {
- // Build the flat graph (consists only of Op nodes).
- var nodes = result.nodes;
- statsJson = result.statsJson;
-
- // This is the whitelist of inputs on op types that are considered
- // reference edges. "Assign 0" indicates that the first input to
- // an OpNode with operation type "Assign" is a reference edge.
- var refEdges = {};
- refEdges["Assign 0"] = true;
- refEdges["AssignAdd 0"] = true;
- refEdges["AssignSub 0"] = true;
- refEdges["assign 0"] = true;
- refEdges["assign_add 0"] = true;
- refEdges["assign_sub 0"] = true;
- refEdges["count_up_to 0"] = true;
- refEdges["ScatterAdd 0"] = true;
- refEdges["ScatterSub 0"] = true;
- refEdges["ScatterUpdate 0"] = true;
- refEdges["scatter_add 0"] = true;
- refEdges["scatter_sub 0"] = true;
- refEdges["scatter_update 0"] = true;
- var buildParams = {
- enableEmbedding: true,
- inEmbeddingTypes: ['Const'],
- outEmbeddingTypes: ['^[a-zA-Z]+Summary$'],
- refEdges: refEdges
- };
- var graphTracker = tf.getSubtaskTracker(tracker, 20,
- 'Graph');
- return tf.graph.build(nodes, buildParams, graphTracker);
- })
- .then(function(graph) {
- this._setOutGraph(graph);
- if (statsJson) {
- // If there are associated stats, join them with the graph.
- tf.time('Joining stats info with graph...', function() {
- tf.graph.joinStatsInfoWithGraph(graph, statsJson);
- });
- }
- var hierarchyParams = {
- verifyTemplate: true,
- // If a set of numbered op nodes has at least this number of nodes
- // then group them into a series node.
- seriesNodeMinSize: 5,
- };
- var hierarchyTracker = tf.getSubtaskTracker(tracker, 50,
- 'Namespace hierarchy');
- return tf.graph.hierarchy.build(graph, hierarchyParams, hierarchyTracker);
- }.bind(this))
- .then(function(graphHierarchy) {
- // Update the properties which notify the parent with the
- // graph hierarchy and whether the data has live stats or not.
- this._setHasStats(statsJson != null);
- this._setOutGraphHierarchy(graphHierarchy);
- }.bind(this))
- .catch(function(reason) {
- tracker.reportError("Graph visualization failed: " + reason);
- });
- },
- _selectedDatasetChanged: function(datasetIndex, datasets) {
- var dataset = datasets[datasetIndex];
- this._parseAndConstructHierarchicalGraph(dataset);
- this._setOutGraphName(dataset.name);
- },
- _selectedFileChanged: function(e) {
- if (!e) {
- return;
- }
- var file = e.target.files[0];
- if (!file) {
- return;
- }
-
- // Clear out the value of the file chooser. This ensures that if the user
- // selects the same file, we'll re-read it.
- e.target.value = '';
-
- var reader = new FileReader();
-
- reader.onload = function(e) {
- this._parseAndConstructHierarchicalGraph(null, e.target.result);
- }.bind(this);
-
- reader.readAsText(file);
- }
-});
-</script>
<dom-module id="tf-graph-style" assetpath="../tf-graph/">
<template>
<style>
@@ -8523,6 +8244,263 @@ Polymer({
</style>
</template>
</dom-module>
+<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.
+==============================================================================*/
+/// <reference path="../../../../typings/tsd.d.ts" />
+/// <reference path="../common.ts" />
+var tf;
+(function (tf) {
+ var scene;
+ (function (scene) {
+ /** Show minimap when the viewpoint area is less than X% of the whole area. */
+ var FRAC_VIEWPOINT_AREA = 0.8;
+ var Minimap = (function () {
+ /**
+ * Constructs a new minimap.
+ *
+ * @param svg The main svg element.
+ * @param zoomG The svg group used for panning and zooming the main svg.
+ * @param mainZoom The main zoom behavior.
+ * @param minimap The minimap container.
+ * @param maxWandH The maximum width/height for the minimap.
+ * @param labelPadding Padding in pixels due to the main graph labels.
+ */
+ function Minimap(svg, zoomG, mainZoom, minimap, maxWandH, labelPadding) {
+ var _this = this;
+ this.svg = svg;
+ this.labelPadding = labelPadding;
+ this.zoomG = zoomG;
+ this.mainZoom = mainZoom;
+ this.maxWandH = maxWandH;
+ var $minimap = d3.select(minimap);
+ // The minimap will have 2 main components: the canvas showing the content
+ // and an svg showing a rectangle of the currently zoomed/panned viewpoint.
+ var $minimapSvg = $minimap.select("svg");
+ // Make the viewpoint rectangle draggable.
+ var $viewpoint = $minimapSvg.select("rect");
+ var dragmove = function (d) {
+ _this.viewpointCoord.x = d3.event.x;
+ _this.viewpointCoord.y = d3.event.y;
+ _this.updateViewpoint();
+ };
+ this.viewpointCoord = { x: 0, y: 0 };
+ var drag = d3.behavior.drag().origin(Object).on("drag", dragmove);
+ $viewpoint.datum(this.viewpointCoord).call(drag);
+ // Make the minimap clickable.
+ $minimapSvg.on("click", function () {
+ if (d3.event.defaultPrevented) {
+ // This click was part of a drag event, so suppress it.
+ return;
+ }
+ // Update the coordinates of the viewpoint.
+ var width = Number($viewpoint.attr("width"));
+ var height = Number($viewpoint.attr("height"));
+ var clickCoords = d3.mouse($minimapSvg.node());
+ _this.viewpointCoord.x = clickCoords[0] - width / 2;
+ _this.viewpointCoord.y = clickCoords[1] - height / 2;
+ _this.updateViewpoint();
+ });
+ this.viewpoint = $viewpoint.node();
+ this.minimapSvg = $minimapSvg.node();
+ this.minimap = minimap;
+ this.canvas = $minimap.select("canvas.first").node();
+ this.canvasBuffer =
+ $minimap.select("canvas.second").node();
+ this.downloadCanvas =
+ $minimap.select("canvas.download").node();
+ d3.select(this.downloadCanvas).style("display", "none");
+ }
+ /**
+ * Updates the position and the size of the viewpoint rectangle.
+ * It also notifies the main svg about the new panned position.
+ */
+ Minimap.prototype.updateViewpoint = function () {
+ // Update the coordinates of the viewpoint rectangle.
+ d3.select(this.viewpoint)
+ .attr("x", this.viewpointCoord.x)
+ .attr("y", this.viewpointCoord.y);
+ // Update the translation vector of the main svg to reflect the
+ // new viewpoint.
+ var mainX = -this.viewpointCoord.x * this.scaleMain / this.scaleMinimap;
+ var mainY = -this.viewpointCoord.y * this.scaleMain / this.scaleMinimap;
+ var zoomEvent = this.mainZoom.translate([mainX, mainY]).event;
+ d3.select(this.zoomG).call(zoomEvent);
+ };
+ /**
+ * Redraws the minimap. Should be called whenever the main svg
+ * was updated (e.g. when a node was expanded).
+ */
+ Minimap.prototype.update = function () {
+ var _this = this;
+ // The origin hasn't rendered yet. Ignore making an update.
+ if (this.zoomG.childElementCount === 0) {
+ return;
+ }
+ var $download = d3.select("#graphdownload");
+ this.download = $download.node();
+ $download.on("click", function (d) {
+ _this.download.href = _this.downloadCanvas.toDataURL("image/png");
+ });
+ var $svg = d3.select(this.svg);
+ // Read all the style rules in the document and embed them into the svg.
+ // The svg needs to be self contained, i.e. all the style rules need to be
+ // embedded so the canvas output matches the origin.
+ var stylesText = "";
+ for (var k = 0; k < document.styleSheets.length; k++) {
+ try {
+ var cssRules = document.styleSheets[k].cssRules ||
+ document.styleSheets[k].rules;
+ if (cssRules == null) {
+ continue;
+ }
+ for (var i = 0; i < cssRules.length; i++) {
+ stylesText += cssRules[i].cssText + "\n";
+ }
+ }
+ catch (e) {
+ if (e.name !== "SecurityError") {
+ throw e;
+ }
+ }
+ }
+ // Temporarily add the css rules to the main svg.
+ var svgStyle = $svg.append("style");
+ svgStyle.text(stylesText);
+ // Temporarily remove the zoom/pan transform from the main svg since we
+ // want the minimap to show a zoomed-out and centered view.
+ var $zoomG = d3.select(this.zoomG);
+ var zoomTransform = $zoomG.attr("transform");
+ $zoomG.attr("transform", null);
+ // Get the size of the entire scene.
+ var sceneSize = this.zoomG.getBBox();
+ // Since we add padding, account for that here.
+ sceneSize.height += this.labelPadding * 2;
+ sceneSize.width += this.labelPadding * 2;
+ // Temporarily assign an explicit width/height to the main svg, since
+ // it doesn't have one (uses flex-box), but we need it for the canvas
+ // to work.
+ $svg.attr({
+ width: sceneSize.width,
+ height: sceneSize.height,
+ });
+ // Since the content inside the svg changed (e.g. a node was expanded),
+ // the aspect ratio have also changed. Thus, we need to update the scale
+ // factor of the minimap. The scale factor is determined such that both
+ // the width and height of the minimap are <= maximum specified w/h.
+ this.scaleMinimap =
+ this.maxWandH / Math.max(sceneSize.width, sceneSize.height);
+ this.minimapSize = {
+ width: sceneSize.width * this.scaleMinimap,
+ height: sceneSize.height * this.scaleMinimap
+ };
+ // Update the size of the minimap's svg, the buffer canvas and the
+ // viewpoint rect.
+ d3.select(this.minimapSvg).attr(this.minimapSize);
+ d3.select(this.canvasBuffer).attr(this.minimapSize);
+ // Download canvas width and height are multiples of the style width and
+ // height in order to increase pixel density of the PNG for clarity.
+ d3.select(this.downloadCanvas).style({ width: sceneSize.width, height: sceneSize.height });
+ d3.select(this.downloadCanvas).attr({ width: sceneSize.width * 3, height: sceneSize.height * 3 });
+ if (this.translate != null && this.zoom != null) {
+ // Update the viewpoint rectangle shape since the aspect ratio of the
+ // map has changed.
+ requestAnimationFrame(function () { return _this.zoom(); });
+ }
+ // Serialize the main svg to a string which will be used as the rendering
+ // content for the canvas.
+ var svgXml = (new XMLSerializer()).serializeToString(this.svg);
+ // Now that the svg is serialized for rendering, remove the temporarily
+ // assigned styles, explicit width and height and bring back the pan/zoom
+ // transform.
+ svgStyle.remove();
+ $svg.attr({
+ width: null,
+ height: null
+ });
+ $zoomG.attr("transform", zoomTransform);
+ var image = new Image();
+ image.onload = function () {
+ // Draw the svg content onto the buffer canvas.
+ var context = _this.canvasBuffer.getContext("2d");
+ context.clearRect(0, 0, _this.canvasBuffer.width, _this.canvasBuffer.height);
+ context.drawImage(image, 0, 0, _this.minimapSize.width, _this.minimapSize.height);
+ requestAnimationFrame(function () {
+ // Hide the old canvas and show the new buffer canvas.
+ d3.select(_this.canvasBuffer).style("display", null);
+ d3.select(_this.canvas).style("display", "none");
+ // Swap the two canvases.
+ _a = [_this.canvasBuffer, _this.canvas], _this.canvas = _a[0], _this.canvasBuffer = _a[1];
+ var _a;
+ });
+ var downloadContext = _this.downloadCanvas.getContext("2d");
+ downloadContext.clearRect(0, 0, _this.downloadCanvas.width, _this.downloadCanvas.height);
+ downloadContext.drawImage(image, 0, 0, _this.downloadCanvas.width, _this.downloadCanvas.height);
+ };
+ image.src = "data:image/svg+xml;base64," + btoa(svgXml);
+ };
+ /**
+ * Handles changes in zooming/panning. Should be called from the main svg
+ * to notify that a zoom/pan was performed and this minimap will update it's
+ * viewpoint rectangle.
+ *
+ * @param translate The translate vector, or none to use the last used one.
+ * @param scale The scaling factor, or none to use the last used one.
+ */
+ Minimap.prototype.zoom = function (translate, scale) {
+ // Update the new translate and scale params, only if specified.
+ this.translate = translate || this.translate;
+ this.scaleMain = scale || this.scaleMain;
+ // Update the location of the viewpoint rectangle.
+ var svgRect = this.svg.getBoundingClientRect();
+ var $viewpoint = d3.select(this.viewpoint);
+ this.viewpointCoord.x = -this.translate[0] * this.scaleMinimap /
+ this.scaleMain;
+ this.viewpointCoord.y = -this.translate[1] * this.scaleMinimap /
+ this.scaleMain;
+ var viewpointWidth = svgRect.width * this.scaleMinimap / this.scaleMain;
+ var viewpointHeight = svgRect.height * this.scaleMinimap / this.scaleMain;
+ $viewpoint.attr({
+ x: this.viewpointCoord.x,
+ y: this.viewpointCoord.y,
+ width: viewpointWidth,
+ height: viewpointHeight
+ });
+ // Show/hide the minimap depending on the viewpoint area as fraction of the
+ // whole minimap.
+ var mapWidth = this.minimapSize.width;
+ var mapHeight = this.minimapSize.height;
+ var x = this.viewpointCoord.x;
+ var y = this.viewpointCoord.y;
+ var w = Math.min(Math.max(0, x + viewpointWidth), mapWidth) -
+ Math.min(Math.max(0, x), mapWidth);
+ var h = Math.min(Math.max(0, y + viewpointHeight), mapHeight) -
+ Math.min(Math.max(0, y), mapHeight);
+ var fracIntersect = (w * h) / (mapWidth * mapHeight);
+ if (fracIntersect < FRAC_VIEWPOINT_AREA) {
+ this.minimap.classList.remove("hidden");
+ }
+ else {
+ this.minimap.classList.add("hidden");
+ }
+ };
+ return Minimap;
+ })();
+ scene.Minimap = Minimap;
+ })(scene = tf.scene || (tf.scene = {}));
+})(tf || (tf = {})); // close module tf.scene
+</script>
<dom-module id="tf-graph-minimap" assetpath="../tf-graph/">
<template>
<style>
@@ -8591,6 +8569,7 @@ Polymer({
});
</script>
</dom-module>
+
<dom-module id="tf-graph-scene" assetpath="../tf-graph/">
<template>
<style include="tf-graph-style">
@@ -9044,6 +9023,7 @@ Polymer({
},
});
</script>
+
<dom-module id="tf-graph-params" assetpath="../tf-graph/">
</dom-module>
<script>
@@ -10137,6 +10117,8 @@ h2 {
})();
</script>
</dom-module>
+
+
<dom-module id="tf-graph-board" assetpath="../tf-graph-board/">
<template>
<style>
@@ -10798,6 +10780,8 @@ function convertToHumanReadable(value, units, unitIndex) {
})(); // Closing private scope.
</script>
</dom-module>
+
+
<dom-module id="tf-graph-dashboard" assetpath="../tf-graph-dashboard/">
<template>
<div id="plumbing">
@@ -10827,7 +10811,8 @@ function convertToHumanReadable(value, units, unitIndex) {
<tf-graph-board id="graphboard" graph-hierarchy="[[_graphHierarchy]]" graph="[[_graph]]" has-stats="[[_hasStats]]" graph-name="[[_graphName]]" progress="[[_progress]]" color-by="[[_colorBy]]" color-by-params="{{_colorByParams}}">
</tf-graph-board>
</div>
-</tf-dashboard-layout></template>
+</tf-dashboard-layout>
+</template>
<style>
:host /deep/ {