Skip to content

Commit fc1c0b5

Browse files
authored
Embedded Single Card: scalar card line chart (#6445)
Decompose the existing scalar card by creating a separate component for the scalar card line chart to be re-used by both the scalar card and the eventual embedded single card.
1 parent a495a48 commit fc1c0b5

7 files changed

+2508
-0
lines changed

tensorboard/webapp/metrics/views/card_renderer/BUILD

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,53 @@ tf_ng_module(
371371
],
372372
)
373373

374+
tf_sass_binary(
375+
name = "scalar_card_line_chart_styles",
376+
src = "scalar_card_line_chart_component.scss",
377+
strict_deps = False,
378+
deps = [
379+
"//tensorboard/webapp/metrics/views:metrics_common_styles",
380+
"//tensorboard/webapp/theme",
381+
],
382+
)
383+
384+
tf_ng_module(
385+
name = "scalar_card_line_chart",
386+
srcs = [
387+
"scalar_card_line_chart_component.ts",
388+
"scalar_card_line_chart_container.ts",
389+
"scalar_card_line_chart_module.ts",
390+
],
391+
assets = [
392+
":scalar_card_line_chart_styles",
393+
"scalar_card_line_chart_component.ng.html",
394+
],
395+
deps = [
396+
":scalar_card",
397+
":scalar_card_types",
398+
":utils",
399+
"//tensorboard/webapp:app_state",
400+
"//tensorboard/webapp:selectors",
401+
"//tensorboard/webapp/feature_flag/store",
402+
"//tensorboard/webapp/metrics:types",
403+
"//tensorboard/webapp/metrics/actions",
404+
"//tensorboard/webapp/metrics/store",
405+
"//tensorboard/webapp/metrics/views:types",
406+
"//tensorboard/webapp/types",
407+
"//tensorboard/webapp/widgets/card_fob",
408+
"//tensorboard/webapp/widgets/card_fob:types",
409+
"//tensorboard/webapp/widgets/line_chart_v2",
410+
"//tensorboard/webapp/widgets/line_chart_v2:line_chart_utils",
411+
"//tensorboard/webapp/widgets/line_chart_v2/lib:formatter",
412+
"//tensorboard/webapp/widgets/line_chart_v2/lib:public_types",
413+
"//tensorboard/webapp/widgets/line_chart_v2/sub_view",
414+
"@npm//@angular/common",
415+
"@npm//@angular/core",
416+
"@npm//@ngrx/store",
417+
"@npm//rxjs",
418+
],
419+
)
420+
374421
tf_ts_library(
375422
name = "card_renderer_tests",
376423
testonly = True,
@@ -382,6 +429,7 @@ tf_ts_library(
382429
"image_card_test.ts",
383430
"run_name_test.ts",
384431
"scalar_card_fob_test.ts",
432+
"scalar_card_line_chart_test.ts",
385433
"scalar_card_test.ts",
386434
"utils_test.ts",
387435
],
@@ -393,6 +441,7 @@ tf_ts_library(
393441
":run_name",
394442
":scalar_card",
395443
":scalar_card_data_table",
444+
":scalar_card_line_chart",
396445
":scalar_card_types",
397446
":utils",
398447
":vis_linked_time_selection_warning",
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
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+
<line-chart
18+
[disableUpdate]="disableUpdate"
19+
[preferredRendererType]="forceSvg ? RendererType.SVG : RendererType.WEBGL"
20+
[seriesData]="seriesData"
21+
[seriesMetadataMap]="seriesMetadataMap"
22+
[xScaleType]="xScaleType"
23+
[yScaleType]="yScaleType"
24+
[customXFormatter]="getCustomXFormatter()"
25+
[tooltipTemplate]="tooltipTemplate"
26+
[ignoreYOutliers]="ignoreOutliers"
27+
[useDarkMode]="useDarkMode"
28+
[userViewBox]="userViewBox"
29+
(onViewBoxOverridden)="isViewBoxOverridden = $event"
30+
(viewBoxChanged)="onLineChartZoom.emit($event)"
31+
[customVisTemplate]="lineChartCustomVis"
32+
[customChartOverlayTemplate]="lineChartCustomXAxisVis"
33+
></line-chart>
34+
<ng-template
35+
#lineChartCustomVis
36+
let-viewExtent="viewExtent"
37+
let-domDim="domDimension"
38+
let-xScale="xScale"
39+
>
40+
<ng-container *ngIf="stepOrLinkedTimeSelection">
41+
<div
42+
[ngClass]="{
43+
'out-of-selected-time': true,
44+
start: true,
45+
range: !!stepOrLinkedTimeSelection.end?.step
46+
}"
47+
[style.right]="
48+
xScale.forward(
49+
viewExtent.x,
50+
[domDim.width, 0],
51+
stepOrLinkedTimeSelection.start.step
52+
) + 'px'
53+
"
54+
></div>
55+
<div
56+
*ngIf="stepOrLinkedTimeSelection.end?.step"
57+
[ngClass]="{
58+
'out-of-selected-time': true,
59+
end: true,
60+
range: true
61+
}"
62+
[style.left]="
63+
xScale.forward(
64+
viewExtent.x,
65+
[0, domDim.width],
66+
stepOrLinkedTimeSelection.end?.step
67+
) + 'px'
68+
"
69+
></div>
70+
</ng-container>
71+
</ng-template>
72+
73+
<ng-template
74+
#lineChartCustomXAxisVis
75+
let-viewExtent="viewExtent"
76+
let-domDim="domDimension"
77+
let-xScale="xScale"
78+
let-interactionState="interactionState"
79+
>
80+
<ng-container *ngIf="showFobController()">
81+
<scalar-card-fob-controller
82+
[disableInteraction]="interactionState !== 'NONE'"
83+
[timeSelection]="stepOrLinkedTimeSelection"
84+
[scale]="xScale"
85+
[minMaxHorizontalViewExtend]="viewExtent.x"
86+
[minMaxStep]="minMaxStep"
87+
[axisSize]="domDim.width"
88+
(onTimeSelectionChanged)="onTimeSelectionChanged.emit($event)"
89+
(onTimeSelectionToggled)="onFobRemoved()"
90+
></scalar-card-fob-controller>
91+
</ng-container>
92+
</ng-template>
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
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 'tensorboard/webapp/theme/tb_theme';
16+
17+
:host {
18+
display: flex;
19+
flex-direction: column;
20+
box-sizing: border-box;
21+
height: 100%;
22+
// padding: $metrics-preferred-gap;
23+
// When vertically centered, the title's text-top contains extra space above
24+
// the text, which counts towards the visually perceived white space.
25+
// padding-top: $_card_padding_top;
26+
}
27+
28+
line-chart {
29+
flex-grow: 1;
30+
}
31+
32+
.out-of-selected-time {
33+
height: 100%;
34+
position: absolute;
35+
36+
$_border-width: 2px;
37+
38+
&.start {
39+
border-right-width: $_border-width;
40+
margin-left: -0.5 * $_border-width;
41+
42+
&.range {
43+
left: 0;
44+
}
45+
}
46+
47+
&.end {
48+
border-left-width: $_border-width;
49+
margin-right: -0.5 * $_border-width;
50+
right: 0;
51+
}
52+
53+
&.range {
54+
// Replace this with backdrop-filter if opacity works.
55+
background-color: rgba(255, 255, 255, 0.5);
56+
57+
@include tb-dark-theme {
58+
background-color: rgba(0, 0, 0, 0.4);
59+
}
60+
}
61+
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
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 {
16+
ChangeDetectionStrategy,
17+
Component,
18+
ChangeDetectorRef,
19+
ElementRef,
20+
EventEmitter,
21+
Input,
22+
Output,
23+
ViewChild,
24+
} from '@angular/core';
25+
import {DataLoadState} from '../../../types/data';
26+
import {
27+
TimeSelection,
28+
TimeSelectionAffordance,
29+
TimeSelectionToggleAffordance,
30+
} from '../../../widgets/card_fob/card_fob_types';
31+
import {
32+
Formatter,
33+
intlNumberFormatter,
34+
numberFormatter,
35+
relativeTimeFormatter,
36+
siNumberFormatter,
37+
} from '../../../widgets/line_chart_v2/lib/formatter';
38+
import {Extent} from '../../../widgets/line_chart_v2/lib/public_types';
39+
import {LineChartComponent} from '../../../widgets/line_chart_v2/line_chart_component';
40+
import {RendererType, ScaleType} from '../../../widgets/line_chart_v2/types';
41+
import {XAxisType} from '../../types';
42+
import {TooltipTemplate} from '../../../widgets/line_chart_v2/line_chart_component';
43+
import {
44+
MinMaxStep,
45+
ScalarCardDataSeries,
46+
ScalarCardSeriesMetadataMap,
47+
} from './scalar_card_types';
48+
49+
@Component({
50+
selector: 'scalar-card-line-chart-component',
51+
templateUrl: 'scalar_card_line_chart_component.ng.html',
52+
styleUrls: ['scalar_card_line_chart_component.css'],
53+
changeDetection: ChangeDetectionStrategy.OnPush,
54+
})
55+
export class ScalarCardLineChartComponent {
56+
readonly DataLoadState = DataLoadState;
57+
readonly RendererType = RendererType;
58+
readonly ScaleType = ScaleType;
59+
60+
@Input() cardId!: string;
61+
@Input() seriesMetadataMap!: ScalarCardSeriesMetadataMap;
62+
@Input() seriesData!: ScalarCardDataSeries[];
63+
@Input() ignoreOutliers!: boolean;
64+
@Input() disableUpdate!: boolean;
65+
@Input() loadState!: DataLoadState;
66+
@Input() smoothingEnabled!: boolean;
67+
@Input() xAxisType!: XAxisType;
68+
@Input() xScaleType!: ScaleType;
69+
@Input() yScaleType!: ScaleType;
70+
@Input() useDarkMode!: boolean;
71+
@Input() forceSvg!: boolean;
72+
@Input() stepOrLinkedTimeSelection: TimeSelection | undefined;
73+
@Input() minMaxStep!: MinMaxStep;
74+
@Input() userViewBox!: Extent | null;
75+
@Input() tooltipTemplate!: TooltipTemplate | null;
76+
77+
@Output()
78+
onTimeSelectionChanged = new EventEmitter<{
79+
timeSelection: TimeSelection;
80+
affordance?: TimeSelectionAffordance;
81+
}>();
82+
@Output()
83+
onStepSelectorToggled = new EventEmitter<TimeSelectionToggleAffordance>();
84+
85+
@Output() onLineChartZoom = new EventEmitter<Extent | null>();
86+
87+
@ViewChild(LineChartComponent) lineChart?: LineChartComponent;
88+
89+
constructor(private readonly changeDetector: ChangeDetectorRef) {}
90+
91+
isViewBoxOverridden: boolean = false;
92+
93+
resetDomain() {
94+
if (this.lineChart) {
95+
this.lineChart.viewBoxReset();
96+
}
97+
}
98+
99+
readonly relativeXFormatter = relativeTimeFormatter;
100+
readonly valueFormatter = numberFormatter;
101+
readonly stepFormatter = intlNumberFormatter;
102+
103+
getCustomXFormatter(): Formatter | undefined {
104+
switch (this.xAxisType) {
105+
case XAxisType.RELATIVE:
106+
return relativeTimeFormatter;
107+
case XAxisType.STEP:
108+
return siNumberFormatter;
109+
case XAxisType.WALL_TIME:
110+
default:
111+
return undefined;
112+
}
113+
}
114+
115+
onFobRemoved() {
116+
this.onStepSelectorToggled.emit(TimeSelectionToggleAffordance.FOB_DESELECT);
117+
}
118+
119+
showFobController() {
120+
return this.xAxisType === XAxisType.STEP && this.minMaxStep;
121+
}
122+
}

0 commit comments

Comments
 (0)