Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,12 @@
</td>
</tr>
</ng-container>
<tr class="legend" *ngIf="additionalItemsCount > 0">
<td colspan="100">
{{ additionalItemsCount }} additional {{ additionalItemsCount ===
1 ? 'item' : 'items' }}
</td>
</tr>
</tbody>
</table>
</ng-template>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,13 +115,21 @@ $_data_table_initial_height: 100px;
}

.tooltip {
border-spacing: 4px;
border-spacing: 8px;
font-size: 13px;

th {
text-align: left;
}

td {
text-align: justify;

.legend {
font-weight: 500;
}
}

$_circle-size: 12px;

.tooltip-row {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,10 +149,13 @@ export class ScalarCardComponent<Downloader> {
@ViewChild('dataTableContainer')
dataTableContainer?: ElementRef;

readonly MAX_TOOLTIP_ITEMS = 5;

constructor(private readonly ref: ElementRef, private dialog: MatDialog) {}

yScaleType = ScaleType.LINEAR;
isViewBoxOverridden: boolean = false;
tooltipTotalCount = 0;

toggleYScaleType() {
this.yScaleType =
Expand Down Expand Up @@ -224,22 +227,31 @@ export class ScalarCardComponent<Downloader> {
scalarTooltipData[minIndex].metadata.closest = true;
}

let sortedData: ScalarTooltipDatum[];
switch (this.tooltipSort) {
case TooltipSort.ASCENDING:
return scalarTooltipData.sort((a, b) => a.dataPoint.y - b.dataPoint.y);
sortedData = scalarTooltipData.sort(
(a, b) => a.dataPoint.y - b.dataPoint.y
);
break;
case TooltipSort.DESCENDING:
return scalarTooltipData.sort((a, b) => b.dataPoint.y - a.dataPoint.y);
sortedData = scalarTooltipData.sort(
(a, b) => b.dataPoint.y - a.dataPoint.y
);
break;
case TooltipSort.NEAREST:
return scalarTooltipData.sort((a, b) => {
sortedData = scalarTooltipData.sort((a, b) => {
return a.metadata.distToCursorPixels - b.metadata.distToCursorPixels;
});
break;
case TooltipSort.NEAREST_Y:
return scalarTooltipData.sort((a, b) => {
sortedData = scalarTooltipData.sort((a, b) => {
return a.metadata.distToCursorY - b.metadata.distToCursorY;
});
break;
case TooltipSort.DEFAULT:
case TooltipSort.ALPHABETICAL:
return scalarTooltipData.sort((a, b) => {
sortedData = scalarTooltipData.sort((a, b) => {
if (a.metadata.displayName < b.metadata.displayName) {
return -1;
}
Expand All @@ -248,7 +260,15 @@ export class ScalarCardComponent<Downloader> {
}
return 0;
});
break;
}

this.tooltipTotalCount = sortedData.length;
return sortedData.slice(0, this.MAX_TOOLTIP_ITEMS);
}

get additionalItemsCount(): number {
return Math.max(0, this.tooltipTotalCount - this.MAX_TOOLTIP_ITEMS);
}

openDataDownloadDialog(): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,9 @@ class TestableLineChart {
<ng-container
*ngFor="
let datum of getCursorAwareTooltipData(
tooltipDataForTesting,
cursorLocationInDataCoordForTesting,
cursorLocationForTesting
tooltipData,
cursorLocationInDataCoord,
cursorLocation
)
"
>
Expand Down Expand Up @@ -233,6 +233,12 @@ class TestableLineChart {
</td>
</tr>
</ng-container>
<tr class="legend" *ngIf="additionalItemsCount > 0">
<td colspan="100">
{{ additionalItemsCount }} additional
{{ additionalItemsCount === 1 ? 'item' : 'items' }}
</td>
</tr>
</tbody>
</table>
</ng-template>
Expand All @@ -257,10 +263,13 @@ class TestableScalarCardLineChart {
readonly valueFormatter = numberFormatter;
readonly stepFormatter = intlNumberFormatter;

readonly MAX_TOOLTIP_ITEMS = 5;
tooltipTotalCount = 0;

constructor(public readonly changeDetectorRef: ChangeDetectorRef) {}

getCursorAwareTooltipData(
tooltipData: TooltipDatum<ScalarCardSeriesMetadata>[],
tooltipData: TooltipDatum<ScalarCardSeriesMetadata, ScalarCardPoint>[],
cursorLocationInDataCoord: {x: number; y: number},
cursorLocation: {x: number; y: number}
) {
Expand Down Expand Up @@ -293,22 +302,31 @@ class TestableScalarCardLineChart {
scalarTooltipData[minIndex].metadata.closest = true;
}

let sortedData;
switch (this.tooltipSort) {
case TooltipSort.ASCENDING:
return scalarTooltipData.sort((a, b) => a.dataPoint.y - b.dataPoint.y);
sortedData = scalarTooltipData.sort(
(a, b) => a.dataPoint.y - b.dataPoint.y
);
break;
case TooltipSort.DESCENDING:
return scalarTooltipData.sort((a, b) => b.dataPoint.y - a.dataPoint.y);
sortedData = scalarTooltipData.sort(
(a, b) => b.dataPoint.y - a.dataPoint.y
);
break;
case TooltipSort.NEAREST:
return scalarTooltipData.sort((a, b) => {
sortedData = scalarTooltipData.sort((a, b) => {
return a.metadata.distToCursorPixels - b.metadata.distToCursorPixels;
});
break;
case TooltipSort.NEAREST_Y:
return scalarTooltipData.sort((a, b) => {
sortedData = scalarTooltipData.sort((a, b) => {
return a.metadata.distToCursorY - b.metadata.distToCursorY;
});
break;
case TooltipSort.DEFAULT:
case TooltipSort.ALPHABETICAL:
return scalarTooltipData.sort((a, b) => {
sortedData = scalarTooltipData.sort((a, b) => {
if (a.metadata.displayName < b.metadata.displayName) {
return -1;
}
Expand All @@ -317,7 +335,15 @@ class TestableScalarCardLineChart {
}
return 0;
});
break;
}

this.tooltipTotalCount = sortedData.length;
return sortedData.slice(0, this.MAX_TOOLTIP_ITEMS);
}

get additionalItemsCount(): number {
return Math.max(0, this.tooltipTotalCount - this.MAX_TOOLTIP_ITEMS);
}
}

Expand Down Expand Up @@ -784,6 +810,11 @@ describe('scalar card line chart', () => {
tooltipData: TooltipDatum[]
) {
fixture.componentInstance.tooltipDataForTesting = tooltipData;
const lineChart = fixture.debugElement.query(Selector.LINE_CHART);
if (lineChart) {
lineChart.componentInstance.tooltipDataForTesting = tooltipData;
lineChart.componentInstance.changeDetectorRef.markForCheck();
}
fixture.componentInstance.changeDetectorRef.markForCheck();
}

Expand All @@ -792,11 +823,23 @@ describe('scalar card line chart', () => {
dataPoint?: {x: number; y: number},
domPoint?: Point
) {
const lineChart = fixture.debugElement.query(Selector.LINE_CHART);
if (dataPoint) {
fixture.componentInstance.dataPointForTesting = dataPoint;
if (lineChart) {
lineChart.componentInstance.dataPointForTesting = dataPoint;
lineChart.componentInstance.cursorLocationInDataCoordForTesting =
dataPoint;
}
}
if (domPoint) {
fixture.componentInstance.cursorLocationForTesting = domPoint;
if (lineChart) {
lineChart.componentInstance.cursorLocationForTesting = domPoint;
}
}
if (lineChart) {
lineChart.componentInstance.changeDetectorRef.markForCheck();
}
fixture.componentInstance.changeDetectorRef.markForCheck();
}
Expand Down Expand Up @@ -1369,6 +1412,96 @@ describe('scalar card line chart', () => {
['', 'world', '-500', '1,000', anyString, anyString],
]);
}));

describe('tooltip item limiting and legend', () => {
const colors = [
'#00f',
'#0f0',
'#f00',
'#ff0',
'#0ff',
'#f0f',
'#fff',
'#000',
];

function buildTooltipData(count: number) {
return Array.from({length: count}, (_, i) =>
buildTooltipDatum({
id: `row${i + 1}`,
type: SeriesType.ORIGINAL,
displayName: `Row ${i + 1}`,
alias: null,
visible: true,
color: colors[i % colors.length],
})
);
}

function getLegendRow(
fixture: ComponentFixture<TestableScalarCardLineChart>
) {
return fixture.debugElement.query(By.css('table.tooltip tr.legend'));
}

it('displays all items when there are 5 or fewer', fakeAsync(() => {
store.overrideSelector(selectors.getMetricsScalarSmoothing, 0);
const fixture = createComponent();
setTooltipData(fixture, buildTooltipData(5));
fixture.detectChanges();

expect(fixture.debugElement.queryAll(Selector.TOOLTIP_ROW).length).toBe(
5
);
expect(getLegendRow(fixture)).toBeNull();
}));

it('limits tooltip to 5 items when there are more than 5', fakeAsync(() => {
store.overrideSelector(selectors.getMetricsScalarSmoothing, 0);
const fixture = createComponent();
setTooltipData(fixture, buildTooltipData(7));
fixture.detectChanges();

expect(fixture.debugElement.queryAll(Selector.TOOLTIP_ROW).length).toBe(
5
);
}));

it('shows legend with singular text for 1 additional item', fakeAsync(() => {
store.overrideSelector(selectors.getMetricsScalarSmoothing, 0);
const fixture = createComponent();
setTooltipData(fixture, buildTooltipData(6));
fixture.detectChanges();

const legendRow = getLegendRow(fixture);
expect(legendRow).not.toBeNull();
expect(legendRow.nativeElement.textContent.trim()).toBe(
'1 additional item'
);
}));

it('shows legend with plural text for multiple additional items', fakeAsync(() => {
store.overrideSelector(selectors.getMetricsScalarSmoothing, 0);
const fixture = createComponent();
setTooltipData(fixture, buildTooltipData(8));
fixture.detectChanges();

const legendRow = getLegendRow(fixture);
expect(legendRow).not.toBeNull();
expect(legendRow.nativeElement.textContent.trim()).toBe(
'3 additional items'
);
}));

it('does not show legend when there are exactly 5 items', fakeAsync(() => {
store.overrideSelector(selectors.getMetricsScalarSmoothing, 0);
const fixture = createComponent();
setTooltipData(fixture, buildTooltipData(5));
fixture.detectChanges();

expect(getLegendRow(fixture)).toBeNull();
}));
});
});

describe('linked time feature integration', () => {
Expand Down
Loading