Skip to content

Projector: Metadata editor #753

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

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
<style include="vz-projector-styles"></style>
<style>
.container {
padding: 10px 20px 20px 20px;
padding: 5px 20px 20px 20px;
}

input[type=file] {
Expand Down Expand Up @@ -77,8 +77,29 @@
font-size: 12px;
}

paper-input {
font-size: 15px;
--paper-input-container: {
padding: 5px 0;
};
--paper-input-container-label-floating: {
white-space: normal;
line-height: normal;
};
}

paper-dropdown-menu {
width: 100%;
--paper-input-container: {
padding: 5px 0;
};
--paper-input-container-input: {
font-size: 15px;
};
--paper-input-container-label-floating: {
white-space: normal;
line-height: normal;
};
}

paper-dropdown-menu paper-item {
Expand Down Expand Up @@ -116,6 +137,19 @@
margin: 10px 0;
}

.metadata-editor {
display: flex;
}

.metadata-editor paper-dropdown-menu {
width: 100px;
margin-right: 10px;
}

.metadata-editor paper-input {
width: calc(100% - 110px);
}

.config-checkbox {
display: inline-block;
font-size: 11px;
Expand All @@ -142,6 +176,20 @@
color: #B71C1C;
}

.button-container {
flex: 1 100%;
margin-right: 5px;
}

.button-container paper-button {
min-width: 50px;
width: 100%;
}

#label-button {
margin-right: 0px;
}

.upload-step {
display: flex;
justify-content: space-between;
Expand Down Expand Up @@ -187,10 +235,7 @@

#demo-data-buttons-container {
display: none;
}

