Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(165)

Side by Side Diff: tracing/tracing/value/ui/histogram_set_table_row.html

Issue 2747453003: Refactor histogram-set-view to an MVC pattern. (Closed)
Patch Set: Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 <!DOCTYPE html> 1 <!DOCTYPE html>
2 <!-- 2 <!--
3 Copyright 2016 The Chromium Authors. All rights reserved. 3 Copyright 2016 The Chromium Authors. All rights reserved.
4 Use of this source code is governed by a BSD-style license that can be 4 Use of this source code is governed by a BSD-style license that can be
5 found in the LICENSE file. 5 found in the LICENSE file.
6 --> 6 -->
7 7
8 <link rel="import" href="/tracing/value/ui/histogram_set_table_cell.html"> 8 <link rel="import" href="/tracing/value/ui/histogram_set_table_cell.html">
9 <link rel="import" href="/tracing/value/ui/histogram_set_table_name_cell.html"> 9 <link rel="import" href="/tracing/value/ui/histogram_set_table_name_cell.html">
10 10
11 <script> 11 <script>
12 'use strict'; 12 'use strict';
13 tr.exportTo('tr.v.ui', function() { 13 tr.exportTo('tr.v.ui', function() {
14 class HistogramSetTableRow { 14 class HistogramSetTableRow {
15 constructor(name) { 15 /**
16 this.name = name; 16 * @param {!tr.v.HistogramSetHierarchy} hierarchy
17 this.description = ''; 17 * @param {!Element} baseTable tr-ui-b-table
18 this.depth = 0; 18 * @param {!tr.v.ui.HistogramSetViewState} rootViewState
19 this.subRows = []; 19 */
20 this.columns = new Map(); 20 constructor(hierarchy, baseTable, rootViewState) {
21 this.hierarchy_ = hierarchy;
22 this.baseTable_ = baseTable;
23 this.rootViewState_ = rootViewState;
24 this.viewState_ = new tr.v.ui.HistogramSetTableRowState();
25 this.viewState_.addUpdateListener(this.onViewStateUpdate_.bind(this));
26
21 this.nameCell_ = undefined; 27 this.nameCell_ = undefined;
22 this.cells = new Map(); 28 this.cells_ = new Map();
23 this.constrainNameColumnWidth_ = false; 29 this.subRows_ = [];
24 this.overviewDataRange_ = undefined; 30 for (const subHierarchy of hierarchy.subRows) {
25 this.displayStatistic_ = 'avg'; 31 const subRow = new HistogramSetTableRow(
26 this.doMergeRelationshipsForColumn_ = new Map(); 32 subHierarchy, baseTable, rootViewState);
33 this.subRows_.push(subRow);
34 this.viewState.subRows.set(subRow.name, subRow.viewState);
35 }
36 // Don't assign this.viewState.subRows. There can't be anything listening
37 // to it, so avoid the overhead of dispatching an event.
27 } 38 }
28 39
29 /** 40 /**
30 * Clones and filters |rows| to contain only |histograms|. 41 * @return {string}
31 *
32 * @param {!Array.<HistogramSetTableRow>} rows
33 * @param {!tr.v.HistogramSet} histograms
34 * @returns {!Array.<HistogramSetTableRow>}
35 */ 42 */
36 static filter(rows, histograms) { 43 get name() {
37 let results = []; 44 return this.hierarchy_.name;
38 for (let row of rows) {
39 let filteredSubRows = [];
40 if (row.subRows.length > 0) {
41 // This is a branch row. Drop it if all of its subrows were dropped.
42 filteredSubRows = HistogramSetTableRow.filter(
43 row.subRows, histograms);
44 if (filteredSubRows.length === 0) continue;
45 } else {
46 // This is a leaf row. Drop it if none of the Histograms in
47 // |row.columns| were merged from any in |histograms|.
48 let found = false;
49 for (let testHist of row.columns.values()) {
50 if (!(testHist instanceof tr.v.Histogram)) continue;
51 if (histograms.lookupHistogram(testHist.guid) !== undefined) {
52 found = true;
53 break;
54 }
55 let mergedFrom = testHist.diagnostics.get(
56 tr.v.d.MERGED_FROM_DIAGNOSTIC_KEY);
57 if (mergedFrom !== undefined) {
58 for (let origHist of mergedFrom) {
59 if (histograms.lookupHistogram(origHist.guid) !== undefined) {
60 found = true;
61 break;
62 }
63 }
64 }
65 if (found) break;
66 }
67 // If none of the Histograms in |row| were merged from any of
68 // |histograms|, then drop this row.
69 if (!found) continue;
70 }
71
72 let clone = new HistogramSetTableRow(row.name);
73 clone.description = row.description;
74 clone.depth = row.depth;
75 clone.subRows = filteredSubRows;
76 // Don't need to clone Histograms.
77 clone.columns = row.columns;
78 // Don't need to rebuild nameCell or cells.
79 clone.nameCell_ = row.nameCell_;
80 clone.cells = row.cells;
81 clone.constrainNameColumnWidth_ = row.constrainNameColumnWidth_;
82 clone.overviewDataRange_ = row.overviewDataRange_;
83 clone.displayStatistic_ = row.displayStatistic_;
84 results.push(clone);
85 }
86 return results;
87 } 45 }
88 46
89 /** 47 /**
90 * Build table rows recursively from grouped Histograms. 48 * @return {number}
91 *
92 * @param {!(HistogramArray|HistogramArrayMap)}
93 * @returns {!Array.<!HistogramSetTableRow>}
94 */ 49 */
95 static build(histogramArrayMap) { 50 get depth() {
96 const rootRows = []; 51 return this.hierarchy_.depth;
97 HistogramSetTableRow.buildInternal_(histogramArrayMap, [], rootRows); 52 }
98 53
99 const histograms = new tr.v.HistogramSet(); 54 /**
55 * @return {string}
56 */
57 get description() {
58 return this.hierarchy_.description;
59 }
100 60
101 for (const row of HistogramSetTableRow.walkAll(rootRows)) { 61 /**
102 for (const hist of row.columns.values()) { 62 * @return {!Map.<string, !(undefined|tr.v.Histogram|tr.v.HistogramSet)>}
103 if (!(hist instanceof tr.v.Histogram)) continue; 63 */
104 histograms.addHistogram(hist); 64 get columns() {
105 } 65 return this.hierarchy_.columns;
106 } 66 }
107 67
108 histograms.deduplicateDiagnostics(); 68 /**
69 * @return {!tr.b.Range}
70 */
71 get overviewDataRange() {
72 return this.hierarchy_.overviewDataRange;
73 }
109 74
110 for (const row of HistogramSetTableRow.walkAll(rootRows)) { 75 /**
111 for (const [name, hist] of row.columns) { 76 * @return {!tr.v.ui.HistogramSetViewState}
112 if (!(hist instanceof tr.v.Histogram)) continue; 77 */
113 if (!row.doMergeRelationshipsForColumn_.get(name)) continue; 78 get rootViewState() {
114 hist.diagnostics.mergeRelationships(hist); 79 return this.rootViewState_;
115 } 80 }
116 }
117 81
118 // Delete "merged to" diagnostics from the original Histograms, or else 82 /**
119 // they'll accumulate as the user re-groups them, and slow down future 83 * @return {!Map.<string, !Element>} tr-v-ui-histogram-set-table-cell
120 // mergeRelationships operations. 84 */
121 for (const row of HistogramSetTableRow.walkAll(rootRows)) { 85 get cells() {
122 // Walk directly down to the leaves in order to avoid touching 86 return this.cells_;
123 // original Histograms more than once. 87 }
124 if (row.subRows.length) continue;
125 88
126 for (const hist of row.columns.values()) { 89 /**
127 if (!(hist instanceof tr.v.Histogram)) continue; 90 * @return {!Array.<tr.v.ui.HistogramSetTableRow>}
91 */
92 get subRows() {
93 return this.subRows_;
94 }
128 95
129 const mergedFrom = hist.diagnostics.get( 96 /**
130 tr.v.MERGED_FROM_DIAGNOSTIC_KEY); 97 * @return {!Array.<tr.v.ui.HistogramSetTableRowState>}
131 if (mergedFrom !== undefined) { 98 */
132 for (const other of mergedFrom) { 99 get viewState() {
133 other.diagnostics.delete(tr.v.MERGED_TO_DIAGNOSTIC_KEY); 100 return this.viewState_;
134 }
135 }
136 }
137 }
138
139 for (const row of HistogramSetTableRow.walkAll(rootRows)) {
140 row.maybeRebin_();
141 }
142
143 return rootRows;
144 } 101 }
145 102
146 * walk() { 103 * walk() {
147 yield this; 104 yield this;
148 for (const row of this.subRows) yield* row.walk(); 105 for (const row of this.subRows) yield* row.walk();
149 } 106 }
150 107
151 static* walkAll(rootRows) { 108 static* walkAll(rootRows) {
152 for (const rootRow of rootRows) yield* rootRow.walk(); 109 for (const rootRow of rootRows) yield* rootRow.walk();
153 } 110 }
154 111
155 maybeRebin_() {
156 // if all of |this| row's columns are single-bin, then re-bin all of them.
157 const dataRange = new tr.b.math.Range();
158 for (const hist of this.columns.values()) {
159 if (!(hist instanceof tr.v.Histogram)) continue;
160 if (hist.allBins.length > 1) return; // don't re-bin
161 if (hist.numValues === 0) continue; // ignore hist
162 dataRange.addValue(hist.min);
163 dataRange.addValue(hist.max);
164 }
165
166 dataRange.addValue(tr.b.math.lesserWholeNumber(dataRange.min));
167 dataRange.addValue(tr.b.math.greaterWholeNumber(dataRange.max));
168
169 if (dataRange.min === dataRange.max) return; // don't rebin
170
171 const boundaries = tr.v.HistogramBinBoundaries.createLinear(
172 dataRange.min, dataRange.max, tr.v.DEFAULT_REBINNED_COUNT);
173
174 for (const [name, hist] of this.columns) {
175 if (!(hist instanceof tr.v.Histogram)) continue;
176 this.columns.set(name, hist.rebin(boundaries));
177 }
178 }
179
180 static mergeHistogramDownHierarchy_(histogram, hierarchy, columnName) {
181 // Track the path down the grouping tree to each Histogram,
182 // but only start tracking the path at the grouping level that
183 // corresponds to the Histogram NAME Grouping. This groupingPath will be
184 // attached to Histograms in order to help mergeRelationships() figure out
185 // which merged Histograms should be related to which other merged
186 // Histograms.
187 let groupingPath = undefined;
188
189 for (let row of hierarchy) {
190 if (groupingPath !== undefined) {
191 groupingPath.push(row.name);
192 } else if (row.name === histogram.name) {
193 // Start tracking the path, but don't add histogram.name to the path,
194 // since related histograms won't have the same name.
195 groupingPath = [];
196 }
197
198 if (!row.description) {
199 row.description = histogram.description;
200 }
201
202 if (row.columns.get(columnName) === undefined) {
203 let clone = histogram.clone();
204 if (groupingPath !== undefined) {
205 new tr.v.d.GroupingPath(groupingPath).addToHistogram(clone);
206 }
207 row.columns.set(columnName, clone);
208 row.doMergeRelationshipsForColumn_.set(columnName, true);
209 continue;
210 }
211
212 if (!(row.columns.get(columnName) instanceof tr.v.Histogram)) continue;
213
214 if (!row.columns.get(columnName).canAddHistogram(histogram)) {
215 row.columns.set(columnName, tr.v.ui.UNMERGEABLE);
216 continue;
217 }
218
219 let merged = row.columns.get(columnName);
220
221 // If row.columns.get(columnName).name != histogram.name, then it won't
222 // make sense to merge relationships for this histogram.
223 if (merged.name !== histogram.name) {
224 row.doMergeRelationshipsForColumn_.set(name, false);
225 }
226
227 merged.addHistogram(histogram);
228 }
229 }
230
231 static buildInternal_(histogramArrayMap, hierarchy, rootRows) {
232 for (let [name, histograms] of histogramArrayMap) {
233 if (histograms instanceof Array) {
234 // This recursion base case corresponds to the recursion base case of
235 // groupHistogramsRecursively(). The last groupingCallback is always
236 // getDisplayLabel, which defines the columns of the table and is
237 // unskippable.
238 for (let histogram of histograms) {
239 HistogramSetTableRow.mergeHistogramDownHierarchy_(
240 histogram, hierarchy, name);
241 }
242 } else if (histograms instanceof Map) {
243 // |histograms| is actually a nested histogramArrayMap.
244 let row = new HistogramSetTableRow(name);
245 row.depth = hierarchy.length;
246 hierarchy.push(row);
247 HistogramSetTableRow.buildInternal_(histograms, hierarchy, rootRows);
248 hierarchy.pop();
249
250 if (hierarchy.length === 0) {
251 rootRows.push(row);
252 } else {
253 hierarchy[hierarchy.length - 1].subRows.push(row);
254 }
255 }
256 }
257 }
258
259 get nameCell() { 112 get nameCell() {
260 if (this.nameCell_ === undefined) { 113 if (this.nameCell_ === undefined) {
261 this.nameCell_ = document.createElement( 114 this.nameCell_ = document.createElement(
262 'tr-v-ui-histogram-set-table-name-cell'); 115 'tr-v-ui-histogram-set-table-name-cell');
263 this.nameCell_.row = this; 116 this.nameCell_.build(this);
264 this.nameCell_.constrainWidth = this.constrainNameColumnWidth_;
265 } 117 }
266 return this.nameCell_; 118 return this.nameCell_;
267 } 119 }
268 120
269 set constrainNameColumnWidth(constrain) { 121 getCell(displayLabel) {
270 for (const row of this.walk()) { 122 // Build all of the cells at once so that we only need to set
271 row.constrainNameColumnWidth_ = constrain; 123 // this.viewState.cells once, which dispatches an event to the nameCell.
272 if (row.nameCell_ !== undefined) { 124 if (!this.cells.has(displayLabel)) this.buildCells();
273 row.nameCell_.constrainWidth = constrain; 125 return this.cells.get(displayLabel);
274 }
275 }
276 } 126 }
277 127
278 get isNameCellOverflowing() { 128 buildCells() {
279 for (const row of this.walk()) { 129 const cellStates = new Map();
280 if (row.nameCell.isOverflowing) return true; 130 for (const displayLabel of this.columns.keys()) {
131 const cell = document.createElement('tr-v-ui-histogram-set-table-cell');
132 cell.build(this, displayLabel);
133 this.cells.set(displayLabel, cell);
134
135 const previousState = this.viewState.cells.get(displayLabel);
136 if (previousState !== undefined) {
137 cell.viewState.updateFromViewState(previousState);
138 }
139 cellStates.set(displayLabel, cell.viewState);
281 } 140 }
282 return false; 141 this.viewState.cells = cellStates;
283 }
284
285 get displayStatistic() {
286 return this.displayStatistic_;
287 }
288
289 set displayStatistic(statName) {
290 for (const row of this.walk()) {
291 row.displayStatistic_ = statName;
292 for (let [displayLabel, cell] of row.cells) {
293 cell.displayStatistic = statName;
294 }
295 }
296 }
297
298 buildCell(displayLabel, referenceDisplayLabel) {
299 let cell = document.createElement('tr-v-ui-histogram-set-table-cell');
300 cell.row = this;
301 cell.histogram = this.columns.get(displayLabel);
302 cell.displayStatistic = this.displayStatistic;
303 if (referenceDisplayLabel &&
304 referenceDisplayLabel !== displayLabel) {
305 cell.referenceHistogram = this.columns.get(
306 referenceDisplayLabel);
307 }
308 this.cells.set(displayLabel, cell);
309 return cell;
310 }
311
312 get overviewDataRange() {
313 if (this.overviewDataRange_ === undefined) {
314 this.overviewDataRange_ = new tr.b.math.Range();
315 for (let [displayLabel, hist] of this.columns) {
316 if (hist.average !== undefined) {
317 this.overviewDataRange_.addValue(hist.average);
318 }
319
320 for (let subRow of this.subRows) {
321 let subHist = subRow.columns.get(displayLabel);
322 if (!(subHist instanceof tr.v.Histogram)) continue;
323 if (subHist.average === undefined) continue;
324 this.overviewDataRange_.addValue(subHist.average);
325 }
326 }
327 }
328 return this.overviewDataRange_;
329 }
330
331 getLeafHistograms(histograms) {
332 for (const row of this.walk()) {
333 if (row.subRows.length) return;
334
335 for (const hist of this.columns.values()) {
336 histograms.addHistogram(hist);
337 }
338 }
339 } 142 }
340 143
341 compareNames(other) { 144 compareNames(other) {
342 return this.name.localeCompare(other.name); 145 return this.name.localeCompare(other.name);
343 } 146 }
344 147
345 compareCells(other, displayLabel, referenceDisplayLabel) { 148 compareCells(other, displayLabel, referenceDisplayLabel) {
346 let cellA = this.columns.get(displayLabel); 149 const cellA = this.columns.get(displayLabel);
347 let cellB = other.columns.get(displayLabel); 150 const cellB = other.columns.get(displayLabel);
348 if (!(cellA instanceof tr.v.Histogram) || 151 if (!(cellA instanceof tr.v.Histogram) ||
349 !(cellB instanceof tr.v.Histogram)) { 152 !(cellB instanceof tr.v.Histogram)) {
350 return undefined; 153 return undefined;
351 } 154 }
352 155
353 let referenceCellA; 156 let referenceCellA;
354 let referenceCellB; 157 let referenceCellB;
355 158
356 // If a reference column is selected, compare the absolute deltas 159 // If a reference column is selected, compare the absolute deltas
357 // between the two cells and their references. 160 // between the two cells and their references.
358 if (referenceDisplayLabel && 161 if (referenceDisplayLabel &&
359 referenceDisplayLabel !== displayLabel) { 162 referenceDisplayLabel !== displayLabel) {
360 referenceCellA = this.columns.get(referenceDisplayLabel); 163 referenceCellA = this.columns.get(referenceDisplayLabel);
361 referenceCellB = other.columns.get(referenceDisplayLabel); 164 referenceCellB = other.columns.get(referenceDisplayLabel);
362 } 165 }
363 166
364 const statisticA = cellA.getAvailableStatisticName( 167 const statisticA = cellA.getAvailableStatisticName(
365 this.displayStatistic, referenceCellA); 168 this.rootViewState.displayStatisticName, referenceCellA);
366 const statisticB = cellB.getAvailableStatisticName( 169 const statisticB = cellB.getAvailableStatisticName(
367 this.displayStatistic, referenceCellB); 170 this.rootViewState.displayStatisticName, referenceCellB);
368 const valueA = cellA.getStatisticScalar(statisticA, referenceCellA).value; 171 const valueA = cellA.getStatisticScalar(statisticA, referenceCellA).value;
369 const valueB = cellB.getStatisticScalar(statisticB, referenceCellB).value; 172 const valueB = cellB.getStatisticScalar(statisticB, referenceCellB).value;
370 173
371 return valueA - valueB; 174 return valueA - valueB;
372 } 175 }
373 176
374 getExpansionStates(table) { 177 onViewStateUpdate_(event) {
375 let states = { 178 if (event.delta.isExpanded) {
376 expanded: table.getExpandedForTableRow(this), 179 this.baseTable_.setExpandedForTableRow(this, this.viewState.isExpanded);
377 cells: new Map(),
378 subRows: new Map(),
379 };
380
381 for (let [displayLabel, cell] of this.cells) {
382 if (cell.isHistogramOpen) {
383 states.cells.set(displayLabel, true);
384 }
385 } 180 }
386 181
387 if (states.expanded) { 182 if (event.delta.subRows) {
388 for (let i = 0; i < this.subRows.length; ++i) { 183 throw new Error('HistogramSetTableRow.subRows must not be reassigned.');
389 states.subRows.set(i, this.subRows[i].getExpansionStates(table));
390 }
391 } 184 }
392 return states;
393 }
394 185
395 setExpansionStates(states, table) { 186 if (event.delta.cells) {
396 if (states.expanded) { 187 // Only validate the cells that have already been built.
397 if (this.subRows.length) { 188 // Cells may not have been built yet, so only validate the cells that
398 table.setExpandedForTableRow(this, true); 189 // have been built.
399 for (let [index, subStates] of states.subRows) { 190 for (const [displayLabel, cell] of this.cells) {
400 this.subRows[index].setExpansionStates(subStates, table); 191 if (cell.viewState !== this.viewState.cells.get(displayLabel)) {
192 throw new Error('Only HistogramSetTableRow may update cells');
401 } 193 }
402 } 194 }
403 } 195 }
196 }
404 197
405 for (let [displayLabel, isHistogramOpen] of states.cells) { 198 async restoreState(vs) {
406 let cell = this.cells.get(displayLabel); 199 // Don't use updateFromViewState() because it would overwrite cells and
407 if (cell) { 200 // subRows, but we just want to restore them.
408 cell.isHistogramOpen = isHistogramOpen; 201 await this.viewState.update({
409 } 202 isExpanded: vs.isExpanded,
203 isOverviewed: vs.isOverviewed,
204 });
205
206 // If cells haven't been built yet, then their state will be restored when
207 // they are built.
208 for (const [displayLabel, cell] of this.cells) {
209 const previousState = vs.cells.get(displayLabel);
210 if (!previousState) continue;
211 await cell.viewState.updateFromViewState(previousState);
212 }
213 for (const row of this.subRows) {
214 const previousState = vs.subRows.get(row.name);
215 if (!previousState) continue;
216 await row.restoreState(previousState);
410 } 217 }
411 } 218 }
412 } 219 }
413 220
414 return { 221 return {
415 HistogramSetTableRow, 222 HistogramSetTableRow,
416 }; 223 };
417 }); 224 });
418 </script> 225 </script>
OLDNEW
« no previous file with comments | « tracing/tracing/value/ui/histogram_set_table_name_cell.html ('k') | tracing/tracing/value/ui/histogram_set_table_test.html » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698