summaryrefslogtreecommitdiff
path: root/Source/WebInspectorUI/UserInterface/Views/ChartDetailsSectionRow.js
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@lorry>2017-06-27 06:07:23 +0000
committerLorry Tar Creator <lorry-tar-importer@lorry>2017-06-27 06:07:23 +0000
commit1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch)
tree46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebInspectorUI/UserInterface/Views/ChartDetailsSectionRow.js
parent32761a6cee1d0dee366b885b7b9c777e67885688 (diff)
downloadWebKitGtk-tarball-master.tar.gz
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/Views/ChartDetailsSectionRow.js')
-rw-r--r--Source/WebInspectorUI/UserInterface/Views/ChartDetailsSectionRow.js392
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"
+};