.colorby-container {
margin-bottom: 10px;
margin-top: 10px;
}
</style>
<div class="title">DATA</div>
Expand Down Expand Up @@ -262,28 +307,53 @@
<vz-projector-legend render-info="[[colorLegendRenderInfo]]"></vz-projector-legend>
</template>
</div>
<paper-checkbox id="normalize-data-checkbox" checked="{{normalizeData}}">
Sphereize data
<paper-icon-button icon="help" class="help-icon"></paper-icon-button>
<paper-tooltip position="bottom" animation-delay="0" fit-to-visible-bounds>
The data is normalized by shifting each point by the centroid and making
it unit norm.
</paper-tooltip>
</paper-checkbox>
<p id="demo-data-buttons-container">
<span>
<template is="dom-if" if="[[_hasChoice(labelOptions)]]">
<!-- Edit by -->
<div class="metadata-editor">
<paper-dropdown-menu no-animations label="Edit by">
<paper-listbox attr-for-selected="value" class="dropdown-content" slot="dropdown-content"
on-selected-item-changed="metadataEditorColumnChange"
selected="{{metadataEditorColumn}}" >
<template is="dom-repeat" items="[[metadataFields]]">
<paper-item value="[[item]]" label="[[item]]">
[[item]]
</paper-item>
</template>
</paper-listbox>
</paper-dropdown-menu>
<paper-input value="{{metadataEditorInput}}" label="{{metadataEditorInputLabel}}"
on-input="metadataEditorInputChange" on-keydown="metadataEditorInputKeydown">
</paper-input>
</div>
</template>
<div id="demo-data-buttons-container">
<span class="button-container">
<paper-tooltip position="bottom" animation-delay="0" fit-to-visible-bounds>
Load data from your computer
</paper-tooltip>
<paper-button id="upload" class="ink-button" onclick="dataDialog.open()">Load data</paper-button>
<paper-button id="upload" class="ink-button" onclick="dataDialog.open()">Load</paper-button>
</span>
<span id="publish-container">
<span id="publish-container" class="button-container">
<paper-tooltip position="bottom" animation-delay="0" fit-to-visible-bounds>
Publish your embedding visualization and data
</paper-tooltip>
<paper-button id="host-embedding" class="ink-button" onclick="projectorConfigDialog.open()">Publish</paper-button>
</span>
</p>
<span class="button-container">
<paper-tooltip position="bottom" animation-delay="0" fit-to-visible-bounds>
Download the metadata with applied modifications
</paper-tooltip>
<paper-button class="ink-button" on-click="downloadMetadataClicked">Download</paper-button>
<a href="#" id="downloadMetadataLink" hidden"></a>
</span>
<span id="label-button" class="button-container">
<paper-tooltip position="bottom" animation-delay="0" fit-to-visible-bounds>
Label selected metadata
</paper-tooltip>
<paper-button class="ink-button" on-click="metadataEditorButtonClicked"
disabled="[[metadataEditorButtonDisabled]]">Label</paper-button>
</span>
</div>
<div>
<paper-dialog id="dataDialog" with-backdrop>
<h2>Load data from your computer</h2>
Expand Down Expand Up @@ -383,6 +453,14 @@ <h4><span class="step-label">Step 3:</span> Host projector config</h4>
<div class="dismiss-dialog-note">Click outside to dismiss.</div>
</paper-dialog>
</div>
<paper-checkbox id="normalize-data-checkbox" checked="{{normalizeData}}">
Sphereize data
<paper-icon-button icon="help" class="help-icon"></paper-icon-button>
<paper-tooltip position="bottom" animation-delay="0" fit-to-visible-bounds>
The data is normalized by shifting each point by the centroid and making
it unit norm.
</paper-tooltip>
</paper-checkbox>
<div class="dirs">
<table>
<tr>
Expand Down
161 changes: 155 additions & 6 deletions tensorboard/plugins/projector/vz_projector/vz-projector-data-panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,15 @@ export let DataPanelPolymer = PolymerElement({
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},
downloadMetadataClicked: {type: Boolean}
},
observers: [
'_generateUiForNewCheckpointForRun(selectedRun)',
Expand All @@ -44,6 +52,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;
Expand Down Expand Up @@ -115,12 +129,39 @@ export class DataPanel extends DataPanelPolymer {
}

metadataChanged(
spriteAndMetadata: SpriteAndMetadataInfo, metadataFile: string) {
spriteAndMetadata: SpriteAndMetadataInfo, metadataFile?: string) {
this.spriteAndMetadata = spriteAndMetadata;
this.metadataFile = metadataFile;
if (metadataFile != null) {
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 {
Expand All @@ -145,7 +186,16 @@ export class DataPanel extends DataPanelPolymer {
}
return stats.name;
});
this.selectedLabelOption = this.labelOptions[Math.max(0, labelIndex)];

if (this.selectedLabelOption == null || this.labelOptions.filter(name =>
name === this.selectedLabelOption).length === 0) {
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[] = [
Expand Down Expand Up @@ -207,6 +257,101 @@ export class DataPanel extends DataPanelPolymer {
this.colorOptions = standardColorOption.concat(metadataColorOption);
}

private metadataEditorContext(enabled: boolean) {
this.metadataEditorButtonDisabled = !enabled;
if (this.projector) {
this.projector.metadataEditorContext(enabled, this.metadataEditorColumn);
}
}

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() !== '') {
if (this.spriteAndMetadata.stats.filter(s => s.name===col)[0].isNumeric
&& isNaN(+value)) {
this.metadataEditorInputLabel = `Label must be numeric`;
this.metadataEditorContext(false);
}
else {
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.metadataEditorContext(true);
}
}
else {
this.metadataEditorInputLabel = 'Tag selection as';
this.metadataEditorContext(false);
}
}
else {
this.metadataEditorContext(false);

if (value != null && value.trim() !== '') {
this.metadataEditorInputLabel = 'Select points to tag';
}
else {
this.metadataEditorInputLabel = 'Tag selection as';
}
}
}

private metadataEditorInputKeydown(e) {
// Check if 'Enter' was pressed
if (e.keyCode === 13) {
this.metadataEditorButtonClicked();
}
e.stopPropagation();
}

private metadataEditorColumnChange() {
this.metadataEditorInputChange();
}

private metadataEditorButtonClicked() {
if (!this.metadataEditorButtonDisabled) {
let value = this.metadataEditorInput.trim();
let selectionSize = this.selectedPointIndices.length +
this.neighborsOfFirstPoint.length;
this.projector.metadataEdit(this.metadataEditorColumn, value);
this.projector.metadataEditorContext(true, this.metadataEditorColumn);
this.metadataEditorInputLabel = `${selectionSize} labeled as '${value}'`;
}
}

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;
}
Expand Down Expand Up @@ -403,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 =
Expand Down Expand Up @@ -492,6 +637,10 @@ export class DataPanel extends DataPanelPolymer {
this.runNames.length + ' runs';
}

_hasChoice(choices: any[]): boolean {
return choices.length > 0;
}

_hasChoices(choices: any[]): boolean {
return choices.length > 1;
}
Expand Down
Loading