2 * Copyright (C) 2015 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
26 WebInspector.ChartDetailsSectionRow = class ChartDetailsSectionRow extends WebInspector.DetailsSectionRow
30 super(WebInspector.UIString("No Chart Available"));
32 this.element.classList.add("chart");
34 this._titleElement = document.createElement("div");
35 this._titleElement.className = "title";
36 this.element.appendChild(this._titleElement);
38 var chartContentElement = document.createElement("div");
39 chartContentElement.className = "chart-content";
40 this.element.appendChild(chartContentElement);
42 this._canvas = document.createElement("canvas");
43 this._canvas.className = "chart";
44 chartContentElement.appendChild(this._canvas);
46 this._legendElement = document.createElement("div");
47 this._legendElement.className = "legend";
48 chartContentElement.appendChild(this._legendElement);
50 this._delegate = delegate;
51 this._items = new Map;
53 this._innerLabel = "";
54 this._innerRadius = 0;
55 this._innerLabelFontSize = 11;
56 this._shadowColor = "rgba(0, 0, 0, 0.6)";
59 this._svgFiltersElement = document.createElement("svg");
60 this._svgFiltersElement.classList.add("defs-only");
61 this.element.append(this._svgFiltersElement);
63 this._checkboxStyleElement = document.createElement("style");
64 this._checkboxStyleElement.id = "checkbox-styles";
65 document.getElementsByTagName("head")[0].append(this._checkboxStyleElement);
72 if (this._title === title)
76 this._titleElement.textContent = title;
81 if (this._innerLabel === label)
84 this._innerLabel = label;
89 set innerRadius(radius)
91 if (this._innerRadius === radius)
94 this._innerRadius = radius;
104 addItem(id, label, value, color, checkbox, checked)
106 console.assert(!this._items.has(id), "Already added item with id: " + id);
107 if (this._items.has(id))
110 console.assert(value >= 0, "Value cannot be negative.");
114 this._items.set(id, {label, value, color, checkbox, checked});
115 this._total += value;
120 setItemValue(id, value)
122 let item = this._items.get(id);
123 console.assert(item, "Cannot set value for invalid item id: " + id);
127 console.assert(value >= 0, "Value cannot be negative.");
131 if (item.value === value)
134 this._total += value - item.value;
150 _addCheckboxColorFilter(id, r, g, b)
152 for (let i = 0; i < this._svgFiltersElement.childNodes.length; ++i) {
153 if (this._svgFiltersElement.childNodes[i].id === id)
161 // Create an svg:filter element that approximates "background-blend-mode: color", for grayscale input.
162 let filterElement = createSVGElement("filter");
163 filterElement.id = id;
164 filterElement.setAttribute("color-interpolation-filters", "sRGB");
166 let values = [1 - r, 0, 0, 0, r,
171 let colorMatrixPrimitive = createSVGElement("feColorMatrix");
172 colorMatrixPrimitive.setAttribute("type", "matrix");
173 colorMatrixPrimitive.setAttribute("values", values.join(" "));
175 function createGammaPrimitive(tagName, value)
177 let gammaPrimitive = createSVGElement(tagName);
178 gammaPrimitive.setAttribute("type", "gamma");
179 gammaPrimitive.setAttribute("value", value);
180 return gammaPrimitive;
183 let componentTransferPrimitive = createSVGElement("feComponentTransfer");
184 componentTransferPrimitive.append(createGammaPrimitive("feFuncR", 1.2), createGammaPrimitive("feFuncG", 1.2), createGammaPrimitive("feFuncB", 1.2));
185 filterElement.append(colorMatrixPrimitive, componentTransferPrimitive);
187 this._svgFiltersElement.append(filterElement);
189 let styleSheet = this._checkboxStyleElement.sheet;
190 styleSheet.insertRule(".details-section > .content > .group > .row.chart > .chart-content > .legend > .legend-item > label > input[type=checkbox]." + id + " { filter: grayscale(1) url(#" + id + ") }", 0);
195 if (!this._items.size) {
196 this._legendElement.removeChildren();
200 function formatItemValue(item)
202 if (this._delegate && typeof this._delegate.formatChartValue === "function")
203 return this._delegate.formatChartValue(item.value);
207 for (let [id, item] of this._items) {
208 if (item[WebInspector.ChartDetailsSectionRow.LegendItemValueElementSymbol]) {
209 let valueElement = item[WebInspector.ChartDetailsSectionRow.LegendItemValueElementSymbol];
210 valueElement.textContent = formatItemValue.call(this, item);
214 let labelElement = document.createElement("label");
217 let className = id.toLowerCase();
218 let rgb = item.color.substring(4, item.color.length - 1).replace(/ /g, "").split(",");
219 if (rgb[0] === rgb[1] && rgb[1] === rgb[2])
220 rgb[0] = rgb[1] = rgb[2] = Math.min(160, rgb[0]);
222 keyElement = document.createElement("input");
223 keyElement.type = "checkbox";
224 keyElement.classList.add(className);
225 keyElement.checked = item.checked;
226 keyElement[WebInspector.ChartDetailsSectionRow.DataItemIdSymbol] = id;
228 keyElement.addEventListener("change", this._legendItemCheckboxValueChanged.bind(this));
230 this._addCheckboxColorFilter(className, rgb[0], rgb[1], rgb[2]);
232 keyElement = document.createElement("div");
233 keyElement.classList.add("color-key");
234 keyElement.style.backgroundColor = item.color;
237 labelElement.append(keyElement, item.label);
239 let valueElement = document.createElement("div");
240 valueElement.classList.add("value");
241 valueElement.textContent = formatItemValue.call(this, item);
243 item[WebInspector.ChartDetailsSectionRow.LegendItemValueElementSymbol] = valueElement;
245 let legendItemElement = document.createElement("div");
246 legendItemElement.classList.add("legend-item");
247 legendItemElement.append(labelElement, valueElement);
249 this._legendElement.append(legendItemElement);
253 _legendItemCheckboxValueChanged(event)
255 let checkbox = event.target;
256 let id = checkbox[WebInspector.ChartDetailsSectionRow.DataItemIdSymbol];
257 this.dispatchEventToListeners(WebInspector.ChartDetailsSectionRow.Event.LegendItemChecked, {id, checked: checkbox.checked});
262 if (this._scheduledLayoutUpdateIdentifier)
265 this._scheduledLayoutUpdateIdentifier = requestAnimationFrame(this.updateLayout.bind(this));
270 if (this._scheduledLayoutUpdateIdentifier) {
271 cancelAnimationFrame(this._scheduledLayoutUpdateIdentifier);
272 this._scheduledLayoutUpdateIdentifier = undefined;
275 this._updateLegend();
277 var width = this._canvas.clientWidth * window.devicePixelRatio;
278 var height = this._canvas.clientHeight * window.devicePixelRatio;
279 this._canvas.width = width;
280 this._canvas.height = height;
282 var context = this._canvas.getContext("2d");
283 context.clearRect(0, 0, width, height);
285 var x = Math.floor(width / 2);
286 var y = Math.floor(height / 2);
287 var radius = Math.floor(Math.min(x, y) * 0.96); // Add a small margin to prevent clipping of the chart shadow.
288 var innerRadius = Math.floor(radius * this._innerRadius);
289 var startAngle = 1.5 * Math.PI;
290 var endAngle = startAngle;
292 function drawSlice(x, y, startAngle, endAngle, color)
295 context.moveTo(x, y);
296 context.arc(x, y, radius, startAngle, endAngle, false);
298 context.arc(x, y, innerRadius, endAngle, startAngle, true);
299 context.fillStyle = color;
304 context.shadowBlur = 2 * window.devicePixelRatio;
305 context.shadowOffsetY = window.devicePixelRatio;
306 context.shadowColor = this._shadowColor;
307 drawSlice(x, y, 0, 2.0 * Math.PI, "rgb(242, 242, 242)");
310 for (let [id, item] of this._items) {
311 if (item.value === 0)
313 endAngle += (item.value / this._total) * 2.0 * Math.PI;
314 drawSlice(x, y, startAngle, endAngle, item.color);
315 startAngle = endAngle;
318 if (this._innerLabel) {
319 context.font = (this._innerLabelFontSize * window.devicePixelRatio) + "px sans-serif";
320 var metrics = context.measureText(this._innerLabel);
321 var offsetX = centerX - metrics.width / 2;
322 context.fillStyle = "rgb(68, 68, 68)";
323 context.fillText(this._innerLabel, offsetX, centerY);
328 WebInspector.ChartDetailsSectionRow.DataItemIdSymbol = Symbol("chart-details-section-row-data-item-id");
329 WebInspector.ChartDetailsSectionRow.LegendItemValueElementSymbol = Symbol("chart-details-section-row-legend-item-value-element");
331 WebInspector.ChartDetailsSectionRow.Event = {
332 LegendItemChecked: "chart-details-section-row-legend-item-checked"