1/* 2 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. 3 * Copyright (C) 2008, 2009 Anthony Ricaud <rik@webkit.org> 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 15 * its contributors may be used to endorse or promote products derived 16 * from this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30WebInspector.SummaryBar = function(categories) 31{ 32 this.categories = categories; 33 34 this.element = document.createElement("div"); 35 this.element.className = "summary-bar"; 36 37 this.graphElement = document.createElement("canvas"); 38 this.graphElement.setAttribute("width", "450"); 39 this.graphElement.setAttribute("height", "38"); 40 this.graphElement.className = "summary-graph"; 41 this.element.appendChild(this.graphElement); 42 43 this.legendElement = document.createElement("div"); 44 this.legendElement.className = "summary-graph-legend"; 45 this.element.appendChild(this.legendElement); 46} 47 48WebInspector.SummaryBar.prototype = { 49 50 get calculator() { 51 return this._calculator; 52 }, 53 54 set calculator(x) { 55 this._calculator = x; 56 }, 57 58 reset: function() 59 { 60 this.legendElement.removeChildren(); 61 this._drawSummaryGraph(); 62 }, 63 64 update: function(data) 65 { 66 var graphInfo = this.calculator.computeSummaryValues(data); 67 68 var fillSegments = []; 69 70 this.legendElement.removeChildren(); 71 72 for (var category in this.categories) { 73 var size = graphInfo.categoryValues[category]; 74 if (!size) 75 continue; 76 77 var colorString = this.categories[category].color; 78 79 var fillSegment = {color: colorString, value: size}; 80 fillSegments.push(fillSegment); 81 82 var legendLabel = this._makeLegendElement(this.categories[category].title, this.calculator.formatValue(size), colorString); 83 this.legendElement.appendChild(legendLabel); 84 } 85 86 if (graphInfo.total) { 87 var totalLegendLabel = this._makeLegendElement(WebInspector.UIString("Total"), this.calculator.formatValue(graphInfo.total)); 88 totalLegendLabel.addStyleClass("total"); 89 this.legendElement.appendChild(totalLegendLabel); 90 } 91 92 this._drawSummaryGraph(fillSegments); 93 }, 94 95 _drawSwatch: function(canvas, color) 96 { 97 var ctx = canvas.getContext("2d"); 98 99 function drawSwatchSquare() { 100 ctx.fillStyle = color; 101 ctx.fillRect(0, 0, 13, 13); 102 103 var gradient = ctx.createLinearGradient(0, 0, 13, 13); 104 gradient.addColorStop(0.0, "rgba(255, 255, 255, 0.2)"); 105 gradient.addColorStop(1.0, "rgba(255, 255, 255, 0.0)"); 106 107 ctx.fillStyle = gradient; 108 ctx.fillRect(0, 0, 13, 13); 109 110 gradient = ctx.createLinearGradient(13, 13, 0, 0); 111 gradient.addColorStop(0.0, "rgba(0, 0, 0, 0.2)"); 112 gradient.addColorStop(1.0, "rgba(0, 0, 0, 0.0)"); 113 114 ctx.fillStyle = gradient; 115 ctx.fillRect(0, 0, 13, 13); 116 117 ctx.strokeStyle = "rgba(0, 0, 0, 0.6)"; 118 ctx.strokeRect(0.5, 0.5, 12, 12); 119 } 120 121 ctx.clearRect(0, 0, 13, 24); 122 123 drawSwatchSquare(); 124 125 ctx.save(); 126 127 ctx.translate(0, 25); 128 ctx.scale(1, -1); 129 130 drawSwatchSquare(); 131 132 ctx.restore(); 133 134 this._fadeOutRect(ctx, 0, 13, 13, 13, 0.5, 0.0); 135 }, 136 137 _drawSummaryGraph: function(segments) 138 { 139 if (!segments || !segments.length) { 140 segments = [{color: "white", value: 1}]; 141 this._showingEmptySummaryGraph = true; 142 } else 143 delete this._showingEmptySummaryGraph; 144 145 // Calculate the total of all segments. 146 var total = 0; 147 for (var i = 0; i < segments.length; ++i) 148 total += segments[i].value; 149 150 // Calculate the percentage of each segment, rounded to the nearest percent. 151 var percents = segments.map(function(s) { return Math.max(Math.round(100 * s.value / total), 1) }); 152 153 // Calculate the total percentage. 154 var percentTotal = 0; 155 for (var i = 0; i < percents.length; ++i) 156 percentTotal += percents[i]; 157 158 // Make sure our percentage total is not greater-than 100, it can be greater 159 // if we rounded up for a few segments. 160 while (percentTotal > 100) { 161 for (var i = 0; i < percents.length && percentTotal > 100; ++i) { 162 if (percents[i] > 1) { 163 --percents[i]; 164 --percentTotal; 165 } 166 } 167 } 168 169 // Make sure our percentage total is not less-than 100, it can be less 170 // if we rounded down for a few segments. 171 while (percentTotal < 100) { 172 for (var i = 0; i < percents.length && percentTotal < 100; ++i) { 173 ++percents[i]; 174 ++percentTotal; 175 } 176 } 177 178 var ctx = this.graphElement.getContext("2d"); 179 180 var x = 0; 181 var y = 0; 182 var w = 450; 183 var h = 19; 184 var r = (h / 2); 185 186 function drawPillShadow() 187 { 188 // This draws a line with a shadow that is offset away from the line. The line is stroked 189 // twice with different X shadow offsets to give more feathered edges. Later we erase the 190 // line with destination-out 100% transparent black, leaving only the shadow. This only 191 // works if nothing has been drawn into the canvas yet. 192 193 ctx.beginPath(); 194 ctx.moveTo(x + 4, y + h - 3 - 0.5); 195 ctx.lineTo(x + w - 4, y + h - 3 - 0.5); 196 ctx.closePath(); 197 198 ctx.save(); 199 200 ctx.shadowBlur = 2; 201 ctx.shadowColor = "rgba(0, 0, 0, 0.5)"; 202 ctx.shadowOffsetX = 3; 203 ctx.shadowOffsetY = 5; 204 205 ctx.strokeStyle = "white"; 206 ctx.lineWidth = 1; 207 208 ctx.stroke(); 209 210 ctx.shadowOffsetX = -3; 211 212 ctx.stroke(); 213 214 ctx.restore(); 215 216 ctx.save(); 217 218 ctx.globalCompositeOperation = "destination-out"; 219 ctx.strokeStyle = "rgba(0, 0, 0, 1)"; 220 ctx.lineWidth = 1; 221 222 ctx.stroke(); 223 224 ctx.restore(); 225 } 226 227 function drawPill() 228 { 229 // Make a rounded rect path. 230 ctx.beginPath(); 231 ctx.moveTo(x, y + r); 232 ctx.lineTo(x, y + h - r); 233 ctx.arc(x + r, y + h - r, r, Math.PI, Math.PI / 2, true); 234 ctx.lineTo(x + w - r, y + h); 235 ctx.arc(x + w - r, y + h - r, r, Math.PI / 2, 0, true); 236 ctx.lineTo(x + w, y + r); 237 ctx.arc(x + w - r, y + r, r, 0, 3 * Math.PI / 2, true); 238 ctx.lineTo(x + r, y); 239 ctx.arc(x + r, y + r, r, Math.PI / 2, Math.PI, true); 240 ctx.closePath(); 241 242 // Clip to the rounded rect path. 243 ctx.save(); 244 ctx.clip(); 245 246 // Fill the segments with the associated color. 247 var previousSegmentsWidth = 0; 248 for (var i = 0; i < segments.length; ++i) { 249 var segmentWidth = Math.round(w * percents[i] / 100); 250 ctx.fillStyle = segments[i].color; 251 ctx.fillRect(x + previousSegmentsWidth, y, segmentWidth, h); 252 previousSegmentsWidth += segmentWidth; 253 } 254 255 // Draw the segment divider lines. 256 ctx.lineWidth = 1; 257 for (var i = 1; i < 20; ++i) { 258 ctx.beginPath(); 259 ctx.moveTo(x + (i * Math.round(w / 20)) + 0.5, y); 260 ctx.lineTo(x + (i * Math.round(w / 20)) + 0.5, y + h); 261 ctx.closePath(); 262 263 ctx.strokeStyle = "rgba(0, 0, 0, 0.2)"; 264 ctx.stroke(); 265 266 ctx.beginPath(); 267 ctx.moveTo(x + (i * Math.round(w / 20)) + 1.5, y); 268 ctx.lineTo(x + (i * Math.round(w / 20)) + 1.5, y + h); 269 ctx.closePath(); 270 271 ctx.strokeStyle = "rgba(255, 255, 255, 0.2)"; 272 ctx.stroke(); 273 } 274 275 // Draw the pill shading. 276 var lightGradient = ctx.createLinearGradient(x, y, x, y + (h / 1.5)); 277 lightGradient.addColorStop(0.0, "rgba(220, 220, 220, 0.6)"); 278 lightGradient.addColorStop(0.4, "rgba(220, 220, 220, 0.2)"); 279 lightGradient.addColorStop(1.0, "rgba(255, 255, 255, 0.0)"); 280 281 var darkGradient = ctx.createLinearGradient(x, y + (h / 3), x, y + h); 282 darkGradient.addColorStop(0.0, "rgba(0, 0, 0, 0.0)"); 283 darkGradient.addColorStop(0.8, "rgba(0, 0, 0, 0.2)"); 284 darkGradient.addColorStop(1.0, "rgba(0, 0, 0, 0.5)"); 285 286 ctx.fillStyle = darkGradient; 287 ctx.fillRect(x, y, w, h); 288 289 ctx.fillStyle = lightGradient; 290 ctx.fillRect(x, y, w, h); 291 292 ctx.restore(); 293 } 294 295 ctx.clearRect(x, y, w, (h * 2)); 296 297 drawPillShadow(); 298 drawPill(); 299 300 ctx.save(); 301 302 ctx.translate(0, (h * 2) + 1); 303 ctx.scale(1, -1); 304 305 drawPill(); 306 307 ctx.restore(); 308 309 this._fadeOutRect(ctx, x, y + h + 1, w, h, 0.5, 0.0); 310 }, 311 312 _fadeOutRect: function(ctx, x, y, w, h, a1, a2) 313 { 314 ctx.save(); 315 316 var gradient = ctx.createLinearGradient(x, y, x, y + h); 317 gradient.addColorStop(0.0, "rgba(0, 0, 0, " + (1.0 - a1) + ")"); 318 gradient.addColorStop(0.8, "rgba(0, 0, 0, " + (1.0 - a2) + ")"); 319 gradient.addColorStop(1.0, "rgba(0, 0, 0, 1.0)"); 320 321 ctx.globalCompositeOperation = "destination-out"; 322 323 ctx.fillStyle = gradient; 324 ctx.fillRect(x, y, w, h); 325 326 ctx.restore(); 327 }, 328 329 _makeLegendElement: function(label, value, color) 330 { 331 var legendElement = document.createElement("label"); 332 legendElement.className = "summary-graph-legend-item"; 333 334 if (color) { 335 var swatch = document.createElement("canvas"); 336 swatch.className = "summary-graph-legend-swatch"; 337 swatch.setAttribute("width", "13"); 338 swatch.setAttribute("height", "24"); 339 340 legendElement.appendChild(swatch); 341 342 this._drawSwatch(swatch, color); 343 } 344 345 var labelElement = document.createElement("div"); 346 labelElement.className = "summary-graph-legend-label"; 347 legendElement.appendChild(labelElement); 348 349 var headerElement = document.createElement("div"); 350 headerElement.className = "summary-graph-legend-header"; 351 headerElement.textContent = label; 352 labelElement.appendChild(headerElement); 353 354 var valueElement = document.createElement("div"); 355 valueElement.className = "summary-graph-legend-value"; 356 valueElement.textContent = value; 357 labelElement.appendChild(valueElement); 358 359 return legendElement; 360 } 361} 362 363WebInspector.SummaryBar.prototype.__proto__ = WebInspector.Object.prototype; 364