aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--WORKSPACE6
-rw-r--r--tensorflow/tensorboard/TAG2
-rw-r--r--tensorflow/tensorboard/dist/tf-tensorboard.html1730
3 files changed, 1226 insertions, 512 deletions
diff --git a/WORKSPACE b/WORKSPACE
index a0aaefa6f5..a55fe2146a 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -175,7 +175,7 @@ new_git_repository(
name = "iron_menu_behavior",
build_file = "bower.BUILD",
remote = "https://github.com/polymerelements/iron-menu-behavior.git",
- tag = "v1.1.7",
+ tag = "v1.1.8",
)
new_git_repository(
@@ -189,7 +189,7 @@ new_git_repository(
name = "iron_overlay_behavior",
build_file = "bower.BUILD",
remote = "https://github.com/polymerelements/iron-overlay-behavior.git",
- tag = "v1.8.2",
+ tag = "v1.8.3",
)
new_git_repository(
@@ -322,7 +322,7 @@ new_git_repository(
name = "paper_menu_button",
build_file = "bower.BUILD",
remote = "https://github.com/polymerelements/paper-menu-button.git",
- tag = "v1.1.1",
+ tag = "v1.3.0",
)
new_git_repository(
diff --git a/tensorflow/tensorboard/TAG b/tensorflow/tensorboard/TAG
index 2bd5a0a98a..409940768f 100644
--- a/tensorflow/tensorboard/TAG
+++ b/tensorflow/tensorboard/TAG
@@ -1 +1 @@
-22
+23
diff --git a/tensorflow/tensorboard/dist/tf-tensorboard.html b/tensorflow/tensorboard/dist/tf-tensorboard.html
index 1c0297dbdb..8fe8e895ac 100644
--- a/tensorflow/tensorboard/dist/tf-tensorboard.html
+++ b/tensorflow/tensorboard/dist/tf-tensorboard.html
@@ -18,7 +18,66 @@ Instead, use `gulp regenerate` to create a new version with your changes.
-->
<html><head><meta charset="UTF-8">
+<script>/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+var TF;
+(function (TF) {
+ var TensorBoard;
+ (function (TensorBoard) {
+ TensorBoard.AUTORELOAD_LOCALSTORAGE_KEY = 'TF.TensorBoard.autoReloadEnabled';
+ var getAutoReloadFromLocalStorage = function () {
+ var val = window.localStorage.getItem(TensorBoard.AUTORELOAD_LOCALSTORAGE_KEY);
+ return val === 'true' || val == null; // defaults to true
+ };
+ TensorBoard.AutoReloadBehavior = {
+ properties: {
+ autoReloadEnabled: {
+ type: Boolean,
+ observer: '_autoReloadObserver',
+ value: getAutoReloadFromLocalStorage,
+ },
+ _autoReloadId: {
+ type: Number,
+ },
+ autoReloadIntervalSecs: {
+ type: Number,
+ value: 120,
+ },
+ },
+ detached: function () { window.clearTimeout(this._autoReloadId); },
+ _autoReloadObserver: function (autoReload) {
+ window.localStorage.setItem(TensorBoard.AUTORELOAD_LOCALSTORAGE_KEY, autoReload);
+ if (autoReload) {
+ var _this = this;
+ this._autoReloadId = window.setTimeout(this._doAutoReload.bind(this), this.autoReloadIntervalSecs * 1000);
+ }
+ else {
+ window.clearTimeout(this._autoReloadId);
+ }
+ },
+ _doAutoReload: function () {
+ if (this.reload == null) {
+ throw new Error('AutoReloadBehavior requires a reload method');
+ }
+ this.reload();
+ this._autoReloadId = window.setTimeout(this._doAutoReload.bind(this), this.autoReloadIntervalSecs * 1000);
+ }
+ };
+ })(TensorBoard = TF.TensorBoard || (TF.TensorBoard = {}));
+})(TF || (TF = {}));
+</script>
</head><body><div hidden="" by-vulcanize=""><dom-module id="tf-globals" assetpath="../tf-globals/">
<script>/* Copyright 2015 Google Inc. All Rights Reserved.
@@ -139,7 +198,327 @@ var TF;
</style>
</template>
</dom-module>
+<dom-module id="tf-storage" assetpath="../tf-storage/">
+ <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.
+==============================================================================*/
+/* tslint:disable:no-namespace variable-name */
+/**
+ * The Storage Module provides storage for URL parameters, and an API for
+ * getting and setting TensorBoard's stateful URI.
+ *
+ * It generates URI components like: events&runPrefix=train*
+ * which TensorBoard uses after like localhost:8000/#events&runPrefix=train*
+ * to store state in the URI.
+ */
+var TF;
+(function (TF) {
+ var URIStorage;
+ (function (URIStorage) {
+ /**
+ * A key that users cannot use, since TensorBoard uses this to store info
+ * about the active tab.
+ */
+ URIStorage.TAB = '__tab__';
+ /**
+ * The name of the property for users to set on a Polymer component
+ * in order for its stored properties to be stored in the URI unambiguously.
+ * (No need to set this if you want mutliple instances of the component to
+ * share URI state)
+ *
+ * Example:
+ * <my-component disambiguator="0"></my-component>
+ *
+ * The disambiguator should be set to any unique value so that multiple
+ * instances of the component can store properties in URI storage.
+ *
+ * Because it's hard to dereference this variable in HTML property bindings,
+ * it is NOT safe to change the disambiguator string without find+replace
+ * across the codebase.
+ */
+ URIStorage.DISAMBIGUATOR = 'disambiguator';
+ /**
+ * Return a boolean stored in the URI, given a corresponding key.
+ * Undefined if not found.
+ */
+ function getBoolean(key) {
+ var items = _componentToDict(_readComponent());
+ var item = items[key];
+ return item === 'true' ? true : item === 'false' ? false : undefined;
+ }
+ URIStorage.getBoolean = getBoolean;
+ /**
+ * Store a boolean in the URI, with a corresponding key.
+ */
+ function setBoolean(key, value) {
+ var items = _componentToDict(_readComponent());
+ items[key] = value.toString();
+ _writeComponent(_dictToComponent(items));
+ }
+ URIStorage.setBoolean = setBoolean;
+ /**
+ * Return a string stored in the URI, given a corresponding key.
+ * Undefined if not found.
+ */
+ function getString(key) {
+ var items = _componentToDict(_readComponent());
+ return items[key];
+ }
+ URIStorage.getString = getString;
+ /**
+ * Store a string in the URI, with a corresponding key.
+ */
+ function setString(key, value) {
+ var items = _componentToDict(_readComponent());
+ items[key] = value;
+ _writeComponent(_dictToComponent(items));
+ }
+ URIStorage.setString = setString;
+ /**
+ * Return a number stored in the URI, given a corresponding key.
+ * Undefined if not found.
+ */
+ function getNumber(key) {
+ var items = _componentToDict(_readComponent());
+ return items[key] === undefined ? undefined : +items[key];
+ }
+ URIStorage.getNumber = getNumber;
+ /**
+ * Store a number in the URI, with a corresponding key.
+ */
+ function setNumber(key, value) {
+ var items = _componentToDict(_readComponent());
+ items[key] = '' + value;
+ _writeComponent(_dictToComponent(items));
+ }
+ URIStorage.setNumber = setNumber;
+ /**
+ * Return an object stored in the URI, given a corresponding key.
+ * Undefined if not found.
+ */
+ function getObject(key) {
+ var items = _componentToDict(_readComponent());
+ return items[key] === undefined ? undefined : JSON.parse(atob(items[key]));
+ }
+ URIStorage.getObject = getObject;
+ /**
+ * Store an object in the URI, with a corresponding key.
+ */
+ function setObject(key, value) {
+ var items = _componentToDict(_readComponent());
+ items[key] = btoa(JSON.stringify(value));
+ _writeComponent(_dictToComponent(items));
+ }
+ URIStorage.setObject = setObject;
+ /**
+ * Get a unique storage name for a (Polymer component, propertyName) tuple.
+ *
+ * DISAMBIGUATOR must be set on the component, if other components use the
+ * same propertyName.
+ */
+ function getURIStorageName(component, propertyName) {
+ var d = component[URIStorage.DISAMBIGUATOR];
+ var components = d == null ? [propertyName] : [d, propertyName];
+ return components.join('.');
+ }
+ URIStorage.getURIStorageName = getURIStorageName;
+ /**
+ * Return a function that:
+ * (1) Initializes a Polymer boolean property with a default value, if its
+ * value is not already set
+ * (2) Sets up listener that updates Polymer property on hash change.
+ */
+ function getBooleanInitializer(propertyName, defaultVal) {
+ return _getInitializer(getBoolean, propertyName, defaultVal);
+ }
+ URIStorage.getBooleanInitializer = getBooleanInitializer;
+ /**
+ * Return a function that:
+ * (1) Initializes a Polymer string property with a default value, if its
+ * value is not already set
+ * (2) Sets up listener that updates Polymer property on hash change.
+ */
+ function getStringInitializer(propertyName, defaultVal) {
+ return _getInitializer(getString, propertyName, defaultVal);
+ }
+ URIStorage.getStringInitializer = getStringInitializer;
+ /**
+ * Return a function that:
+ * (1) Initializes a Polymer number property with a default value, if its
+ * value is not already set
+ * (2) Sets up listener that updates Polymer property on hash change.
+ */
+ function getNumberInitializer(propertyName, defaultVal) {
+ return _getInitializer(getNumber, propertyName, defaultVal);
+ }
+ URIStorage.getNumberInitializer = getNumberInitializer;
+ /**
+ * Return a function that:
+ * (1) Initializes a Polymer Object property with a default value, if its
+ * value is not already set
+ * (2) Sets up listener that updates Polymer property on hash change.
+ *
+ * Generates a deep clone of the defaultVal to avoid mutation issues.
+ */
+ function getObjectInitializer(propertyName, defaultVal) {
+ var clone = _.cloneDeep(defaultVal);
+ return _getInitializer(getObject, propertyName, clone);
+ }
+ URIStorage.getObjectInitializer = getObjectInitializer;
+ /**
+ * Return a function that updates URIStorage when a string property changes.
+ */
+ function getBooleanObserver(propertyName, defaultVal) {
+ return _getObserver(getBoolean, setBoolean, propertyName, defaultVal);
+ }
+ URIStorage.getBooleanObserver = getBooleanObserver;
+ /**
+ * Return a function that updates URIStorage when a string property changes.
+ */
+ function getStringObserver(propertyName, defaultVal) {
+ return _getObserver(getString, setString, propertyName, defaultVal);
+ }
+ URIStorage.getStringObserver = getStringObserver;
+ /**
+ * Return a function that updates URIStorage when a number property changes.
+ */
+ function getNumberObserver(propertyName, defaultVal) {
+ return _getObserver(getNumber, setNumber, propertyName, defaultVal);
+ }
+ URIStorage.getNumberObserver = getNumberObserver;
+ /**
+ * Return a function that updates URIStorage when an object property changes.
+ * Generates a deep clone of the defaultVal to avoid mutation issues.
+ */
+ function getObjectObserver(propertyName, defaultVal) {
+ var clone = _.cloneDeep(defaultVal);
+ return _getObserver(getObject, setObject, propertyName, clone);
+ }
+ URIStorage.getObjectObserver = getObjectObserver;
+ /**
+ * Read component from URI (e.g. returns "events&runPrefix=train*").
+ */
+ function _readComponent() {
+ return TF.Globals.USE_HASH ? window.location.hash.slice(1) :
+ TF.Globals.FAKE_HASH;
+ }
+ /**
+ * Write component to URI.
+ */
+ function _writeComponent(component) {
+ if (TF.Globals.USE_HASH) {
+ window.location.hash = component;
+ }
+ else {
+ TF.Globals.FAKE_HASH = component;
+ }
+ }
+ /**
+ * Convert dictionary of strings into a URI Component.
+ * All key value entries get added as key value pairs in the component,
+ * with the exception of a key with the TAB value, which if present
+ * gets prepended to the URI Component string for backwards comptability
+ * reasons.
+ */
+ function _dictToComponent(items) {
+ var component = '';
+ // Add the tab name e.g. 'events', 'images', 'histograms' as a prefix
+ // for backwards compatbility.
+ if (items[URIStorage.TAB] !== undefined) {
+ component += items[URIStorage.TAB];
+ }
+ // Join other strings with &key=value notation
+ var nonTab = _.pairs(items)
+ .filter(function (pair) { return pair[0] !== URIStorage.TAB; })
+ .map(function (pair) {
+ return encodeURIComponent(pair[0]) + '=' +
+ encodeURIComponent(pair[1]);
+ })
+ .join('&');
+ return nonTab.length > 0 ? (component + '&' + nonTab) : component;
+ }
+ /**
+ * Convert a URI Component into a dictionary of strings.
+ * Component should consist of key-value pairs joined by a delimiter
+ * with the exception of the tabName.
+ * Returns dict consisting of all key-value pairs and
+ * dict[TAB] = tabName
+ */
+ function _componentToDict(component) {
+ var items = {};
+ var tokens = component.split('&');
+ tokens.forEach(function (token) {
+ var kv = token.split('=');
+ // Special backwards compatibility for URI components like #events
+ if (kv.length === 1 && _.contains(TF.Globals.TABS, kv[0])) {
+ items[URIStorage.TAB] = kv[0];
+ }
+ else if (kv.length === 2) {
+ items[decodeURIComponent(kv[0])] = decodeURIComponent(kv[1]);
+ }
+ });
+ return items;
+ }
+ /**
+ * Return a function that:
+ * (1) Initializes a Polymer property with a default value, if its
+ * value is not already set
+ * (2) Sets up listener that updates Polymer property on hash change.
+ */
+ function _getInitializer(get, propertyName, defaultVal) {
+ return function () {
+ var _this = this;
+ var URIStorageName = getURIStorageName(this, propertyName);
+ var setComponentValue = function () {
+ var uriValue = get(URIStorageName);
+ _this[propertyName] = uriValue !== undefined ? uriValue : defaultVal;
+ };
+ // Set the value on the property.
+ setComponentValue();
+ // Update it when the hashchanges.
+ window.addEventListener('hashchange', setComponentValue);
+ };
+ }
+ /**
+ * Return a function that updates URIStorage when a property changes.
+ */
+ function _getObserver(get, set, propertyName, defaultVal) {
+ return function () {
+ var URIStorageName = getURIStorageName(this, propertyName);
+ var newVal = this[propertyName];
+ if (!_.isEqual(newVal, get(URIStorageName))) {
+ if (_.isEqual(newVal, defaultVal)) {
+ _unset(URIStorageName);
+ }
+ else {
+ set(URIStorageName, newVal);
+ }
+ }
+ };
+ }
+ /**
+ * Delete a key from the URI.
+ */
+ function _unset(key) {
+ var items = _componentToDict(_readComponent());
+ delete items[key];
+ _writeComponent(_dictToComponent(items));
+ }
+ })(URIStorage = TF.URIStorage || (TF.URIStorage = {}));
+})(TF || (TF = {}));
+</script>
+</dom-module>
<dom-module id="tf-multi-checkbox" assetpath="../tf-multi-checkbox/">
@@ -148,7 +527,7 @@ var TF;
<template>
<div id="outer-container" class="scrollbar">
- <paper-input id="runs-regex" no-label-float="" label="Write a regex to filter runs" bind-value="{{regexInput}}"></paper-input>
+ <paper-input id="runs-regex" no-label-float="" label="Write a regex to filter runs" value="{{regexInput}}"></paper-input>
<template is="dom-repeat" items="[[namesMatchingRegex]]">
<div class="run-row">
<div class="checkbox-container vertical-align-container">
@@ -229,7 +608,12 @@ var TF;
is: "tf-multi-checkbox",
properties: {
names: Array, // All the runs in consideration
- regexInput: {type: String, value: "",}, // Regex for filtering the runs
+
+ regexInput: {
+ type: String,
+ value: TF.URIStorage.getStringInitializer("regexInput", ""),
+ observer: "_regexInputObserver"
+ }, // Regex for filtering the runs
regex: {
type: Object,
computed: "makeRegex(regexInput)"
@@ -320,6 +704,7 @@ var TF;
_initializeRuns: function(change) {
this.outSelected = change.base.slice();
},
+ _regexInputObserver: TF.URIStorage.getStringObserver("regexInput", ""),
toggleAll: function() {
var _this = this;
var allOn = this.namesMatchingRegex
@@ -397,6 +782,128 @@ var TF;
});
</script>
</dom-module>
+
+<dom-module id="tf-smoothing-input" assetpath="../tf-event-dashboard/">
+ <template>
+ <div class="smoothing-line">
+ <paper-checkbox checked="{{enabled}}">Exponential smoothing</paper-checkbox>
+ <div class="smoothing-block">
+ <paper-slider value="{{decay}}" immediate-value="{{_immediateDecayNumberForPaperSlider}}" type="number" step="[[step]]" min="[[min]]" max="[[max]]" disabled="[[!enabled]]"></paper-slider>
+ <paper-input id="input" label="decay" no-label-float="" value="{{_inputDecayStringForPaperInput}}" type="number" step="[[step]]" min="[[min]]" max="[[max]]" disabled="[[!enabled]]"></paper-input>
+ </div>
+ </div>
+ <style>
+ .smoothing-line {
+ margin-top: 15px;
+ }
+
+ .smoothing-block {
+ 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;
+ }
+
+ paper-slider {
+ margin-left: 12px;
+ --paper-slider-knob-color: var(--tb-orange-strong);
+ --paper-slider-active-color: var(--tb-orange-strong);
+ flex-grow: 2;
+ }
+
+ paper-input {
+ --paper-input-container-focus-color: var(--tb-orange-strong);
+ --paper-input-container-input: {
+ font-size: 14px;
+ };
+ --paper-input-container-label: {
+ font-size: 14px;
+ };
+ width: 60px;
+ }
+ </style>
+ </template>
+ <script>
+ Polymer({
+ is: "tf-smoothing-input",
+
+ properties: {
+ enabled: {
+ type: Boolean,
+ value: false,
+ notify: true,
+ observer: '_enabledChanged'
+ },
+
+ step: Number,
+ max: Number,
+ min: Number,
+
+ decay: {
+ type: Number,
+ value: 0.9,
+ notify: true
+ },
+
+ _immediateDecayNumberForPaperSlider: {
+ type: Number,
+ notify: true,
+ observer: '_immediateDecayNumberForPaperSliderChanged'
+ },
+
+ // Paper input treats values as strings even if you specify them as
+ // numbers.
+ _inputDecayStringForPaperInput: {
+ type: String,
+ notify: true,
+ observer: '_inputDecayStringForPaperInputChanged'
+ }
+ },
+
+ _updateDecay: _.debounce(function(val) {
+ this.decay = val;
+ }, 250),
+
+ _enabledChanged: function() {
+ // Workaround for paper-input bug on firefox :(
+ // Element crashes if we change value while it is disabled.
+ if (this.enabled) {
+ this.$.input.label = '';
+ this._immediateDecayNumberForPaperSliderChanged();
+ }
+ },
+
+ _immediateDecayNumberForPaperSliderChanged: function() {
+ if(!this.enabled) {
+ return;
+ }
+ this._inputDecayStringForPaperInput =
+ this._immediateDecayNumberForPaperSlider.toString();
+ this._updateDecay.call(this, this._immediateDecayNumberForPaperSlider);
+ },
+
+ _inputDecayStringForPaperInputChanged: function() {
+ if(!this.enabled) {
+ return;
+ }
+ if (+this._inputDecayStringForPaperInput < 0) {
+ this._inputDecayStringForPaperInput = '0';
+ }
+ else if (+this._inputDecayStringForPaperInput > 1) {
+ this._inputDecayStringForPaperInput = '1';
+ }
+
+ var d = +this._inputDecayStringForPaperInput;
+ if (!isNaN(d)) {
+ this._updateDecay.call(this, d);
+ }
+ }
+ });
+ </script>
+</dom-module>
<style is="custom-style">
:root {
@@ -919,7 +1426,7 @@ var Categorizer;
</script>
</dom-module>
-<dom-module id="tf-chart" assetpath="../tf-event-dashboard/">
+<dom-module id="tf-line-chart" assetpath="../tf-event-dashboard/">
<template>
<svg id="chartsvg"></svg>
<div id="tooltip">
@@ -997,6 +1504,11 @@ var Categorizer;
opacity: 1;
}
+ .ghost {
+ opacity: 0.2;
+ stroke-width: 1px;
+ }
+
</style>
</template>
<script>/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
@@ -1119,8 +1631,6 @@ var Plottable;
}
if (!this.isZoomed) {
this.isZoomed = true;
- this.xDomainToRestore = this.xScale().domain();
- this.yDomainToRestore = this.yScale().domain();
}
this.interpolateZoom(x0, x1, y0, y1);
};
@@ -1130,7 +1640,21 @@ var Plottable;
return;
}
this.isZoomed = false;
- this.interpolateZoom(this.xDomainToRestore[0], this.xDomainToRestore[1], this.yDomainToRestore[0], this.yDomainToRestore[1]);
+ // Some Plottable magic follows which ensures that when we un-zoom, we
+ // un-zoom to the current extent of the data; i.e. if new data was loaded
+ // since we zoomed, we should un-zoom to the extent of the new data.
+ // this basically replicates the autoDomain logic in Plottable.
+ // it uses the internal methods to get the same boundaries that autoDomain
+ // would, but allows us to interpolate the zoom with a nice animation.
+ var xScale = this.xScale();
+ var yScale = this.yScale();
+ xScale._domainMin = null;
+ xScale._domainMax = null;
+ yScale._domainMin = null;
+ yScale._domainMax = null;
+ var xDomain = xScale._getExtent();
+ var yDomain = yScale._getExtent();
+ this.interpolateZoom(xDomain[0], xDomain[1], yDomain[0], yDomain[1]);
};
// If we are zooming, disable interactions, to avoid contention
DragZoomLayer.prototype.isZooming = function (isZooming) {
@@ -1178,12 +1702,7 @@ var Plottable;
Plottable.DragZoomLayer = DragZoomLayer;
})(Plottable || (Plottable = {}));
</script>
- <script>var __extends = (this && this.__extends) || function (d, b) {
- for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
- function __() { this.constructor = d; }
- d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
-};
-/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
+ <script>/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -1197,63 +1716,40 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
+/* tslint:disable:no-namespace variable-name */
var TF;
(function (TF) {
- var Y_TOOLTIP_FORMATTER_PRECISION = 4;
- var STEP_FORMATTER_PRECISION = 4;
- var Y_AXIS_FORMATTER_PRECISION = 3;
- var TOOLTIP_Y_PIXEL_OFFSET = 20;
- var TOOLTIP_CIRCLE_SIZE = 4;
- var NAN_SYMBOL_SIZE = 6;
- var BaseChart = (function () {
- function BaseChart(tag, dataFn, xType, colorScale, tooltip) {
+ var LineChart = (function () {
+ function LineChart(tag, dataFn, xType, colorScale, tooltip) {
this.dataFn = dataFn;
this.run2datasets = {};
this.tag = tag;
this.colorScale = colorScale;
this.tooltip = tooltip;
+ this.datasets = [];
+ this.smoothDatasets = [];
+ this.run2smoothDatasets = {};
+ // lastPointDataset is a dataset that contains just the last point of
+ // every dataset we're currently drawing.
+ this.lastPointsDataset = new Plottable.Dataset();
+ this.nanDataset = new Plottable.Dataset();
+ // need to do a single bind, so we can deregister the callback from
+ // old Plottable.Datasets. (Deregistration is done by identity checks.)
+ this.onDatasetChanged = this._onDatasetChanged.bind(this);
+ this.buildChart(xType);
}
- /**
- * Change the runs on the chart. The work of actually setting the dataset
- * on the plot is deferred to the subclass because it is impl-specific.
- * Changing runs automatically triggers a reload; this ensures that the
- * newly selected run will have data, and that all the runs will be current
- * (it would be weird if one run was ahead of the others, and the display
- * depended on the order in which runs were added)
- */
- BaseChart.prototype.changeRuns = function (runs) {
- this.runs = runs;
- this.reload();
- };
- /**
- * Reload data for each run in view.
- */
- BaseChart.prototype.reload = function () {
- var _this = this;
- this.runs.forEach(function (run) {
- var dataset = _this.getDataset(run);
- _this.dataFn(_this.tag, run).then(function (x) { return dataset.data(x); });
- });
- };
- BaseChart.prototype.getDataset = function (run) {
- if (this.run2datasets[run] === undefined) {
- this.run2datasets[run] =
- new Plottable.Dataset([], { run: run, tag: this.tag });
- }
- return this.run2datasets[run];
- };
- BaseChart.prototype.buildChart = function (xType) {
+ LineChart.prototype.buildChart = function (xType) {
if (this.outer) {
this.outer.destroy();
}
- var xComponents = getXComponents(xType);
+ var xComponents = TF.ChartHelpers.getXComponents(xType);
this.xAccessor = xComponents.accessor;
this.xScale = xComponents.scale;
this.xAxis = xComponents.axis;
this.xAxis.margin(0).tickLabelPadding(3);
this.yScale = new Plottable.Scales.Linear();
this.yAxis = new Plottable.Axes.Numeric(this.yScale, 'left');
- var yFormatter = multiscaleFormatter(Y_AXIS_FORMATTER_PRECISION);
+ var yFormatter = TF.ChartHelpers.multiscaleFormatter(TF.ChartHelpers.Y_AXIS_FORMATTER_PRECISION);
this.yAxis.margin(0).tickLabelPadding(5).formatter(yFormatter);
this.yAxis.usesTextWidthApproximation(true);
this.dzl = new Plottable.DragZoomLayer(this.xScale, this.yScale);
@@ -1267,35 +1763,6 @@ var TF;
[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(tag, dataFn, xType, colorScale, tooltip) {
- _super.call(this, tag, dataFn, xType, colorScale, tooltip);
- this.datasets = [];
- // lastPointDataset is a dataset that contains just the last point of
- // every dataset we're currently drawing.
- this.lastPointsDataset = new Plottable.Dataset();
- this.nanDataset = new Plottable.Dataset();
- // need to do a single bind, so we can deregister the callback from
- // old Plottable.Datasets. (Deregistration is done by identity checks.)
- this.updateSpecialDatasets = this._updateSpecialDatasets.bind(this);
- this.buildChart(xType);
- }
LineChart.prototype.buildPlot = function (xAccessor, xScale, yScale) {
var _this = this;
this.yAccessor = function (d) { return d.scalar; };
@@ -1307,6 +1774,13 @@ var TF;
});
this.linePlot = linePlot;
var group = this.setupTooltips(linePlot);
+ var smoothLinePlot = new Plottable.Plots.Line();
+ smoothLinePlot.x(xAccessor, xScale);
+ smoothLinePlot.y(this.yAccessor, yScale);
+ smoothLinePlot.attr('stroke', function (d, i, dataset) {
+ return _this.colorScale.scale(dataset.metadata().run);
+ });
+ this.smoothLinePlot = smoothLinePlot;
// The scatterPlot will display the last point for each dataset.
// This way, if there is only one datum for the series, it is still
// visible. We hide it when tooltips are active to keep things clean.
@@ -1315,7 +1789,7 @@ var TF;
scatterPlot.y(this.yAccessor, yScale);
scatterPlot.attr('fill', function (d) { return _this.colorScale.scale(d.run); });
scatterPlot.attr('opacity', 1);
- scatterPlot.size(TOOLTIP_CIRCLE_SIZE * 2);
+ scatterPlot.size(TF.ChartHelpers.TOOLTIP_CIRCLE_SIZE * 2);
scatterPlot.datasets([this.lastPointsDataset]);
this.scatterPlot = scatterPlot;
var nanDisplay = new Plottable.Plots.Scatter();
@@ -1323,19 +1797,31 @@ var TF;
nanDisplay.y(function (x) { return x.displayY; }, yScale);
nanDisplay.attr('fill', function (d) { return _this.colorScale.scale(d.run); });
nanDisplay.attr('opacity', 1);
- nanDisplay.size(NAN_SYMBOL_SIZE * 2);
+ nanDisplay.size(TF.ChartHelpers.NAN_SYMBOL_SIZE * 2);
nanDisplay.datasets([this.nanDataset]);
nanDisplay.symbol(Plottable.SymbolFactories.triangleUp);
this.nanDisplay = nanDisplay;
- return new Plottable.Components.Group([nanDisplay, scatterPlot, group]);
+ return new Plottable.Components.Group([nanDisplay, scatterPlot, smoothLinePlot, group]);
+ };
+ /** Updates the chart when a dataset changes. Called every time the data of
+ * a dataset changes to update the charts.
+ */
+ LineChart.prototype._onDatasetChanged = function (dataset) {
+ if (this.smoothingEnabled) {
+ this.smoothDataset(this.getSmoothDataset(dataset.metadata().run));
+ this.updateSpecialDatasets(this.smoothDatasets);
+ }
+ else {
+ this.updateSpecialDatasets(this.datasets);
+ }
};
/** Constructs special datasets. Each special dataset contains exceptional
- * values from all of the regular datasetes, e.g. last points in series, or
+ * values from all of the regular datasets, e.g. last points in series, or
* NaN values. Those points will have a `run` and `relative` property added
* (since usually those are context in the surrounding dataset).
*/
- LineChart.prototype._updateSpecialDatasets = function () {
- var lastPointsData = this.datasets
+ LineChart.prototype.updateSpecialDatasets = function (datasets) {
+ var lastPointsData = datasets
.map(function (d) {
var datum = null;
// filter out NaNs to ensure last point is a clean one
@@ -1344,7 +1830,8 @@ var TF;
var idx = nonNanData.length - 1;
datum = nonNanData[idx];
datum.run = d.metadata().run;
- datum.relative = relativeAccessor(datum, -1, d);
+ datum.relative =
+ TF.ChartHelpers.relativeAccessor(datum, -1, d);
}
return datum;
})
@@ -1374,13 +1861,13 @@ var TF;
else {
data[i].run = d.metadata().run;
data[i].displayY = displayY;
- data[i].relative = relativeAccessor(data[i], -1, d);
+ data[i].relative = TF.ChartHelpers.relativeAccessor(data[i], -1, d);
nanData.push(data[i]);
}
}
return nanData;
};
- var nanData = _.flatten(this.datasets.map(datasetToNaNData));
+ var nanData = _.flatten(datasets.map(datasetToNaNData));
this.nanDataset.data(nanData);
};
LineChart.prototype.setupTooltips = function (plot) {
@@ -1415,13 +1902,14 @@ var TF;
dataset: null,
};
var centerBBox = _this.gridlines.content().node().getBBox();
- var points = plot.datasets().map(function (dataset) { return _this.findClosestPoint(target, dataset); });
+ var datasets = _this.smoothingEnabled ? _this.smoothDatasets : plot.datasets();
+ var points = datasets.map(function (dataset) { return _this.findClosestPoint(target, dataset); });
var pointsToCircle = points.filter(function (p) { return p != null &&
Plottable.Utils.DOM.intersectsBBox(p.x, p.y, centerBBox); });
var pts = pointsComponent.content().selectAll('.point').data(pointsToCircle, function (p) { return p.dataset.metadata().run; });
if (points.length !== 0) {
pts.enter().append('circle').classed('point', true);
- pts.attr('r', TOOLTIP_CIRCLE_SIZE)
+ pts.attr('r', TF.ChartHelpers.TOOLTIP_CIRCLE_SIZE)
.attr('cx', function (p) { return p.x; })
.attr('cy', function (p) { return p.y; })
.style('stroke', 'none')
@@ -1440,7 +1928,7 @@ var TF;
var _this = this;
// Formatters for value, step, and wall_time
this.scatterPlot.attr('opacity', 0);
- var valueFormatter = multiscaleFormatter(Y_TOOLTIP_FORMATTER_PRECISION);
+ var valueFormatter = TF.ChartHelpers.multiscaleFormatter(TF.ChartHelpers.Y_TOOLTIP_FORMATTER_PRECISION);
var dist = function (p) {
return Math.pow(p.x - target.x, 2) + Math.pow(p.y - target.y, 2);
};
@@ -1483,9 +1971,9 @@ var TF;
rows.append('td').text(function (d) {
return isNaN(d.datum.scalar) ? 'NaN' : valueFormatter(d.datum.scalar);
});
- rows.append('td').text(function (d) { return stepFormatter(d.datum.step); });
- rows.append('td').text(function (d) { return timeFormatter(d.datum.wall_time); });
- rows.append('td').text(function (d) { return relativeFormatter(relativeAccessor(d.datum, -1, d.dataset)); });
+ rows.append('td').text(function (d) { return TF.ChartHelpers.stepFormatter(d.datum.step); });
+ rows.append('td').text(function (d) { return TF.ChartHelpers.timeFormatter(d.datum.wall_time); });
+ rows.append('td').text(function (d) { return TF.ChartHelpers.relativeFormatter(TF.ChartHelpers.relativeAccessor(d.datum, -1, d.dataset)); });
// compute left position
var documentWidth = document.body.clientWidth;
var node = this.tooltip.node();
@@ -1495,12 +1983,13 @@ var TF;
var left = Math.min(0, documentWidth - parentRect.left - nodeRect.width - 60);
this.tooltip.style('left', left + 'px');
// compute top position
- if (parentRect.bottom + nodeRect.height + TOOLTIP_Y_PIXEL_OFFSET <
+ if (parentRect.bottom + nodeRect.height +
+ TF.ChartHelpers.TOOLTIP_Y_PIXEL_OFFSET <
document.body.clientHeight) {
- this.tooltip.style('top', parentRect.bottom + TOOLTIP_Y_PIXEL_OFFSET);
+ this.tooltip.style('top', parentRect.bottom + TF.ChartHelpers.TOOLTIP_Y_PIXEL_OFFSET);
}
else {
- this.tooltip.style('bottom', parentRect.top - TOOLTIP_Y_PIXEL_OFFSET);
+ this.tooltip.style('bottom', parentRect.top - TF.ChartHelpers.TOOLTIP_Y_PIXEL_OFFSET);
}
this.tooltip.style('opacity', 1);
};
@@ -1531,191 +2020,253 @@ var TF;
return prevDist < nextDist ? prev : next;
}
};
+ LineChart.prototype.getSmoothDataset = function (run) {
+ if (this.run2smoothDatasets[run] === undefined) {
+ this.run2smoothDatasets[run] =
+ new Plottable.Dataset([], { run: run, tag: this.tag });
+ }
+ return this.run2smoothDatasets[run];
+ };
+ LineChart.prototype.smoothDataset = function (dataset) {
+ var _this = this;
+ var data = this.getDataset(dataset.metadata().run).data();
+ // EMA with first step initialized to first element.
+ var smoothedData = _.cloneDeep(data);
+ smoothedData.forEach(function (d, i) {
+ if (i === 0) {
+ return;
+ }
+ d.scalar = (1.0 - _this.smoothingDecay) * d.scalar +
+ _this.smoothingDecay * smoothedData[i - 1].scalar;
+ });
+ dataset.data(smoothedData);
+ };
+ LineChart.prototype.getDataset = function (run) {
+ if (this.run2datasets[run] === undefined) {
+ this.run2datasets[run] =
+ new Plottable.Dataset([], { run: run, tag: this.tag });
+ }
+ return this.run2datasets[run];
+ };
+ /**
+ * Change the runs on the chart.
+ * Changing runs automatically triggers a reload; this ensures that the
+ * newly selected run will have data, and that all the runs will be current
+ * (it would be weird if one run was ahead of the others, and the display
+ * depended on the order in which runs were added)
+ */
LineChart.prototype.changeRuns = function (runs) {
var _this = this;
- _super.prototype.changeRuns.call(this, runs);
+ this.runs = runs;
+ this.reload();
runs.reverse(); // draw first run on top
- this.datasets.forEach(function (d) { return d.offUpdate(_this.updateSpecialDatasets); });
+ this.datasets.forEach(function (d) { return d.offUpdate(_this.onDatasetChanged); });
this.datasets = runs.map(function (r) { return _this.getDataset(r); });
- this.datasets.forEach(function (d) { return d.onUpdate(_this.updateSpecialDatasets); });
+ this.datasets.forEach(function (d) { return d.onUpdate(_this.onDatasetChanged); });
this.linePlot.datasets(this.datasets);
+ if (this.smoothingEnabled) {
+ this.smoothDatasets = runs.map(function (r) { return _this.getSmoothDataset(r); });
+ this.smoothLinePlot.datasets(this.smoothDatasets);
+ }
};
- return LineChart;
- }(BaseChart));
- TF.LineChart = LineChart;
- var HistogramChart = (function (_super) {
- __extends(HistogramChart, _super);
- function HistogramChart(tag, dataFn, xType, colorScale, tooltip) {
- _super.call(this, tag, dataFn, xType, colorScale, tooltip);
- this.buildChart(xType);
- }
- HistogramChart.prototype.changeRuns = function (runs) {
+ LineChart.prototype.smoothingUpdate = function (decay) {
var _this = this;
- _super.prototype.changeRuns.call(this, runs);
- var datasets = runs.map(function (r) { return _this.getDataset(r); });
- this.plots.forEach(function (p) { return p.datasets(datasets); });
+ if (!this.smoothingEnabled) {
+ this.linePlot.addClass('ghost');
+ this.smoothingEnabled = true;
+ this.smoothDatasets = this.runs.map(function (r) { return _this.getSmoothDataset(r); });
+ this.smoothLinePlot.datasets(this.smoothDatasets);
+ }
+ this.smoothingDecay = decay;
+ this.smoothDatasets.forEach(function (d) { return _this.smoothDataset(d); });
+ this.updateSpecialDatasets(this.smoothDatasets);
};
- 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[i][1]; }; });
- var median = 4;
- var medianAccessor = accessors[median];
- var plots = _.range(accessors.length - 1).map(function (i) {
- var p = new Plottable.Plots.Area();
- p.x(xAccessor, xScale);
- var y0 = i > median ? accessors[i] : accessors[i + 1];
- var y = i > median ? accessors[i + 1] : accessors[i];
- p.y(y, yScale);
- p.y0(y0);
- p.attr('fill', function (d, i, dataset) {
- return _this.colorScale.scale(dataset.metadata().run);
- });
- p.attr('stroke', function (d, i, dataset) {
- return _this.colorScale.scale(dataset.metadata().run);
- });
- p.attr('stroke-weight', function (d, i, m) { return '0.5px'; });
- p.attr('stroke-opacity', function () { return opacities[i]; });
- p.attr('fill-opacity', function () { return opacities[i]; });
- return p;
- });
- var medianPlot = new Plottable.Plots.Line();
- medianPlot.x(xAccessor, xScale);
- medianPlot.y(medianAccessor, yScale);
- medianPlot.attr('stroke', function (d, i, m) { return _this.colorScale.scale(m.run); });
- this.plots = plots;
- return new Plottable.Components.Group(plots);
- };
- 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');
+ LineChart.prototype.smoothingDisable = function () {
+ if (this.smoothingEnabled) {
+ this.linePlot.removeClass('ghost');
+ this.smoothDatasets = [];
+ this.smoothLinePlot.datasets(this.smoothDatasets);
+ this.smoothingEnabled = false;
+ this.updateSpecialDatasets(this.datasets);
}
- else {
- f = d3.format('.' + digits + 'g');
- }
- return f(v);
};
- }
- function accessorize(key) {
- return function (d, index, dataset) { return d[key]; };
- }
- var stepFormatter = Plottable.Formatters.siSuffix(STEP_FORMATTER_PRECISION);
- function stepX() {
- var scale = new Plottable.Scales.Linear();
- var axis = new Plottable.Axes.Numeric(scale, 'bottom');
- axis.formatter(stepFormatter);
- return {
- scale: scale,
- axis: axis,
- accessor: function (d) { return d.step; },
- };
- }
- var timeFormatter = Plottable.Formatters.time('%a %b %e, %H:%M:%S');
- function wallX() {
- var scale = new Plottable.Scales.Time();
- return {
- scale: scale,
- axis: new Plottable.Axes.Time(scale, 'bottom'),
- accessor: function (d) { return d.wall_time; },
+ /**
+ * Reload data for each run in view.
+ */
+ LineChart.prototype.reload = function () {
+ var _this = this;
+ this.runs.forEach(function (run) {
+ var dataset = _this.getDataset(run);
+ _this.dataFn(_this.tag, run).then(function (x) { return dataset.data(x); });
+ });
};
- }
- var relativeAccessor = function (d, index, dataset) {
- // We may be rendering the final-point datum for scatterplot.
- // If so, we will have already provided the 'relative' property
- if (d.relative != null) {
- return d.relative;
+ LineChart.prototype.renderTo = function (target) { this.outer.renderTo(target); };
+ LineChart.prototype.redraw = function () { this.outer.redraw(); };
+ LineChart.prototype.destroy = function () { this.outer.destroy(); };
+ return LineChart;
+ }());
+ TF.LineChart = LineChart;
+})(TF || (TF = {}));
+</script>
+ <script>/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+/* tslint:disable:no-namespace variable-name */
+var TF;
+(function (TF) {
+ var ChartHelpers;
+ (function (ChartHelpers) {
+ ChartHelpers.Y_TOOLTIP_FORMATTER_PRECISION = 4;
+ ChartHelpers.STEP_FORMATTER_PRECISION = 4;
+ ChartHelpers.Y_AXIS_FORMATTER_PRECISION = 3;
+ ChartHelpers.TOOLTIP_Y_PIXEL_OFFSET = 20;
+ ChartHelpers.TOOLTIP_CIRCLE_SIZE = 4;
+ ChartHelpers.NAN_SYMBOL_SIZE = 6;
+ /* Create a formatter function that will switch between exponential and
+ * regular display depending on the scale of the number being formatted,
+ * and show `digits` significant digits.
+ */
+ function multiscaleFormatter(digits) {
+ return function (v) {
+ var absv = Math.abs(v);
+ if (absv < 1E-15) {
+ // Sometimes zero-like values get an annoying representation
+ absv = 0;
+ }
+ var f;
+ if (absv >= 1E4) {
+ f = d3.format('.' + digits + 'e');
+ }
+ else if (absv > 0 && absv < 0.01) {
+ f = d3.format('.' + digits + 'e');
+ }
+ else {
+ f = d3.format('.' + digits + 'g');
+ }
+ return f(v);
+ };
}
- var data = dataset.data();
- // I can't imagine how this function would be called when the data is
- // empty (after all, it iterates over the data), but lets guard just
- // to be safe.
- var first = data.length > 0 ? +data[0].wall_time : 0;
- return (+d.wall_time - first) / (60 * 60 * 1000); // ms to hours
- };
- var relativeFormatter = function (n) {
- // we will always show 2 units of precision, e.g days and hours, or
- // minutes and seconds, but not hours and minutes and seconds
- var ret = '';
- var days = Math.floor(n / 24);
- n -= (days * 24);
- if (days) {
- ret += days + 'd ';
+ ChartHelpers.multiscaleFormatter = multiscaleFormatter;
+ function accessorize(key) {
+ return function (d, index, dataset) { return d[key]; };
}
- var hours = Math.floor(n);
- n -= hours;
- n *= 60;
- if (hours || days) {
- ret += hours + 'h ';
+ ChartHelpers.accessorize = accessorize;
+ ChartHelpers.stepFormatter = Plottable.Formatters.siSuffix(ChartHelpers.STEP_FORMATTER_PRECISION);
+ function stepX() {
+ var scale = new Plottable.Scales.Linear();
+ var axis = new Plottable.Axes.Numeric(scale, 'bottom');
+ axis.formatter(ChartHelpers.stepFormatter);
+ return {
+ scale: scale,
+ axis: axis,
+ accessor: function (d) { return d.step; },
+ };
}
- var minutes = Math.floor(n);
- n -= minutes;
- n *= 60;
- if (minutes || hours || days) {
- ret += minutes + 'm ';
+ ChartHelpers.stepX = stepX;
+ ChartHelpers.timeFormatter = Plottable.Formatters.time('%a %b %e, %H:%M:%S');
+ function wallX() {
+ var scale = new Plottable.Scales.Time();
+ return {
+ scale: scale,
+ axis: new Plottable.Axes.Time(scale, 'bottom'),
+ accessor: function (d) { return d.wall_time; },
+ };
}
- var seconds = Math.floor(n);
- return ret + seconds + 's';
- };
- function relativeX() {
- var scale = new Plottable.Scales.Linear();
- return {
- scale: scale,
- axis: new Plottable.Axes.Numeric(scale, 'bottom'),
- accessor: relativeAccessor,
+ ChartHelpers.wallX = wallX;
+ ChartHelpers.relativeAccessor = function (d, index, dataset) {
+ // We may be rendering the final-point datum for scatterplot.
+ // If so, we will have already provided the 'relative' property
+ if (d.relative != null) {
+ return d.relative;
+ }
+ var data = dataset.data();
+ // I can't imagine how this function would be called when the data is
+ // empty (after all, it iterates over the data), but lets guard just
+ // to be safe.
+ var first = data.length > 0 ? +data[0].wall_time : 0;
+ return (+d.wall_time - first) / (60 * 60 * 1000); // ms to hours
};
- }
- // a very literal definition of NaN: true for NaN for a non-number type
- // or null, etc. False for Infinity or -Infinity
- var isNaN = function (x) { return +x !== x; };
- function getXComponents(xType) {
- switch (xType) {
- case 'step':
- return stepX();
- case 'wall_time':
- return wallX();
- case 'relative':
- return relativeX();
- default:
- throw new Error('invalid xType: ' + xType);
+ ChartHelpers.relativeFormatter = function (n) {
+ // we will always show 2 units of precision, e.g days and hours, or
+ // minutes and seconds, but not hours and minutes and seconds
+ var ret = '';
+ var days = Math.floor(n / 24);
+ n -= (days * 24);
+ if (days) {
+ ret += days + 'd ';
+ }
+ var hours = Math.floor(n);
+ n -= hours;
+ n *= 60;
+ if (hours || days) {
+ ret += hours + 'h ';
+ }
+ var minutes = Math.floor(n);
+ n -= minutes;
+ n *= 60;
+ if (minutes || hours || days) {
+ ret += minutes + 'm ';
+ }
+ var seconds = Math.floor(n);
+ return ret + seconds + 's';
+ };
+ function relativeX() {
+ var scale = new Plottable.Scales.Linear();
+ return {
+ scale: scale,
+ axis: new Plottable.Axes.Numeric(scale, 'bottom'),
+ accessor: ChartHelpers.relativeAccessor,
+ };
}
- }
+ ChartHelpers.relativeX = relativeX;
+ // a very literal definition of NaN: true for NaN for a non-number type
+ // or null, etc. False for Infinity or -Infinity
+ ChartHelpers.isNaN = function (x) { return +x !== x; };
+ function getXComponents(xType) {
+ switch (xType) {
+ case 'step':
+ return stepX();
+ case 'wall_time':
+ return wallX();
+ case 'relative':
+ return relativeX();
+ default:
+ throw new Error('invalid xType: ' + xType);
+ }
+ }
+ ChartHelpers.getXComponents = getXComponents;
+ })(ChartHelpers = TF.ChartHelpers || (TF.ChartHelpers = {}));
})(TF || (TF = {}));
</script>
<script>
Polymer({
- is: "tf-chart",
+ is: "tf-line-chart",
properties: {
- type: String, // "scalar" or "compressedHistogram"
_chart: Object,
colorScale: Object,
tag: String,
selectedRuns: Array,
+ smoothingDecay: Number,
+ smoothingEnabled: Boolean,
xType: String,
dataProvider: Function,
_initialized: Boolean,
},
observers: [
- "_makeChart(type, tag, dataProvider, xType, colorScale, _initialized)",
- "_changeRuns(_chart, selectedRuns.*)"
+ "_makeChart(tag, dataProvider, xType, colorScale, _initialized)",
+ "_changeRuns(_chart, selectedRuns.*)",
+ "_smoothingChanged(smoothingDecay, smoothingEnabled, _chart)"
],
_changeRuns: function(chart) {
if (chart.tag !== this.tag) {
@@ -1730,30 +2281,32 @@ var TF;
reload: function() {
this._chart.reload();
},
- _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, dataProvider, xType, colorScale, _initialized) {
+ _makeChart: function(tag, dataProvider, xType, colorScale, _initialized) {
if (!_initialized) {
return;
}
if (this._chart) this._chart.destroy();
- var cns = this._constructor(type);
var tooltip = d3.select(this.$.tooltip);
this.scopeSubtree(this.$.tooltip, true);
- var chart = new cns(tag, dataProvider, xType, colorScale, tooltip);
+ var chart = new TF.LineChart(tag, dataProvider, xType, colorScale, tooltip);
var svg = d3.select(this.$.chartsvg);
this.async(function() {
chart.renderTo(svg);
+ this.scopeSubtree(this.$.chartsvg, true);
this._chart = chart;
}, 350);
},
+ _smoothingChanged: function() {
+ if(!this._chart) {
+ return;
+ }
+ if(this.smoothingEnabled) {
+ this._chart.smoothingUpdate(this.smoothingDecay);
+ }
+ else {
+ this._chart.smoothingDisable();
+ }
+ },
attached: function() {
this._initialized = true;
},
@@ -2168,7 +2721,7 @@ var TF;
/**
* ReloadBehavior: A simple behavior for dashboards where the
* frontendReload() function should find every child element with a
- * given tag name (e.g. "tf-chart" or "tf-image-loader")
+ * given tag name (e.g. "tf-line-chart" or "tf-image-loader")
* and call a `reload` method on that child.
* May later extend it so it has more sophisticated logic, e.g. reloading
* only tags that are in view.
@@ -2895,7 +3448,8 @@ var TF;
<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>
+ <paper-checkbox id="download-option" checked="{{_showDownloadLinks}}">Data download links</paper-checkbox>
+ <tf-smoothing-input enabled="{{_smoothingEnabled}}" decay="{{_smoothingDecay}}" step="0.001" min="0" max="1"></tf-smoothing-input>
</div>
<div class="sidebar-section">
<tf-x-type-selector id="xTypeSelector" out-x-type="{{xType}}"></tf-x-type-selector>
@@ -2913,10 +3467,10 @@ var TF;
<div class="card">
<span class="card-title">[[tag]]</span>
<div class="card-content">
- <tf-chart tag="[[tag]]" data-provider="[[dataProvider]]" type="scalar" id="chart" selected-runs="[[validRuns(tag, selectedRuns.*, run2tag.*)]]" x-type="[[xType]]" color-scale="[[colorScale]]" on-keyup="toggleSelected" tabindex="2"></tf-chart>
- <paper-icon-button class="expand-button" shift$="[[_show_download_links]]" icon="fullscreen" on-tap="toggleSelected"></paper-icon-button>
+ <tf-line-chart tag="[[tag]]" data-provider="[[dataProvider]]" id="chart" selected-runs="[[validRuns(tag, selectedRuns.*, run2tag.*)]]" x-type="[[xType]]" color-scale="[[colorScale]]" smoothing-decay="[[_smoothingDecay]]" smoothing-enabled="[[_smoothingEnabled]]" on-keyup="toggleSelected" tabindex="2"></tf-line-chart>
+ <paper-icon-button class="expand-button" shift$="[[_showDownloadLinks]]" icon="fullscreen" on-tap="toggleSelected"></paper-icon-button>
</div>
- <template is="dom-if" if="[[_show_download_links]]">
+ <template is="dom-if" if="[[_showDownloadLinks]]">
<div class="card-bottom-row">
<tf-downloader selected-runs="[[selectedRuns]]" tag="[[tag]]" url-fn="[[scalarUrl]]" run-to-tag="[[run2tag]]">
</tf-downloader>
@@ -2938,7 +3492,7 @@ var TF;
Polymer({
is: "tf-event-dashboard",
behaviors: [
- TF.Dashboard.ReloadBehavior("tf-chart"),
+ TF.Dashboard.ReloadBehavior("tf-line-chart"),
TF.Backend.Behavior,
],
properties: {
@@ -2952,7 +3506,15 @@ var TF;
type: Array,
computed: "_getVisibleTags(selectedRuns.*, run2tag.*)"
},
- _show_download_links: Boolean,
+ _showDownloadLinks: {
+ type: Boolean,
+ notify: true,
+ value: TF.URIStorage.getBooleanInitializer('_showDownloadLinks',
+ false),
+ observer: '_showDownloadLinksObserver'
+ },
+ _smoothingDecay: Number,
+ _smoothingEnabled: Boolean,
colorScale: {
type: Object,
notify: true,
@@ -2963,9 +3525,9 @@ var TF;
this.fire("rendered");
});
},
- observers: ['redraw(_show_download_links)'],
- redraw: function(_show_download_links) {
- var els = this.getElementsByTagName("tf-chart");
+ observers: ['redraw(_showDownloadLinks)'],
+ redraw: function(_showDownloadLinks) {
+ var els = this.getElementsByTagName("tf-line-chart");
for (var i=0; i<els.length; i++) {
els[i].redraw();
}
@@ -2978,6 +3540,8 @@ var TF;
_getScalarUrl: function() {
return this.router.scalars;
},
+ _showDownloadLinksObserver: TF.URIStorage.getBooleanObserver(
+ '_showDownloadLinks', false),
toggleSelected: function(e) {
var currentTarget = Polymer.dom(e.currentTarget);
var parentDiv = currentTarget.parentNode.parentNode;
@@ -2997,6 +3561,338 @@ var TF;
});
</script>
</dom-module>
+<dom-module id="tf-obsolete-histogram-chart" assetpath="../tf-histogram-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;
+ position: relative;
+ }
+ svg {
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ flex-grow: 1;
+ flex-shrink: 1;
+ }
+
+ </style>
+ </template>
+ <script>/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+/* tslint:disable:no-namespace variable-name */
+var TF;
+(function (TF) {
+ var HistogramChart = (function () {
+ function HistogramChart(tag, dataFn, xType, colorScale, tooltip) {
+ this.dataFn = dataFn;
+ this.run2datasets = {};
+ this.tag = tag;
+ this.colorScale = colorScale;
+ this.tooltip = tooltip;
+ this.buildChart(xType);
+ }
+ /**
+ * Change the runs on the chart. The work of actually setting the dataset
+ * on the plot is deferred to the subclass because it is impl-specific.
+ * Changing runs automatically triggers a reload; this ensures that the
+ * newly selected run will have data, and that all the runs will be current
+ * (it would be weird if one run was ahead of the others, and the display
+ * depended on the order in which runs were added)
+ */
+ HistogramChart.prototype.changeRuns = function (runs) {
+ var _this = this;
+ this.runs = runs;
+ this.reload();
+ var datasets = runs.map(function (r) { return _this.getDataset(r); });
+ this.plots.forEach(function (p) { return p.datasets(datasets); });
+ };
+ /**
+ * Reload data for each run in view.
+ */
+ HistogramChart.prototype.reload = function () {
+ var _this = this;
+ this.runs.forEach(function (run) {
+ var dataset = _this.getDataset(run);
+ _this.dataFn(_this.tag, run).then(function (x) { return dataset.data(x); });
+ });
+ };
+ HistogramChart.prototype.getDataset = function (run) {
+ if (this.run2datasets[run] === undefined) {
+ this.run2datasets[run] =
+ new Plottable.Dataset([], { run: run, tag: this.tag });
+ }
+ return this.run2datasets[run];
+ };
+ HistogramChart.prototype.buildChart = function (xType) {
+ if (this.outer) {
+ this.outer.destroy();
+ }
+ var xComponents = TF.ChartHelpers.getXComponents(xType);
+ this.xAccessor = xComponents.accessor;
+ this.xScale = xComponents.scale;
+ this.xAxis = xComponents.axis;
+ this.xAxis.margin(0).tickLabelPadding(3);
+ this.yScale = new Plottable.Scales.Linear();
+ this.yAxis = new Plottable.Axes.Numeric(this.yScale, 'left');
+ var yFormatter = TF.ChartHelpers.multiscaleFormatter(TF.ChartHelpers.Y_AXIS_FORMATTER_PRECISION);
+ this.yAxis.margin(0).tickLabelPadding(5).formatter(yFormatter);
+ this.yAxis.usesTextWidthApproximation(true);
+ var center = this.buildPlot(this.xAccessor, this.xScale, this.yScale);
+ this.gridlines =
+ new Plottable.Components.Gridlines(this.xScale, this.yScale);
+ this.center = new Plottable.Components.Group([this.gridlines, center]);
+ this.outer = new Plottable.Components.Table([[this.yAxis, this.center], [null, this.xAxis]]);
+ };
+ 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[i][1]; }; });
+ var median = 4;
+ var medianAccessor = accessors[median];
+ var plots = _.range(accessors.length - 1).map(function (i) {
+ var p = new Plottable.Plots.Area();
+ p.x(xAccessor, xScale);
+ var y0 = i > median ? accessors[i] : accessors[i + 1];
+ var y = i > median ? accessors[i + 1] : accessors[i];
+ p.y(y, yScale);
+ p.y0(y0);
+ p.attr('fill', function (d, i, dataset) {
+ return _this.colorScale.scale(dataset.metadata().run);
+ });
+ p.attr('stroke', function (d, i, dataset) {
+ return _this.colorScale.scale(dataset.metadata().run);
+ });
+ p.attr('stroke-weight', function (d, i, m) { return '0.5px'; });
+ p.attr('stroke-opacity', function () { return opacities[i]; });
+ p.attr('fill-opacity', function () { return opacities[i]; });
+ return p;
+ });
+ var medianPlot = new Plottable.Plots.Line();
+ medianPlot.x(xAccessor, xScale);
+ medianPlot.y(medianAccessor, yScale);
+ medianPlot.attr('stroke', function (d, i, m) { return _this.colorScale.scale(m.run); });
+ this.plots = plots;
+ return new Plottable.Components.Group(plots);
+ };
+ HistogramChart.prototype.renderTo = function (target) { this.outer.renderTo(target); };
+ HistogramChart.prototype.redraw = function () { this.outer.redraw(); };
+ HistogramChart.prototype.destroy = function () { this.outer.destroy(); };
+ return HistogramChart;
+ }());
+ TF.HistogramChart = HistogramChart;
+})(TF || (TF = {}));
+</script>
+ <script>/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+/* tslint:disable:no-namespace variable-name */
+var TF;
+(function (TF) {
+ var ChartHelpers;
+ (function (ChartHelpers) {
+ ChartHelpers.Y_TOOLTIP_FORMATTER_PRECISION = 4;
+ ChartHelpers.STEP_FORMATTER_PRECISION = 4;
+ ChartHelpers.Y_AXIS_FORMATTER_PRECISION = 3;
+ ChartHelpers.TOOLTIP_Y_PIXEL_OFFSET = 20;
+ ChartHelpers.TOOLTIP_CIRCLE_SIZE = 4;
+ ChartHelpers.NAN_SYMBOL_SIZE = 6;
+ /* Create a formatter function that will switch between exponential and
+ * regular display depending on the scale of the number being formatted,
+ * and show `digits` significant digits.
+ */
+ function multiscaleFormatter(digits) {
+ return function (v) {
+ var absv = Math.abs(v);
+ if (absv < 1E-15) {
+ // Sometimes zero-like values get an annoying representation
+ absv = 0;
+ }
+ var f;
+ if (absv >= 1E4) {
+ f = d3.format('.' + digits + 'e');
+ }
+ else if (absv > 0 && absv < 0.01) {
+ f = d3.format('.' + digits + 'e');
+ }
+ else {
+ f = d3.format('.' + digits + 'g');
+ }
+ return f(v);
+ };
+ }
+ ChartHelpers.multiscaleFormatter = multiscaleFormatter;
+ function accessorize(key) {
+ return function (d, index, dataset) { return d[key]; };
+ }
+ ChartHelpers.accessorize = accessorize;
+ ChartHelpers.stepFormatter = Plottable.Formatters.siSuffix(ChartHelpers.STEP_FORMATTER_PRECISION);
+ function stepX() {
+ var scale = new Plottable.Scales.Linear();
+ var axis = new Plottable.Axes.Numeric(scale, 'bottom');
+ axis.formatter(ChartHelpers.stepFormatter);
+ return {
+ scale: scale,
+ axis: axis,
+ accessor: function (d) { return d.step; },
+ };
+ }
+ ChartHelpers.stepX = stepX;
+ ChartHelpers.timeFormatter = Plottable.Formatters.time('%a %b %e, %H:%M:%S');
+ function wallX() {
+ var scale = new Plottable.Scales.Time();
+ return {
+ scale: scale,
+ axis: new Plottable.Axes.Time(scale, 'bottom'),
+ accessor: function (d) { return d.wall_time; },
+ };
+ }
+ ChartHelpers.wallX = wallX;
+ ChartHelpers.relativeAccessor = function (d, index, dataset) {
+ // We may be rendering the final-point datum for scatterplot.
+ // If so, we will have already provided the 'relative' property
+ if (d.relative != null) {
+ return d.relative;
+ }
+ var data = dataset.data();
+ // I can't imagine how this function would be called when the data is
+ // empty (after all, it iterates over the data), but lets guard just
+ // to be safe.
+ var first = data.length > 0 ? +data[0].wall_time : 0;
+ return (+d.wall_time - first) / (60 * 60 * 1000); // ms to hours
+ };
+ ChartHelpers.relativeFormatter = function (n) {
+ // we will always show 2 units of precision, e.g days and hours, or
+ // minutes and seconds, but not hours and minutes and seconds
+ var ret = '';
+ var days = Math.floor(n / 24);
+ n -= (days * 24);
+ if (days) {
+ ret += days + 'd ';
+ }
+ var hours = Math.floor(n);
+ n -= hours;
+ n *= 60;
+ if (hours || days) {
+ ret += hours + 'h ';
+ }
+ var minutes = Math.floor(n);
+ n -= minutes;
+ n *= 60;
+ if (minutes || hours || days) {
+ ret += minutes + 'm ';
+ }
+ var seconds = Math.floor(n);
+ return ret + seconds + 's';
+ };
+ function relativeX() {
+ var scale = new Plottable.Scales.Linear();
+ return {
+ scale: scale,
+ axis: new Plottable.Axes.Numeric(scale, 'bottom'),
+ accessor: ChartHelpers.relativeAccessor,
+ };
+ }
+ ChartHelpers.relativeX = relativeX;
+ // a very literal definition of NaN: true for NaN for a non-number type
+ // or null, etc. False for Infinity or -Infinity
+ ChartHelpers.isNaN = function (x) { return +x !== x; };
+ function getXComponents(xType) {
+ switch (xType) {
+ case 'step':
+ return stepX();
+ case 'wall_time':
+ return wallX();
+ case 'relative':
+ return relativeX();
+ default:
+ throw new Error('invalid xType: ' + xType);
+ }
+ }
+ ChartHelpers.getXComponents = getXComponents;
+ })(ChartHelpers = TF.ChartHelpers || (TF.ChartHelpers = {}));
+})(TF || (TF = {}));
+</script>
+ <script>
+ Polymer({
+ is: "tf-obsolete-histogram-chart",
+ properties: {
+ _chart: Object,
+ colorScale: Object,
+ tag: String,
+ selectedRuns: Array,
+ xType: String,
+ dataProvider: Function,
+ _initialized: Boolean,
+ },
+ observers: [
+ "_makeChart(tag, dataProvider, xType, colorScale, _initialized)",
+ "_changeRuns(_chart, selectedRuns.*)"
+ ],
+ _changeRuns: function(chart) {
+ if (chart.tag !== this.tag) {
+ return; // hack around some weird polymer bug :(
+ }
+ this._chart.changeRuns(this.selectedRuns);
+ this.redraw();
+ },
+ redraw: function() {
+ this._chart.redraw();
+ },
+ reload: function() {
+ this._chart.reload();
+ },
+ _makeChart: function(tag, dataProvider, xType, colorScale, _initialized) {
+ if (!_initialized) {
+ return;
+ }
+ if (this._chart) this._chart.destroy();
+ var chart = new TF.HistogramChart(tag, dataProvider, 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-histogram-dashboard" assetpath="../tf-histogram-dashboard/">
<template>
@@ -3028,7 +3924,7 @@ var TF;
<div class="card">
<span class="card-title">[[tag]]</span>
<div class="card-content">
- <tf-chart tag="[[tag]]" type="compressedHistogram" id="chart" selected-runs="[[_array(run)]]" x-type="[[xType]]" data-provider="[[dataProvider]]" color-scale="[[colorScale]]" on-keyup="toggleSelected" tabindex="2"></tf-chart>
+ <tf-obsolete-histogram-chart tag="[[tag]]" id="chart" selected-runs="[[_array(run)]]" x-type="[[xType]]" data-provider="[[dataProvider]]" color-scale="[[colorScale]]" on-keyup="toggleSelected" tabindex="2"></tf-obsolete-histogram-chart>
<paper-icon-button class="expand-button" icon="fullscreen" on-tap="toggleSelected"></paper-icon-button>
</div>
</div>
@@ -3048,7 +3944,7 @@ var TF;
Polymer({
is: "tf-histogram-dashboard",
behaviors: [
- TF.Dashboard.ReloadBehavior("tf-chart"),
+ TF.Dashboard.ReloadBehavior("tf-obsolete-histogram-chart"),
TF.Backend.Behavior,
],
properties: {
@@ -10051,6 +10947,24 @@ var tf;
return _.map(strings, function (str) { return str.substring(largestIndex); });
}
util.removeCommonPrefix = removeCommonPrefix;
+ /**
+ * Given a queryString, aka ?foo=1&bar=2, return the object representation.
+ */
+ function getQueryParams(queryString) {
+ if (queryString.charAt(0) === '?') {
+ queryString = queryString.slice(1);
+ }
+ var queryParams = _.chain(queryString.split('&'))
+ .map(function (item) {
+ if (item) {
+ return item.split('=');
+ }
+ })
+ .compact()
+ .value();
+ return _.object(queryParams);
+ }
+ util.getQueryParams = getQueryParams;
})(util = graph.util || (graph.util = {}));
})(graph = tf.graph || (tf.graph = {}));
})(tf || (tf = {}));
@@ -13018,24 +13932,28 @@ span.counter {
</paper-menu>
</paper-dropdown-menu>
</div>
- <div class="control-holder">
- <div class="title">Session runs <span class="counter">([[_numSessionRuns(metadataTags)]])</span></div>
- <paper-dropdown-menu no-label-float="" no-animations="" noink="" class="run-dropdown">
- <paper-menu id="select" class="dropdown-content" selected="{{selectedMetadataTag}}">
- <template is="dom-repeat" items="[[metadataTags]]">
- <paper-item>[[item.tag]]</paper-item>
- </template>
- <paper-item>None</paper-item>
- </paper-menu>
- </paper-dropdown-menu>
- </div>
- <div class="control-holder">
- <div class="title">Upload</div>
- <paper-button raised="" class="text-button upload-button" on-click="_getFile">Choose File</paper-button>
- <div class="hidden-input">
- <input type="file" id="file" name="file" on-change="_updateFileInput">
+ <template is="dom-if" if="[[showSessionRunsDropdown]]">
+ <div class="control-holder">
+ <div class="title">Session runs <span class="counter">([[_numSessionRuns(metadataTags)]])</span></div>
+ <paper-dropdown-menu no-label-float="" no-animations="" noink="" class="run-dropdown">
+ <paper-menu id="select" class="dropdown-content" selected="{{selectedMetadataTag}}">
+ <template is="dom-repeat" items="[[metadataTags]]">
+ <paper-item>[[item.tag]]</paper-item>
+ </template>
+ <paper-item>None</paper-item>
+ </paper-menu>
+ </paper-dropdown-menu>
</div>
- </div>
+ </template>
+ <template is="dom-if" if="[[showUploadButton]]">
+ <div class="control-holder">
+ <div class="title">Upload</div>
+ <paper-button raised="" class="text-button upload-button" on-click="_getFile">Choose File</paper-button>
+ <div class="hidden-input">
+ <input type="file" id="file" name="file" on-change="_updateFileInput">
+ </div>
+ </div>
+ </template>
<div class="control-holder">
<div class="title">
Trace inputs
@@ -13299,6 +14217,14 @@ Polymer({
_currentGradientParams: {
type: Object,
computed: '_getCurrentGradientParams(colorByParams, colorBy)'
+ },
+ showSessionRunsDropdown: {
+ type: Boolean,
+ value: true
+ },
+ showUploadButton: {
+ type: Boolean,
+ value: true
}
},
listeners: {
@@ -13432,7 +14358,7 @@ Polymer({
}
},
_getFile: function() {
- this.$.file.click();
+ this.$$("#file").click();
},
_setDownloadFilename: function(graphPath) {
// Strip off everything before the last "/" and strip off the file
@@ -13519,219 +14445,7 @@ Polymer({
});
})();
</script>
-<dom-module id="tf-storage" assetpath="../tf-storage/">
- <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.
-==============================================================================*/
-/* tslint:disable:no-namespace */
-/**
- * The Storage Module provides storage for URL parameters, and an API for
- * getting and setting TensorBoard's stateful URI.
- *
- * It generates URI components like: events&runPrefix=train*
- * which TensorBoard uses after like localhost:8000/#events&runPrefix=train*
- * to store state in the URI.
- */
-var TF;
-(function (TF) {
- var URIStorage;
- (function (URIStorage) {
- /**
- * A key that users cannot use, since TensorBoard uses this to store info
- * about the active tab.
- */
- URIStorage.TAB = '__tab__';
- /**
- * Return a string stored in the URI, given a corresonding key.
- * Null if not found.
- */
- function getString(key) {
- var items = _componentToDict(_readComponent());
- return _.isUndefined(items[key]) ? null : items[key];
- }
- URIStorage.getString = getString;
- /**
- * Store a string in the URI, with a corresponding key.
- */
- function setString(key, value) {
- var items = _componentToDict(_readComponent());
- items[key] = value;
- _writeComponent(_dictToComponent(items));
- }
- URIStorage.setString = setString;
- /**
- * Return a number stored in the URI, given a corresponding key.
- */
- function getNumber(key) {
- var items = _componentToDict(_readComponent());
- return _.isUndefined(items[key]) ? null : +items[key];
- }
- URIStorage.getNumber = getNumber;
- /**
- * Store a number in the URI, with a corresponding key.
- */
- function setNumber(key, value) {
- var items = _componentToDict(_readComponent());
- items[key] = '' + value;
- _writeComponent(_dictToComponent(items));
- }
- URIStorage.setNumber = setNumber;
- /**
- * Return an object stored in the URI, given a corresponding key.
- */
- function getObject(key) {
- var items = _componentToDict(_readComponent());
- return _.isUndefined(items[key]) ? null : JSON.parse(atob(items[key]));
- }
- URIStorage.getObject = getObject;
- /**
- * Store an object in the URI, with a corresponding key.
- */
- function setObject(key, value) {
- var items = _componentToDict(_readComponent());
- items[key] = btoa(JSON.stringify(value));
- _writeComponent(_dictToComponent(items));
- }
- URIStorage.setObject = setObject;
- /**
- * Read component from URI (e.g. returns "events&runPrefix=train*").
- */
- function _readComponent() {
- return TF.Globals.USE_HASH ? window.location.hash.slice(1) :
- TF.Globals.FAKE_HASH;
- }
- /**
- * Write component to URI.
- */
- function _writeComponent(component) {
- if (TF.Globals.USE_HASH) {
- window.location.hash = component;
- }
- else {
- TF.Globals.FAKE_HASH = component;
- }
- }
- /**
- * Convert dictionary of strings into a URI Component.
- * All key value entries get added as key value pairs in the component,
- * with the exception of a key with the TAB value, which if present
- * gets prepended to the URI Component string for backwards comptability
- * reasons.
- */
- function _dictToComponent(items) {
- var component = '';
- // Add the tab name e.g. 'events', 'images', 'histograms' as a prefix
- // for backwards compatbility.
- if (items[URIStorage.TAB] !== undefined) {
- component += items[URIStorage.TAB];
- }
- // Join other strings with &key=value notation
- var nonTab = _.pairs(items)
- .filter(function (pair) { return pair[0] !== URIStorage.TAB; })
- .map(function (pair) {
- return encodeURIComponent(pair[0]) + '=' +
- encodeURIComponent(pair[1]);
- })
- .join('&');
- return nonTab.length > 0 ? (component + '&' + nonTab) : component;
- }
- /**
- * Convert a URI Component into a dictionary of strings.
- * Component should consist of key-value pairs joined by a delimiter
- * with the exception of the tabName.
- * Returns dict consisting of all key-value pairs and
- * dict[TAB] = tabName
- */
- function _componentToDict(component) {
- var items = {};
- var tokens = component.split('&');
- tokens.forEach(function (token) {
- var kv = token.split('=');
- // Special backwards compatibility for URI components like #events
- if (kv.length === 1 && _.contains(TF.Globals.TABS, kv[0])) {
- items[URIStorage.TAB] = kv[0];
- }
- else if (kv.length === 2) {
- items[decodeURIComponent(kv[0])] = decodeURIComponent(kv[1]);
- }
- });
- return items;
- }
- })(URIStorage = TF.URIStorage || (TF.URIStorage = {}));
-})(TF || (TF = {}));
-</script>
-</dom-module>
-<script>/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-==============================================================================*/
-var TF;
-(function (TF) {
- var TensorBoard;
- (function (TensorBoard) {
- TensorBoard.AUTORELOAD_LOCALSTORAGE_KEY = 'TF.TensorBoard.autoReloadEnabled';
- var getAutoReloadFromLocalStorage = function () {
- var val = window.localStorage.getItem(TensorBoard.AUTORELOAD_LOCALSTORAGE_KEY);
- return val === 'true' || val == null; // defaults to true
- };
- TensorBoard.AutoReloadBehavior = {
- properties: {
- autoReloadEnabled: {
- type: Boolean,
- observer: '_autoReloadObserver',
- value: getAutoReloadFromLocalStorage,
- },
- _autoReloadId: {
- type: Number,
- },
- autoReloadIntervalSecs: {
- type: Number,
- value: 120,
- },
- },
- detached: function () { window.clearTimeout(this._autoReloadId); },
- _autoReloadObserver: function (autoReload) {
- window.localStorage.setItem(TensorBoard.AUTORELOAD_LOCALSTORAGE_KEY, autoReload);
- if (autoReload) {
- var _this = this;
- this._autoReloadId = window.setTimeout(this._doAutoReload.bind(this), this.autoReloadIntervalSecs * 1000);
- }
- else {
- window.clearTimeout(this._autoReloadId);
- }
- },
- _doAutoReload: function () {
- if (this.reload == null) {
- throw new Error('AutoReloadBehavior requires a reload method');
- }
- this.reload();
- this._autoReloadId = window.setTimeout(this._doAutoReload.bind(this), this.autoReloadIntervalSecs * 1000);
- }
- };
- })(TensorBoard = TF.TensorBoard || (TF.TensorBoard = {}));
-})(TF || (TF = {}));
-</script></div><dom-module id="tf-tensorboard">
+</div><dom-module id="tf-tensorboard">
<template>
<paper-dialog with-backdrop="" id="settings">
<h2>Settings</h2>