Home | History | Annotate | Download | only in front-end
      1 /*
      2  * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
      3  * Copyright (C) 2008, 2009 Anthony Ricaud <rik (at) 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 
     30 WebInspector.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 
     48 WebInspector.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 
    363 WebInspector.SummaryBar.prototype.__proto__ = WebInspector.Object.prototype;
    364