Index: tracing/tracing/value/ui/value_set_table.html |
diff --git a/tracing/tracing/value/ui/value_set_table.html b/tracing/tracing/value/ui/value_set_table.html |
index 76fc197afce591037985a390aaa31283247d38cb..4eb50e885fa2fbd4b6402bb4dbbf3b495affb564 100644 |
--- a/tracing/tracing/value/ui/value_set_table.html |
+++ b/tracing/tracing/value/ui/value_set_table.html |
@@ -28,8 +28,21 @@ found in the LICENSE file. |
div#error { |
color: red; |
} |
+ #histogram { |
+ display: none; |
+ } |
+ #search { |
+ max-width: 20em; |
+ margin-top: 5px; |
+ margin-bottom: 5px; |
+ } |
</style> |
+ <input id="search" placeholder="Find Value name"> |
+ <div> |
+ <input type="checkbox" id="show_all"> |
+ <label for="show_all">Show all</label> |
+ </div> |
<div id="error"></div> |
<table-container> |
<tr-ui-b-table id="table"></tr-ui-b-table> |
@@ -41,6 +54,79 @@ found in the LICENSE file. |
<script> |
'use strict'; |
tr.exportTo('tr.ui', function() { |
+ |
+ /** |
+ * @param {!tr.v.Value} value |
+ * @param {string} fieldName |
+ * @param {*} defaultValue |
+ * @return {*} |
+ */ |
+ function getIterationInfoField(value, fieldName, defaultValue) { |
+ var iteration = tr.v.d.IterationInfo.getFromValue(value); |
+ if (!(iteration instanceof tr.v.d.IterationInfo)) |
+ return defaultValue; |
+ return iteration[fieldName]; |
+ } |
+ |
+ /** |
+ * @param {!tr.v.Value} value |
+ * @param {string} fieldName |
+ * @return {string} |
+ */ |
+ function getStoryGroupingKeyLabel(value, storyGroupingKey) { |
+ var iteration = tr.v.d.IterationInfo.getFromValue(value); |
+ if (!(iteration instanceof tr.v.d.IterationInfo)) |
+ return storyGroupingKey + ': undefined'; |
+ return storyGroupingKey + ': ' + |
+ iteration.storyGroupingKeys[storyGroupingKey]; |
+ } |
+ |
+ /** |
+ * @param {!tr.v.Value} value |
+ * @return {string} |
+ */ |
+ var getDisplayLabel = v => getIterationInfoField(v, 'displayLabel', 'Value'); |
+ |
+ var SELECTED_VALUE_SETTINGS_KEY = 'tr-v-ui-value-set-table-value'; |
+ var SHOW_ALL_SETTINGS_KEY = 'tr-v-ui-value-set-table-show-all'; |
+ |
+ /** |
+ * Recursively groups |values|. |
+ * TODO(benjhayden): Use ES6 Maps instead of dictionaries? |
+ * |
+ * @param {!Array.<!tr.v.Value>} values |
+ * @param {!Array.<!function(!tr.v.Value):(string|number)>} groupingCallbacks |
+ * @return {!(Object|tr.v.Value)} |
+ */ |
+ function organizeValues(values, groupingCallbacks, level) { |
+ if (groupingCallbacks.length === level) { |
+ // Recursion base case: there should only be a single value when we've |
+ // grouped by every possible grouping. |
+ if (values.length > 1) { |
+ console.warn('Multiple Values with same name, benchmarkName, ' + |
+ 'storyGroupingKeys, storyName, start, storysetRepeatCounter, ' + |
+ 'storyRepeatCounter, and displayLabel', values); |
+ } |
+ return values[0]; |
+ } |
+ |
+ // Group the values by the current grouping. |
+ var groupedValues = tr.b.group(values, groupingCallbacks[level]); |
+ |
+ // Skip this grouping level if it contains only a single group, |
+ // but never skip the zeroth grouping level (value name) nor the last |
+ // (displayLabel). |
+ if (level > 0 && level < (groupingCallbacks.length - 1) && |
+ tr.b.dictionaryLength(groupedValues) === 1) { |
+ return organizeValues(values, groupingCallbacks, level + 1); |
+ } |
+ |
+ // Recursively group groupedValues. |
+ return tr.b.mapItems(groupedValues, function(key, groupValues) { |
+ return organizeValues(groupValues, groupingCallbacks, level + 1); |
+ }); |
+ } |
+ |
Polymer({ |
is: 'tr-v-ui-value-set-table', |
@@ -64,111 +150,583 @@ tr.exportTo('tr.ui', function() { |
return 'Table'; |
}, |
+ created: function() { |
+ // TODO(benjhayden): Should these all be ValueSets? |
+ /** @type {undefined|!tr.v.ValueSet} */ |
+ this.values_ = undefined; |
+ /** @type {!Array.<!tr.v.Value>} */ |
+ this.sourceValues_ = []; |
+ /** @type {!Object} */ |
+ this.summaryValuesByGuid_ = {}; |
+ |
+ this.rows_ = undefined; |
+ this.columns_ = undefined; |
+ }, |
+ |
ready: function() { |
- this.$.table.sortDescending = true; |
- this.$.table.selectionMode = tr.ui.b.TableFormat.SelectionMode.ROW; |
- this.$.table.tableColumns = [ |
- { |
- title: 'Name', |
- value: function(value) { |
- var nameEl = document.createElement('span'); |
- Polymer.dom(nameEl).textContent = value.name; |
- if (value.description) |
- nameEl.title = value.description; |
- nameEl.style.textOverflow = 'ellipsis'; |
- return nameEl; |
- }, |
- cmp: function(a, b) { |
- return a.name.localeCompare(b.name); |
- } |
- }, |
- { |
- title: 'Value', |
- align: tr.ui.b.TableFormat.ColumnAlignment.RIGHT, |
- value: function(value) { |
- if (value.diagnostic instanceof tr.v.d.Diagnostic) |
- return tr.v.ui.createDiagnosticSpan(value.diagnostic); |
- if (value.unit) |
- return tr.v.ui.createScalarSpan(value.value, {unit: value.unit}); |
- return value.value; |
- }, |
- cmp: function(a, b) { |
- return a.value - b.value; |
+ this.$.table.selectionMode = tr.ui.b.TableFormat.SelectionMode.CELL; |
+ this.$.table.addEventListener('selection-changed', |
+ this.onSelectionChanged_.bind(this)); |
+ this.$.table.addEventListener('selected-column-changed', |
+ this.onSelectedColumnChanged_.bind(this)); |
+ this.addEventListener('requestSelectionChange', |
+ this.onRelatedValueSelected_.bind(this)); |
+ this.$.search.addEventListener('keyup', this.onSearch_.bind(this)); |
+ this.$.show_all.checked = tr.b.Settings.get(SHOW_ALL_SETTINGS_KEY, false); |
+ this.$.show_all.addEventListener('change', |
+ this.onShowAllChange_.bind(this)); |
+ }, |
+ |
+ onShowAllChange_: function() { |
+ tr.b.Settings.set(SHOW_ALL_SETTINGS_KEY, this.$.show_all.checked); |
+ this.updateContents_(); |
+ }, |
+ |
+ onSelectedColumnChanged_: function() { |
+ // Force the table to rebuild the cell values without forgetting which |
+ // rows were expanded. |
+ var expansionStates = this.getExpansionStates_(this.rows_); |
+ this.$.table.tableRows = this.rows_; |
+ this.setExpansionStates_(expansionStates, this.rows_); |
+ }, |
+ |
+ getExpansionStates_: function(rows) { |
+ var states = {}; |
+ for (var i = 0; i < rows.length; ++i) { |
+ var row = rows[i]; |
+ if (row.subRows && row.subRows.length && |
+ this.$.table.getExpandedForTableRow(row)) { |
+ states[i] = this.getExpansionStates_(row.subRows); |
+ } |
+ } |
+ return states; |
+ }, |
+ |
+ setExpansionStates_: function(states, rows) { |
+ for (var i = 0; i < rows.length; ++i) { |
+ if (states[i]) { |
+ this.$.table.setExpandedForTableRow(rows[i], true); |
+ this.setExpansionStates_(states[i], rows[i].subRows); |
+ } |
+ } |
+ }, |
+ |
+ onSearch_: function() { |
+ this.updateContents_(); |
+ }, |
+ |
+ rowMatchesSearch_: function(row) { |
+ return row.name.indexOf(this.$.search.value) >= 0; |
+ }, |
+ |
+ onRelatedValueSelected_: function(event) { |
+ var value = event.selection; |
+ if (!(value instanceof tr.v.Value)) |
+ return; |
+ |
+ event.stopPropagation(); |
+ |
+ var displayLabel = getDisplayLabel(value); |
+ var columnIndex = -1; |
+ for (var i = 0; i < this.columns_.length; ++i) { |
+ if (this.columns_[i].title === displayLabel) { |
+ columnIndex = i; |
+ break; |
+ } |
+ } |
+ if (columnIndex < 0) |
+ return; |
+ |
+ var hierarchy = []; |
+ var found = false; |
+ function search(row) { |
+ if (row.columns[displayLabel] === value) { |
+ for (var hirow in hierarchy) { |
+ this.$.table.setExpandedForTableRow(hirow, true); |
} |
+ found = true; |
+ this.$.table.selectedTableRow = row; |
+ this.$.table.selectedColumnIndex = columnIndex; |
+ return; |
} |
- ]; |
- this.$.table.sortColumnIndex = 1; |
- this.$.table.addEventListener('selection-changed', |
- this.onRowSelected_.bind(this)); |
+ if (!row.subRows) |
+ return; |
+ hierarchy.push(row); |
+ row.subRows.forEach(search, this); |
+ hierarchy.pop(row); |
+ } |
+ this.rows_.forEach(search, this); |
+ |
+ if (found || this.$.show_all.checked) |
+ return; |
+ |
+ // Search hidden values for |value|. |
+ for (var test of this.values) { |
+ // Skip values that are already displayed -- we would have found them |
+ // in search() above. |
+ if (this.sourceValues_.indexOf(test) >= 0) |
+ continue; |
+ |
+ if (test === value) { |
+ this.$.show_all.checked = true; |
+ this.onShowAllChange_(); |
+ this.onRelatedValueSelected_(event); |
+ break; |
+ } |
+ } |
}, |
- onRowSelected_: function() { |
+ onSelectionChanged_: function() { |
var row = this.$.table.selectedTableRow; |
- if (row && row.numeric) { |
- this.$.histogram.style.display = ''; |
- this.$.histogram.histogram = row.numeric; |
+ var col = this.$.table.selectedColumnIndex; |
+ var cell = undefined; |
+ if (row && col && this.columns_) |
+ cell = row.columns[this.columns_[col].title]; |
+ |
+ if ((cell instanceof tr.v.NumericValue) && |
+ (cell.numeric instanceof tr.v.Numeric)) { |
+ this.$.histogram.style.display = 'block'; |
+ this.$.histogram.histogram = cell.numeric; |
+ |
+ tr.b.Settings.set(SELECTED_VALUE_SETTINGS_KEY, JSON.stringify({ |
+ row: row.name, |
+ column: this.columns_[col].title |
+ })); |
} else { |
this.$.histogram.style.display = 'none'; |
} |
}, |
+ handleFailureValues_: function() { |
+ this.values.map(function(value) { |
+ if (value instanceof tr.v.FailureValue) { |
+ Polymer.dom(this.$.error).textContent = value.description; |
+ this.$.table.style.display = 'none'; |
+ this.style.width = '10em'; |
+ } |
+ }, this); |
+ }, |
+ |
+ addDiagnosticSubRows_: function(value, row, column) { |
+ value.diagnostics.forEach(function(name, diagnostic) { |
+ if (name === tr.v.SUMMARY_VALUE_MAP_DIAGNOSTIC_NAME) |
+ return; |
+ |
+ // If a previous |value| had a diagnostic with the same name, then |
+ // there is already a subRow that should contain this diagnostic. |
+ for (var subRow of row.subRows) { |
+ if (subRow.name === name) { |
+ subRow.columns[column] = diagnostic; |
+ return; |
+ } |
+ } |
+ |
+ // This is the first time that a diagnostic with this name has been |
+ // seen for Values whose name is |value.name|, so create a new subRow. |
+ var subRow = {name: name, columns: {}}; |
+ subRow.columns[column] = diagnostic; |
+ row.subRows.push(subRow); |
+ }); |
+ }, |
+ |
+ get values() { |
+ return this.values_; |
+ }, |
+ |
+ findSummaryValues_: function() { |
+ this.summaryValuesByGuid_ = {}; |
+ this.values.map(function(value) { |
+ var summaryValueMap = value.diagnostics.get( |
+ tr.v.SUMMARY_VALUE_MAP_DIAGNOSTIC_NAME); |
+ if (!(summaryValueMap instanceof tr.v.d.RelatedValueMap)) |
+ return; |
+ |
+ summaryValueMap.values.forEach(function(summaryValue) { |
+ this.summaryValuesByGuid_[summaryValue.guid] = summaryValue; |
+ }, this); |
+ }, this); |
+ }, |
+ |
/** |
* @param {!tr.v.ValueSet} values |
*/ |
set values(values) { |
+ this.values_ = values; |
+ this.sourceValues_ = values.sourceValues; |
+ this.findSummaryValues_(); |
+ this.updateContents_(); |
+ }, |
+ |
+ updateContents_: function() { |
+ if (!this.values_) |
+ return; |
this.style.width = ''; |
this.$.table.style.display = ''; |
Polymer.dom(this.$.error).textContent = ''; |
+ this.$.histogram.style.display = 'none'; |
- values.map(function(value) { |
- if (value instanceof tr.v.FailureValue) { |
- Polymer.dom(this.$.error).textContent = value.description; |
- this.$.table.style.display = 'none'; |
- this.style.width = '10em'; |
- } |
- }, this); |
+ this.handleFailureValues_(); |
if (Polymer.dom(this.$.error).textContent) |
return; |
- this.$.table.tableRows = values.map(function(value) { |
- var row = { |
- name: value.name, |
- value: '', |
- unit: undefined, |
- description: value.description, |
- subRows: [] |
- }; |
- |
- if (value.numeric) { |
- row.unit = value.numeric.unit; |
- if (value.numeric.value !== undefined) { |
- row.value = value.numeric.value; |
- } else if (value.numeric.average !== undefined) { |
- row.numeric = value.numeric; |
- row.value = value.numeric.average; |
- } |
- } |
+ this.buildRows_(); |
- value.diagnostics.forEach(function(name, diagnostic) { |
- row.subRows.push({ |
- name: name, |
- diagnostic: diagnostic |
- }); |
- }); |
+ if (this.rows_.length === 0) { |
+ Polymer.dom(this.$.error).textContent = 'zero values'; |
+ this.$.table.style.display = 'none'; |
+ this.style.width = '10em'; |
+ return; |
+ } |
- return row; |
- }); |
+ this.buildColumns_(); |
+ this.$.table.tableColumns = this.columns_; |
+ this.$.table.tableRows = this.rows_; |
+ this.$.table.sortColumnIndex = 0; |
this.$.table.rebuild(); |
- this.onRowSelected_(); |
+ this.selectValue_(); |
tr.b.requestAnimationFrame(function() { |
this.style.width = this.$.table.getBoundingClientRect().width; |
}, this); |
+ }, |
+ |
+ selectValue_: function() { |
+ var selectedValue = tr.b.Settings.get( |
+ SELECTED_VALUE_SETTINGS_KEY, undefined); |
+ if (selectedValue) { |
+ selectedValue = JSON.parse(selectedValue); |
+ for (var row of this.rows_) { |
+ if (row.name === selectedValue.row) { |
+ for (var coli = 1; coli < this.columns_.length; ++coli) { |
+ var column = this.columns_[coli]; |
+ if (column.title === selectedValue.column) { |
+ this.$.table.selectedTableRow = row; |
+ this.$.table.selectedColumnIndex = coli; |
+ return; |
+ } |
+ } |
+ } |
+ } |
+ } |
+ this.$.table.selectedTableRow = this.rows_[0]; |
+ this.$.table.selectedColumnIndex = 1; |
+ }, |
+ |
+ /** |
+ * Build table rows recursively from organized Values. The recursion stack |
+ * of subRows is maintained in |hierarchy|. |
+ * |
+ * @param {!Object} organizedValues |
+ * @param {!Array.<!Object>} hierarchy |
+ */ |
+ buildRow_: function(organizedValues, hierarchy) { |
+ tr.b.iterItems(organizedValues, function(name, value) { |
+ if (value instanceof tr.v.Value) { |
+ // This recursion base case corresponds to the recursion base case of |
+ // organizeValues(). The last groupingCallback is getDisplayLabel, |
+ // which defines the columns of the table. |
+ |
+ // Merge Values up the grouping hierarchy. |
+ for (var row of hierarchy) { |
+ if (row.description === undefined) |
+ row.description = value.description; |
+ |
+ if (row.columns[name]) |
+ row.columns[name] = row.columns[name].merge(value); |
+ else |
+ row.columns[name] = value; |
+ } |
+ |
+ var row = hierarchy[hierarchy.length - 1]; |
+ this.addDiagnosticSubRows_(value, row, name); |
+ } else { |
+ // |value| is actually a nested organizedValues. |
+ var row = {name: name, subRows: [], columns: {}}; |
+ hierarchy.push(row); |
+ this.buildRow_(value, hierarchy); |
+ hierarchy.pop(); |
+ |
+ if (hierarchy.length === 0) |
+ this.rows_.push(row); |
+ else |
+ hierarchy[hierarchy.length - 1].subRows.push(row); |
+ } |
+ }, this); |
+ }, |
+ |
+ get storyGroupingKeys() { |
+ var keys = new Set(); |
+ for (var value of this.values) { |
+ var iteration = tr.v.d.IterationInfo.getFromValue(value); |
+ if (!(iteration instanceof tr.v.d.IterationInfo) || |
+ !iteration.storyGroupingKeys) |
+ continue; |
+ |
+ for (var key in iteration.storyGroupingKeys) |
+ keys.add(key); |
+ } |
+ return [...keys.values()].sort(); |
+ }, |
+ |
+ /** |
+ * A ValueSet is a flat set of Values. Value-set-table must present a |
+ * hierarchical view. This method recursively groups this.values as an |
+ * intermediate step towards building tableRows in buildRow_(). |
+ * { |
+ * valueA: { |
+ * benchmarkA: { |
+ * storyA: { |
+ * startA: { |
+ * storysetRepeatCounterA: { |
+ * storyRepeatCounterA: { |
+ * displayLabelA: Value, |
+ * displayLabelB: Value |
+ * } |
+ * } |
+ * } |
+ * } |
+ * } |
+ * } |
+ * } |
+ * @return {!Object} |
+ */ |
+ get organizedValues_() { |
+ var showingValues = this.$.show_all.checked ? |
+ this.values : this.sourceValues_; |
+ var values = []; |
+ for (var value of showingValues) |
+ if (this.summaryValuesByGuid_[value.guid] === undefined) |
+ values.push(value); |
+ |
+ var groupingCallbacks = []; |
+ groupingCallbacks.push(v => v.name); |
+ groupingCallbacks.push( |
+ v => getIterationInfoField(v, 'benchmarkName', '')); |
+ for (var storyGroupingKey of this.storyGroupingKeys) { |
+ // Javascript closures are dumb. |
+ groupingCallbacks.push((sgk => ( |
+ v => getStoryGroupingKeyLabel(v, sgk)))(storyGroupingKey)); |
+ } |
+ groupingCallbacks.push( |
+ v => getIterationInfoField(v, 'storyDisplayName', '')); |
+ groupingCallbacks.push( |
+ v => getIterationInfoField(v, 'benchmarkStartString', '')); |
+ groupingCallbacks.push( |
+ v => getIterationInfoField(v, 'storysetRepeatCounterLabel', 0)); |
+ groupingCallbacks.push( |
+ v => getIterationInfoField(v, 'storyRepeatCounterLabel', 0)); |
+ groupingCallbacks.push(getDisplayLabel); |
+ |
+ return organizeValues(values, groupingCallbacks, 0); |
+ }, |
+ |
+ /* this.rows_ will look something like |
+ * [ |
+ * { |
+ * name: 'value name', |
+ * columns: { |
+ * displayLabelA: Value, |
+ * displayLabelB: Value, |
+ * }, |
+ * subRows: [ |
+ * { |
+ * name: 'benchmark name if multiple', |
+ * columns: { |
+ * displayLabelA: Value, |
+ * displayLabelB: Value, |
+ * }, |
+ * subRows: [ |
+ * { |
+ * name: 'story name if multiple', |
+ * columns: { |
+ * displayLabelA: Value, |
+ * displayLabelB: Value, |
+ * }, |
+ * subRows: [ |
+ * { |
+ * name: 'benchmark start if multiple', |
+ * columns: { |
+ * displayLabelA: Value, |
+ * displayLabelB: Value, |
+ * }, |
+ * subRows: [ |
+ * { |
+ * name: 'storyset repeat counter if multiple', |
+ * columns: { |
+ * displayLabelA: Value, |
+ * displayLabelB: Value, |
+ * }, |
+ * subRows: [ |
+ * { |
+ * name: 'story repeat counter if multiple', |
+ * columns: { |
+ * displayLabelA: Value, |
+ * displayLabelB: Value, |
+ * }, |
+ * subRows: [ |
+ * { |
+ * name: 'diagnostic map key', |
+ * columns: { |
+ * displayLabelA: Diagnostic, |
+ * displayLabelB: Diagnostic, |
+ * }, |
+ * } |
+ * ] |
+ * } |
+ * ] |
+ * } |
+ * ] |
+ * } |
+ * ] |
+ * } |
+ * ] |
+ * } |
+ * ] |
+ * } |
+ * ] |
+ * |
+ * Any of those layers may be missing except 'value name'. |
+ */ |
+ buildRows_: function() { |
+ this.rows_ = []; |
+ var hierarchy = []; |
+ this.buildRow_(this.organizedValues_, hierarchy); |
+ this.rows_ = this.rows_.filter(this.rowMatchesSearch_.bind(this)); |
+ }, |
+ |
+ get startTimesForDisplayLabels() { |
+ var startTimesForDisplayLabels = {}; |
+ for (var value of this.values) { |
+ if (this.summaryValuesByGuid_[value.guid]) |
+ continue; |
+ |
+ var displayLabel = getDisplayLabel(value); |
+ startTimesForDisplayLabels[displayLabel] = Math.min( |
+ startTimesForDisplayLabels[displayLabel] || 0, |
+ getIterationInfoField( |
+ value, 'benchmarkStart', new Date(0)).getTime()); |
+ } |
+ return startTimesForDisplayLabels; |
+ }, |
+ |
+ get displayLabels() { |
+ var startTimesForDisplayLabels = this.startTimesForDisplayLabels; |
+ var displayLabels = Object.keys(startTimesForDisplayLabels); |
+ displayLabels.sort(function(a, b) { |
+ return startTimesForDisplayLabels[a] - startTimesForDisplayLabels[b]; |
+ }); |
+ return displayLabels; |
+ }, |
+ |
+ buildColumn_: function(displayLabel) { |
+ function getValueForValue(value) { |
+ return value.numeric instanceof tr.v.Numeric ? value.numeric.average : |
+ value.numeric.value; |
+ } |
+ |
+ return { |
+ title: displayLabel, |
+ align: tr.ui.b.TableFormat.ColumnAlignment.RIGHT, |
+ supportsCellSelection: true, |
+ selectable: true, |
+ |
+ value: function(row) { |
+ var cell = row.columns[displayLabel]; |
+ if (cell === undefined) |
+ return ''; |
+ |
+ if (cell instanceof tr.v.NumericValue) { |
+ if (this.$.table.selectedTableColumn && |
+ this.$.table.selectedTableColumn.title !== displayLabel) { |
+ var referenceCell = row.columns[ |
+ this.$.table.selectedTableColumn.title]; |
+ |
+ if (referenceCell instanceof tr.v.NumericValue && |
+ cell.numeric.unit === referenceCell.numeric.unit) { |
+ var significance = tr.v.Significance.DONT_CARE; |
+ |
+ if (cell.numeric instanceof tr.v.Numeric && |
+ referenceCell.numeric instanceof tr.v.Numeric) { |
+ significance = cell.numeric.getDifferenceSignificance( |
+ referenceCell.numeric); |
+ } |
+ |
+ return tr.v.ui.createScalarSpan( |
+ getValueForValue(cell) - getValueForValue(referenceCell), |
+ {unit: cell.numeric.unit.correspondingDeltaUnit, |
+ significance: significance}); |
+ } |
+ } |
+ |
+ return tr.v.ui.createScalarSpan(cell); |
+ } |
+ if (cell instanceof tr.v.d.Diagnostic) { |
+ var span = tr.v.ui.createDiagnosticSpan(cell); |
+ span.style.textAlign = 'left'; |
+ return span; |
+ } |
+ throw new Error('Invalid cell', cell); |
+ }.bind(this), |
+ |
+ cmp: function(rowA, rowB) { |
+ var cellA = rowA.columns[displayLabel]; |
+ var cellB = rowB.columns[displayLabel]; |
+ if (!(cellA instanceof tr.v.NumericValue) || |
+ !(cellB instanceof tr.v.NumericValue)) { |
+ return undefined; |
+ } |
+ |
+ var valueA = getValueForValue(cellA); |
+ var valueB = getValueForValue(cellB); |
+ |
+ // If a reference column is selected, compare the *differences* |
+ // between the two cells and their references. |
+ if (this.$.table.selectedTableColumn && |
+ this.$.table.selectedTableColumn.title !== displayLabel) { |
+ var referenceColumn = this.$.table.selectedTableColumn.title; |
+ var referenceCellA = rowA.columns[referenceColumn]; |
+ var referenceCellB = rowB.columns[referenceColumn]; |
+ if (referenceCellA instanceof tr.v.NumericValue && |
+ referenceCellB instanceof tr.v.NumericValue && |
+ cellA.numeric.unit === referenceCellA.numeric.unit && |
+ cellB.numeric.unit === referenceCellB.numeric.unit) { |
+ valueA -= getValueForValue(referenceCellA); |
+ valueB -= getValueForValue(referenceCellB); |
+ } |
+ } |
+ |
+ return valueA - valueB; |
+ }.bind(this) |
+ }; |
+ }, |
+ |
+ buildColumns_: function() { |
+ this.columns_ = [ |
+ { |
+ title: 'Name', |
+ align: tr.ui.b.TableFormat.ColumnAlignment.LEFT, |
+ supportsCellSelection: false, |
+ |
+ value: function(row) { |
+ var nameEl = document.createElement('span'); |
+ Polymer.dom(nameEl).textContent = row.name; |
+ if (row.description) |
+ nameEl.title = row.description; |
+ nameEl.style.textOverflow = 'ellipsis'; |
+ return nameEl; |
+ }, |
+ |
+ cmp: (a, b) => a.name.localeCompare(b.name) |
+ } |
+ ]; |
+ |
+ for (var displayLabel of this.displayLabels) |
+ this.columns_.push(this.buildColumn_(displayLabel)); |
} |
}); |
- tr.ui.registerValueSetView('tr-v-ui-value-set-table'); |
+ tr.v.ui.registerValueSetView('tr-v-ui-value-set-table'); |
return {}; |
}); |