| <!DOCTYPE html> |
| <!-- |
| Copyright 2016 The Chromium Authors. All rights reserved. |
| Use of this source code is governed by a BSD-style license that can be |
| found in the LICENSE file. |
| --> |
| |
| <link rel="import" href="/tracing/value/ui/histogram_set_table_cell.html"> |
| <link rel="import" href="/tracing/value/ui/histogram_set_table_name_cell.html"> |
| |
| <script> |
| 'use strict'; |
| tr.exportTo('tr.v.ui', function() { |
| class HistogramSetTableRow { |
| constructor(name) { |
| this.name = name; |
| this.description = ''; |
| this.depth = 0; |
| this.subRows = []; |
| this.columns = new Map(); |
| this.nameCell_ = undefined; |
| this.cells = new Map(); |
| this.constrainNameColumnWidth_ = false; |
| this.overviewDataRange_ = undefined; |
| this.displayStatistic_ = 'avg'; |
| this.doMergeRelationshipsForColumn_ = new Map(); |
| } |
| |
| /** |
| * Clones and filters |rows| to contain only |histograms|. |
| * |
| * @param {!Array.<HistogramSetTableRow>} rows |
| * @param {!tr.v.HistogramSet} histograms |
| * @returns {!Array.<HistogramSetTableRow>} |
| */ |
| static filter(rows, histograms) { |
| let results = []; |
| for (let row of rows) { |
| let filteredSubRows = []; |
| if (row.subRows.length > 0) { |
| // This is a branch row. Drop it if all of its subrows were dropped. |
| filteredSubRows = HistogramSetTableRow.filter( |
| row.subRows, histograms); |
| if (filteredSubRows.length === 0) continue; |
| } else { |
| // This is a leaf row. Drop it if none of the Histograms in |
| // |row.columns| were merged from any in |histograms|. |
| let found = false; |
| for (let testHist of row.columns.values()) { |
| if (!(testHist instanceof tr.v.Histogram)) continue; |
| if (histograms.lookupHistogram(testHist.guid) !== undefined) { |
| found = true; |
| break; |
| } |
| let mergedFrom = testHist.diagnostics.get( |
| tr.v.d.MERGED_FROM_DIAGNOSTIC_KEY); |
| if (mergedFrom !== undefined) { |
| for (let origHist of mergedFrom) { |
| if (histograms.lookupHistogram(origHist.guid) !== undefined) { |
| found = true; |
| break; |
| } |
| } |
| } |
| if (found) break; |
| } |
| // If none of the Histograms in |row| were merged from any of |
| // |histograms|, then drop this row. |
| if (!found) continue; |
| } |
| |
| let clone = new HistogramSetTableRow(row.name); |
| clone.description = row.description; |
| clone.depth = row.depth; |
| clone.subRows = filteredSubRows; |
| // Don't need to clone Histograms. |
| clone.columns = row.columns; |
| // Don't need to rebuild nameCell or cells. |
| clone.nameCell_ = row.nameCell_; |
| clone.cells = row.cells; |
| clone.constrainNameColumnWidth_ = row.constrainNameColumnWidth_; |
| clone.overviewDataRange_ = row.overviewDataRange_; |
| clone.displayStatistic_ = row.displayStatistic_; |
| results.push(clone); |
| } |
| return results; |
| } |
| |
| /** |
| * Build table rows recursively from grouped Histograms. |
| * |
| * @param {!(HistogramArray|HistogramArrayMap)} |
| * @returns {!Array.<!HistogramSetTableRow>} |
| */ |
| static build(histogramArrayMap) { |
| const rootRows = []; |
| HistogramSetTableRow.buildInternal_(histogramArrayMap, [], rootRows); |
| |
| const histograms = new tr.v.HistogramSet(); |
| |
| for (const row of HistogramSetTableRow.walkAll(rootRows)) { |
| for (const hist of row.columns.values()) { |
| if (!(hist instanceof tr.v.Histogram)) continue; |
| histograms.addHistogram(hist); |
| } |
| } |
| |
| histograms.deduplicateDiagnostics(); |
| |
| for (const row of HistogramSetTableRow.walkAll(rootRows)) { |
| for (const [name, hist] of row.columns) { |
| if (!(hist instanceof tr.v.Histogram)) continue; |
| if (!row.doMergeRelationshipsForColumn_.get(name)) continue; |
| hist.diagnostics.mergeRelationships(hist); |
| } |
| } |
| |
| // Delete "merged to" diagnostics from the original Histograms, or else |
| // they'll accumulate as the user re-groups them, and slow down future |
| // mergeRelationships operations. |
| for (const row of HistogramSetTableRow.walkAll(rootRows)) { |
| // Walk directly down to the leaves in order to avoid touching |
| // original Histograms more than once. |
| if (row.subRows.length) continue; |
| |
| for (const hist of row.columns.values()) { |
| if (!(hist instanceof tr.v.Histogram)) continue; |
| |
| const mergedFrom = hist.diagnostics.get( |
| tr.v.MERGED_FROM_DIAGNOSTIC_KEY); |
| if (mergedFrom !== undefined) { |
| for (const other of mergedFrom) { |
| other.diagnostics.delete(tr.v.MERGED_TO_DIAGNOSTIC_KEY); |
| } |
| } |
| } |
| } |
| |
| for (const row of HistogramSetTableRow.walkAll(rootRows)) { |
| row.maybeRebin_(); |
| } |
| |
| return rootRows; |
| } |
| |
| * walk() { |
| yield this; |
| for (const row of this.subRows) yield* row.walk(); |
| } |
| |
| static* walkAll(rootRows) { |
| for (const rootRow of rootRows) yield* rootRow.walk(); |
| } |
| |
| maybeRebin_() { |
| // if all of |this| row's columns are single-bin, then re-bin all of them. |
| const dataRange = new tr.b.math.Range(); |
| for (const hist of this.columns.values()) { |
| if (!(hist instanceof tr.v.Histogram)) continue; |
| if (hist.allBins.length > 1) return; // don't re-bin |
| if (hist.numValues === 0) continue; // ignore hist |
| dataRange.addValue(hist.min); |
| dataRange.addValue(hist.max); |
| } |
| |
| dataRange.addValue(tr.b.math.lesserWholeNumber(dataRange.min)); |
| dataRange.addValue(tr.b.math.greaterWholeNumber(dataRange.max)); |
| |
| if (dataRange.min === dataRange.max) return; // don't rebin |
| |
| const boundaries = tr.v.HistogramBinBoundaries.createLinear( |
| dataRange.min, dataRange.max, tr.v.DEFAULT_REBINNED_COUNT); |
| |
| for (const [name, hist] of this.columns) { |
| if (!(hist instanceof tr.v.Histogram)) continue; |
| this.columns.set(name, hist.rebin(boundaries)); |
| } |
| } |
| |
| static mergeHistogramDownHierarchy_(histogram, hierarchy, columnName) { |
| // Track the path down the grouping tree to each Histogram, |
| // but only start tracking the path at the grouping level that |
| // corresponds to the Histogram NAME Grouping. This groupingPath will be |
| // attached to Histograms in order to help mergeRelationships() figure out |
| // which merged Histograms should be related to which other merged |
| // Histograms. |
| let groupingPath = undefined; |
| |
| for (let row of hierarchy) { |
| if (groupingPath !== undefined) { |
| groupingPath.push(row.name); |
| } else if (row.name === histogram.name) { |
| // Start tracking the path, but don't add histogram.name to the path, |
| // since related histograms won't have the same name. |
| groupingPath = []; |
| } |
| |
| if (!row.description) { |
| row.description = histogram.description; |
| } |
| |
| if (row.columns.get(columnName) === undefined) { |
| let clone = histogram.clone(); |
| if (groupingPath !== undefined) { |
| new tr.v.d.GroupingPath(groupingPath).addToHistogram(clone); |
| } |
| row.columns.set(columnName, clone); |
| row.doMergeRelationshipsForColumn_.set(columnName, true); |
| continue; |
| } |
| |
| if (!(row.columns.get(columnName) instanceof tr.v.Histogram)) continue; |
| |
| if (!row.columns.get(columnName).canAddHistogram(histogram)) { |
| row.columns.set(columnName, tr.v.ui.UNMERGEABLE); |
| continue; |
| } |
| |
| let merged = row.columns.get(columnName); |
| |
| // If row.columns.get(columnName).name != histogram.name, then it won't |
| // make sense to merge relationships for this histogram. |
| if (merged.name !== histogram.name) { |
| row.doMergeRelationshipsForColumn_.set(name, false); |
| } |
| |
| merged.addHistogram(histogram); |
| } |
| } |
| |
| static buildInternal_(histogramArrayMap, hierarchy, rootRows) { |
| for (let [name, histograms] of histogramArrayMap) { |
| if (histograms instanceof Array) { |
| // This recursion base case corresponds to the recursion base case of |
| // groupHistogramsRecursively(). The last groupingCallback is always |
| // getDisplayLabel, which defines the columns of the table and is |
| // unskippable. |
| for (let histogram of histograms) { |
| HistogramSetTableRow.mergeHistogramDownHierarchy_( |
| histogram, hierarchy, name); |
| } |
| } else if (histograms instanceof Map) { |
| // |histograms| is actually a nested histogramArrayMap. |
| let row = new HistogramSetTableRow(name); |
| row.depth = hierarchy.length; |
| hierarchy.push(row); |
| HistogramSetTableRow.buildInternal_(histograms, hierarchy, rootRows); |
| hierarchy.pop(); |
| |
| if (hierarchy.length === 0) { |
| rootRows.push(row); |
| } else { |
| hierarchy[hierarchy.length - 1].subRows.push(row); |
| } |
| } |
| } |
| } |
| |
| get nameCell() { |
| if (this.nameCell_ === undefined) { |
| this.nameCell_ = document.createElement( |
| 'tr-v-ui-histogram-set-table-name-cell'); |
| this.nameCell_.row = this; |
| this.nameCell_.constrainWidth = this.constrainNameColumnWidth_; |
| } |
| return this.nameCell_; |
| } |
| |
| set constrainNameColumnWidth(constrain) { |
| for (const row of this.walk()) { |
| row.constrainNameColumnWidth_ = constrain; |
| if (row.nameCell_ !== undefined) { |
| row.nameCell_.constrainWidth = constrain; |
| } |
| } |
| } |
| |
| get isNameCellOverflowing() { |
| for (const row of this.walk()) { |
| if (row.nameCell.isOverflowing) return true; |
| } |
| return false; |
| } |
| |
| get displayStatistic() { |
| return this.displayStatistic_; |
| } |
| |
| set displayStatistic(statName) { |
| for (const row of this.walk()) { |
| row.displayStatistic_ = statName; |
| for (let [displayLabel, cell] of row.cells) { |
| cell.displayStatistic = statName; |
| } |
| } |
| } |
| |
| buildCell(displayLabel, referenceDisplayLabel) { |
| let cell = document.createElement('tr-v-ui-histogram-set-table-cell'); |
| cell.row = this; |
| cell.histogram = this.columns.get(displayLabel); |
| cell.displayStatistic = this.displayStatistic; |
| if (referenceDisplayLabel && |
| referenceDisplayLabel !== displayLabel) { |
| cell.referenceHistogram = this.columns.get( |
| referenceDisplayLabel); |
| } |
| this.cells.set(displayLabel, cell); |
| return cell; |
| } |
| |
| get overviewDataRange() { |
| if (this.overviewDataRange_ === undefined) { |
| this.overviewDataRange_ = new tr.b.math.Range(); |
| for (let [displayLabel, hist] of this.columns) { |
| if (hist.average !== undefined) { |
| this.overviewDataRange_.addValue(hist.average); |
| } |
| |
| for (let subRow of this.subRows) { |
| let subHist = subRow.columns.get(displayLabel); |
| if (!(subHist instanceof tr.v.Histogram)) continue; |
| if (subHist.average === undefined) continue; |
| this.overviewDataRange_.addValue(subHist.average); |
| } |
| } |
| } |
| return this.overviewDataRange_; |
| } |
| |
| getLeafHistograms(histograms) { |
| for (const row of this.walk()) { |
| if (row.subRows.length) return; |
| |
| for (const hist of this.columns.values()) { |
| histograms.addHistogram(hist); |
| } |
| } |
| } |
| |
| compareNames(other) { |
| return this.name.localeCompare(other.name); |
| } |
| |
| compareCells(other, displayLabel, referenceDisplayLabel) { |
| let cellA = this.columns.get(displayLabel); |
| let cellB = other.columns.get(displayLabel); |
| if (!(cellA instanceof tr.v.Histogram) || |
| !(cellB instanceof tr.v.Histogram)) { |
| return undefined; |
| } |
| |
| let referenceCellA; |
| let referenceCellB; |
| |
| // If a reference column is selected, compare the absolute deltas |
| // between the two cells and their references. |
| if (referenceDisplayLabel && |
| referenceDisplayLabel !== displayLabel) { |
| referenceCellA = this.columns.get(referenceDisplayLabel); |
| referenceCellB = other.columns.get(referenceDisplayLabel); |
| } |
| |
| const statisticA = cellA.getAvailableStatisticName( |
| this.displayStatistic, referenceCellA); |
| const statisticB = cellB.getAvailableStatisticName( |
| this.displayStatistic, referenceCellB); |
| const valueA = cellA.getStatisticScalar(statisticA, referenceCellA).value; |
| const valueB = cellB.getStatisticScalar(statisticB, referenceCellB).value; |
| |
| return valueA - valueB; |
| } |
| |
| getExpansionStates(table) { |
| let states = { |
| expanded: table.getExpandedForTableRow(this), |
| cells: new Map(), |
| subRows: new Map(), |
| }; |
| |
| for (let [displayLabel, cell] of this.cells) { |
| if (cell.isHistogramOpen) { |
| states.cells.set(displayLabel, true); |
| } |
| } |
| |
| if (states.expanded) { |
| for (let i = 0; i < this.subRows.length; ++i) { |
| states.subRows.set(i, this.subRows[i].getExpansionStates(table)); |
| } |
| } |
| return states; |
| } |
| |
| setExpansionStates(states, table) { |
| if (states.expanded) { |
| if (this.subRows.length) { |
| table.setExpandedForTableRow(this, true); |
| for (let [index, subStates] of states.subRows) { |
| this.subRows[index].setExpansionStates(subStates, table); |
| } |
| } |
| } |
| |
| for (let [displayLabel, isHistogramOpen] of states.cells) { |
| let cell = this.cells.get(displayLabel); |
| if (cell) { |
| cell.isHistogramOpen = isHistogramOpen; |
| } |
| } |
| } |
| } |
| |
| return { |
| HistogramSetTableRow, |
| }; |
| }); |
| </script> |