diff options
Diffstat (limited to 'tensorflow/tensorboard/dist/tf-tensorboard.html')
-rw-r--r-- | tensorflow/tensorboard/dist/tf-tensorboard.html | 5901 |
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/ { |