From b01b4647ca2f23630893bde51662fa830647a3bb Mon Sep 17 00:00:00 2001 From: Francois Luus Date: Sun, 19 Nov 2017 17:49:21 +0200 Subject: [PATCH 1/9] Projector: Metadata editor Add a metadata editor to the Projector, which gives the option to modify attributes of selected points. Projector components related to metadata display are refreshed after attribute changes, which also expands the color palette for the modified attribute when a new class is added. The main 'Label by' dropdown-menu is incorporated into the metadata editor, which requires the user to view the point labels for the metadata column being changed. --- .../vz_projector/vz-projector-data-panel.html | 69 ++++++++-- .../vz_projector/vz-projector-data-panel.ts | 122 ++++++++++++++++-- .../vz-projector-inspector-panel.ts | 11 +- .../projector/vz_projector/vz-projector.ts | 19 +++ 4 files changed, 192 insertions(+), 29 deletions(-) diff --git a/tensorboard/plugins/projector/vz_projector/vz-projector-data-panel.html b/tensorboard/plugins/projector/vz_projector/vz-projector-data-panel.html index 455716992a..ee528eee25 100644 --- a/tensorboard/plugins/projector/vz_projector/vz-projector-data-panel.html +++ b/tensorboard/plugins/projector/vz_projector/vz-projector-data-panel.html @@ -79,6 +79,13 @@ paper-dropdown-menu { width: 100%; + --paper-input-container-input: { + font-size: 15px; + }; + --paper-input-container-label-floating: { + white-space: normal; + line-height: normal; + }; } paper-dropdown-menu paper-item { @@ -116,6 +123,34 @@ margin: 10px 0; } +.metadata-editor { + display: flex; +} + +.metadata-editor paper-input { + width: calc(100%-150px); + --paper-input-container-label-floating: { + white-space: normal; + line-height: normal; + }; +} + +.metadata-editor paper-button { + margin-left: 10px; + margin-right: 0px; + margin-top: 20px; + padding-left: 8px; + padding-right: 8px; + min-width: 35px; + height: 36px; + vertical-align: bottom; +} + +.metadata-editor paper-dropdown-menu { + margin-right: 10px; + width: 120px; +} + .config-checkbox { display: inline-block; font-size: 11px; @@ -190,7 +225,7 @@ } .colorby-container { - margin-bottom: 10px; + margin-bottom: 0px; }
DATA
@@ -223,19 +258,6 @@ - - -
@@ -262,6 +284,25 @@
+ Sphereize data diff --git a/tensorboard/plugins/projector/vz_projector/vz-projector-data-panel.ts b/tensorboard/plugins/projector/vz_projector/vz-projector-data-panel.ts index 814066eaeb..d3db2a3275 100644 --- a/tensorboard/plugins/projector/vz_projector/vz-projector-data-panel.ts +++ b/tensorboard/plugins/projector/vz_projector/vz-projector-data-panel.ts @@ -24,10 +24,15 @@ export let DataPanelPolymer = PolymerElement({ notify: true, observer: '_selectedColorOptionNameChanged' }, - selectedLabelOption: - {type: String, notify: true, observer: '_selectedLabelOptionChanged'}, normalizeData: Boolean, - showForceCategoricalColorsCheckbox: Boolean + showForceCategoricalColorsCheckbox: Boolean, + metadataEditorInput: {type: String}, + metadataEditorInputLabel: {type: String, value: 'Tag selection as'}, + metadataEditorInputChange: {type: Object}, + metadataEditorColumn: {type: String}, + metadataEditorColumnChange: {type: Object}, + metadataEditorButtonClicked: {type: Object}, + metadataEditorButtonDisabled: {type: Boolean} }, observers: [ '_generateUiForNewCheckpointForRun(selectedRun)', @@ -35,7 +40,6 @@ export let DataPanelPolymer = PolymerElement({ }); export class DataPanel extends DataPanelPolymer { - selectedLabelOption: string; selectedColorOptionName: string; showForceCategoricalColorsCheckbox: boolean; @@ -44,6 +48,12 @@ export class DataPanel extends DataPanelPolymer { private colorOptions: ColorOption[]; forceCategoricalColoring: boolean = false; + private metadataEditorInput: string; + private metadataEditorInputLabel: string; + private metadataEditorButtonDisabled: boolean; + + private selectedPointIndices: number[]; + private neighborsOfFirstPoint: knn.NearestEntry[]; private selectedTensor: string; private selectedRun: string; private dataProvider: DataProvider; @@ -120,7 +130,33 @@ export class DataPanel extends DataPanelPolymer { this.metadataFile = metadataFile; this.updateMetadataUI(this.spriteAndMetadata.stats, this.metadataFile); - this.selectedColorOptionName = this.colorOptions[0].name; + + if (this.selectedColorOptionName == null || this.colorOptions.filter(c => + c.name == this.selectedColorOptionName).length == 0) { + this.selectedColorOptionName = this.colorOptions[0].name; + } + + let labelIndex = -1; + this.metadataFields = spriteAndMetadata.stats.map((stats, i) => { + if (!stats.isNumeric && labelIndex === -1) { + labelIndex = i; + } + return stats.name; + }); + + if (this.metadataEditorColumn == null || this.metadataFields.filter(name => + name == this.metadataEditorColumn).length == 0) { + // Make the default label the first non-numeric column. + this.metadataEditorColumn = this.metadataFields[Math.max(0, labelIndex)]; + } + } + + onProjectorSelectionChanged( + selectedPointIndices: number[], + neighborsOfFirstPoint: knn.NearestEntry[]) { + this.selectedPointIndices = selectedPointIndices; + this.neighborsOfFirstPoint = neighborsOfFirstPoint; + this.metadataEditorInputChange(); } private addWordBreaks(longString: string): string { @@ -145,7 +181,11 @@ export class DataPanel extends DataPanelPolymer { } return stats.name; }); - this.selectedLabelOption = this.labelOptions[Math.max(0, labelIndex)]; + + if (this.metadataEditorColumn == null || this.labelOptions.filter(name => + name == this.metadataEditorColumn).length == 0) { + this.metadataEditorColumn = this.labelOptions[Math.max(0, labelIndex)]; + } // Color by options. const standardColorOption: ColorOption[] = [ @@ -207,6 +247,68 @@ export class DataPanel extends DataPanelPolymer { this.colorOptions = standardColorOption.concat(metadataColorOption); } + private metadataEditorInputChange() { + let value = this.metadataEditorInput; + let selectionSize = this.selectedPointIndices.length + + this.neighborsOfFirstPoint.length; + + if (selectionSize > 0) { + if (value != null && value.trim() != '') { + let numMatches = this.projector.dataSet.points.filter(p => + p.metadata[this.metadataEditorColumn].toString() == value).length; + + if (numMatches === 0) { + this.metadataEditorInputLabel = `Tag ${selectionSize} with new label`; + } + else { + this.metadataEditorInputLabel = + `Tag ${selectionSize} points as`; + } + this.metadataEditorButtonDisabled = false; + } + else { + this.metadataEditorInputLabel = 'Tag selection as'; + this.metadataEditorButtonDisabled = true; + } + } + else { + this.metadataEditorButtonDisabled = true; + + if (value != null && value.trim() != '') { + this.metadataEditorInputLabel = 'Select points to tag'; + } + else { + this.metadataEditorInputLabel = 'Tag selection as'; + } + } + } + + private metadataEditorColumnChange() { + this.projector.setSelectedLabelOption(this.metadataEditorColumn); + this.metadataEditorInputChange(); + } + + private metadataEditorButtonClicked() { + let selectionSize = this.selectedPointIndices.length + + this.neighborsOfFirstPoint.length; + this.metadataEditorButtonDisabled = true; + this.metadataEditorInputLabel = + `${selectionSize} labeled as '${this.metadataEditorInput}'`; + + this.selectedPointIndices.forEach(i => + this.projector.dataSet.points[i].metadata[this.metadataEditorColumn] = + this.metadataEditorInput); + + this.neighborsOfFirstPoint.forEach(p => + this.projector.dataSet.points[p.index] + .metadata[this.metadataEditorColumn] = this.metadataEditorInput); + + this.spriteAndMetadata.stats = analyzeMetadata( + this.spriteAndMetadata.stats.map(s => s.name), + this.projector.dataSet.points.map(p => p.metadata)); + this.projector.metadataChanged(this.spriteAndMetadata, this.metadataFile); + } + setNormalizeData(normalizeData: boolean) { this.normalizeData = normalizeData; } @@ -288,10 +390,6 @@ export class DataPanel extends DataPanelPolymer { }); } - _selectedLabelOptionChanged() { - this.projector.setSelectedLabelOption(this.selectedLabelOption); - } - _selectedColorOptionNameChanged() { let colorOption: ColorOption; for (let i = 0; i < this.colorOptions.length; i++) { @@ -492,8 +590,8 @@ export class DataPanel extends DataPanelPolymer { this.runNames.length + ' runs'; } - _hasChoices(choices: any[]): boolean { - return choices.length > 1; + _hasChoice(choices: any[]): boolean { + return choices.length > 0; } } diff --git a/tensorboard/plugins/projector/vz_projector/vz-projector-inspector-panel.ts b/tensorboard/plugins/projector/vz_projector/vz-projector-inspector-panel.ts index a5afa91a72..448bdaa05f 100644 --- a/tensorboard/plugins/projector/vz_projector/vz-projector-inspector-panel.ts +++ b/tensorboard/plugins/projector/vz_projector/vz-projector-inspector-panel.ts @@ -95,9 +95,14 @@ export class InspectorPanel extends InspectorPanelPolymer { } return stats.name; }); - labelIndex = Math.max(0, labelIndex); - // Make the default label the first non-numeric column. - this.selectedMetadataField = spriteAndMetadata.stats[labelIndex].name; + + if (this.selectedMetadataField == null || this.metadataFields.filter(name => + name == this.selectedMetadataField).length == 0) { + // Make the default label the first non-numeric column. + this.selectedMetadataField = this.metadataFields[Math.max(0, labelIndex)]; + } + this.updateInspectorPane(this.selectedPointIndices, + this.neighborsOfFirstPoint); } datasetChanged() { diff --git a/tensorboard/plugins/projector/vz_projector/vz-projector.ts b/tensorboard/plugins/projector/vz_projector/vz-projector.ts index de1efdad98..9b9658ba09 100644 --- a/tensorboard/plugins/projector/vz_projector/vz-projector.ts +++ b/tensorboard/plugins/projector/vz_projector/vz-projector.ts @@ -182,6 +182,23 @@ export class Projector extends ProjectorPolymer implements } } + metadataChanged(spriteAndMetadata: SpriteAndMetadataInfo, + metadataFile: string) { + this.dataSet.spriteAndMetadataInfo = spriteAndMetadata; + this.projectionsPanel.metadataChanged(spriteAndMetadata); + this.inspectorPanel.metadataChanged(spriteAndMetadata); + this.dataPanel.metadataChanged(spriteAndMetadata, metadataFile); + + if (this.selectedPointIndices.length > 0) { // at least one selected point + this.metadataCard.updateMetadata( // show metadata for first selected point + this.dataSet.points[this.selectedPointIndices[0]].metadata); + } + else { // no points selected + this.metadataCard.updateMetadata(null); // clear metadata + } + this.setSelectedLabelOption(this.selectedLabelOption); + } + setSelectedTensor(run: string, tensorInfo: EmbeddingInfo) { this.bookmarkPanel.setSelectedTensor(run, tensorInfo, this.dataProvider); } @@ -500,6 +517,8 @@ export class Projector extends ProjectorPolymer implements neighborsOfFirstPoint: knn.NearestEntry[]) { this.selectedPointIndices = selectedPointIndices; this.neighborsOfFirstPoint = neighborsOfFirstPoint; + this.dataPanel.onProjectorSelectionChanged(selectedPointIndices, + neighborsOfFirstPoint); let totalNumPoints = this.selectedPointIndices.length + neighborsOfFirstPoint.length; this.statusBar.innerText = `Selected ${totalNumPoints} points`; From 81c7ad7106344fb34cb30ab341ea4be514354409 Mon Sep 17 00:00:00 2001 From: Francois Luus Date: Sun, 10 Dec 2017 20:32:33 +0200 Subject: [PATCH 2/9] [Formatting] Spaces and exact equalities Corrected spacing and now checking for exact non-cast equalities, according to pull request feedback. --- .../vz_projector/vz-projector-data-panel.ts | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/tensorboard/plugins/projector/vz_projector/vz-projector-data-panel.ts b/tensorboard/plugins/projector/vz_projector/vz-projector-data-panel.ts index d3db2a3275..4ab9600a3d 100644 --- a/tensorboard/plugins/projector/vz_projector/vz-projector-data-panel.ts +++ b/tensorboard/plugins/projector/vz_projector/vz-projector-data-panel.ts @@ -130,9 +130,8 @@ export class DataPanel extends DataPanelPolymer { this.metadataFile = metadataFile; this.updateMetadataUI(this.spriteAndMetadata.stats, this.metadataFile); - if (this.selectedColorOptionName == null || this.colorOptions.filter(c => - c.name == this.selectedColorOptionName).length == 0) { + c.name === this.selectedColorOptionName).length === 0) { this.selectedColorOptionName = this.colorOptions[0].name; } @@ -145,7 +144,7 @@ export class DataPanel extends DataPanelPolymer { }); if (this.metadataEditorColumn == null || this.metadataFields.filter(name => - name == this.metadataEditorColumn).length == 0) { + name === this.metadataEditorColumn).length === 0) { // Make the default label the first non-numeric column. this.metadataEditorColumn = this.metadataFields[Math.max(0, labelIndex)]; } @@ -183,7 +182,7 @@ export class DataPanel extends DataPanelPolymer { }); if (this.metadataEditorColumn == null || this.labelOptions.filter(name => - name == this.metadataEditorColumn).length == 0) { + name === this.metadataEditorColumn).length === 0) { this.metadataEditorColumn = this.labelOptions[Math.max(0, labelIndex)]; } @@ -251,18 +250,16 @@ export class DataPanel extends DataPanelPolymer { let value = this.metadataEditorInput; let selectionSize = this.selectedPointIndices.length + this.neighborsOfFirstPoint.length; - if (selectionSize > 0) { if (value != null && value.trim() != '') { let numMatches = this.projector.dataSet.points.filter(p => - p.metadata[this.metadataEditorColumn].toString() == value).length; + p.metadata[this.metadataEditorColumn].toString() === value).length; if (numMatches === 0) { this.metadataEditorInputLabel = `Tag ${selectionSize} with new label`; } else { - this.metadataEditorInputLabel = - `Tag ${selectionSize} points as`; + this.metadataEditorInputLabel = `Tag ${selectionSize} points as`; } this.metadataEditorButtonDisabled = false; } @@ -296,12 +293,12 @@ export class DataPanel extends DataPanelPolymer { `${selectionSize} labeled as '${this.metadataEditorInput}'`; this.selectedPointIndices.forEach(i => - this.projector.dataSet.points[i].metadata[this.metadataEditorColumn] = - this.metadataEditorInput); + this.projector.dataSet.points[i].metadata[this.metadataEditorColumn] = + this.metadataEditorInput); this.neighborsOfFirstPoint.forEach(p => - this.projector.dataSet.points[p.index] - .metadata[this.metadataEditorColumn] = this.metadataEditorInput); + this.projector.dataSet.points[p.index] + .metadata[this.metadataEditorColumn] = this.metadataEditorInput); this.spriteAndMetadata.stats = analyzeMetadata( this.spriteAndMetadata.stats.map(s => s.name), @@ -593,6 +590,10 @@ export class DataPanel extends DataPanelPolymer { _hasChoice(choices: any[]): boolean { return choices.length > 0; } + + _hasChoices(choices: any[]): boolean { + return choices.length > 1; + } } document.registerElement(DataPanel.prototype.is, DataPanel); From e06620813ee729c421723192f814798916e2ca82 Mon Sep 17 00:00:00 2001 From: Francois Luus Date: Mon, 11 Dec 2017 11:08:57 +0200 Subject: [PATCH 3/9] Projector: Metadata editor (Enter keydown = Label button press) Allow for the metadata editor label paper-input to capture Enter keydown as a Label button press. Now button becomes disabled upon application of a label, then the labeling is applied, then the metadata editor paper-input status message confirms application. --- .../vz_projector/vz-projector-data-panel.html | 3 +- .../vz_projector/vz-projector-data-panel.ts | 45 +++++++++++-------- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/tensorboard/plugins/projector/vz_projector/vz-projector-data-panel.html b/tensorboard/plugins/projector/vz_projector/vz-projector-data-panel.html index ee528eee25..25e44e0fad 100644 --- a/tensorboard/plugins/projector/vz_projector/vz-projector-data-panel.html +++ b/tensorboard/plugins/projector/vz_projector/vz-projector-data-panel.html @@ -298,7 +298,8 @@ + on-input="metadataEditorInputChange" on-keydown="metadataEditorInputKeydown"> + Label diff --git a/tensorboard/plugins/projector/vz_projector/vz-projector-data-panel.ts b/tensorboard/plugins/projector/vz_projector/vz-projector-data-panel.ts index 4ab9600a3d..555ac9bbaa 100644 --- a/tensorboard/plugins/projector/vz_projector/vz-projector-data-panel.ts +++ b/tensorboard/plugins/projector/vz_projector/vz-projector-data-panel.ts @@ -280,30 +280,39 @@ export class DataPanel extends DataPanelPolymer { } } + private metadataEditorInputKeydown(e) { + // Check if 'Enter' was pressed + if (e.keyCode === 13) { + this.metadataEditorButtonClicked(); + } + } + private metadataEditorColumnChange() { this.projector.setSelectedLabelOption(this.metadataEditorColumn); this.metadataEditorInputChange(); } private metadataEditorButtonClicked() { - let selectionSize = this.selectedPointIndices.length + - this.neighborsOfFirstPoint.length; - this.metadataEditorButtonDisabled = true; - this.metadataEditorInputLabel = - `${selectionSize} labeled as '${this.metadataEditorInput}'`; - - this.selectedPointIndices.forEach(i => - this.projector.dataSet.points[i].metadata[this.metadataEditorColumn] = - this.metadataEditorInput); - - this.neighborsOfFirstPoint.forEach(p => - this.projector.dataSet.points[p.index] - .metadata[this.metadataEditorColumn] = this.metadataEditorInput); - - this.spriteAndMetadata.stats = analyzeMetadata( - this.spriteAndMetadata.stats.map(s => s.name), - this.projector.dataSet.points.map(p => p.metadata)); - this.projector.metadataChanged(this.spriteAndMetadata, this.metadataFile); + if (!this.metadataEditorButtonDisabled) { + this.metadataEditorButtonDisabled = true; + let selectionSize = this.selectedPointIndices.length + + this.neighborsOfFirstPoint.length; + + this.selectedPointIndices.forEach(i => + this.projector.dataSet.points[i].metadata[this.metadataEditorColumn] = + this.metadataEditorInput); + + this.neighborsOfFirstPoint.forEach(p => + this.projector.dataSet.points[p.index] + .metadata[this.metadataEditorColumn] = this.metadataEditorInput); + + this.spriteAndMetadata.stats = analyzeMetadata( + this.spriteAndMetadata.stats.map(s => s.name), + this.projector.dataSet.points.map(p => p.metadata)); + this.projector.metadataChanged(this.spriteAndMetadata, this.metadataFile); + this.metadataEditorInputLabel = + `${selectionSize} labeled as '${this.metadataEditorInput}'`; + } } setNormalizeData(normalizeData: boolean) { From 3c19ea45e5f6f9cb08679e9aa9024215a6ed8168 Mon Sep 17 00:00:00 2001 From: Francois Luus Date: Mon, 11 Dec 2017 12:05:26 +0200 Subject: [PATCH 4/9] Projector: Metadata editor (isNumeric enforce consistency) Check if metadata column isNumeric, and disable label button if label input is not numeric. This enforces isNumeric metadata columns to remain numeric when label changes are made. --- .../vz_projector/vz-projector-data-panel.ts | 41 +++++++++++-------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/tensorboard/plugins/projector/vz_projector/vz-projector-data-panel.ts b/tensorboard/plugins/projector/vz_projector/vz-projector-data-panel.ts index 555ac9bbaa..75e74f1ea6 100644 --- a/tensorboard/plugins/projector/vz_projector/vz-projector-data-panel.ts +++ b/tensorboard/plugins/projector/vz_projector/vz-projector-data-panel.ts @@ -247,21 +247,30 @@ export class DataPanel extends DataPanelPolymer { } private metadataEditorInputChange() { + let col = this.metadataEditorColumn; let value = this.metadataEditorInput; let selectionSize = this.selectedPointIndices.length + this.neighborsOfFirstPoint.length; if (selectionSize > 0) { - if (value != null && value.trim() != '') { - let numMatches = this.projector.dataSet.points.filter(p => - p.metadata[this.metadataEditorColumn].toString() === value).length; - - if (numMatches === 0) { - this.metadataEditorInputLabel = `Tag ${selectionSize} with new label`; + if (value != null && value.trim() !== '') { + if (this.spriteAndMetadata.stats.filter(s => s.name===col)[0].isNumeric + && isNaN(+value)) { + this.metadataEditorInputLabel = `Label must be numeric`; + this.metadataEditorButtonDisabled = true; } else { - this.metadataEditorInputLabel = `Tag ${selectionSize} points as`; + let numMatches = this.projector.dataSet.points.filter(p => + p.metadata[col].toString() === value.trim()).length; + + if (numMatches === 0) { + this.metadataEditorInputLabel = + `Tag ${selectionSize} with new label`; + } + else { + this.metadataEditorInputLabel = `Tag ${selectionSize} points as`; + } + this.metadataEditorButtonDisabled = false; } - this.metadataEditorButtonDisabled = false; } else { this.metadataEditorInputLabel = 'Tag selection as'; @@ -271,7 +280,7 @@ export class DataPanel extends DataPanelPolymer { else { this.metadataEditorButtonDisabled = true; - if (value != null && value.trim() != '') { + if (value != null && value.trim() !== '') { this.metadataEditorInputLabel = 'Select points to tag'; } else { @@ -285,6 +294,7 @@ export class DataPanel extends DataPanelPolymer { if (e.keyCode === 13) { this.metadataEditorButtonClicked(); } + e.stopPropagation(); } private metadataEditorColumnChange() { @@ -294,24 +304,23 @@ export class DataPanel extends DataPanelPolymer { private metadataEditorButtonClicked() { if (!this.metadataEditorButtonDisabled) { - this.metadataEditorButtonDisabled = true; + let col = this.metadataEditorColumn; + let value = this.metadataEditorInput.trim(); let selectionSize = this.selectedPointIndices.length + this.neighborsOfFirstPoint.length; + this.metadataEditorButtonDisabled = true; this.selectedPointIndices.forEach(i => - this.projector.dataSet.points[i].metadata[this.metadataEditorColumn] = - this.metadataEditorInput); + this.projector.dataSet.points[i].metadata[col] = value); this.neighborsOfFirstPoint.forEach(p => - this.projector.dataSet.points[p.index] - .metadata[this.metadataEditorColumn] = this.metadataEditorInput); + this.projector.dataSet.points[p.index].metadata[col] = value); this.spriteAndMetadata.stats = analyzeMetadata( this.spriteAndMetadata.stats.map(s => s.name), this.projector.dataSet.points.map(p => p.metadata)); this.projector.metadataChanged(this.spriteAndMetadata, this.metadataFile); - this.metadataEditorInputLabel = - `${selectionSize} labeled as '${this.metadataEditorInput}'`; + this.metadataEditorInputLabel = `${selectionSize} labeled as '${value}'`; } } From a5a71a3d2b54b32fdd805f90604734487f2c1bd6 Mon Sep 17 00:00:00 2001 From: Francois Luus Date: Mon, 11 Dec 2017 13:04:00 +0200 Subject: [PATCH 5/9] Projector: Metadata editor (Label by & Edit by selections) Reintroduce 'Label by' field, which is necessary to view text-based data representations while labeling in another 'Edit by' field. --- .../vz_projector/vz-projector-data-panel.html | 17 ++++++++++++++++- .../vz_projector/vz-projector-data-panel.ts | 13 ++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/tensorboard/plugins/projector/vz_projector/vz-projector-data-panel.html b/tensorboard/plugins/projector/vz_projector/vz-projector-data-panel.html index 25e44e0fad..084556353f 100644 --- a/tensorboard/plugins/projector/vz_projector/vz-projector-data-panel.html +++ b/tensorboard/plugins/projector/vz_projector/vz-projector-data-panel.html @@ -258,6 +258,19 @@ + + +
@@ -284,9 +297,11 @@
+ + - - - - Sphereize data - - - The data is normalized by shifting each point by the centroid and making - it unit norm. - - -

- +

+ Load data from your computer - Load data + Load - + Publish your embedding visualization and data Publish -

+ + + Download the metadata with applied modifications + + Download + + + + + Label selected metadata + + Label + +

Load data from your computer

@@ -440,6 +453,14 @@

Step 3: Host projector config

Click outside to dismiss.
+ + Sphereize data + + + The data is normalized by shifting each point by the centroid and making + it unit norm. + +
diff --git a/tensorboard/plugins/projector/vz_projector/vz-projector-data-panel.ts b/tensorboard/plugins/projector/vz_projector/vz-projector-data-panel.ts index ed7da044f2..63bfc71201 100644 --- a/tensorboard/plugins/projector/vz_projector/vz-projector-data-panel.ts +++ b/tensorboard/plugins/projector/vz_projector/vz-projector-data-panel.ts @@ -34,7 +34,8 @@ export let DataPanelPolymer = PolymerElement({ metadataEditorColumn: {type: String}, metadataEditorColumnChange: {type: Object}, metadataEditorButtonClicked: {type: Object}, - metadataEditorButtonDisabled: {type: Boolean} + metadataEditorButtonDisabled: {type: Boolean}, + downloadMetadataClicked: {type: Boolean} }, observers: [ '_generateUiForNewCheckpointForRun(selectedRun)', @@ -329,6 +330,28 @@ export class DataPanel extends DataPanelPolymer { } } + private downloadMetadataClicked() { + if (this.projector && this.projector.dataSet + && this.projector.dataSet.spriteAndMetadataInfo) { + let tsvFile = this.projector.dataSet.spriteAndMetadataInfo.stats.map(s => + s.name).join('\t'); + + this.projector.dataSet.spriteAndMetadataInfo.pointsInfo.forEach(p => { + let vals = []; + + for (const column in p) { + vals.push(p[column]); + } + tsvFile += '\n' + vals.join('\t'); + }); + + const textBlob = new Blob([tsvFile], {type: 'text/plain'}); + this.$.downloadMetadataLink.download = 'metadata-edited.tsv'; + this.$.downloadMetadataLink.href = window.URL.createObjectURL(textBlob); + this.$.downloadMetadataLink.click(); + } + } + setNormalizeData(normalizeData: boolean) { this.normalizeData = normalizeData; } @@ -525,7 +548,7 @@ export class DataPanel extends DataPanelPolymer { } (this.$$('#demo-data-buttons-container') as HTMLElement).style.display = - 'block'; + 'flex'; // Fill out the projector config. const projectorConfigTemplate = From 43127d0a33d02ce06fdeb3e39e1e9822cf52c4d4 Mon Sep 17 00:00:00 2001 From: Francois Luus Date: Mon, 11 Dec 2017 16:02:38 +0200 Subject: [PATCH 9/9] [Fix] Use exact equality Replace == with === in inspector-panel code. --- .../projector/vz_projector/vz-projector-inspector-panel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorboard/plugins/projector/vz_projector/vz-projector-inspector-panel.ts b/tensorboard/plugins/projector/vz_projector/vz-projector-inspector-panel.ts index 0f68c59c7d..265d7c1235 100644 --- a/tensorboard/plugins/projector/vz_projector/vz-projector-inspector-panel.ts +++ b/tensorboard/plugins/projector/vz_projector/vz-projector-inspector-panel.ts @@ -106,7 +106,7 @@ export class InspectorPanel extends InspectorPanelPolymer { }); if (this.selectedMetadataField == null || this.metadataFields.filter(name => - name == this.selectedMetadataField).length == 0) { + name === this.selectedMetadataField).length === 0) { // Make the default label the first non-numeric column. this.selectedMetadataField = this.metadataFields[Math.max(0, labelIndex)]; }