]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-2200 Provide a new JS API to display a stack area
authorFabrice Bellingard <bellingard@gmail.com>
Wed, 7 Sep 2011 16:12:59 +0000 (18:12 +0200)
committerFabrice Bellingard <bellingard@gmail.com>
Wed, 7 Sep 2011 16:31:24 +0000 (18:31 +0200)
Using Protovis

sonar-server/src/main/webapp/javascripts/protovis-sonar.js

index 0aee692325cbe510c6083417278b30ce0e0f1dd3..6cb1c67fc33d7f0ab9318e9dc3513aa15ce69468 100755 (executable)
@@ -1,6 +1,183 @@
 window.SonarWidgets = {}
 
 
+//******************* STACK AREA CHART ******************* //
+
+SonarWidgets.StackArea = function (divId) {
+       this.wDivId = divId;
+       this.wHeight;
+       this.wData;
+       this.wSnapshots;
+       this.wMetrics;
+       this.wColors;
+       this.height = function(height) {
+               this.wHeight = height;
+               return this;
+       }
+       this.data = function(data) {
+               this.wData = data;
+               return this;
+       }
+       this.snapshots = function(snapshots) {
+               this.wSnapshots = snapshots;
+               return this;
+       }
+       this.metrics = function(metrics) {
+               this.wMetrics = metrics;
+               return this;
+       }
+       this.colors = function(colors) {
+               this.wColors = colors;
+               return this;
+       }
+}
+
+SonarWidgets.StackArea.prototype.render = function() {
+       
+       var trendData = this.wData;
+       var metrics = this.wMetrics;
+       var snapshots = this.wSnapshots;
+       var colors = this.wColors;
+       
+       var widgetDiv = $(this.wDivId);
+       var headerFont = "10.5px Arial,Helvetica,sans-serif";
+       
+       /* Sizing and scales. */
+       var headerHeight = 40;
+       var w = widgetDiv.getOffsetParent().getWidth() - 60; 
+       var     h = (this.wHeight == null ? 120 : this.wHeight) + headerHeight;
+
+       var x = pv.Scale.linear(pv.blend(pv.map(trendData, function(d) {return d;})), function(d) {return d.x}).range(0, w);
+       var maxY = 0;
+       for (i=0; i<trendData[0].size(); i++) {
+               var currentYSum = 0;
+               for (j=0; j<trendData.size(); j++) {
+                       currentYSum += trendData[j][i].y;
+               }
+               if (currentYSum > maxY) { maxY = currentYSum;}
+       }
+       var y = pv.Scale.linear(0, maxY).range(0, h-headerHeight);
+       var idx_numbers = trendData[0].size();
+       var idx = idx_numbers - 1;
+
+       /* The root panel. */
+       var vis = new pv.Panel()
+               .canvas(widgetDiv)
+               .width(w)
+               .height(h)
+               .left(20)
+               .right(20)
+               .bottom(30)
+               .top(20)
+               .strokeStyle("#CCC");
+
+       /* X-axis */
+       vis.add(pv.Rule)
+               .data(x.ticks())
+               .left(x)
+               .bottom(-10)
+               .height(10)
+               .anchor("bottom")
+               .add(pv.Label)
+               .text(x.tickFormat);
+       
+       /* Y-axis and ticks. */
+       vis.add(pv.Rule)
+           .data(y.ticks())
+           .bottom(y)
+           .strokeStyle("rgba(128,128,128,.2)")
+           .anchor("left")
+           .add(pv.Label)
+           .text(y.tickFormat);
+       
+       /* The stack layout */
+       var area = vis.add(pv.Layout.Stack)
+           .layers(trendData)
+           .x(function(d) {return x(d.x);})
+           .y(function(d) {return y(d.y);})
+           .layer
+           .add(pv.Area)
+           .fillStyle(function() {return colors[this.parent.index % colors.size()][0];})
+           .strokeStyle("rgba(128,128,128,.8)")
+           .add(pv.Dot)
+           .radius(1);
+       
+       /* Stack labels. */
+       vis.add(pv.Panel)
+           .extend(area.parent)
+           .add(pv.Area)
+           .extend(area)
+           .fillStyle(null)
+           .strokeStyle(null)
+           .anchor(function() {return idx == 0 ? "left" : (idx == idx_numbers-1 ? "right" : "center");})
+           .add(pv.Label)
+           .visible(function(d) {return this.index == idx && d.y != 0;})
+           .font(function(d) { return Math.round(5 + Math.sqrt(y(d.y))) + "px sans-serif";})
+           .textStyle("#FFF")
+           .text(function(d) {return metrics[this.parent.index] + ": " + d.y;});
+       
+       /* The date of the selected dot in the header. */
+       vis.add(pv.Label)
+               .left(8)
+               .top(16)
+               .font(headerFont)
+               .text(function() {return snapshots[idx].ld;});
+       
+       
+       /* The event labels */
+       if (true) {
+         eventColor = "rgba(75,159,213,1)";
+         eventHoverColor = "rgba(202,227,242,1)";
+         vis.add(pv.Line)
+               .strokeStyle("rgba(0,0,0,.001)")
+               .data(snapshots)
+               .left(function(s) {return x(s.d);})
+               .bottom(0)
+               .anchor("top")
+               .add(pv.Dot)
+               .bottom(-6)
+               .shape("triangle")
+               .angle(3.14159265)
+               .strokeStyle("grey")
+               .visible(function(s) {return s.e.size() > 0;})
+               .fillStyle(function() {return this.index == idx ? eventHoverColor : eventColor;})
+               .add(pv.Dot)
+               .radius(3)
+               .visible(function(s) {return s.e.size() > 0 && this.index == idx;})
+               .left(16)
+               .top(24)
+               .shape("triangle")
+               .fillStyle(function() {return this.index == idx ? eventHoverColor : eventColor;})
+               .strokeStyle("grey")
+               .anchor("right")
+               .add(pv.Label)
+               .font(headerFont)
+               .text(function(s) {return s.e.size() == 0 ? "" : s.e[0] + ( s.e[1] ? " (... +" + (s.e.size()-1) + ")" : "");});
+       }
+       
+       /* An invisible bar to capture events (without flickering). */
+       vis.add(pv.Bar)
+               .fillStyle("rgba(0,0,0,.001)")
+               .width(w+30)
+               .height(h+30)
+               .event("mouseout", function() {
+                       i = -1;
+                       return vis;
+               })
+               .event("mousemove", function() {
+                       var mx = x.invert(vis.mouse().x);
+                       idx = pv.search(trendData[0].map(function(d) {return d.x;}), mx);
+                       idx = idx < 0 ? (-idx - 2) : idx;
+                       idx = idx < 0 ? 0 : idx;
+                       return vis;
+               });
+       
+       vis.render();
+       
+}
+
+
+
 //******************* TIMELINE CHART ******************* //
 /*
  * Displays the evolution of metrics on a line chart, displaying related events.
@@ -80,8 +257,7 @@ SonarWidgets.Timeline.prototype.render = function() {
        var events = this.wEvents;
        
        var widgetDiv = $(this.wDivId);
-       var footerFont = "10.5px Arial,Helvetica,sans-serif";
-       var show_y_axis = (trendData.size()==1)
+       var headerFont = "10.5px Arial,Helvetica,sans-serif";
        
        /* Sizing and scales. */
        var headerHeight = 4 + Math.max(this.wMetrics.size(), events ? 2 : 1) * 18;
