summaryrefslogtreecommitdiff
path: root/Source/WebInspectorUI/UserInterface/Views/CircleChart.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/CircleChart.js
parent32761a6cee1d0dee366b885b7b9c777e67885688 (diff)
downloadWebKitGtk-tarball-master.tar.gz
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/Views/CircleChart.js')
-rw-r--r--Source/WebInspectorUI/UserInterface/Views/CircleChart.js216
1 files changed, 216 insertions, 0 deletions
diff --git a/Source/WebInspectorUI/UserInterface/Views/CircleChart.js b/Source/WebInspectorUI/UserInterface/Views/CircleChart.js
new file mode 100644
index 000000000..f053ad282
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Views/CircleChart.js
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+// CircleChart creates a donut/pie chart of colored sections.
+//
+// Initialize the chart with a size and inner radius to get a blank chart.
+// To populate with data, first initialize the segments. The class names you
+// provide for the segments will allow you to style them. You can then update
+// the chart with new values (in the same order as the segments) at any time.
+//
+// SVG:
+//
+// - There is a single background path for the background.
+// - There is a path for each segment.
+// - If you want to put something inside the middle of the chart you can use `centerElement`.
+//
+// <div class="circle-chart">
+// <svg width="120" height="120" viewbox="0 0 120 120">
+// <path class="background" d="..."/>
+// <path class="segment segment-class-name-1" d="..."/>
+// <path class="segment segment-class-name-2" d="..."/>
+// ...
+// </svg>
+// <div class="center"></div>
+// </div>
+
+WebInspector.CircleChart = class CircleChart
+{
+ constructor({size, innerRadiusRatio})
+ {
+ this._data = [];
+ this._size = size;
+ this._radius = (size / 2) - 1;
+ this._innerRadius = innerRadiusRatio ? Math.floor(this._radius * innerRadiusRatio) : 0;
+
+ this._element = document.createElement("div");
+ this._element.classList.add("circle-chart");
+
+ this._chartElement = this._element.appendChild(createSVGElement("svg"));
+ this._chartElement.setAttribute("width", size);
+ this._chartElement.setAttribute("height", size);
+ this._chartElement.setAttribute("viewbox", `0 0 ${size} ${size}`);
+
+ this._pathElements = [];
+ this._values = [];
+ this._total = 0;
+
+ let backgroundPath = this._chartElement.appendChild(createSVGElement("path"));
+ backgroundPath.setAttribute("d", this._createCompleteCirclePathData(this.size / 2, this._radius, this._innerRadius));
+ backgroundPath.classList.add("background");
+ }
+
+ // Public
+
+ get element() { return this._element; }
+ get points() { return this._points; }
+ get size() { return this._size; }
+
+ get centerElement()
+ {
+ if (!this._centerElement) {
+ this._centerElement = this._element.appendChild(document.createElement("div"));
+ this._centerElement.classList.add("center");
+ this._centerElement.style.width = this._centerElement.style.height = this._radius + "px";
+ this._centerElement.style.top = this._centerElement.style.left = (this._radius - this._innerRadius) + "px";
+ }
+
+ return this._centerElement;
+ }
+
+ get segments()
+ {
+ return this._segments;
+ }
+
+ set segments(segmentClassNames)
+ {
+ for (let pathElement of this._pathElements)
+ pathElement.remove();
+
+ this._pathElements = [];
+
+ for (let className of segmentClassNames) {
+ let pathElement = this._chartElement.appendChild(createSVGElement("path"));
+ pathElement.classList.add("segment", className);
+ this._pathElements.push(pathElement);
+ }
+ }
+
+ get values()
+ {
+ return this._values;
+ }
+
+ set values(values)
+ {
+ console.assert(!values.length || values.length === this._pathElements.length, "Should have the same number of values as segments");
+
+ this._values = values;
+ this._total = 0;
+
+ for (let value of values)
+ this._total += value;
+ }
+
+ clear()
+ {
+ this.values = new Array(this._values.length).fill(0);
+ }
+
+ needsLayout()
+ {
+ if (this._scheduledLayoutUpdateIdentifier)
+ return;
+
+ this._scheduledLayoutUpdateIdentifier = requestAnimationFrame(this.updateLayout.bind(this));
+ }
+
+ updateLayout()
+ {
+ if (this._scheduledLayoutUpdateIdentifier) {
+ cancelAnimationFrame(this._scheduledLayoutUpdateIdentifier);
+ this._scheduledLayoutUpdateIdentifier = undefined;
+ }
+
+ if (!this._values.length)
+ return;
+
+ const center = this._size / 2;
+ let startAngle = -Math.PI / 2;
+ let endAngle = 0;
+
+ for (let i = 0; i < this._values.length; ++i) {
+ let value = this._values[i];
+ let pathElement = this._pathElements[i];
+
+ if (value === 0)
+ pathElement.removeAttribute("d");
+ else if (value === this._total)
+ pathElement.setAttribute("d", this._createCompleteCirclePathData(center, this._radius, this._innerRadius));
+ else {
+ let angle = (value / this._total) * Math.PI * 2;
+ endAngle = startAngle + angle;
+
+ pathElement.setAttribute("d", this._createSegmentPathData(center, startAngle, endAngle, this._radius, this._innerRadius));
+ startAngle = endAngle;
+ }
+ }
+ }
+
+ // Private
+
+ _createCompleteCirclePathData(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(" ");
+ }
+
+ _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(" ");
+ }
+};