diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebInspectorUI/UserInterface/Views/ChartDetailsSectionRow.js | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/Views/ChartDetailsSectionRow.js')
-rw-r--r-- | Source/WebInspectorUI/UserInterface/Views/ChartDetailsSectionRow.js | 392 |
1 files changed, 392 insertions, 0 deletions
diff --git a/Source/WebInspectorUI/UserInterface/Views/ChartDetailsSectionRow.js b/Source/WebInspectorUI/UserInterface/Views/ChartDetailsSectionRow.js new file mode 100644 index 000000000..1a6bfef44 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Views/ChartDetailsSectionRow.js @@ -0,0 +1,392 @@ +/* + * Copyright (C) 2015 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.ChartDetailsSectionRow = class ChartDetailsSectionRow extends WebInspector.DetailsSectionRow +{ + constructor(delegate, chartSize, innerRadiusRatio) + { + super(WebInspector.UIString("No Chart Available")); + + innerRadiusRatio = innerRadiusRatio || 0; + console.assert(chartSize > 0, chartSize); + console.assert(innerRadiusRatio >= 0 && innerRadiusRatio < 1, innerRadiusRatio); + + this.element.classList.add("chart"); + + this._titleElement = document.createElement("div"); + this._titleElement.className = "title"; + this.element.appendChild(this._titleElement); + + let chartContentElement = document.createElement("div"); + chartContentElement.className = "chart-content"; + this.element.appendChild(chartContentElement); + + this._chartElement = createSVGElement("svg"); + chartContentElement.appendChild(this._chartElement); + + this._legendElement = document.createElement("div"); + this._legendElement.className = "legend"; + chartContentElement.appendChild(this._legendElement); + + this._delegate = delegate; + this._items = new Map; + this._title = ""; + this._chartSize = chartSize; + this._radius = (this._chartSize / 2) - 1; // Subtract one to accomodate chart stroke width. + this._innerRadius = innerRadiusRatio ? Math.floor(this._radius * innerRadiusRatio) : 0; + this._total = 0; + + this._svgFiltersElement = document.createElement("svg"); + this._svgFiltersElement.classList.add("defs-only"); + this.element.append(this._svgFiltersElement); + + this._checkboxStyleElement = document.createElement("style"); + this._checkboxStyleElement.id = "checkbox-styles"; + document.getElementsByTagName("head")[0].append(this._checkboxStyleElement); + + function createEmptyChartPathData(c, r1, r2) + { + const a1 = 0; + const a2 = Math.PI * 1.9999; + let x1 = c + Math.cos(a1) * r1, + y1 = c + Math.sin(a1) * r1, + x2 = c + Math.cos(a2) * r1, + y2 = c + Math.sin(a2) * r1, + x3 = c + Math.cos(a2) * r2, + y3 = c + Math.sin(a2) * r2, + x4 = c + Math.cos(a1) * r2, + y4 = c + Math.sin(a1) * r2; + return [ + "M", x1, y1, // Starting position. + "A", r1, r1, 0, 1, 1, x2, y2, // Draw outer arc. + "Z", // Close path. + "M", x3, y3, // Starting position. + "A", r2, r2, 0, 1, 0, x4, y4, // Draw inner arc. + "Z" // Close path. + ].join(" "); + } + + this._emptyChartPath = createSVGElement("path"); + this._emptyChartPath.setAttribute("d", createEmptyChartPathData(this._chartSize / 2, this._radius, this._innerRadius)); + this._emptyChartPath.classList.add("empty-chart"); + this._chartElement.appendChild(this._emptyChartPath); + } + + // Public + + get chartSize() + { + return this._chartSize; + } + + set title(title) + { + if (this._title === title) + return; + + this._title = title; + this._titleElement.textContent = title; + } + + get total() + { + return this._total; + } + + addItem(id, label, value, color, checkbox, checked) + { + console.assert(!this._items.has(id), "Already added item with id: " + id); + if (this._items.has(id)) + return; + + console.assert(value >= 0, "Value cannot be negative."); + if (value < 0) + return; + + this._items.set(id, {label, value, color, checkbox, checked}); + this._total += value; + + this._needsLayout(); + } + + setItemValue(id, value) + { + let item = this._items.get(id); + console.assert(item, "Cannot set value for invalid item id: " + id); + if (!item) + return; + + console.assert(value >= 0, "Value cannot be negative."); + if (value < 0) + return; + + if (item.value === value) + return; + + this._total += value - item.value; + item.value = value; + + this._needsLayout(); + } + + clearItems() + { + for (let item of this._items.values()) { + let path = item[WebInspector.ChartDetailsSectionRow.ChartSegmentPathSymbol]; + if (path) + path.remove(); + } + + this._total = 0; + this._items.clear(); + + this._needsLayout(); + } + + // Private + + _addCheckboxColorFilter(id, r, g, b) + { + for (let i = 0; i < this._svgFiltersElement.childNodes.length; ++i) { + if (this._svgFiltersElement.childNodes[i].id === id) + return; + } + + r /= 255; + b /= 255; + g /= 255; + + // Create an svg:filter element that approximates "background-blend-mode: color", for grayscale input. + let filterElement = createSVGElement("filter"); + filterElement.id = id; + filterElement.setAttribute("color-interpolation-filters", "sRGB"); + + let values = [1 - r, 0, 0, 0, r, + 1 - g, 0, 0, 0, g, + 1 - b, 0, 0, 0, b, + 0, 0, 0, 1, 0]; + + let colorMatrixPrimitive = createSVGElement("feColorMatrix"); + colorMatrixPrimitive.setAttribute("type", "matrix"); + colorMatrixPrimitive.setAttribute("values", values.join(" ")); + + function createGammaPrimitive(tagName, value) + { + let gammaPrimitive = createSVGElement(tagName); + gammaPrimitive.setAttribute("type", "gamma"); + gammaPrimitive.setAttribute("exponent", value); + return gammaPrimitive; + } + + let componentTransferPrimitive = createSVGElement("feComponentTransfer"); + componentTransferPrimitive.append(createGammaPrimitive("feFuncR", 1.4), createGammaPrimitive("feFuncG", 1.4), createGammaPrimitive("feFuncB", 1.4)); + filterElement.append(colorMatrixPrimitive, componentTransferPrimitive); + + this._svgFiltersElement.append(filterElement); + + let styleSheet = this._checkboxStyleElement.sheet; + styleSheet.insertRule(".details-section > .content > .group > .row.chart > .chart-content > .legend > .legend-item > label > input[type=checkbox]." + id + " { filter: grayscale(1) url(#" + id + ") }", 0); + } + + _updateLegend() + { + if (!this._items.size) { + this._legendElement.removeChildren(); + return; + } + + function formatItemValue(item) + { + if (this._delegate && typeof this._delegate.formatChartValue === "function") + return this._delegate.formatChartValue(item.value); + return item.value; + } + + for (let [id, item] of this._items) { + if (item[WebInspector.ChartDetailsSectionRow.LegendItemValueElementSymbol]) { + let valueElement = item[WebInspector.ChartDetailsSectionRow.LegendItemValueElementSymbol]; + valueElement.textContent = formatItemValue.call(this, item); + continue; + } + + let labelElement = document.createElement("label"); + let keyElement; + if (item.checkbox) { + let className = id.toLowerCase(); + let rgb = item.color.substring(4, item.color.length - 1).replace(/ /g, "").split(","); + if (rgb[0] === rgb[1] && rgb[1] === rgb[2]) + rgb[0] = rgb[1] = rgb[2] = Math.min(160, rgb[0]); + + keyElement = document.createElement("input"); + keyElement.type = "checkbox"; + keyElement.classList.add(className); + keyElement.checked = item.checked; + keyElement[WebInspector.ChartDetailsSectionRow.DataItemIdSymbol] = id; + + keyElement.addEventListener("change", this._legendItemCheckboxValueChanged.bind(this)); + + this._addCheckboxColorFilter(className, rgb[0], rgb[1], rgb[2]); + } else { + keyElement = document.createElement("div"); + keyElement.classList.add("color-key"); + keyElement.style.backgroundColor = item.color; + } + + labelElement.append(keyElement, item.label); + + let valueElement = document.createElement("div"); + valueElement.classList.add("value"); + valueElement.textContent = formatItemValue.call(this, item); + + item[WebInspector.ChartDetailsSectionRow.LegendItemValueElementSymbol] = valueElement; + + let legendItemElement = document.createElement("div"); + legendItemElement.classList.add("legend-item"); + legendItemElement.append(labelElement, valueElement); + + this._legendElement.append(legendItemElement); + } + } + + _legendItemCheckboxValueChanged(event) + { + let checkbox = event.target; + let id = checkbox[WebInspector.ChartDetailsSectionRow.DataItemIdSymbol]; + this.dispatchEventToListeners(WebInspector.ChartDetailsSectionRow.Event.LegendItemChecked, {id, checked: checkbox.checked}); + } + + _needsLayout() + { + if (this._scheduledLayoutUpdateIdentifier) + return; + + this._scheduledLayoutUpdateIdentifier = requestAnimationFrame(this._updateLayout.bind(this)); + } + + _updateLayout() + { + if (this._scheduledLayoutUpdateIdentifier) { + cancelAnimationFrame(this._scheduledLayoutUpdateIdentifier); + this._scheduledLayoutUpdateIdentifier = undefined; + } + + this._updateLegend(); + + this._chartElement.setAttribute("width", this._chartSize); + this._chartElement.setAttribute("height", this._chartSize); + this._chartElement.setAttribute("viewbox", "0 0 " + this._chartSize + " " + this._chartSize); + + function createSegmentPathData(c, a1, a2, r1, r2) + { + const largeArcFlag = ((a2 - a1) % (Math.PI * 2)) > Math.PI ? 1 : 0; + let x1 = c + Math.cos(a1) * r1, + y1 = c + Math.sin(a1) * r1, + x2 = c + Math.cos(a2) * r1, + y2 = c + Math.sin(a2) * r1, + x3 = c + Math.cos(a2) * r2, + y3 = c + Math.sin(a2) * r2, + x4 = c + Math.cos(a1) * r2, + y4 = c + Math.sin(a1) * r2; + return [ + "M", x1, y1, // Starting position. + "A", r1, r1, 0, largeArcFlag, 1, x2, y2, // Draw outer arc. + "L", x3, y3, // Connect outer and innner arcs. + "A", r2, r2, 0, largeArcFlag, 0, x4, y4, // Draw inner arc. + "Z" // Close path. + ].join(" "); + } + + // Balance item values so that all non-zero chart segments are visible. + const minimumDisplayValue = this._total * 0.015; + + let items = []; + for (let item of this._items.values()) { + item.displayValue = item.value ? Math.max(minimumDisplayValue, item.value) : 0; + if (item.displayValue) + items.push(item); + } + + if (items.length > 1) { + items.sort(function(a, b) { return a.value - b.value; }); + + let largeItemCount = items.length; + let totalAdjustedValue = 0; + for (let item of items) { + if (item.value < minimumDisplayValue) { + totalAdjustedValue += minimumDisplayValue - item.value; + largeItemCount--; + continue; + } + + if (!totalAdjustedValue || !largeItemCount) + break; + + const donatedValue = totalAdjustedValue / largeItemCount; + if (item.displayValue - donatedValue >= minimumDisplayValue) { + item.displayValue -= donatedValue; + totalAdjustedValue -= donatedValue; + } + + largeItemCount--; + } + } + + const center = this._chartSize / 2; + let startAngle = -Math.PI / 2; + let endAngle = 0; + for (let [id, item] of this._items) { + let path = item[WebInspector.ChartDetailsSectionRow.ChartSegmentPathSymbol]; + if (!path) { + path = createSVGElement("path"); + path.classList.add("chart-segment"); + path.setAttribute("fill", item.color); + this._chartElement.appendChild(path); + + item[WebInspector.ChartDetailsSectionRow.ChartSegmentPathSymbol] = path; + } + + if (!item.value) { + path.classList.add("hidden"); + continue; + } + + const angle = (item.displayValue / this._total) * Math.PI * 2; + endAngle = startAngle + angle; + + path.setAttribute("d", createSegmentPathData(center, startAngle, endAngle, this._radius, this._innerRadius)); + path.classList.remove("hidden"); + + startAngle = endAngle; + } + } +}; + +WebInspector.ChartDetailsSectionRow.DataItemIdSymbol = Symbol("chart-details-section-row-data-item-id"); +WebInspector.ChartDetailsSectionRow.ChartSegmentPathSymbol = Symbol("chart-details-section-row-chart-segment-path"); +WebInspector.ChartDetailsSectionRow.LegendItemValueElementSymbol = Symbol("chart-details-section-row-legend-item-value-element"); + +WebInspector.ChartDetailsSectionRow.Event = { + LegendItemChecked: "chart-details-section-row-legend-item-checked" +}; |