diff options
author | Justine Tunney <jart@google.com> | 2016-08-22 13:03:29 -0800 |
---|---|---|
committer | TensorFlower Gardener <gardener@tensorflow.org> | 2016-08-22 14:18:16 -0700 |
commit | d1abf00728cb42ac341a3614b2038aad89f8279e (patch) | |
tree | 484698355e81f9d9c944c127489ee684430bcde2 | |
parent | cd95f3a7d66833cdb55fa180d5002f2cf686bcef (diff) |
Use asciinumerical sort for tooltips and more
In order to accomplish this, I refactored the new sorting algorithm
out into a separate tf-sorting component.
Change: 130979195
15 files changed, 206 insertions, 115 deletions
diff --git a/tensorflow/tensorboard/components/tf-backend/backend.ts b/tensorflow/tensorboard/components/tf-backend/backend.ts index 5c75d79010..39c967b239 100644 --- a/tensorflow/tensorboard/components/tf-backend/backend.ts +++ b/tensorflow/tensorboard/components/tf-backend/backend.ts @@ -260,64 +260,12 @@ module TF.Backend { /** Given a RunToTag, return sorted array of all runs */ export function getRuns(r: RunToTag): string[] { - return _.keys(r).sort(compareTagNames); + return _.keys(r).sort(VZ.Sorting.compareTagNames); } /** Given a RunToTag, return array of all tags (sorted + dedup'd) */ export function getTags(r: RunToTag): string[] { - return _.union.apply(null, _.values(r)).sort(compareTagNames); - } - - /** Compares tag names asciinumerically broken into components. */ - export function compareTagNames(a, b: string): number { - let ai = 0; - let bi = 0; - while (true) { - if (ai === a.length) return bi === b.length ? 0 : -1; - if (bi === b.length) return 1; - if (isDigit(a[ai]) && isDigit(b[bi])) { - let ais = ai; - let bis = bi; - ai = consumeNumber(a, ai + 1); - bi = consumeNumber(b, bi + 1); - let an = parseFloat(a.slice(ais, ai)); - let bn = parseFloat(b.slice(bis, bi)); - if (an < bn) return -1; - if (an > bn) return 1; - continue; - } - if (isBreak(a[ai])) { - if (!isBreak(b[bi])) return -1; - } else if (isBreak(b[bi])) { - return 1; - } else if (a[ai] < b[bi]) { - return -1; - } else if (a[ai] > b[bi]) { - return 1; - } - ai++; - bi++; - } - } - - function consumeNumber(s: string, i: number): number { - let decimal = false; - for (; i < s.length; i++) { - if (isDigit(s[i])) continue; - if (!decimal && s[i] === '.') { - decimal = true; - continue; - } - break; - } - return i; - } - - function isDigit(c: string): boolean { return '0' <= c && c <= '9'; } - - function isBreak(c: string): boolean { - // TODO(jart): Remove underscore when people stop using it like a slash. - return c === '/' || c === '_' || isDigit(c); + return _.union.apply(null, _.values(r)).sort(VZ.Sorting.compareTagNames); } /** @@ -328,7 +276,7 @@ module TF.Backend { export function filterTags(r: RunToTag, runs: string[]): string[] { var result = []; runs.forEach((x) => result = result.concat(r[x])); - return _.uniq(result).sort(); + return _.uniq(result).sort(VZ.Sorting.compareTagNames); } function timeToDate(x: number): Date { return new Date(x * 1000); }; diff --git a/tensorflow/tensorboard/components/tf-backend/test/backendTests.ts b/tensorflow/tensorboard/components/tf-backend/test/backendTests.ts index 15cdd0bf95..9df5399db5 100644 --- a/tensorflow/tensorboard/components/tf-backend/test/backendTests.ts +++ b/tensorflow/tensorboard/components/tf-backend/test/backendTests.ts @@ -277,56 +277,4 @@ module TF.Backend { assertHistogramEquality(newHistogram, histogram); }); }); - - describe('sortTagNames', () => { - - let sortTagNames = (a) => a.sort(compareTagNames); - - it('is asciibetical', () => { - assert.deepEqual(sortTagNames(['a', 'b']), ['a', 'b']); - assert.deepEqual(sortTagNames(['a', 'B']), ['B', 'a']); - }); - - it('sorts integer portions', () => { - assert.deepEqual(['03', '1'].sort(), ['03', '1']); - assert.deepEqual(sortTagNames(['03', '1']), ['1', '03']); - assert.deepEqual(sortTagNames(['a03', 'a1']), ['a1', 'a03']); - assert.deepEqual(sortTagNames(['a03', 'b1']), ['a03', 'b1']); - assert.deepEqual(sortTagNames(['x0a03', 'x0a1']), ['x0a1', 'x0a03']); - assert.deepEqual(sortTagNames(['a/b/03', 'a/b/1']), ['a/b/1', 'a/b/03']); - }); - - it('sorts floating point portions', () => { - assert.deepEqual(sortTagNames(['a0.1', 'a0.01']), ['a0.01', 'a0.1']); - }); - - it('is componentized by slash', () => { - assert.deepEqual(['a+/a', 'a/a', 'ab/a'].sort(), ['a+/a', 'a/a', 'ab/a']); - assert.deepEqual( - sortTagNames(['a+/a', 'a/a', 'ab/a']), ['a/a', 'a+/a', 'ab/a']); - }); - - it('is componentized by underscore', () => { - assert.deepEqual( - sortTagNames(['a+_a', 'a_a', 'ab_a']), ['a_a', 'a+_a', 'ab_a']); - assert.deepEqual( - sortTagNames(['a+/a', 'a_a', 'ab_a']), ['a_a', 'a+/a', 'ab_a']); - }); - - it('is componentized by number boundaries', () => { - assert.deepEqual( - sortTagNames(['a+0a', 'a0a', 'ab0a']), ['a0a', 'a+0a', 'ab0a']); - }); - - it('empty comes first', () => { - assert.deepEqual( - sortTagNames(['a', '//', '/', '']), ['', '/', '//', 'a']); - }); - - it('decimal parsed correctly', () => { - assert.deepEqual(sortTagNames(['0.2', '0.03']), ['0.03', '0.2']); - assert.deepEqual(sortTagNames(['0..2', '0..03']), ['0..2', '0..03']); - assert.deepEqual(sortTagNames(['.2', '.03']), ['.2', '.03']); - }); - }); } diff --git a/tensorflow/tensorboard/components/tf-backend/test/index.html b/tensorflow/tensorboard/components/tf-backend/test/index.html index c97873f46a..a1ee080b0e 100644 --- a/tensorflow/tensorboard/components/tf-backend/test/index.html +++ b/tensorflow/tensorboard/components/tf-backend/test/index.html @@ -20,6 +20,7 @@ limitations under the License. <script src="../../web-component-tester/browser.js"></script> <link rel="import" href="../../polymer/polymer.html"> <link rel="import" href="../../tf-imports/d3.html"> + <link rel="import" href="../../vz-sorting/vz-sorting.html"> </head> <body> <test-fixture id="testElementFixture"> diff --git a/tensorflow/tensorboard/components/tf-backend/tf-backend.html b/tensorflow/tensorboard/components/tf-backend/tf-backend.html index bc4f16a5f7..d416787e3f 100644 --- a/tensorflow/tensorboard/components/tf-backend/tf-backend.html +++ b/tensorflow/tensorboard/components/tf-backend/tf-backend.html @@ -1,6 +1,7 @@ <link rel="import" href="../polymer/polymer.html"> <link rel="import" href="../tf-imports/lodash.html"> <link rel="import" href="../tf-imports/d3.html"> +<link rel="import" href="../vz-sorting/vz-sorting.html"> <script src="requestManager.js"></script> <script src="urlPathHelpers.js"></script> diff --git a/tensorflow/tensorboard/components/tf-dashboard-common/categorizer.ts b/tensorflow/tensorboard/components/tf-dashboard-common/categorizer.ts index d9c477e744..5131b108e4 100644 --- a/tensorflow/tensorboard/components/tf-dashboard-common/categorizer.ts +++ b/tensorflow/tensorboard/components/tf-dashboard-common/categorizer.ts @@ -81,7 +81,7 @@ module Categorizer { if (tags.length === 0) { return []; } - var sortedTags = tags.slice().sort(); + var sortedTags = tags.slice().sort(VZ.Sorting.compareTagNames); var categories: Category[] = []; var currentCategory = { name: extractor(sortedTags[0]), @@ -134,7 +134,7 @@ module Categorizer { tags.push(t); } }); - var cat = { name: def.name, tags: tags.sort() }; + var cat = {name: def.name, tags: tags.sort(VZ.Sorting.compareTagNames)}; return cat; }); var defaultCategories = fallback(remaining.values()); diff --git a/tensorflow/tensorboard/components/tf-dashboard-common/test/index.html b/tensorflow/tensorboard/components/tf-dashboard-common/test/index.html index fd4a097708..6336c876b8 100644 --- a/tensorflow/tensorboard/components/tf-dashboard-common/test/index.html +++ b/tensorflow/tensorboard/components/tf-dashboard-common/test/index.html @@ -5,6 +5,7 @@ <script src="../../webcomponentsjs/webcomponents-lite.min.js"></script> <script src="../../web-component-tester/browser.js"></script> <link rel="import" href="../../tf-imports/d3.html"> + <link rel="import" href="../../vz-sorting/vz-sorting.html"> </head> <body> <script src="../categorizer.js"></script> diff --git a/tensorflow/tensorboard/components/tf-dashboard-common/tf-categorizer.html b/tensorflow/tensorboard/components/tf-dashboard-common/tf-categorizer.html index 1c3a3f811e..9eeef71a23 100644 --- a/tensorflow/tensorboard/components/tf-dashboard-common/tf-categorizer.html +++ b/tensorflow/tensorboard/components/tf-dashboard-common/tf-categorizer.html @@ -3,6 +3,7 @@ <link rel="import" href="../tf-storage/tf-storage.html"> <link rel="import" href="tf-regex-group.html"> <link rel="import" href="tensorboard-color.html"> +<link rel="import" href="../vz-sorting/vz-sorting.html"> <!-- `tf-categorizer` turns an array of tags into an array of categories diff --git a/tensorflow/tensorboard/components/tf-event-dashboard/tf-event-dashboard.html b/tensorflow/tensorboard/components/tf-event-dashboard/tf-event-dashboard.html index d3000e4616..db9089280e 100644 --- a/tensorflow/tensorboard/components/tf-event-dashboard/tf-event-dashboard.html +++ b/tensorflow/tensorboard/components/tf-event-dashboard/tf-event-dashboard.html @@ -60,7 +60,7 @@ The #center div contains tf-line-charts embedded inside tf-collapsable-panes. selected-item-label="{{_tooltipSortingMethod}}" > <paper-menu class="dropdown-content" selected="0"> - <paper-item>name</paper-item> + <paper-item>default</paper-item> <paper-item>descending</paper-item> <paper-item>ascending</paper-item> </paper-menu> diff --git a/tensorflow/tensorboard/components/tf-globals/tf-globals.html b/tensorflow/tensorboard/components/tf-globals/tf-globals.html index 3195518f37..a76f21d96c 100644 --- a/tensorflow/tensorboard/components/tf-globals/tf-globals.html +++ b/tensorflow/tensorboard/components/tf-globals/tf-globals.html @@ -14,6 +14,6 @@ limitations under the License. =============================================================================--> <dom-module id="tf-globals"> - <script src="globals.js"></script> + <script src="globals.js"></script> </dom-module> diff --git a/tensorflow/tensorboard/components/vz-line-chart/vz-line-chart.html b/tensorflow/tensorboard/components/vz-line-chart/vz-line-chart.html index 9ca063c082..1d68cc90f7 100644 --- a/tensorflow/tensorboard/components/vz-line-chart/vz-line-chart.html +++ b/tensorflow/tensorboard/components/vz-line-chart/vz-line-chart.html @@ -174,13 +174,13 @@ such as different X scales (linear and temporal), tooltips and smoothing. /** * Change how the tooltip is sorted. Allows: - * - "name" - Sort the tooltip by series name. + * - "default" - Sort the tooltip by input order. * - "ascending" - Sort the tooltip by ascending value. * - "descending" - Sort the tooltip by descending value. */ tooltipSortingMethod: { type: String, - value: 'name' + value: 'default' }, /** diff --git a/tensorflow/tensorboard/components/vz-line-chart/vz-line-chart.ts b/tensorflow/tensorboard/components/vz-line-chart/vz-line-chart.ts index 8e15200011..6fde2706f1 100644 --- a/tensorflow/tensorboard/components/vz-line-chart/vz-line-chart.ts +++ b/tensorflow/tensorboard/components/vz-line-chart/vz-line-chart.ts @@ -304,8 +304,11 @@ module VZ { points = _.sortBy(points, (d) => valueSortMethod(d.datum, -1, d.dataset)) .reverse(); - } else { // Sort by 'name' - points = _.sortBy(points, (d) => d.dataset.metadata().name); + } else { + // The 'default' sorting method maintains the order of names passed to + // setVisibleSeries(). However we reverse that order when defining the + // datasets. So we must call reverse again to restore the order. + points = points.slice(0).reverse(); } let rows = this.tooltip.select('tbody') diff --git a/tensorflow/tensorboard/components/vz-sorting/sorting.ts b/tensorflow/tensorboard/components/vz-sorting/sorting.ts new file mode 100644 index 0000000000..53dd349e9d --- /dev/null +++ b/tensorflow/tensorboard/components/vz-sorting/sorting.ts @@ -0,0 +1,78 @@ +/* Copyright 2016 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +module VZ.Sorting { + + /** + * Compares tag names asciinumerically broken into components. + * + * <p>This is the comparison function used for sorting most string values in + * TensorBoard. Unlike the standard asciibetical comparator, this function + * knows that 'a10b' > 'a2b'. Numbers with decimal points are supported. It + * also splits the input by slash and underscore and performs an array + * sort. Therefore it knows that 'a/a' < 'a+/a' even though '+' < '/' in the + * ASCII table. + */ + export function compareTagNames(a, b: string): number { + let ai = 0; + let bi = 0; + while (true) { + if (ai === a.length) return bi === b.length ? 0 : -1; + if (bi === b.length) return 1; + if (isDigit(a[ai]) && isDigit(b[bi])) { + const ais = ai; + const bis = bi; + ai = consumeNumber(a, ai + 1); + bi = consumeNumber(b, bi + 1); + const an = parseFloat(a.slice(ais, ai)); + const bn = parseFloat(b.slice(bis, bi)); + if (an < bn) return -1; + if (an > bn) return 1; + continue; + } + if (isBreak(a[ai])) { + if (!isBreak(b[bi])) return -1; + } else if (isBreak(b[bi])) { + return 1; + } else if (a[ai] < b[bi]) { + return -1; + } else if (a[ai] > b[bi]) { + return 1; + } + ai++; + bi++; + } + } + + function consumeNumber(s: string, i: number): number { + let decimal = false; + for (; i < s.length; i++) { + if (isDigit(s[i])) continue; + if (!decimal && s[i] === '.') { + decimal = true; + continue; + } + break; + } + return i; + } + + function isDigit(c: string): boolean { return '0' <= c && c <= '9'; } + + function isBreak(c: string): boolean { + // TODO(jart): Remove underscore when people stop using it like a slash. + return c === '/' || c === '_' || isDigit(c); + } +} diff --git a/tensorflow/tensorboard/components/vz-sorting/test/index.html b/tensorflow/tensorboard/components/vz-sorting/test/index.html new file mode 100644 index 0000000000..9ba04e0136 --- /dev/null +++ b/tensorflow/tensorboard/components/vz-sorting/test/index.html @@ -0,0 +1,22 @@ +<!doctype html> +<!-- + Copyright 2016 The TensorFlow Authors. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<meta charset="utf-8"> +<script src="../../web-component-tester/browser.js"></script> +<body> +<script src="../sorting.js"></script> +<script src="sortingTests.js"></script> diff --git a/tensorflow/tensorboard/components/vz-sorting/test/sortingTests.ts b/tensorflow/tensorboard/components/vz-sorting/test/sortingTests.ts new file mode 100644 index 0000000000..0a24463d8a --- /dev/null +++ b/tensorflow/tensorboard/components/vz-sorting/test/sortingTests.ts @@ -0,0 +1,71 @@ +/* Copyright 2016 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +module VZ.Sorting { + + const assert = chai.assert; + + describe('compareTagNames', () => { + + const sortTagNames = (a) => a.sort(compareTagNames); + + it('is asciibetical', () => { + assert.deepEqual(sortTagNames(['a', 'b']), ['a', 'b']); + assert.deepEqual(sortTagNames(['a', 'B']), ['B', 'a']); + }); + + it('sorts integer portions', () => { + assert.deepEqual(['03', '1'].sort(), ['03', '1']); + assert.deepEqual(sortTagNames(['03', '1']), ['1', '03']); + assert.deepEqual(sortTagNames(['a03', 'a1']), ['a1', 'a03']); + assert.deepEqual(sortTagNames(['a03', 'b1']), ['a03', 'b1']); + assert.deepEqual(sortTagNames(['x0a03', 'x0a1']), ['x0a1', 'x0a03']); + assert.deepEqual(sortTagNames(['a/b/03', 'a/b/1']), ['a/b/1', 'a/b/03']); + }); + + it('sorts floating point portions', () => { + assert.deepEqual(sortTagNames(['a0.1', 'a0.01']), ['a0.01', 'a0.1']); + }); + + it('is componentized by slash', () => { + assert.deepEqual(['a+/a', 'a/a', 'ab/a'].sort(), ['a+/a', 'a/a', 'ab/a']); + assert.deepEqual( + sortTagNames(['a+/a', 'a/a', 'ab/a']), ['a/a', 'a+/a', 'ab/a']); + }); + + it('is componentized by underscore', () => { + assert.deepEqual( + sortTagNames(['a+_a', 'a_a', 'ab_a']), ['a_a', 'a+_a', 'ab_a']); + assert.deepEqual( + sortTagNames(['a+/a', 'a_a', 'ab_a']), ['a_a', 'a+/a', 'ab_a']); + }); + + it('is componentized by number boundaries', () => { + assert.deepEqual( + sortTagNames(['a+0a', 'a0a', 'ab0a']), ['a0a', 'a+0a', 'ab0a']); + }); + + it('empty comes first', () => { + assert.deepEqual( + sortTagNames(['a', '//', '/', '']), ['', '/', '//', 'a']); + }); + + it('decimal parsed correctly', () => { + assert.deepEqual(sortTagNames(['0.2', '0.03']), ['0.03', '0.2']); + assert.deepEqual(sortTagNames(['0..2', '0..03']), ['0..2', '0..03']); + assert.deepEqual(sortTagNames(['.2', '.03']), ['.2', '.03']); + }); + }); +} diff --git a/tensorflow/tensorboard/components/vz-sorting/vz-sorting.html b/tensorflow/tensorboard/components/vz-sorting/vz-sorting.html new file mode 100644 index 0000000000..9376663dae --- /dev/null +++ b/tensorflow/tensorboard/components/vz-sorting/vz-sorting.html @@ -0,0 +1,17 @@ +<!-- + Copyright 2016 The TensorFlow Authors. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<script src="sorting.js"></script> |