Skip to content

Commit

Permalink
step selector: gather data for data table (#5744)
Browse files Browse the repository at this point in the history
* step selector: gather data for data table

* fix BUILD file lint issues

* CR
  • Loading branch information
JamesHollyer authored Jun 7, 2022
1 parent 3e6f43b commit f4d1baf
Show file tree
Hide file tree
Showing 7 changed files with 290 additions and 3 deletions.
1 change: 1 addition & 0 deletions tensorboard/webapp/metrics/views/card_renderer/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ tf_ng_module(
"//tensorboard/webapp/widgets/line_chart_v2:line_chart_utils",
"//tensorboard/webapp/widgets/line_chart_v2/lib:formatter",
"//tensorboard/webapp/widgets/line_chart_v2/lib:public_types",
"//tensorboard/webapp/widgets/line_chart_v2/sub_view",
"//tensorboard/webapp/widgets/text:truncated_path",
"@npm//@angular/common",
"@npm//@angular/core",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,10 @@
</div>
<ng-container *ngIf="isDataTableEnabled && selectedTime">
<div>
<tb-data-table></tb-data-table>
<tb-data-table
[headers]="dataHeaders"
[data]="getSelectedTimeTableData()"
></tb-data-table>
</div>
</ng-container>
<ng-template
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,19 @@ import {
siNumberFormatter,
} from '../../../widgets/line_chart_v2/lib/formatter';
import {LineChartComponent} from '../../../widgets/line_chart_v2/line_chart_component';
import {findClosestIndex} from '../../../widgets/line_chart_v2/sub_view/line_chart_interactive_utils';
import {
RendererType,
ScaleType,
TooltipDatum,
} from '../../../widgets/line_chart_v2/types';
import {TooltipSort, XAxisType} from '../../types';
import {
ColumnHeaders,
ScalarCardDataSeries,
ScalarCardSeriesMetadata,
ScalarCardSeriesMetadataMap,
SelectedStepRunData,
} from './scalar_card_types';
import {ViewSelectedTime} from './utils';

Expand Down Expand Up @@ -97,6 +100,12 @@ export class ScalarCardComponent<Downloader> {

yScaleType = ScaleType.LINEAR;
isViewBoxOverridden: boolean = false;
dataHeaders: ColumnHeaders[] = [
ColumnHeaders.RUN,
ColumnHeaders.VALUE,
ColumnHeaders.STEP,
ColumnHeaders.RELATIVE_TIME,
];

toggleYScaleType() {
this.yScaleType =
Expand Down Expand Up @@ -202,4 +211,46 @@ export class ScalarCardComponent<Downloader> {
: null,
};
}

getSelectedTimeTableData(): SelectedStepRunData[] {
if (this.selectedTime === null) {
return [];
}
const dataTableData: SelectedStepRunData[] = this.dataSeries
.filter((datum) => {
const metadata = this.chartMetadataMap[datum.id];
return metadata && metadata.visible && !Boolean(metadata.aux);
})
.map((datum) => {
const metadata = this.chartMetadataMap[datum.id];
const closestStartPoint =
datum.points[
findClosestIndex(datum.points, this.selectedTime!.startStep)
];
const selectedStepData: SelectedStepRunData = {};
selectedStepData.COLOR = metadata.color;
for (const header of this.dataHeaders) {
switch (header) {
case ColumnHeaders.RUN:
selectedStepData.RUN = metadata.displayName;
continue;
case ColumnHeaders.STEP:
selectedStepData.STEP = closestStartPoint.step;
continue;
case ColumnHeaders.VALUE:
selectedStepData.VALUE = closestStartPoint.value;
continue;
case ColumnHeaders.RELATIVE_TIME:
selectedStepData.RELATIVE_TIME =
closestStartPoint.relativeTimeInMs;
continue;
default:
continue;
}
}
return selectedStepData;
});

return dataTableData;
}
}
199 changes: 199 additions & 0 deletions tensorboard/webapp/metrics/views/card_renderer/scalar_card_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2246,4 +2246,203 @@ describe('scalar card', () => {
}));
});
});

