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/CircleChart.js | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/Views/CircleChart.js')
-rw-r--r-- | Source/WebInspectorUI/UserInterface/Views/CircleChart.js | 216 |
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(" "); + } +}; |