Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

timeseries: improved formatting on tooltip #5003

Merged
merged 1 commit into from
May 24, 2021
Merged
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 @@ -140,10 +140,12 @@
<span [style.backgroundColor]="datum.metadata.color"></span>
</td>
<td class="name">{{ datum.metadata.displayName }}</td>
<td *ngIf="smoothingEnabled">{{ datum.point.y }}</td>
<td>{{ datum.point.value }}</td>
<td *ngIf="smoothingEnabled">
{{ valueFormatter.formatShort(datum.point.y) }}
</td>
<td>{{ valueFormatter.formatShort(datum.point.value) }}</td>
<!-- Print the step with comma for readability. -->
<td>{{ datum.point.step | number }}</td>
<td>{{ stepFormatter.formatShort(datum.point.step) }}</td>
<td>{{ datum.point.wallTime | date: 'short' }}</td>
<td *ngIf="xAxisType === XAxisType.RELATIVE">
{{ relativeXFormatter.formatReadable(datum.point.x) }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ import {
import {MatDialog} from '@angular/material/dialog';

import {DataLoadState} from '../../../types/data';
import {relativeTimeFormatter} from '../../../widgets/line_chart_v2/lib/formatter';
import {
numberFormatter,
relativeTimeFormatter,
siNumberFormatter,
} from '../../../widgets/line_chart_v2/lib/formatter';
import {LineChartComponent} from '../../../widgets/line_chart_v2/line_chart_component';
import {
RendererType,
Expand Down Expand Up @@ -102,6 +106,8 @@ export class ScalarCardComponent<Downloader> {
}

readonly relativeXFormatter = relativeTimeFormatter;
readonly valueFormatter = numberFormatter;
readonly stepFormatter = siNumberFormatter;

getCursorAwareTooltipData(
tooltipData: TooltipDatum<ScalarCardSeriesMetadata>[],
Expand Down
24 changes: 12 additions & 12 deletions tensorboard/webapp/metrics/views/card_renderer/scalar_card_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1041,8 +1041,8 @@ describe('scalar card', () => {
{
x: 10,
step: 10,
y: 500,
value: 1000,
y: 10002000,
value: 10001337,
wallTime: new Date('2020-01-01').getTime(),
}
),
Expand All @@ -1059,8 +1059,8 @@ describe('scalar card', () => {
{
x: 1000,
step: 1000,
y: -500,
value: -1000,
y: -0.0005,
value: -0.9312345,
wallTime: new Date('2020-12-31').getTime(),
}
),
Expand All @@ -1081,10 +1081,10 @@ describe('scalar card', () => {
]);

assertTooltipRows(fixture, [
['', 'Row 1', '500', '1000', '10', '1/1/20, 12:00 AM'],
['', 'Row 1', '1e+7', '1e+7', '10', '1/1/20, 12:00 AM'],
// Print the step with comma for readability. The value is yet optimize for
// readability (we may use the scientific formatting).
['', 'Row 2', '-500', '-1000', '1,000', '12/31/20, 12:00 AM'],
['', 'Row 2', '-5e-4', '-0.9312', '1,000', '12/31/20, 12:00 AM'],
]);
}));

Expand Down Expand Up @@ -1222,7 +1222,7 @@ describe('scalar card', () => {

assertTooltipRows(fixture, [
['', 'Row 2', '-500', '1,000', jasmine.any(String)],
['', 'Row 3', '3', '10,000', jasmine.any(String)],
['', 'Row 3', '3', '10k', jasmine.any(String)],
['', 'Row 1', '1000', '10', jasmine.any(String)],
]);
}));
Expand Down Expand Up @@ -1291,7 +1291,7 @@ describe('scalar card', () => {

assertTooltipRows(fixture, [
['', 'Row 1', '1000', '10', jasmine.any(String)],
['', 'Row 3', '3', '10,000', jasmine.any(String)],
['', 'Row 3', '3', '10k', jasmine.any(String)],
['', 'Row 2', '-500', '1,000', jasmine.any(String)],
]);
}));
Expand Down Expand Up @@ -1361,21 +1361,21 @@ describe('scalar card', () => {
assertTooltipRows(fixture, [
['', 'Row 2', '-500', '1,000', jasmine.any(String)],
['', 'Row 1', '1000', '0', jasmine.any(String)],
['', 'Row 3', '3', '10,000', jasmine.any(String)],
['', 'Row 3', '3', '10k', jasmine.any(String)],
]);

setCursorLocation(fixture, {x: 500, y: 600});
fixture.detectChanges();
assertTooltipRows(fixture, [
['', 'Row 1', '1000', '0', jasmine.any(String)],
['', 'Row 2', '-500', '1,000', jasmine.any(String)],
['', 'Row 3', '3', '10,000', jasmine.any(String)],
['', 'Row 3', '3', '10k', jasmine.any(String)],
]);

setCursorLocation(fixture, {x: 10000, y: -100});
fixture.detectChanges();
assertTooltipRows(fixture, [
['', 'Row 3', '3', '10,000', jasmine.any(String)],
['', 'Row 3', '3', '10k', jasmine.any(String)],
['', 'Row 2', '-500', '1,000', jasmine.any(String)],
['', 'Row 1', '1000', '0', jasmine.any(String)],
]);
Expand All @@ -1386,7 +1386,7 @@ describe('scalar card', () => {
assertTooltipRows(fixture, [
['', 'Row 1', '1000', '0', jasmine.any(String)],
['', 'Row 2', '-500', '1,000', jasmine.any(String)],
['', 'Row 3', '3', '10,000', jasmine.any(String)],
['', 'Row 3', '3', '10k', jasmine.any(String)],
]);
}));
});
Expand Down
2 changes: 2 additions & 0 deletions tensorboard/webapp/widgets/line_chart_v2/lib/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ tf_ts_library(
srcs = [
"coordinator_test.ts",
"drawable_test.ts",
"formatter_test.ts",
"integration_test.ts",
"scale_test.ts",
],
Expand All @@ -168,6 +169,7 @@ tf_ts_library(
":chart",
":coordinator",
":drawable",
":formatter",
":internal_types",
":public_types",
":scale",
Expand Down
61 changes: 57 additions & 4 deletions tensorboard/webapp/widgets/line_chart_v2/lib/formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,27 @@ export interface Formatter {
* Possible usage: tooltips on a line chart.
*/
formatReadable(x: number): string;

/**
* Represents a number in a long form of a human readable string. The string
* should not lose information and must follow localization.
*
* Possible usage: on `title` attributes to show raw values.
*/
formatLong(x: number): string;
}

const LARGE_NUMBER = 10000;
const SMALL_NUMBER = 0.001;

/**
* ================
* NUMBER FORMATTER
* ================
*/

const d3NumberFormatter = format('.2~e');
const d3TrimFormatter = format('~');
const d3ShortFormatter = format('.4~r');
const d3LongFormatter = format(',~');

function formatNumberShort(x: number): string {
Expand All @@ -55,19 +66,48 @@ function formatNumberShort(x: number): string {
}

const absNum = Math.abs(x);
if (absNum >= 100000 || absNum < 0.001) {
if (absNum >= LARGE_NUMBER || absNum < SMALL_NUMBER) {
return d3NumberFormatter(x);
}

return d3TrimFormatter(x);
return d3ShortFormatter(x);
}

export const numberFormatter: Formatter = {
formatTick: formatNumberShort,
formatShort: formatNumberShort,
formatReadable(x: number): string {
const absNum = Math.abs(x);
if (absNum >= LARGE_NUMBER || absNum < SMALL_NUMBER) {
return d3NumberFormatter(x);
}
return d3LongFormatter(x);
},
formatLong: d3LongFormatter,
};

/**
* ===================
* SI NUMBER FORMATTER
* ===================
*/

const d3SiFormatter = format('0.3~s');
const d3SiSmallNumberFormatter = format(',.3~f');

function formatSiNumber(x: number): string {
const absNum = Math.abs(x);
if (absNum >= LARGE_NUMBER || absNum < SMALL_NUMBER) {
return d3SiFormatter(x);
}
return d3SiSmallNumberFormatter(x);
}

export const siNumberFormatter: Formatter = {
formatTick: formatSiNumber,
formatShort: formatSiNumber,
formatReadable: formatSiNumber,
formatLong: formatSiNumber,
};

/**
Expand Down Expand Up @@ -112,6 +152,7 @@ export const relativeTimeFormatter: Formatter = {
formatTick: formatRelativeTime,
formatShort: formatRelativeTime,
formatReadable: formatRelativeTime,
formatLong: formatRelativeTime,
};

/**
Expand Down Expand Up @@ -140,7 +181,7 @@ export const wallTimeFormatter: Formatter = {
});
},
formatReadable(x: number): string {
// "Nov 19, 2012, 7:00:00.551 PM PST"
// "Nov 19, 2012, 7:00:00 PM PST"
return new Date(x).toLocaleString(localeOverride, {
year: 'numeric',
month: 'short',
Expand All @@ -149,6 +190,18 @@ export const wallTimeFormatter: Formatter = {
minute: 'numeric',
second: 'numeric',
timeZoneName: 'short',
});
},
formatLong(x: number): string {
// "November 19, 2012, 7:00:00.551 PM PST"
return new Date(x).toLocaleString(localeOverride, {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
timeZoneName: 'short',
// FF 84+ and Chrome 84+ feature.
fractionalSecondDigits: 3,
} as any);
Expand Down
104 changes: 78 additions & 26 deletions tensorboard/webapp/widgets/line_chart_v2/lib/formatter_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ limitations under the License.
import {
numberFormatter,
relativeTimeFormatter,
wallTimeFormatter,
siNumberFormatter,
TEST_ONLY,
wallTimeFormatter,
} from './formatter';

describe('line_chart_v2/lib/formatter test', () => {
Expand Down Expand Up @@ -58,12 +59,25 @@ describe('line_chart_v2/lib/formatter test', () => {
expect(numberFormatter.formatReadable(3.01)).toBe('3.01');
expect(numberFormatter.formatReadable(9999)).toBe('9,999');
expect(numberFormatter.formatReadable(0.09)).toBe('0.09');
expect(numberFormatter.formatReadable(1.004e6)).toBe('1,004,000');
expect(numberFormatter.formatReadable(-1.004e6)).toBe('-1,004,000');
expect(numberFormatter.formatReadable(0.00005)).toBe('0.00005');
expect(numberFormatter.formatReadable(1e5 + 0.00005)).toBe(
'100,000.00005'
);
expect(numberFormatter.formatReadable(1.004e6)).toBe('1e+6');
expect(numberFormatter.formatReadable(-1.004e6)).toBe('-1e+6');
expect(numberFormatter.formatReadable(0.00005)).toBe('5e-5');
expect(numberFormatter.formatReadable(1e5 + 0.00005)).toBe('1e+5');
});
});

describe('formatLong', () => {
it('formats with localization', () => {
expect(numberFormatter.formatLong(1)).toBe('1');
expect(numberFormatter.formatLong(5)).toBe('5');
expect(numberFormatter.formatLong(-100.4)).toBe('-100.4');
expect(numberFormatter.formatLong(3.01)).toBe('3.01');
expect(numberFormatter.formatLong(9999)).toBe('9,999');
expect(numberFormatter.formatLong(0.09)).toBe('0.09');
expect(numberFormatter.formatLong(1.004e6)).toBe('1,004,000');
expect(numberFormatter.formatLong(-1.004e6)).toBe('-1,004,000');
expect(numberFormatter.formatLong(0.00005)).toBe('0.00005');
expect(numberFormatter.formatLong(1e5 + 0.00005)).toBe('100,000.00005');
});
});
});
Expand All @@ -73,31 +87,32 @@ describe('line_chart_v2/lib/formatter test', () => {
{name: 'formatTick', fn: relativeTimeFormatter.formatTick},
{name: 'formatShort', fn: relativeTimeFormatter.formatShort},
{name: 'formatReadable', fn: relativeTimeFormatter.formatReadable},
{name: 'formatLong', fn: relativeTimeFormatter.formatLong},
]) {
describe(name, () => {
it('formats time difference in appropriate unit', () => {
expect(fn(0)).toBe('0');
expect(fn(100)).toBe('100ms');
expect(fn(999)).toBe('999ms');
expect(fn(1000)).toBe('1sec');
expect(fn(4023)).toBe('4.023sec');
expect(fn(60023)).toBe('1min');
expect(fn(61523)).toBe('1.025min');
expect(fn(3700000)).toBe('1.028hr');
expect(fn(86400000 * 3)).toBe('3day');
expect(fn(31536000000 * 5)).toBe('5yr');
expect(fn(100)).toBe('100 ms');
expect(fn(999)).toBe('999 ms');
expect(fn(1000)).toBe('1 sec');
expect(fn(4023)).toBe('4.023 sec');
expect(fn(60023)).toBe('1 min');
expect(fn(61523)).toBe('1.025 min');
expect(fn(3700000)).toBe('1.028 hr');
expect(fn(86400000 * 3)).toBe('3 day');
expect(fn(31536000000 * 5)).toBe('5 yr');
});

it('formats negative time difference in appropriate unit', () => {
expect(fn(-100)).toBe('-100ms');
expect(fn(-999)).toBe('-999ms');
expect(fn(-1000)).toBe('-1sec');
expect(fn(-4023)).toBe('-4.023sec');
expect(fn(-60023)).toBe('-1min');
expect(fn(-61523)).toBe('-1.025min');
expect(fn(-3700000)).toBe('-1.028hr');
expect(fn(-86400000 * 3)).toBe('-3day');
expect(fn(-31536000000 * 5)).toBe('-5yr');
expect(fn(-100)).toBe('-100 ms');
expect(fn(-999)).toBe('-999 ms');
expect(fn(-1000)).toBe('-1 sec');
expect(fn(-4023)).toBe('-4.023 sec');
expect(fn(-60023)).toBe('-1 min');
expect(fn(-61523)).toBe('-1.025 min');
expect(fn(-3700000)).toBe('-1.028 hr');
expect(fn(-86400000 * 3)).toBe('-3 day');
expect(fn(-31536000000 * 5)).toBe('-5 yr');
});
});
}
Expand Down Expand Up @@ -138,8 +153,45 @@ describe('line_chart_v2/lib/formatter test', () => {
// jasmine + Angular seems to mock out the timezone by default (to UTC).
expect(
wallTimeFormatter.formatReadable(new Date('2020-1-5 13:23').getTime())
).toBe('Jan 5, 2020, 1:23:00.000 PM UTC');
).toBe('Jan 5, 2020, 1:23:00 PM UTC');
});
});

describe('formatLong', () => {
it('formats using localization', () => {
// jasmine + Angular seems to mock out the timezone by default (to UTC).
expect(
wallTimeFormatter.formatLong(new Date('2020-1-5 13:23').getTime())
).toBe('January 5, 2020, 1:23:00.000 PM UTC');
});
});
});

describe('#siNumberFormatter', () => {
for (const {name, fn} of [
{name: 'formatTick', fn: siNumberFormatter.formatTick},
{name: 'formatShort', fn: siNumberFormatter.formatShort},
{name: 'formatReadable', fn: siNumberFormatter.formatReadable},
{name: 'formatLong', fn: siNumberFormatter.formatLong},
]) {
describe(`#${name}`, () => {
it('formats without si-suffix for number less than 10k', () => {
expect(fn(1)).toBe('1');
expect(fn(5)).toBe('5');
expect(fn(-100.4)).toBe('-100.4');
expect(fn(3.01)).toBe('3.01');
expect(fn(9999)).toBe('9,999');
expect(fn(9999.9123)).toBe('9,999.912');
expect(fn(0.09)).toBe('0.09');
expect(fn(10000)).toBe('10k');
expect(fn(10001)).toBe('10k');
expect(fn(-10000)).toBe('-10k');
expect(fn(-10001)).toBe('-10k');
expect(fn(-10101)).toBe('-10.1k');
expect(fn(-1.004e6)).toBe('-1M');
expect(fn(0.00005)).toBe('50µ');
});
});
}
});
});
Loading