describe('getSelectedTimeTableData', () => {
it('builds single selected step data object', fakeAsync(() => {
const runToSeries = {
run1: [
{wallTime: 1, value: 1, step: 1},
{wallTime: 2, value: 10, step: 2},
{wallTime: 3, value: 20, step: 3},
],
run2: [
{wallTime: 1, value: 1, step: 1},
{wallTime: 2, value: 10, step: 2},
{wallTime: 3, value: 20, step: 3},
],
};
provideMockCardRunToSeriesData(
selectSpy,
PluginType.SCALARS,
'card1',
null /* metadataOverride */,
runToSeries
);
store.overrideSelector(
selectors.getCurrentRouteRunSelection,
new Map([
['run1', true],
['run2', true],
])
);

store.overrideSelector(getMetricsSelectedTime, {
start: {step: 2},
end: {step: 50},
});

const fixture = createComponent('card1');
const scalarCardComponent = fixture.debugElement.query(
By.directive(ScalarCardComponent)
);
fixture.detectChanges();

const data =
scalarCardComponent.componentInstance.getSelectedTimeTableData();

expect(data).toEqual([
{
COLOR: '#fff',
RELATIVE_TIME: 1000,
RUN: 'run1',
STEP: 2,
VALUE: 10,
},
{
COLOR: '#fff',
RELATIVE_TIME: 1000,
RUN: 'run2',
STEP: 2,
VALUE: 10,
},
]);
}));

it('selects closest points to selectedTime', fakeAsync(() => {
const runToSeries = {
run1: [
{wallTime: 1, value: 1, step: 1},
{wallTime: 2, value: 10, step: 20},
{wallTime: 3, value: 20, step: 35},
],
run2: [
{wallTime: 1, value: 1, step: 1},
{wallTime: 2, value: 10, step: 15},
{wallTime: 3, value: 20, step: 50},
],
};
provideMockCardRunToSeriesData(
selectSpy,
PluginType.SCALARS,
'card1',
null /* metadataOverride */,
runToSeries
);
store.overrideSelector(
selectors.getCurrentRouteRunSelection,
new Map([
['run1', true],
['run2', true],
])
);

store.overrideSelector(getMetricsSelectedTime, {
start: {step: 18},
end: null,
});

const fixture = createComponent('card1');
const scalarCardComponent = fixture.debugElement.query(
By.directive(ScalarCardComponent)
);
fixture.detectChanges();

const data =
scalarCardComponent.componentInstance.getSelectedTimeTableData();

expect(data[0].STEP).toEqual(20);
expect(data[1].STEP).toEqual(15);
}));

it('Selects largest points when selectedTime startStep is greater than any points step', fakeAsync(() => {
const runToSeries = {
run1: [
{wallTime: 1, value: 1, step: 1},
{wallTime: 2, value: 10, step: 20},
{wallTime: 3, value: 20, step: 35},
],
run2: [
{wallTime: 1, value: 1, step: 1},
{wallTime: 2, value: 10, step: 15},
{wallTime: 3, value: 20, step: 50},
],
};
provideMockCardRunToSeriesData(
selectSpy,
PluginType.SCALARS,
'card1',
null /* metadataOverride */,
runToSeries
);
store.overrideSelector(
selectors.getCurrentRouteRunSelection,
new Map([
['run1', true],
['run2', true],
])
);

store.overrideSelector(getMetricsSelectedTime, {
start: {step: 100},
end: null,
});

const fixture = createComponent('card1');
const scalarCardComponent = fixture.debugElement.query(
By.directive(ScalarCardComponent)
);
fixture.detectChanges();

const data =
scalarCardComponent.componentInstance.getSelectedTimeTableData();

expect(data[0].STEP).toEqual(35);
expect(data[1].STEP).toEqual(50);
}));

it('Selects smallest points when selectedTime startStep is less than any points step', fakeAsync(() => {
const runToSeries = {
run1: [
{wallTime: 1, value: 1, step: 10},
{wallTime: 2, value: 10, step: 20},
{wallTime: 3, value: 20, step: 35},
],
run2: [
{wallTime: 1, value: 1, step: 8},
{wallTime: 2, value: 10, step: 15},
{wallTime: 3, value: 20, step: 50},
],
};
provideMockCardRunToSeriesData(
selectSpy,
PluginType.SCALARS,
'card1',
null /* metadataOverride */,
runToSeries
);
store.overrideSelector(
selectors.getCurrentRouteRunSelection,
new Map([
['run1', true],
['run2', true],
])
);
store.overrideSelector(getMetricsSelectedTime, {
start: {step: 1},
end: null,
});

const fixture = createComponent('card1');
const scalarCardComponent = fixture.debugElement.query(
By.directive(ScalarCardComponent)
);
fixture.detectChanges();

const data =
scalarCardComponent.componentInstance.getSelectedTimeTableData();

expect(data[0].STEP).toEqual(10);
expect(data[1].STEP).toEqual(8);
}));
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,26 @@ export interface PartitionedSeries {
runId: string;
points: ScalarCardPoint[];
}

/**
* This enum defines the columns available in the data table. The
* ScalarCardComponent must know which piece of data is associated with each
* value and the DataTable widget must know how to display each value.
*/
export enum ColumnHeaders {
COLOR = 'COLOR',
RELATIVE_TIME = 'RELATIVE_TIME',
RUN = 'RUN',
STEP = 'STEP',
TIME = 'TIME',
VALUE = 'VALUE',
}

/**
* An object which essentially contains the data for an entire row in the
* DataTable. It will have a value for each required ColumnHeader for a given
* run.
*/
export type SelectedStepRunData = {
[key in ColumnHeaders]?: string | number;
};
1 change: 1 addition & 0 deletions tensorboard/webapp/widgets/data_table/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ tf_ng_module(
":data_table_styles",
],
deps = [
"//tensorboard/webapp/metrics/views/card_renderer:scalar_card_types",
"//tensorboard/webapp/widgets/line_chart_v2/lib:formatter",
"@npm//@angular/common",
"@npm//@angular/core",
Expand Down
13 changes: 11 additions & 2 deletions tensorboard/webapp/widgets/data_table/data_table_component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,21 @@ See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/

import {ChangeDetectionStrategy, Component} from '@angular/core';
import {ChangeDetectionStrategy, Component, Input} from '@angular/core';
import {
ColumnHeaders,
SelectedStepRunData,
} from '../../metrics/views/card_renderer/scalar_card_types';

@Component({
selector: 'tb-data-table',
templateUrl: 'data_table_component.ng.html',
styleUrls: ['data_table_component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DataTableComponent {}
export class DataTableComponent {
// The order of this array of headers determines the order which they are
// displayed in the table.
@Input() headers!: ColumnHeaders[];
@Input() data!: SelectedStepRunData[];
}

0 comments on commit f4d1baf

Please sign in to comment.