diff --git a/tensorboard/components/vz_example_viewer/vz-example-viewer.html b/tensorboard/components/vz_example_viewer/vz-example-viewer.html index 5c79b229c76..ef16766baa0 100644 --- a/tensorboard/components/vz_example_viewer/vz-example-viewer.html +++ b/tensorboard/components/vz_example_viewer/vz-example-viewer.html @@ -76,6 +76,7 @@ display: block; padding: 8px; width: fit-content; + border: 1px solid lightgrey; } .hide-saliency-controls { @@ -474,7 +475,7 @@ value="[[getFirstFeatureValue(feat.name)]]"> @@ -498,7 +499,7 @@ value="[[getFirstCompareFeatureValue(feat.name)]]"> @@ -635,7 +636,7 @@ value="[[getFirstSeqFeatureValue(seqfeat.name, seqNumber)]]"> @@ -661,7 +662,7 @@ value="[[getFirstCompareSeqFeatureValue(seqfeat.name, seqNumber)]]"> @@ -685,8 +686,8 @@ - - + +
Saliency legend
@@ -700,7 +701,7 @@ value="[[saliencyCutoff]]">
- +
0; + // Create a dict of feature names to the total absolute saliency of all + // its feature values, to sort features with the most salienct features at + // the top. + const saliencyTotals = checkSal ? + Object.assign({}, ...Object.keys(saliency).map( + name => ({[name]: typeof saliency[name] == 'number' ? + Math.abs(saliency[name] as number) : + (saliency[name] as Array).reduce((total, cur) => + Math.abs(total) + Math.abs(cur) , 0)}))) : + {}; + if (searchValue != '') { const re = new RegExp(searchValue, 'i'); filtered = featureList.filter(feature => re.test(feature.name)); @@ -288,6 +299,18 @@ Polymer({ } else if (this.isImage(b.name) && !this.isImage(a.name)) { return 1; } else { + if (checkSal) { + if (a.name in saliency && !(b.name in saliency)) { + return -1; + } else if (b.name in saliency && !(a.name in saliency)) { + return 1; + } else { + const diff = saliencyTotals[b.name] - saliencyTotals[a.name]; + if (diff != 0) { + return diff; + } + } + } return a.name.localeCompare(b.name); } }); @@ -326,29 +349,32 @@ Polymer({ ]); }, + selectAll: function(query: string) { + return d3.selectAll( + Polymer.dom(this.root).querySelectorAll(query) as any); + }, + haveSaliency: function() { - if (!this.featuresList || !this.saliency || + if (!this.filteredFeaturesList || !this.saliency || Object.keys(this.saliency).length === 0 || !this.colors) { return; } // TODO(jwexler): Find a way to do this without requestAnimationFrame. - // If the paper-inputs for the features have yet to be rendered, wait to - // perform this processing. There should be paper-inputs for all non-image + // If the inputs for the features have yet to be rendered, wait to + // perform this processing. There should be inputs for all non-image // features. - if (d3.selectAll('.value input').size() < - (this.featuresList.length - Object.keys(this.imageInfo).length)) { + if (this.selectAll('input.value-pill').size() < + (this.filteredFeaturesList.length - Object.keys(this.imageInfo).length)) { requestAnimationFrame(() => this.haveSaliency()); return; } - // Reset all text to black - d3.selectAll('.value-pill') - .style('background', 'lightgrey'); - + // Reset all backgrounds to the neutral color. + this.selectAll('.value-pill').style('background', neutralSaliencyColor); // Color the text of each input element of each feature according to the // provided saliency information. - for (const feat of this.featuresList) { + for (const feat of this.filteredFeaturesList) { const val = this.saliency[feat.name] as SaliencyValue; // If there is no saliency information for the feature, do not color it. if (!val) { @@ -357,13 +383,28 @@ Polymer({ const colorFn = Array.isArray(val) ? (d: {}, i: number) => this.getColorForSaliency(val[i]) : () => this.getColorForSaliency(val); - - d3.selectAll( - `.${this.sanitizeFeature(feat.name)}.value-pill`) - .style('background', this.showSaliency ? colorFn : () => 'lightgrey'); + this.selectAll( + `input.${this.sanitizeFeature(feat.name)}.value-pill`) + .style('background', + this.showSaliency ? colorFn : () => neutralSaliencyColor); + + // Color the "more feature values" button with the most extreme saliency + // of any of the feature values hidden behind the button. + if (Array.isArray(val)) { + const valArray = val as Array; + const moreButton = this.selectAll( + `paper-button.${this.sanitizeFeature(feat.name)}.value-pill`); + let mostExtremeSal = 0; + for (let i = 1; i < valArray.length; i++) { + if (Math.abs(valArray[i]) > Math.abs(mostExtremeSal)) { + mostExtremeSal = valArray[i]; + } + } + moreButton.style('background', this.showSaliency ? + () => this.getColorForSaliency(mostExtremeSal) : + () => neutralSaliencyColor); + } } - // TODO(jwexler): Determine how to set non-fixed widths to input boxes - // inside of grid iron-list. }, /** @@ -382,7 +423,7 @@ Polymer({ // TODO(jwexler): Find a way to do this without requestAnimationFrame. // If the paper-inputs for the features have yet to be rendered, wait to // perform this processing. - if (d3.selectAll('.value input').size() < this.seqFeaturesList.length) { + if (this.selectAll('.value input').size() < this.seqFeaturesList.length) { requestAnimationFrame(() => this.seqSaliency()); return; } @@ -401,7 +442,7 @@ Polymer({ (d: {}, i: number) => this.getColorForSaliency(val[i]) : () => this.getColorForSaliency(val); - d3.selectAll( + this.selectAll( `.${this.sanitizeFeature(feat.name)} input`) .style('color', this.showSaliency ? colorFn : () => 'black'); } @@ -954,6 +995,7 @@ Polymer({ this.ignoreChange = false; setTimeout(() => { this.example = temp; + this.haveSaliency(); }, 0); }, @@ -1057,7 +1099,7 @@ Polymer({ const legendSvg = d3.select(this.$.saliencyLegend).append('g'); const gradient = legendSvg.append('defs') .append('linearGradient') - .attr('id', 'gradient') + .attr('id', 'vzexampleviewergradient') .attr("x1", "0%") .attr("y1", "0%") .attr("x2", "100%") @@ -1106,7 +1148,7 @@ Polymer({ .attr('y1', 0) .attr('width', LEGEND_WIDTH_PX) .attr('height', LEGEND_HEIGHT_PX) - .style('fill', 'url(#gradient)'); + .style('fill', 'url(#vzexampleviewergradient)'); const legendScale = d3.scaleLinear().domain([this.minSal, this.maxSal]).range([