Skip to content

Commit 14db605

Browse files
rileyajonesyatbear
authored andcommitted
HParams: Create filter dialog component (tensorflow#6493)
## Motivation for features / changes We want to be able to filter the runs table by hparam. The existing logic for this is handled by a bunch of inner components found in the runs_table_component which makes it hard to share. In this PR I am replicating that UI with some enhancements in a standalone component which will later be used by the data_table_component. See tensorflow#6488 for screenshots and a more complete description of what is being added ## Alternate designs / implementations considered (or N/A) I'm not sure that this component should be part of the data table but that is simplest place to put it for now.
1 parent 1ce1129 commit 14db605

12 files changed

+539
-29
lines changed

tensorboard/webapp/hparams/BUILD

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ tf_ts_library(
2323
deps = [
2424
"//tensorboard/webapp/runs/data_source",
2525
"//tensorboard/webapp/runs/data_source:backend_types",
26+
"//tensorboard/webapp/widgets/data_table:types",
2627
],
2728
)
2829

tensorboard/webapp/hparams/_types.ts

+2-22
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,12 @@ See the License for the specific language governing permissions and
1313
limitations under the License.
1414
==============================================================================*/
1515
import {
16-
HparamValue,
17-
MetricValue,
18-
DiscreteHparamValues,
19-
DomainType,
2016
HparamSpec,
2117
MetricSpec,
2218
} from '../runs/data_source/runs_data_source_types';
2319

20+
export {DiscreteFilter, IntervalFilter} from '../widgets/data_table/types';
21+
2422
export {
2523
DatasetType,
2624
DiscreteHparamValue,
@@ -50,21 +48,3 @@ export interface HparamAndMetricSpec {
5048
hparams: HparamSpec[];
5149
metrics: MetricSpec[];
5250
}
53-
54-
export interface DiscreteFilter {
55-
type: DomainType.DISCRETE;
56-
includeUndefined: boolean;
57-
possibleValues: DiscreteHparamValues;
58-
// Subset of `possibleValues`
59-
filterValues: DiscreteHparamValues;
60-
}
61-
62-
export interface IntervalFilter {
63-
type: DomainType.INTERVAL;
64-
includeUndefined: boolean;
65-
minValue: number;
66-
maxValue: number;
67-
// Filter values have to be in between min and max values (inclusive).
68-
filterLowerValue: number;
69-
filterUpperValue: number;
70-
}

tensorboard/webapp/runs/data_source/BUILD

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ tf_ng_module(
1414
deps = [
1515
":backend_types",
1616
"//tensorboard/webapp/webapp_data_source:http_client",
17+
"//tensorboard/webapp/widgets/data_table:types",
1718
"@npm//@angular/core",
1819
"@npm//rxjs",
1920
],

tensorboard/webapp/runs/data_source/runs_data_source.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ import {
2020
TBHttpClient,
2121
} from '../../webapp_data_source/tb_http_client';
2222
import * as backendTypes from './runs_backend_types';
23+
import {DomainType} from '../../widgets/data_table/types';
2324
import {
2425
Domain,
25-
DomainType,
2626
HparamsAndMetadata,
2727
HparamSpec,
2828
HparamValue,

tensorboard/webapp/runs/data_source/runs_data_source_types.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ import {Injectable} from '@angular/core';
1616
import {Observable} from 'rxjs';
1717
import * as backendTypes from './runs_backend_types';
1818

19+
import {DomainType} from '../../widgets/data_table/types';
20+
export {DomainType} from '../../widgets/data_table/types';
21+
1922
export {
2023
BackendHparamsValueType as HparamsValueType,
2124
DatasetType,
@@ -41,11 +44,6 @@ export interface RunToHparamsAndMetrics {
4144
};
4245
}
4346

44-
export enum DomainType {
45-
DISCRETE,
46-
INTERVAL,
47-
}
48-
4947
interface IntervalDomain {
5048
type: DomainType.INTERVAL;
5149
minValue: number;

tensorboard/webapp/widgets/data_table/BUILD

+40
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,16 @@ tf_sass_binary(
5151
],
5252
)
5353

54+
tf_sass_binary(
55+
name = "filter_dialog_styles",
56+
src = "filter_dialog_component.scss",
57+
strict_deps = False,
58+
deps = [
59+
"//tensorboard/webapp:angular_material_sass_deps",
60+
"//tensorboard/webapp/theme",
61+
],
62+
)
63+
5464
tf_ng_module(
5565
name = "data_table",
5666
srcs = [
@@ -71,12 +81,14 @@ tf_ng_module(
7181
deps = [
7282
":column_selector",
7383
":data_table_header",
84+
":filter_dialog",
7485
":types",
7586
"//tensorboard/webapp/angular:expect_angular_material_button",
7687
"//tensorboard/webapp/angular:expect_angular_material_icon",
7788
"//tensorboard/webapp/metrics/views/card_renderer:scalar_card_types",
7889
"//tensorboard/webapp/widgets/custom_modal",
7990
"//tensorboard/webapp/widgets/line_chart_v2/lib:formatter",
91+
"//tensorboard/webapp/widgets/range_input:types",
8092
"@npm//@angular/common",
8193
"@npm//@angular/core",
8294
"@npm//rxjs",
@@ -123,6 +135,27 @@ tf_ng_module(
123135
],
124136
)
125137

138+
tf_ng_module(
139+
name = "filter_dialog",
140+
srcs = [
141+
"filter_dialog_component.ts",
142+
"filter_dialog_module.ts",
143+
],
144+
assets = [
145+
"filter_dialog_component.ng.html",
146+
":filter_dialog_styles",
147+
],
148+
deps = [
149+
":types",
150+
"//tensorboard/webapp/angular:expect_angular_material_checkbox",
151+
"//tensorboard/webapp/widgets/filter_input",
152+
"//tensorboard/webapp/widgets/range_input",
153+
"//tensorboard/webapp/widgets/range_input:types",
154+
"@npm//@angular/common",
155+
"@npm//@angular/core",
156+
],
157+
)
158+
126159
tf_ts_library(
127160
name = "types",
128161
srcs = [
@@ -137,16 +170,23 @@ tf_ts_library(
137170
"column_selector_test.ts",
138171
"content_cell_component_test.ts",
139172
"data_table_test.ts",
173+
"filter_dialog_test.ts",
140174
"header_cell_component_test.ts",
141175
],
142176
deps = [
143177
":column_selector",
144178
":data_table",
179+
":filter_dialog",
145180
":types",
181+
"//tensorboard/webapp/angular:expect_angular_cdk_testing",
182+
"//tensorboard/webapp/angular:expect_angular_cdk_testing_testbed",
146183
"//tensorboard/webapp/angular:expect_angular_core_testing",
184+
"//tensorboard/webapp/angular:expect_angular_material_checkbox",
147185
"//tensorboard/webapp/angular:expect_angular_platform_browser_animations",
148186
"//tensorboard/webapp/testing:mat_icon",
149187
"//tensorboard/webapp/widgets/custom_modal",
188+
"//tensorboard/webapp/widgets/range_input",
189+
"//tensorboard/webapp/widgets/range_input:types",
150190
"@npm//@angular/core",
151191
"@npm//@angular/forms",
152192
"@npm//@angular/platform-browser",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<!--
2+
@license
3+
Copyright 2023 The TensorFlow Authors. All Rights Reserved.
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
-->
17+
<div class="filter-dialog">
18+
<div
19+
*ngIf="filter.type === DomainType.DISCRETE"
20+
class="discrete-filters-area"
21+
>
22+
<tb-filter-input
23+
*ngIf="filter.possibleValues"
24+
[value]="discreteValueFilter"
25+
(keyup)="discreteValueKeyUp($event)"
26+
placeholder="Filter Discrete Values (regex)"
27+
></tb-filter-input>
28+
<div *ngIf="!getPossibleValues().length" class="no-matches">
29+
No Matching Values
30+
</div>
31+
<div
32+
*ngFor="let value of getPossibleValues(); index"
33+
(click)="$event.stopPropagation()"
34+
>
35+
<mat-checkbox
36+
[checked]="filter.filterValues.includes(value)"
37+
(change)="discreteFilterChanged.emit(value)"
38+
><span>{{ value }}</span></mat-checkbox
39+
>
40+
</div>
41+
</div>
42+
43+
<div
44+
*ngIf="filter.type === DomainType.INTERVAL"
45+
(click)="$event.stopPropagation()"
46+
class="range-input-container"
47+
disableRipple
48+
>
49+
<tb-range-input
50+
[min]="filter.minValue"
51+
[max]="filter.maxValue"
52+
[lowerValue]="filter.filterLowerValue"
53+
[upperValue]="filter.filterUpperValue"
54+
(rangeValuesChanged)="intervalFilterChanged.emit($event)"
55+
></tb-range-input>
56+
</div>
57+
58+
<mat-checkbox
59+
[checked]="filter.includeUndefined"
60+
(change)="includeUndefinedToggled.emit()"
61+
>Include Undefined</mat-checkbox
62+
>
63+
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/* Copyright 2023 The TensorFlow Authors. All Rights Reserved.
2+
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License.
14+
==============================================================================*/
15+
@use '@angular/material' as mat;
16+
@import 'tensorboard/webapp/theme/tb_theme';
17+
18+
.filter-dialog {
19+
padding: 8px;
20+
border-radius: 4px;
21+
border: 1px solid;
22+
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
23+
border-color: mat.get-color-from-palette($tb-foreground, border);
24+
background-color: mat.get-color-from-palette($tb-background, background);
25+
26+
@include tb-dark-theme {
27+
border-color: mat.get-color-from-palette($tb-dark-foreground, border);
28+
background-color: mat.get-color-from-palette(
29+
$tb-dark-background,
30+
'background'
31+
);
32+
}
33+
34+
.discrete-filters-area {
35+
max-height: 100px;
36+
overflow-y: auto;
37+
}
38+
39+
.no-matches {
40+
// 12px is the width of the checkbox so the text should match the
41+
// indentation of the selectable filters.
42+
padding: 8px 12px;
43+
}
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/* Copyright 2023 The TensorFlow Authors. All Rights Reserved.
2+
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License.
14+
==============================================================================*/
15+
import {Component, EventEmitter, Input, Output} from '@angular/core';
16+
import {
17+
DomainType,
18+
DiscreteFilter,
19+
IntervalFilter,
20+
DiscreteFilterValue,
21+
} from './types';
22+
import {RangeValues} from '../range_input/types';
23+
24+
@Component({
25+
selector: 'tb-data-table-filter',
26+
templateUrl: 'filter_dialog_component.ng.html',
27+
styleUrls: ['filter_dialog_component.css'],
28+
})
29+
export class FilterDialog {
30+
DomainType = DomainType;
31+
32+
discreteValueFilter: string = '';
33+
34+
@Input() filter!: DiscreteFilter | IntervalFilter;
35+
36+
@Output() discreteFilterChanged = new EventEmitter<DiscreteFilterValue>();
37+
38+
@Output() intervalFilterChanged = new EventEmitter<RangeValues>();
39+
40+
@Output() includeUndefinedToggled = new EventEmitter<void>();
41+
42+
getPossibleValues() {
43+
const values: DiscreteFilterValue[] =
44+
(this.filter as DiscreteFilter).possibleValues ?? [];
45+
if (!this.discreteValueFilter) {
46+
return values;
47+
}
48+
return values.filter((value) =>
49+
value.toString().match(this.discreteValueFilter)
50+
);
51+
}
52+
53+
discreteValueKeyUp(event: KeyboardEvent) {
54+
this.discreteValueFilter = (event.target! as HTMLInputElement).value;
55+
}
56+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/* Copyright 2023 The TensorFlow Authors. All Rights Reserved.
2+
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License.
14+
==============================================================================*/
15+
16+
import {NgModule} from '@angular/core';
17+
import {FilterDialog} from './filter_dialog_component';
18+
import {CommonModule} from '@angular/common';
19+
import {MatCheckboxModule} from '@angular/material/checkbox';
20+
import {RangeInputModule} from '../range_input/range_input_module';
21+
import {FilterInputModule} from '../filter_input/filter_input_module';
22+
23+
@NgModule({
24+
declarations: [FilterDialog],
25+
imports: [
26+
CommonModule,
27+
MatCheckboxModule,
28+
FilterInputModule,
29+
RangeInputModule,
30+
],
31+
exports: [FilterDialog],
32+
})
33+
export class FilterDialogModule {}

0 commit comments

Comments
 (0)