diff --git a/tensorboard/components_polymer3/tf_backend/canceller.ts b/tensorboard/components_polymer3/tf_backend/canceller.ts index 727d86818f..dc78a9b13b 100644 --- a/tensorboard/components_polymer3/tf_backend/canceller.ts +++ b/tensorboard/components_polymer3/tf_backend/canceller.ts @@ -13,6 +13,11 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ +export interface CancelResult { + value: T; + cancelled: boolean; +} + /** * A class that allows marking promises as cancelled. * @@ -45,9 +50,7 @@ export class Canceller { * a `cancelled` argument. This argument will be `false` unless and * until `cancelAll` is invoked after the creation of this task. */ - public cancellable( - f: (result: {value: T; cancelled: boolean}) => U - ): (T) => U { + public cancellable(f: (result: CancelResult) => U): (T) => U { const originalCancellationCount = this.cancellationCount; return (value) => { const cancelled = this.cancellationCount !== originalCancellationCount; diff --git a/tensorboard/components_polymer3/tf_dashboard_common/data-loader-behavior.ts b/tensorboard/components_polymer3/tf_dashboard_common/data-loader-behavior.ts index 6a4d01bde0..cd3547374b 100644 --- a/tensorboard/components_polymer3/tf_dashboard_common/data-loader-behavior.ts +++ b/tensorboard/components_polymer3/tf_dashboard_common/data-loader-behavior.ts @@ -15,7 +15,7 @@ limitations under the License. import {PolymerElement} from '@polymer/polymer'; import * as _ from 'lodash'; -import {Canceller} from '../tf_backend/canceller'; +import {CancelResult, Canceller} from '../tf_backend/canceller'; import {RequestManager} from '../tf_backend/requestManager'; type CacheKey = string; @@ -34,6 +34,23 @@ export interface DataLoaderBehaviorInterface dataToLoad: Item[]; } +// A function that takes a list of items and asynchronously fetches the +// data for those items. As each item loads, it should invoke the +// `onLoad` callback with an `{item, data}` pair to update the cache. +// After all items have finished loading, it should invoke the +// `onFinish` callback. Conceptually, that this function accepts +// `onLoad` and `onFinish` as arguments is as if it returned an +// Observable-style stream of `{item, data}`-pairs, CPS-transformed. +// +// Used in `DataLoaderBehavior.requestData`. +export interface RequestDataCallback { + ( + items: Item[], + onLoad: (kv: {item: Item; data: Data}) => void, + onFinish: () => void + ): void; +} + export function DataLoaderBehavior( superClass: new () => PolymerElement ): new () => DataLoaderBehaviorInterface { @@ -47,16 +64,16 @@ export function DataLoaderBehavior( */ loadKey = ''; - // List of data to be loaded. By default, a datum is passed to + // List of items to be loaded. By default, items are passed to // `requestData` to fetch data. When the request resolves, invokes // `loadDataCallback` with the datum and its response. dataToLoad: Item[] = []; /** - * A function that takes a datum as an input and returns a unique + * A function that takes an item as an input and returns a unique * identifiable string. Used for caching purposes. */ - getDataLoadName = (datum: Item): CacheKey => String(datum); + getDataLoadName = (item: Item): CacheKey => String(item); /** * A function that takes as inputs: @@ -66,28 +83,11 @@ export function DataLoaderBehavior( * This function will be called when a response from a request to that * data URL is successfully received. */ - loadDataCallback!: (component: this, datum: Item, data: Data) => void; - - public requestManager!: RequestManager; - - // A function that takes a datum as argument and makes the HTTP - // request to fetch the data associated with the datum. It should return - // a promise that either fullfills with the data or rejects with an error. - // If the function doesn't bind 'this', then it will reference the element - // that includes this behavior. - // The default implementation calls this.requestManager.request with - // the value returned by this.getDataLoadUrl(datum) (see below). - // The only place getDataLoadUrl() is called is in the default - // implementation of this method. So if you override this method with - // an implementation that doesn't call getDataLoadUrl, it need not be - // provided. - requestData = (datum: Item) => { - return this.requestManager.request(this.getDataLoadUrl(datum)); - }; - - // A function that takes a datum and returns a string URL for fetching - // data. - getDataLoadUrl!: (datum: Item) => string; + loadDataCallback!: (component: this, item: Item, data: Data) => void; + + // Function that actually loads data from the network. See docs on + // `RequestDataCallback` for details. + requestData: RequestDataCallback; dataLoading = false; @@ -192,62 +192,51 @@ export function DataLoaderBehavior( if (result.cancelled) { return; } - // Read-only property have a special setter. this.dataLoading = true; - // Promises return cacheKeys of the data that were fetched. - const promises = this.dataToLoad - .filter((datum) => { - const cacheKey = this.getDataLoadName(datum); - return !this._dataLoadState.has(cacheKey); - }) - .map((datum) => { - const cacheKey = this.getDataLoadName(datum); - this._dataLoadState.set(cacheKey, LoadState.LOADING); - return this.requestData(datum).then( - this._canceller.cancellable((result) => { - // It was resetted. Do not notify of the response. - if (!result.cancelled) { - this._dataLoadState.set(cacheKey, LoadState.LOADED); - this.loadDataCallback(this, datum, result.value as any); - } - return cacheKey; - }) - ); - }); - return Promise.all(promises) - .then( - this._canceller.cancellable((result) => { - // It was resetted. Do not notify of the data load. - if (!result.cancelled) { - const keysFetched = result.value as any; - const fetched = new Set(keysFetched); - const shouldNotify = this.dataToLoad.some((datum) => - fetched.has(this.getDataLoadName(datum)) - ); - if (shouldNotify) { - this.onLoadFinish(); - } - } - const isDataFetchPending = Array.from( - this._dataLoadState.values() - ).some((loadState) => loadState === LoadState.LOADING); - if (!isDataFetchPending) { - // Read-only property have a special setter. - this.dataLoading = false; - } - }), - // TODO(stephanwlee): remove me when we can use - // Promise.prototype.finally instead - () => {} - ) - .then( - this._canceller.cancellable(({cancelled}) => { - if (cancelled) { - return; + const dirtyItems = this.dataToLoad.filter((datum) => { + const cacheKey = this.getDataLoadName(datum); + return !this._dataLoadState.has(cacheKey); + }); + for (const item of dirtyItems) { + const cacheKey = this.getDataLoadName(item); + this._dataLoadState.set(cacheKey, LoadState.LOADING); + } + const onLoad = this._canceller.cancellable( + (result: CancelResult<{item: Item; data: Data}>) => { + if (result.cancelled) { + return; + } + const {item, data} = result.value; + const cacheKey = this.getDataLoadName(item); + this._dataLoadState.set(cacheKey, LoadState.LOADED); + this.loadDataCallback(this, item, data); + } + ); + const onFinish = this._canceller.cancellable( + (result: CancelResult) => { + // Only notify of data load if the load was not cancelled. + if (!result.cancelled) { + const keysFetched = result.value as any; + const fetched = new Set( + dirtyItems.map((item) => this.getDataLoadName(item)) + ); + const shouldNotify = this.dataToLoad.some((datum) => + fetched.has(this.getDataLoadName(datum)) + ); + if (shouldNotify) { + this.onLoadFinish(); } this._loadDataAsync = null; - }) - ); + } + const isDataFetchPending = Array.from( + this._dataLoadState.values() + ).includes(LoadState.LOADING); + if (!isDataFetchPending) { + this.dataLoading = false; + } + } + ); + this.requestData(dirtyItems, onLoad, () => onFinish(undefined)); }) ); } diff --git a/tensorboard/plugins/custom_scalar/polymer3/tf_custom_scalar_dashboard/tf-custom-scalar-margin-chart-card.ts b/tensorboard/plugins/custom_scalar/polymer3/tf_custom_scalar_dashboard/tf-custom-scalar-margin-chart-card.ts index 92003f3e01..4b3037cbeb 100644 --- a/tensorboard/plugins/custom_scalar/polymer3/tf_custom_scalar_dashboard/tf-custom-scalar-margin-chart-card.ts +++ b/tensorboard/plugins/custom_scalar/polymer3/tf_custom_scalar_dashboard/tf-custom-scalar-margin-chart-card.ts @@ -20,14 +20,17 @@ import * as _ from 'lodash'; import {DomRepeat} from '../../../../components_polymer3/polymer/dom-repeat'; import '../../../../components_polymer3/polymer/irons_and_papers'; import {LegacyElementMixin} from '../../../../components_polymer3/polymer/legacy_element_mixin'; +import {RequestManager} from '../../../../components_polymer3/tf_backend/requestManager'; import {getRouter} from '../../../../components_polymer3/tf_backend/router'; import {addParams} from '../../../../components_polymer3/tf_backend/urlPathHelpers'; import '../../../../components_polymer3/tf_card_heading/tf-card-heading'; +import {RequestDataCallback} from '../../../../components_polymer3/tf_dashboard_common/data-loader-behavior'; import {runsColorScale} from '../../../../components_polymer3/tf_color_scale/colorScale'; import '../../../../components_polymer3/tf_line_chart_data_loader/tf-line-chart-data-loader'; import {TfLineChartDataLoader} from '../../../../components_polymer3/tf_line_chart_data_loader/tf-line-chart-data-loader'; import { SYMBOLS_LIST, + ScalarDatum, Y_TOOLTIP_FORMATTER_PRECISION, multiscaleFormatter, relativeAccessor, @@ -57,6 +60,12 @@ interface StepsMismatch { seriesObject: MarginChartSeries; } +type RunItem = string; +type CustomScalarsDatum = { + regex_valid: boolean; + tag_to_events: Record; +}; + export interface TfCustomScalarMarginChartCard extends HTMLElement { reload(): void; } @@ -72,11 +81,11 @@ class _TfCustomScalarMarginChartCard extends LegacyElementMixin(PolymerElement) active="[[active]]" color-scale="[[_colorScale]]" data-series="[[_seriesNames]]" - get-data-load-url="[[_dataUrl]]" fill-area="[[_fillArea]]" ignore-y-outliers="[[ignoreYOutliers]]" load-key="[[_tagFilter]]" data-to-load="[[runs]]" + request-data="[[_requestData]]" log-scale-active="[[_logScaleActive]]" load-data-callback="[[_createProcessDataFunction(marginChartSeries)]]" request-manager="[[requestManager]]" @@ -315,7 +324,7 @@ class _TfCustomScalarMarginChartCard extends LegacyElementMixin(PolymerElement) ignoreYOutliers: boolean; @property({type: Object}) - requestManager: object; + requestManager: RequestManager; @property({type: Boolean}) showDownloadLinks: boolean; @@ -347,12 +356,23 @@ class _TfCustomScalarMarginChartCard extends LegacyElementMixin(PolymerElement) _logScaleActive: boolean; @property({type: Object}) - _dataUrl: (run: string) => string = (run) => { - const tag = this._tagFilter; - return addParams(getRouter().pluginRoute('custom_scalars', '/scalars'), { - tag, - run, - }); + _requestData: RequestDataCallback = ( + items, + onLoad, + onFinish + ) => { + const router = getRouter(); + const baseUrl = router.pluginRoute('custom_scalars', '/scalars'); + Promise.all( + items.map((item) => { + const run = item; + const tag = this._tagFilter; + const url = addParams(baseUrl, {tag, run}); + return this.requestManager + .request(url) + .then((data) => void onLoad({item, data})); + }) + ).finally(() => void onFinish()); }; @property({type: Object}) diff --git a/tensorboard/plugins/custom_scalar/polymer3/tf_custom_scalar_dashboard/tf-custom-scalar-multi-line-chart-card.ts b/tensorboard/plugins/custom_scalar/polymer3/tf_custom_scalar_dashboard/tf-custom-scalar-multi-line-chart-card.ts index be31c297b2..08ee684453 100644 --- a/tensorboard/plugins/custom_scalar/polymer3/tf_custom_scalar_dashboard/tf-custom-scalar-multi-line-chart-card.ts +++ b/tensorboard/plugins/custom_scalar/polymer3/tf_custom_scalar_dashboard/tf-custom-scalar-multi-line-chart-card.ts @@ -25,10 +25,14 @@ import {getRouter} from '../../../../components_polymer3/tf_backend/router'; import '../../../../components_polymer3/tf_backend/tf-backend'; import {addParams} from '../../../../components_polymer3/tf_backend/urlPathHelpers'; import '../../../../components_polymer3/tf_card_heading/tf-card-heading'; +import {RequestDataCallback} from '../../../../components_polymer3/tf_dashboard_common/data-loader-behavior'; import {runsColorScale} from '../../../../components_polymer3/tf_color_scale/colorScale'; import '../../../../components_polymer3/tf_line_chart_data_loader/tf-line-chart-data-loader'; import {TfLineChartDataLoader} from '../../../../components_polymer3/tf_line_chart_data_loader/tf-line-chart-data-loader'; -import {SYMBOLS_LIST} from '../../../../components_polymer3/vz_chart_helpers/vz-chart-helpers'; +import { + SYMBOLS_LIST, + ScalarDatum, +} from '../../../../components_polymer3/vz_chart_helpers/vz-chart-helpers'; import './tf-custom-scalar-card-style'; import { DataSeries, @@ -40,6 +44,12 @@ export interface TfCustomScalarMultiLineChartCard extends HTMLElement { reload(): void; } +type RunItem = string; +type CustomScalarsDatum = { + regex_valid: boolean; + tag_to_events: Record; +}; + @customElement('tf-custom-scalar-multi-line-chart-card') class _TfCustomScalarMultiLineChartCard extends LegacyElementMixin(PolymerElement) @@ -52,10 +62,10 @@ class _TfCustomScalarMultiLineChartCard active="[[active]]" color-scale="[[_colorScale]]" data-series="[[_seriesNames]]" - get-data-load-url="[[_dataUrl]]" ignore-y-outliers="[[ignoreYOutliers]]" load-key="[[_tagFilter]]" data-to-load="[[runs]]" + request-data="[[_requestData]]" log-scale-active="[[_logScaleActive]]" load-data-callback="[[_createProcessDataFunction()]]" request-manager="[[requestManager]]" @@ -235,12 +245,23 @@ class _TfCustomScalarMultiLineChartCard _logScaleActive: boolean; @property({type: Object}) - _dataUrl: (run: string) => string = (run) => { - const tag = this._tagFilter; - return addParams(getRouter().pluginRoute('custom_scalars', '/scalars'), { - tag, - run, - }); + _requestData: RequestDataCallback = ( + items, + onLoad, + onFinish + ) => { + const router = getRouter(); + const baseUrl = router.pluginRoute('custom_scalars', '/scalars'); + Promise.all( + items.map((item) => { + const run = item; + const tag = this._tagFilter; + const url = addParams(baseUrl, {tag, run}); + return this.requestManager + .request(url) + .then((data) => void onLoad({item, data})); + }) + ).finally(() => void onFinish()); }; @property({type: Object}) diff --git a/tensorboard/plugins/distribution/polymer3/tf_distribution_dashboard/tf-distribution-loader.ts b/tensorboard/plugins/distribution/polymer3/tf_distribution_dashboard/tf-distribution-loader.ts index 2e41bbd4a9..98b0d5efe0 100644 --- a/tensorboard/plugins/distribution/polymer3/tf_distribution_dashboard/tf-distribution-loader.ts +++ b/tensorboard/plugins/distribution/polymer3/tf_distribution_dashboard/tf-distribution-loader.ts @@ -24,7 +24,10 @@ import {getRouter} from '../../../../components_polymer3/tf_backend/router'; import {addParams} from '../../../../components_polymer3/tf_backend/urlPathHelpers'; import '../../../../components_polymer3/tf_card_heading/tf-card-heading'; import {runsColorScale} from '../../../../components_polymer3/tf_color_scale/colorScale'; -import {DataLoaderBehavior} from '../../../../components_polymer3/tf_dashboard_common/data-loader-behavior'; +import { + DataLoaderBehavior, + RequestDataCallback, +} from '../../../../components_polymer3/tf_dashboard_common/data-loader-behavior'; import {VzDistributionChart} from '../vz_distribution_chart/vz-distribution-chart'; import '../vz_distribution_chart/vz-distribution-chart'; @@ -32,6 +35,8 @@ export interface TfDistributionLoader extends HTMLElement { reload(): void; } +type RunTagItem = {run: string; tag: string}; + /** tf-distribution-loader loads an individual distribution from the TensorBoard backend, and renders it into a vz-distribution-chart. @@ -117,13 +122,21 @@ class _TfDistributionLoader @property({type: Object}) getDataLoadName = ({run}) => run; - @property({type: Object}) - getDataLoadUrl = ({tag, run}) => { + requestData: RequestDataCallback = ( + items, + onLoad, + onFinish + ) => { const router = getRouter(); - return addParams(router.pluginRoute('distributions', '/distributions'), { - tag, - run, - }); + const baseUrl = router.pluginRoute('distributions', '/distributions'); + Promise.all( + items.map((item) => { + const url = addParams(baseUrl, {tag: item.tag, run: item.run}); + return this.requestManager + .request(url) + .then((data) => void onLoad({item, data})); + }) + ).finally(() => void onFinish()); }; @property({type: Object}) diff --git a/tensorboard/plugins/histogram/polymer3/tf_histogram_dashboard/tf-histogram-loader.ts b/tensorboard/plugins/histogram/polymer3/tf_histogram_dashboard/tf-histogram-loader.ts index 6c0b9731c5..db6a8497aa 100644 --- a/tensorboard/plugins/histogram/polymer3/tf_histogram_dashboard/tf-histogram-loader.ts +++ b/tensorboard/plugins/histogram/polymer3/tf_histogram_dashboard/tf-histogram-loader.ts @@ -19,11 +19,15 @@ import * as _ from 'lodash'; import {LegacyElementMixin} from '../../../../components_polymer3/polymer/legacy_element_mixin'; import '../../../../components_polymer3/polymer/irons_and_papers'; +import {RequestManager} from '../../../../components_polymer3/tf_backend/requestManager'; import {getRouter} from '../../../../components_polymer3/tf_backend/router'; import {addParams} from '../../../../components_polymer3/tf_backend/urlPathHelpers'; import '../../../../components_polymer3/tf_card_heading/tf-card-heading'; import {runsColorScale} from '../../../../components_polymer3/tf_color_scale/colorScale'; -import {DataLoaderBehavior} from '../../../../components_polymer3/tf_dashboard_common/data-loader-behavior'; +import { + DataLoaderBehavior, + RequestDataCallback, +} from '../../../../components_polymer3/tf_dashboard_common/data-loader-behavior'; import '../vz_histogram_timeseries/vz-histogram-timeseries'; import {VzHistogramTimeseries} from '../vz_histogram_timeseries/vz-histogram-timeseries'; import './histogramCore'; @@ -39,9 +43,11 @@ export interface TfHistogramLoader extends HTMLElement { reload(): void; } +type RunTagItem = {run: string; tag: string}; + @customElement('tf-histogram-loader') class _TfHistogramLoader - extends DataLoaderBehavior<{run: string; tag: string}, VzHistogram[]>( + extends DataLoaderBehavior( LegacyElementMixin(PolymerElement) ) implements TfHistogramLoader { @@ -119,12 +125,23 @@ class _TfHistogramLoader getDataLoadName = ({run}: {run: string; tag: string}): string => run; @property({type: Object}) - getDataLoadUrl = ({tag, run}) => { + requestManager: RequestManager; + + requestData: RequestDataCallback = ( + items, + onLoad, + onFinish + ) => { const router = getRouter(); - return addParams(router.pluginRoute('histograms', '/histograms'), { - tag, - run, - }); + const baseUrl = router.pluginRoute('histograms', '/histograms'); + Promise.all( + items.map((item) => { + const url = addParams(baseUrl, {tag: item.tag, run: item.run}); + return this.requestManager + .request(url) + .then((data) => void onLoad({item, data})); + }) + ).finally(() => void onFinish()); }; @property({type: Object}) diff --git a/tensorboard/plugins/hparams/polymer3/tf_hparams_session_group_details/BUILD b/tensorboard/plugins/hparams/polymer3/tf_hparams_session_group_details/BUILD index 60a9044f83..804ea7b936 100644 --- a/tensorboard/plugins/hparams/polymer3/tf_hparams_session_group_details/BUILD +++ b/tensorboard/plugins/hparams/polymer3/tf_hparams_session_group_details/BUILD @@ -16,6 +16,7 @@ tf_ts_library( "//tensorboard/components_polymer3/polymer:legacy_class", "//tensorboard/components_polymer3/tf_backend", "//tensorboard/components_polymer3/tf_color_scale", + "//tensorboard/components_polymer3/tf_dashboard_common", "//tensorboard/components_polymer3/vz_chart_helpers", "//tensorboard/plugins/hparams/polymer3/tf_hparams_utils", "//tensorboard/plugins/scalar/polymer3/tf_scalar_dashboard:tf_scalar_card", diff --git a/tensorboard/plugins/hparams/polymer3/tf_hparams_session_group_details/tf-hparams-session-group-details.ts b/tensorboard/plugins/hparams/polymer3/tf_hparams_session_group_details/tf-hparams-session-group-details.ts index 31f0fa3fc2..c8ed441be1 100644 --- a/tensorboard/plugins/hparams/polymer3/tf_hparams_session_group_details/tf-hparams-session-group-details.ts +++ b/tensorboard/plugins/hparams/polymer3/tf_hparams_session_group_details/tf-hparams-session-group-details.ts @@ -20,6 +20,7 @@ import * as IronResizableBehavior from '@polymer/iron-resizable-behavior'; import {mixinBehaviors} from '../../../../components_polymer3/polymer/legacy_class'; import '../../../../components_polymer3/polymer/irons_and_papers'; import '../../../../components_polymer3/tf_backend/tf-backend'; +import {RequestDataCallback} from '../../../../components_polymer3/tf_dashboard_common/data-loader-behavior'; import * as tf_hparams_utils from '../tf_hparams_utils/tf-hparams-utils'; import * as tf_color_scale from '../../../../components_polymer3/tf_color_scale/palettes'; import * as vz_chart_helpers from '../../../../components_polymer3/vz_chart_helpers/vz-chart-helpers'; @@ -28,6 +29,11 @@ import '../../../scalar/polymer3/tf_scalar_dashboard/tf-scalar-card'; // TODO: add dependency once the Polymer 3 scalar dashboard is migrated. // import '../tf_scalar_dashboard/tf-scalar-card'; +type RunTagItem = { + run: string; + tag: string; +}; + /** * Shows a session group in more detail. Specifically, shows graphs of the * metrics for the session in the group as a function of the training step. @@ -121,17 +127,28 @@ class TfHparamsSessionGroupDetails extends mixinBehaviors( // '_colorScale'. @property({type: Number}) _sessionGroupNameHash: number; + @property({ type: Object, }) - _requestData = ({tag, run}) => { - const request = { - experimentName: this.experimentName, - sessionName: run, - metricName: tag, - }; - return this.backend.listMetricEvals(request); + _requestData: RequestDataCallback< + RunTagItem, + vz_chart_helpers.ScalarDatum[] + > = (items, onLoad, onFinish) => { + Promise.all( + items.map((item) => { + const request = { + experimentName: this.experimentName, + sessionName: item.run, + metricName: item.tag, + }; + return this.backend + .listMetricEvals(request) + .then((data) => void onLoad({item, data})); + }) + ).finally(() => void onFinish()); }; + @property({ type: Object, }) diff --git a/tensorboard/plugins/pr_curve/polymer3/tf_pr_curve_dashboard/tf-pr-curve-card.ts b/tensorboard/plugins/pr_curve/polymer3/tf_pr_curve_dashboard/tf-pr-curve-card.ts index 6f02b8338f..1c04849537 100644 --- a/tensorboard/plugins/pr_curve/polymer3/tf_pr_curve_dashboard/tf-pr-curve-card.ts +++ b/tensorboard/plugins/pr_curve/polymer3/tf_pr_curve_dashboard/tf-pr-curve-card.ts @@ -18,9 +18,11 @@ import {computed, customElement, observe, property} from '@polymer/decorators'; import '../../../../components_polymer3/polymer/irons_and_papers'; import {Canceller} from '../../../../components_polymer3/tf_backend/canceller'; +import {RequestManager} from '../../../../components_polymer3/tf_backend/requestManager'; import {getRouter} from '../../../../components_polymer3/tf_backend/router'; import {addParams} from '../../../../components_polymer3/tf_backend/urlPathHelpers'; import '../../../../components_polymer3/tf_card_heading/tf-card-heading'; +import {RequestDataCallback} from '../../../../components_polymer3/tf_dashboard_common/data-loader-behavior'; import {runsColorScale} from '../../../../components_polymer3/tf_color_scale/colorScale'; import '../../../../components_polymer3/tf_line_chart_data_loader/tf-line-chart-data-loader'; import * as vz_chart_helpers from '../../../../components_polymer3/vz_chart_helpers/vz-chart-helpers'; @@ -28,6 +30,15 @@ import * as vz_chart_helpers from '../../../../components_polymer3/vz_chart_help import * as _ from 'lodash'; import * as Plottable from 'plottable'; +type RunItem = string; + +interface PrCurveDatum { + wall_time: number; + step: number; + precision: number[]; + recall: number[]; +} + @customElement('tf-pr-curve-card') export class TfPrCurveCard extends PolymerElement { static readonly template = html` @@ -49,7 +60,7 @@ export class TfPrCurveCard extends PolymerElement { data-to-load="[[runs]]" data-series="[[runs]]" load-key="[[tag]]" - get-data-load-url="[[_dataUrl]]" + request-data="[[_requestData]]" load-data-callback="[[_createProcessDataFunction()]]" active="[[active]]" > @@ -158,7 +169,7 @@ export class TfPrCurveCard extends PolymerElement { runToStepCap: object; @property({type: Object}) - requestManager: object; + requestManager: RequestManager; @property({type: Boolean}) active: boolean; @@ -263,12 +274,23 @@ export class TfPrCurveCard extends PolymerElement { _defaultYRange: number[] = [-0.05, 1.05]; @property({type: Object}) - _dataUrl: object = (run) => { - const tag = this.tag; - return addParams(getRouter().pluginRoute('pr_curves', '/pr_curves'), { - tag, - run, - }); + _requestData: RequestDataCallback = ( + items, + onLoad, + onFinish + ) => { + const router = getRouter(); + const baseUrl = router.pluginRoute('pr_curves', '/pr_curves'); + Promise.all( + items.map((item) => { + const run = item; + const tag = this.tag; + const url = addParams(baseUrl, {tag, run}); + return this.requestManager + .request(url) + .then((data) => void onLoad({item, data})); + }) + ).finally(() => void onFinish()); }; @property({type: Boolean}) diff --git a/tensorboard/plugins/scalar/polymer3/tf_scalar_dashboard/BUILD b/tensorboard/plugins/scalar/polymer3/tf_scalar_dashboard/BUILD index 5e584831e1..2289b584cc 100644 --- a/tensorboard/plugins/scalar/polymer3/tf_scalar_dashboard/BUILD +++ b/tensorboard/plugins/scalar/polymer3/tf_scalar_dashboard/BUILD @@ -54,6 +54,7 @@ tf_ts_library( "//tensorboard/components_polymer3/tf_color_scale", "//tensorboard/components_polymer3/tf_dashboard_common", "//tensorboard/components_polymer3/tf_line_chart_data_loader", + "//tensorboard/components_polymer3/vz_chart_helpers", "//tensorboard/components_polymer3/vz_line_chart2", "@npm//@polymer/decorators", "@npm//@polymer/polymer", diff --git a/tensorboard/plugins/scalar/polymer3/tf_scalar_dashboard/tf-scalar-card.ts b/tensorboard/plugins/scalar/polymer3/tf_scalar_dashboard/tf-scalar-card.ts index 85748de707..fd3c0c8941 100644 --- a/tensorboard/plugins/scalar/polymer3/tf_scalar_dashboard/tf-scalar-card.ts +++ b/tensorboard/plugins/scalar/polymer3/tf_scalar_dashboard/tf-scalar-card.ts @@ -17,14 +17,19 @@ import {PolymerElement, html} from '@polymer/polymer'; import {customElement, property} from '@polymer/decorators'; import '../../../../components_polymer3/polymer/irons_and_papers'; import {RequestManager} from '../../../../components_polymer3/tf_backend/requestManager'; +import {RequestDataCallback} from '../../../../components_polymer3/tf_dashboard_common/data-loader-behavior'; import {getRouter} from '../../../../components_polymer3/tf_backend/router'; +import {addParams} from '../../../../components_polymer3/tf_backend/urlPathHelpers'; import '../../../../components_polymer3/tf_card_heading/tf-card-heading'; import {runsColorScale} from '../../../../components_polymer3/tf_color_scale/colorScale'; import '../../../../components_polymer3/tf_dashboard_common/tf-downloader'; import '../../../../components_polymer3/tf_line_chart_data_loader/tf-line-chart-data-loader'; +import {ScalarDatum} from '../../../../components_polymer3/vz_chart_helpers/vz-chart-helpers'; import '../../../../components_polymer3/vz_line_chart2/vz-line-chart2'; import {DEFAULT_TOOLTIP_COLUMNS} from '../../../../components_polymer3/vz_line_chart2/vz-line-chart2'; +type RunTagItem = {run: string; tag: string}; + /** * A card that handles loading data (at the right times), rendering a scalar * chart, and providing UI affordances (such as buttons) for scalar data. @@ -252,7 +257,22 @@ export class TfScalarCard extends PolymerElement { // this.requestManager.request( // this.getDataLoadUrl({tag, run, experiment}) @property({type: Object}) - requestData: object; + requestData: RequestDataCallback = ( + items, + onLoad, + onFinish + ) => { + const router = getRouter(); + const baseUrl = router.pluginRoute('scalars', '/scalars'); + Promise.all( + items.map((item) => { + const url = addParams(baseUrl, {tag: item.tag, run: item.run}); + return this.requestManager + .request(url) + .then((data) => void onLoad({item, data})); + }) + ).finally(() => void onFinish()); + }; @property({type: Object}) _getDataLoadName: object = (datum) => this._getSeriesNameFromDatum(datum);