From 76f4942943576fd67ea8d4409618ebe54aeccc21 Mon Sep 17 00:00:00 2001 From: Jeff Fisher Date: Tue, 21 May 2019 22:42:01 +0000 Subject: [PATCH] DevTools: Move legend into the PieChart control. Today, each tool that uses the PieChart control has to implement its own legend control. This change moves the legend into PieChart and standardizes the look and feel. Screenshots: Application Storage tab: https://imgur.com/laaPFRO Heap statistics: https://imgur.com/SNwpGXt Profiler: https://imgur.com/FiKZtTx Change-Id: I61a8adab0b58fbe49298804f97208da6a6bbbce6 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1607140 Commit-Queue: Jeff Fisher Reviewed-by: Joel Einbinder Reviewed-by: Alexei Filippov Cr-Original-Commit-Position: refs/heads/master@{#661950} Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src Cr-Mirrored-Commit: 9ef252cf698f59acd24f683cd5dcad9d34d33e88 --- front_end/layer_viewer/PaintProfilerView.js | 3 +- front_end/perf_ui/PieChart.js | 67 +++++++++++++++++---- front_end/perf_ui/pieChart.css | 45 +++++++++++++- front_end/profiler/HeapSnapshotView.js | 25 +++----- front_end/profiler/heapProfiler.css | 32 ---------- front_end/resources/ClearStorageView.js | 12 +--- front_end/resources/clearStorageView.css | 24 -------- front_end/timeline/TimelineUIUtils.js | 15 +++-- front_end/timeline/timelinePanel.css | 26 -------- front_end/ui/ARIAUtils.js | 17 ++++++ 10 files changed, 134 insertions(+), 132 deletions(-) diff --git a/front_end/layer_viewer/PaintProfilerView.js b/front_end/layer_viewer/PaintProfilerView.js index c5d88b9fb0..011753a029 100644 --- a/front_end/layer_viewer/PaintProfilerView.js +++ b/front_end/layer_viewer/PaintProfilerView.js @@ -42,7 +42,8 @@ LayerViewer.PaintProfilerView = class extends UI.HBox { this._canvasContainer = this.contentElement.createChild('div', 'paint-profiler-canvas-container'); this._progressBanner = this.contentElement.createChild('div', 'full-widget-dimmed-banner hidden'); this._progressBanner.textContent = Common.UIString('Profiling\u2026'); - this._pieChart = new PerfUI.PieChart(55, this._formatPieChartTime.bind(this), true); + this._pieChart = new PerfUI.PieChart( + {chartName: ls`Profiling Results`, size: 55, formatter: this._formatPieChartTime.bind(this)}); this._pieChart.element.classList.add('paint-profiler-pie-chart'); this.contentElement.appendChild(this._pieChart.element); diff --git a/front_end/perf_ui/PieChart.js b/front_end/perf_ui/PieChart.js index 773c9ed0ee..4ce73b2a40 100644 --- a/front_end/perf_ui/PieChart.js +++ b/front_end/perf_ui/PieChart.js @@ -28,20 +28,25 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +/** @typedef {{size: number, formatter: function(number):string, showLegend: (boolean|undefined), chartName: string}} */ +PerfUI.PieChartOptions; + /** * @unrestricted */ PerfUI.PieChart = class { /** - * @param {number} size - * @param {function(number):string=} formatter - * @param {boolean=} showTotal + * @param {!PerfUI.PieChartOptions} options */ - constructor(size, formatter, showTotal) { + constructor(options) { + const {size, formatter, showLegend, chartName} = options; this.element = createElement('div'); this._shadowRoot = UI.createShadowRootWithCoreStyles(this.element, 'perf_ui/pieChart.css'); const root = this._shadowRoot.createChild('div', 'root'); - const svg = this._createSVGChild(root, 'svg'); + UI.ARIAUtils.markAsGroup(root); + UI.ARIAUtils.setAccessibleName(root, chartName); + this._chartRoot = root.createChild('div', 'chart-root'); + const svg = this._createSVGChild(this._chartRoot, 'svg'); this._group = this._createSVGChild(svg, 'g'); this._innerR = 0.618; const strokeWidth = 1 / size; @@ -55,12 +60,13 @@ PerfUI.PieChart = class { circle.setAttribute('stroke', 'hsl(0, 0%, 80%)'); circle.setAttribute('fill', 'transparent'); circle.setAttribute('stroke-width', strokeWidth); - this._foregroundElement = root.createChild('div', 'pie-chart-foreground'); - if (showTotal) - this._totalElement = this._foregroundElement.createChild('div', 'pie-chart-total'); + this._foregroundElement = this._chartRoot.createChild('div', 'pie-chart-foreground'); + this._totalElement = this._foregroundElement.createChild('div', 'pie-chart-total'); this._formatter = formatter; this._slices = []; this._lastAngle = -Math.PI / 2; + if (showLegend) + this._legend = root.createChild('div', 'pie-chart-legend'); this._setSize(size); } @@ -78,8 +84,12 @@ PerfUI.PieChart = class { totalString = this._formatter ? this._formatter(totalValue) : totalValue; else totalString = ''; - if (this._totalElement) - this._totalElement.textContent = totalString; + this._totalElement.textContent = totalString; + if (this._legend) { + this._legend.removeChildren(); + const legendItem = this._addLegendItem(totalValue, ls`Total`); + UI.ARIAUtils.setLabelledBy(this._totalElement, legendItem); + } } /** @@ -88,15 +98,16 @@ PerfUI.PieChart = class { _setSize(value) { this._group.setAttribute('transform', 'scale(' + (value / 2) + ') translate(1, 1) scale(0.99, 0.99)'); const size = value + 'px'; - this.element.style.width = size; - this.element.style.height = size; + this._chartRoot.style.width = size; + this._chartRoot.style.height = size; } /** * @param {number} value * @param {string} color + * @param {string=} name */ - addSlice(value, color) { + addSlice(value, color, name) { let sliceAngle = value / this._totalValue * 2 * Math.PI; if (!isFinite(sliceAngle)) return; @@ -117,6 +128,10 @@ PerfUI.PieChart = class { `M${x1},${y1} A1,1,0,${largeArc},1,${x2},${y2} L${x3},${y3} A${r2},${r2},0,${largeArc},0,${x4},${y4} Z`); path.setAttribute('fill', color); this._slices.push(path); + if (this._legend) { + const legendItem = this._addLegendItem(value, name, color); + UI.ARIAUtils.setLabelledBy(path, legendItem); + } } /** @@ -129,4 +144,30 @@ PerfUI.PieChart = class { parent.appendChild(child); return child; } + + /** + * @param {number} value + * @param {string=} name + * @param {string=} color + * @returns {!Element} + */ + _addLegendItem(value, name, color) { + const node = this._legend.ownerDocument.createElement('div'); + node.className = 'pie-chart-legend-row'; + // make sure total always appears at the bottom + if (this._legend.childElementCount) + this._legend.insertBefore(node, this._legend.lastElementChild); + else + this._legend.appendChild(node); + const sizeDiv = node.createChild('div', 'pie-chart-size'); + const swatchDiv = node.createChild('div', 'pie-chart-swatch'); + const nameDiv = node.createChild('div', 'pie-chart-name'); + if (color) + swatchDiv.style.backgroundColor = color; + else + swatchDiv.classList.add('pie-chart-empty-swatch'); + nameDiv.textContent = name; + sizeDiv.textContent = this._formatter ? this._formatter(value) : value; + return node; + } }; diff --git a/front_end/perf_ui/pieChart.css b/front_end/perf_ui/pieChart.css index bef554fa1e..ee46974086 100644 --- a/front_end/perf_ui/pieChart.css +++ b/front_end/perf_ui/pieChart.css @@ -5,9 +5,15 @@ */ .root { + align-items: center; + display: flex; + min-width: fit-content; + white-space: nowrap; +} + +.chart-root { position: relative; - width: 100%; - height: 100%; + overflow: hidden; } .pie-chart-foreground { @@ -17,10 +23,45 @@ z-index: 10; top: 0; display: flex; + pointer-events: none; } .pie-chart-total { margin: auto; padding: 2px 5px; background-color: rgba(255, 255, 255, 0.6); + pointer-events: auto; +} + +.pie-chart-legend { + margin-left: 30px; +} + +.pie-chart-legend-row { + margin: 5px auto; + padding-right: 25px; +} + +.pie-chart-swatch { + display: inline-block; + width: 11px; + height: 11px; + margin: 0 6px; + top: 1px; + position: relative; + border: 1px solid rgba(100, 100, 100, 0.2); +} + +.pie-chart-swatch.pie-chart-empty-swatch { + border: none; +} + +.pie-chart-name { + display: inline-block; +} + +.pie-chart-size { + display: inline-block; + text-align: right; + width: 70px; } diff --git a/front_end/profiler/HeapSnapshotView.js b/front_end/profiler/HeapSnapshotView.js index c33e950627..6bedd8e7ab 100644 --- a/front_end/profiler/HeapSnapshotView.js +++ b/front_end/profiler/HeapSnapshotView.js @@ -292,7 +292,6 @@ Profiler.HeapSnapshotView = class extends UI.SimpleView { this._statisticsView.addRecord(statistics.jsArrays, Common.UIString('JS Arrays'), '#7af'); this._statisticsView.addRecord(statistics.native, Common.UIString('Typed Arrays'), '#fc5'); this._statisticsView.addRecord(statistics.system, Common.UIString('System Objects'), '#98f'); - this._statisticsView.addRecord(statistics.total, Common.UIString('Total')); return statistics; } @@ -1611,10 +1610,14 @@ Profiler.HeapSnapshotStatisticsView = class extends UI.VBox { constructor() { super(); this.element.classList.add('heap-snapshot-statistics-view'); - this._pieChart = new PerfUI.PieChart(150, Profiler.HeapSnapshotStatisticsView._valueFormatter, true); + this._pieChart = new PerfUI.PieChart({ + chartName: ls`Heap memory usage`, + size: 150, + formatter: Profiler.HeapSnapshotStatisticsView._valueFormatter, + showLegend: true + }); this._pieChart.element.classList.add('heap-snapshot-stats-pie-chart'); this.element.appendChild(this._pieChart.element); - this._labels = this.element.createChild('div', 'heap-snapshot-stats-legend'); } /** @@ -1635,22 +1638,10 @@ Profiler.HeapSnapshotStatisticsView = class extends UI.VBox { /** * @param {number} value * @param {string} name - * @param {string=} color + * @param {string} color */ addRecord(value, name, color) { - if (color) - this._pieChart.addSlice(value, color); - - const node = this._labels.createChild('div'); - const swatchDiv = node.createChild('div', 'heap-snapshot-stats-swatch'); - const nameDiv = node.createChild('div', 'heap-snapshot-stats-name'); - const sizeDiv = node.createChild('div', 'heap-snapshot-stats-size'); - if (color) - swatchDiv.style.backgroundColor = color; - else - swatchDiv.classList.add('heap-snapshot-stats-empty-swatch'); - nameDiv.textContent = name; - sizeDiv.textContent = Profiler.HeapSnapshotStatisticsView._valueFormatter(value); + this._pieChart.addSlice(value, color, name); } }; diff --git a/front_end/profiler/heapProfiler.css b/front_end/profiler/heapProfiler.css index 7e5ce499e7..45027e45a0 100644 --- a/front_end/profiler/heapProfiler.css +++ b/front_end/profiler/heapProfiler.css @@ -182,38 +182,6 @@ flex-shrink: 0; } -.heap-snapshot-stats-legend { - margin-left: 24px; - flex-shrink: 0; -} - -.heap-snapshot-stats-legend > div { - margin-top: 1px; - width: 170px; -} - -.heap-snapshot-stats-swatch { - display: inline-block; - width: 10px; - height: 10px; - border: 1px solid rgba(100, 100, 100, 0.3); -} - -.heap-snapshot-stats-swatch.heap-snapshot-stats-empty-swatch { - border: none; -} - -.heap-snapshot-stats-name, -.heap-snapshot-stats-size { - display: inline-block; - margin-left: 6px; -} - -.heap-snapshot-stats-size { - float: right; - text-align: right; -} - .heap-allocation-stack .stack-frame { display: flex; justify-content: space-between; diff --git a/front_end/resources/ClearStorageView.js b/front_end/resources/ClearStorageView.js index ceb8b9f56d..9b0601967f 100644 --- a/front_end/resources/ClearStorageView.js +++ b/front_end/resources/ClearStorageView.js @@ -40,12 +40,11 @@ Resources.ClearStorageView = class extends UI.ThrottledWidget { ls`Learn more`); learnMoreRow.appendChild(learnMore); this._quotaUsage = null; - this._pieChart = new PerfUI.PieChart(110, Number.bytesToString, true); - this._pieChartLegend = createElement('div'); + this._pieChart = new PerfUI.PieChart( + {chartName: ls`Storage Usage`, size: 110, formatter: Number.bytesToString, showLegend: true}); const usageBreakdownRow = quota.appendRow(); usageBreakdownRow.classList.add('usage-breakdown-row'); usageBreakdownRow.appendChild(this._pieChart.element); - usageBreakdownRow.appendChild(this._pieChartLegend); const clearButtonSection = this._reportView.appendSection('', 'clear-storage-button').appendRow(); this._clearButton = UI.createTextButton(ls`Clear site data`, this._clear.bind(this)); @@ -234,11 +233,7 @@ Resources.ClearStorageView = class extends UI.ThrottledWidget { continue; const title = this._getStorageTypeName(usageForType.storageType); const color = this._pieColors.get(usageForType.storageType) || '#ccc'; - this._pieChart.addSlice(value, color); - const rowElement = this._pieChartLegend.createChild('div', 'usage-breakdown-legend-row'); - rowElement.createChild('span', 'usage-breakdown-legend-value').textContent = Number.bytesToString(value); - rowElement.createChild('span', 'usage-breakdown-legend-swatch').style.backgroundColor = color; - rowElement.createChild('span', 'usage-breakdown-legend-title').textContent = title; + this._pieChart.addSlice(value, color, title); } } @@ -251,7 +246,6 @@ Resources.ClearStorageView = class extends UI.ThrottledWidget { */ _resetPieChart(total) { this._pieChart.setTotal(total); - this._pieChartLegend.removeChildren(); } /** diff --git a/front_end/resources/clearStorageView.css b/front_end/resources/clearStorageView.css index 9cb3cf5061..e1bb6a1c14 100644 --- a/front_end/resources/clearStorageView.css +++ b/front_end/resources/clearStorageView.css @@ -26,27 +26,3 @@ .usage-breakdown-row { min-width: fit-content; } - -.usage-breakdown-legend-row { - margin: 5px auto; -} - -.usage-breakdown-legend-title { - display: inline-block; -} - -.usage-breakdown-legend-value { - display: inline-block; - width: 70px; - text-align: right; -} - -.usage-breakdown-legend-swatch { - display: inline-block; - width: 11px; - height: 11px; - margin: 0 6px; - position: relative; - top: 1px; - border: 1px solid rgba(0, 0, 0, 0.2); -} diff --git a/front_end/timeline/TimelineUIUtils.js b/front_end/timeline/TimelineUIUtils.js index 7c64b4973e..4242a120f5 100644 --- a/front_end/timeline/TimelineUIUtils.js +++ b/front_end/timeline/TimelineUIUtils.js @@ -1523,12 +1523,16 @@ Timeline.TimelineUIUtils = class { total += aggregatedStats[categoryName]; const element = createElementWithClass('div', 'timeline-details-view-pie-chart-wrapper hbox'); - const pieChart = new PerfUI.PieChart(110, value => Number.preciseMillisToString(value), true); + const pieChart = new PerfUI.PieChart({ + chartName: ls`Time spent in rendering`, + size: 110, + formatter: value => Number.preciseMillisToString(value), + showLegend: true, + }); pieChart.element.classList.add('timeline-details-view-pie-chart'); pieChart.setTotal(total); const pieChartContainer = element.createChild('div', 'vbox'); pieChartContainer.appendChild(pieChart.element); - const footerElement = element.createChild('div', 'timeline-aggregated-info-legend'); /** * @param {string} name @@ -1539,12 +1543,7 @@ Timeline.TimelineUIUtils = class { function appendLegendRow(name, title, value, color) { if (!value) return; - pieChart.addSlice(value, color); - const rowElement = footerElement.createChild('div'); - rowElement.createChild('span', 'timeline-aggregated-legend-value').textContent = - Number.preciseMillisToString(value, 1); - rowElement.createChild('span', 'timeline-aggregated-legend-swatch').style.backgroundColor = color; - rowElement.createChild('span', 'timeline-aggregated-legend-title').textContent = title; + pieChart.addSlice(value, color, title); } // In case of self time, first add self, then children of the same category. diff --git a/front_end/timeline/timelinePanel.css b/front_end/timeline/timelinePanel.css index f54a86c249..225c6a9caf 100644 --- a/front_end/timeline/timelinePanel.css +++ b/front_end/timeline/timelinePanel.css @@ -172,26 +172,6 @@ height: 100%; } -.timeline-aggregated-legend-title { - display: inline-block; -} - -.timeline-aggregated-legend-value { - display: inline-block; - width: 70px; - text-align: right; -} - -.timeline-aggregated-legend-swatch { - display: inline-block; - width: 11px; - height: 11px; - margin: 0 6px; - position: relative; - top: 1px; - border: 1px solid rgba(0, 0, 0, 0.2); -} - .popover ul { margin: 0; padding: 0; @@ -385,12 +365,6 @@ line-height: 12px; } -.timeline-aggregated-info-legend > div { - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; -} - .timeline-flamechart { overflow: hidden; } diff --git a/front_end/ui/ARIAUtils.js b/front_end/ui/ARIAUtils.js index b612b4ec18..84e9ceec8a 100644 --- a/front_end/ui/ARIAUtils.js +++ b/front_end/ui/ARIAUtils.js @@ -109,6 +109,14 @@ UI.ARIAUtils.markAsPresentation = function(element) { element.setAttribute('role', 'presentation'); }; +/** + * @param {!Element} element + */ +UI.ARIAUtils.ensureId = function(element) { + if (!element.id) + element.id = UI.ARIAUtils.nextId('ariaElement'); +}; + /** * @param {string} prefix * @return {string} @@ -186,6 +194,15 @@ UI.ARIAUtils.setAccessibleName = function(element, name) { element.setAttribute('aria-label', name); }; +/** + * @param {!Element} element + * @param {!Element} labelElement + */ +UI.ARIAUtils.setLabelledBy = function(element, labelElement) { + UI.ARIAUtils.ensureId(labelElement); + element.setAttribute('aria-labelledby', labelElement.id); +}; + /** * @param {string} message * @param {!Element} element