@@ -95,7 +271,7 @@ SonarWidgets.Timeline.prototype.render = function() {
                y[i]=pv.Scale.linear(trendData[i], function(d) {return d.y;}).range(20, yMaxHeight);
        }
        var interpolate = "linear"; /* cardinal or linear */
-       var idx = this.wData[0].size() - 1;
+       var idx = trendData[0].size() - 1;
 
        /* The root panel. */
        var vis = new pv.Panel()
@@ -142,14 +318,14 @@ SonarWidgets.Timeline.prototype.render = function() {
                .left(10)
                .top(function() {return 10 + this.parent.index * 14;})
                .anchor("right").add(pv.Label)
-               .font(footerFont)
+               .font(headerFont)
                .text(function(d) {return metrics[this.parent.index] + ": " + d.y.toFixed(2);});
        
-       /* The date of the selected dot in footer. */
+       /* The date of the selected dot in the header. */
        vis.add(pv.Label)
                .left(w/2)
                .top(16)
-               .font(footerFont)
+               .font(headerFont)
                .text(function() {return snapshots[idx].d;});
        
        /* The event labels */
@@ -166,18 +342,18 @@ SonarWidgets.Timeline.prototype.render = function() {
                .bottom(6)
                .shape("triangle")
                .strokeStyle("grey")
-               .fillStyle(function(e) {return e.sid == snapshots[idx].sid ? eventHoverColor : eventColor})
+               .fillStyle(function(e) {return e.sid == snapshots[idx].sid ? eventHoverColor : eventColor;})
                .add(pv.Dot)
                .radius(3)
                .visible(function(e) { return e.sid == snapshots[idx].sid;})
                .left(w/2+8)
                .top(24)
                .shape("triangle")
-               .fillStyle(function(e) {return e.sid == snapshots[idx].sid ? eventHoverColor : eventColor})
+               .fillStyle(function(e) {return e.sid == snapshots[idx].sid ? eventHoverColor : eventColor;})
                .strokeStyle("grey")
                .anchor("right")
                .add(pv.Label)
-               .font(footerFont)
+               .font(headerFont)
                .text(function(e) {return e.l[0].n + ( e.l[1] ? " (... +" + (e.l.size()-1) + ")" : "");});
        }