]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-3762 Provide a new Histogram widget to display a measure filter
authorStas Vilchik <vilchiks@gmail.com>
Mon, 16 Dec 2013 10:49:07 +0000 (16:49 +0600)
committerStas Vilchik <vilchiks@gmail.com>
Mon, 16 Dec 2013 10:49:17 +0000 (16:49 +0600)
First preview

plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/histogram.html.erb
sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_head.html.erb
sonar-server/src/main/webapp/javascripts/widgets/histogram.js [new file with mode: 0644]
sonar-server/src/main/webapp/stylesheets/style.css
sonar-server/wro.xml

index 7a2951cd6c4c639db94306c6889118eaaa43c06f..7838b44ebfa801c69154a83f6c0564d3375e3958 100644 (file)
@@ -1,8 +1,53 @@
+<%
+containerId = 'pie-chart-widget' + widget.id.to_s
+chartHeight = widget_properties["chartHeight"]
+chartTitle = widget_properties["chartTitle"]
+filterId = widget_properties["filter"].to_i
+%>
+
+<div class="histogram-widget" id="<%= containerId %>">
+  <!--[if lte IE 8 ]> <h3><%= message('widget.unsupported_browser_warning') -%></h3> <![endif]-->
+
+  <!--[if (gte IE 9)|!(IE)]><!-->
+  <% if chartTitle %>
+  <h3 style="text-align: center;"><%= h(chartTitle) -%></h3>
+  <% end %>
+  <!--<![endif]-->
+</div>
+
+<!--[if (gte IE 9)|!(IE)]><!-->
 <script>
-  var filterId = <%= widget_properties['filter'].to_s %>;
-  var chartTitle = <%= widget_properties['chartTitle'].to_s %>;
-  var chartHeight = <%= widget_properties['chartHeight'].to_i %>;
-  var metric = <%= widget_properties['metric'].key.to_s %>;
+  (function () {
+    var metrics = [
+          '<%= widget_properties["metric"].name %>',
+        ],
+        query = [
+          'filter=<%= filterId %>',
+          'metrics=' + metrics.join(','),
+          'fields=name',
+          'pageSize=40',
+          'page=1',
+          'sort=metric:' + metrics[0],
+          'asc=false'
+        ].join('&');
+        widget = new SonarWidgets.Widget();
+
+    widget
+      .type('Histogram')
+      .source(baseUrl + '/measures/search_filter?' + query)
+      .metricsPriority(metrics)
+      .height(<%= chartHeight %>)
+      .options({
+        baseUrl: baseUrl + '/dashboard/index/'
+      })
+      .render('#<%= containerId %>');
+
+    autoResize(500, function() {
+      widget.update('#<%= containerId %>');
+    });
+  })();
 </script>
+<!--<![endif]-->
+
 
 
index cd405b7318fa73bedaf9c3e2c8a94c56c189cf55..77d4b16e06d4677b03f733d3c136e73ec398dc1a 100644 (file)
@@ -52,6 +52,7 @@
     <%= javascript_include_tag 'widgets/timeline' %>
     <%= javascript_include_tag 'widgets/stack-area' %>
     <%= javascript_include_tag 'widgets/pie-chart' %>
+    <%= javascript_include_tag 'widgets/histogram' %>
 
     <%= javascript_include_tag 'select-list' %>
 
diff --git a/sonar-server/src/main/webapp/javascripts/widgets/histogram.js b/sonar-server/src/main/webapp/javascripts/widgets/histogram.js
new file mode 100644 (file)
index 0000000..147d606
--- /dev/null
@@ -0,0 +1,290 @@
+/*global d3:false, _:false */
+/*jshint eqnull:true */
+
+window.SonarWidgets = window.SonarWidgets == null ? {} : window.SonarWidgets;
+
+(function () {
+
+  window.SonarWidgets.Histogram = function () {
+    // Set default values
+    this._components = [];
+    this._metrics = [];
+    this._metricsPriority = [];
+    this._width = window.SonarWidgets.Histogram.defaults.width;
+    this._height = window.SonarWidgets.Histogram.defaults.height;
+    this._margin = window.SonarWidgets.Histogram.defaults.margin;
+    this._legendWidth = window.SonarWidgets.Histogram.defaults.legendWidth;
+    this._legendMargin = window.SonarWidgets.Histogram.defaults.legendMargin;
+    this._detailsWidth = window.SonarWidgets.Histogram.defaults.detailsWidth;
+    this._options = {};
+
+    this._lineHeight = 20;
+
+
+    // Export global variables
+    this.metrics = function (_) {
+      return param.call(this, '_metrics', _);
+    };
+
+    this.metricsPriority = function (_) {
+      return param.call(this, '_metricsPriority', _);
+    };
+
+    this.components = function (_) {
+      return param.call(this, '_components', _);
+    };
+
+    this.width = function (_) {
+      return param.call(this, '_width', _);
+    };
+
+    this.height = function (_) {
+      return param.call(this, '_height', _);
+    };
+
+    this.margin = function (_) {
+      return param.call(this, '_margin', _);
+    };
+
+    this.legendWidth = function (_) {
+      return param.call(this, '_legendWidth', _);
+    };
+
+    this.legendMargin = function (_) {
+      return param.call(this, '_legendMargin', _);
+    };
+
+    this.detailsWidth = function (_) {
+      return param.call(this, '_detailsWidth', _);
+    };
+
+    this.options = function (_) {
+      return param.call(this, '_options', _);
+    };
+  };
+
+  window.SonarWidgets.Histogram.prototype.render = function (container) {
+    var widget = this,
+        containerS = container;
+
+    container = d3.select(container);
+
+    this.width(container.property('offsetWidth'));
+
+    this.svg = container.append('svg')
+        .attr('class', 'sonar-d3');
+    this.gWrap = this.svg.append('g');
+
+    this.plotWrap = this.gWrap.append('g')
+        .classed('plot', true);
+
+    this.gWrap
+        .attr('transform', trans(this.margin().left, this.margin().top));
+
+
+    // Configure metrics
+    this.mainMetric = this.metricsPriority()[0];
+    this.getMainMetric = function(d) {
+      return d.measures[widget.mainMetric].val;
+    };
+    this.fm = function(value, name) {
+      var type = this.metrics()[name].type;
+
+      switch (type) {
+        case 'FLOAT':
+          return d3.format('.1f')(value);
+        case 'INT':
+          return d3.format('d')(value);
+        default :
+          return value;
+      }
+    };
+
+
+    // Configure scales
+    this.x = d3.scale.ordinal();
+    this.y = d3.scale.linear();
+    this.color = d3.scale.ordinal()
+        .range([
+          '#1f77b4', '#aec7e8', '#3182bd', '#6baed6',
+          '#ff7f0e', '#ffbb78', '#e6550d', '#fd8d3c',
+          '#2ca02c', '#98df8a', '#31a354', '#74c476',
+          '#d62728', '#ff9896', '#ad494a', '#d6616b',
+          '#9467bd', '#c5b0d5', '#756bb1', '#9e9ac8',
+          '#8c564b', '#c49c94', '#ad494a', '#d6616b',
+          '#e377c2', '#f7b6d2', '#ce6dbd', ' #de9ed6',
+          '#7f7f7f', '#c7c7c7', '#969696', ' #bdbdbd',
+          '#bcbd22', '#dbdb8d', '#8ca252', ' #b5cf6b',
+          '#17becf', '#9edae5', '#6baed6', ' #9ecae1'
+        ]);
+
+
+    // Configure details
+    this._metricsCount = Object.keys(this.metrics()).length + 1;
+
+    this.detailsWrap = this.gWrap.append('g');
+
+    this.detailsColorIndicator = this.detailsWrap.append('rect')
+        .classed('details-color-indicator', true)
+        .attr('transform', trans(-1, 0))
+        .attr('x', 0)
+        .attr('y', 0)
+        .attr('width', 3)
+        .attr('height', 2 * this._lineHeight)
+        .style('opacity', 1);
+
+
+    // Update widget
+    this.update(containerS);
+
+    return this;
+  };
+
+
+
+  window.SonarWidgets.Histogram.prototype.update = function(container) {
+    container = d3.select(container);
+
+    var widget = this,
+        width = container.property('offsetWidth');
+    this.width(width > 100 ? width : 100);
+
+
+    // Update svg canvas
+    this.svg
+        .attr('width', this.width())
+        .attr('height', this.height());
+
+
+    // Update available size
+    var detailsHeight = 2 * this._lineHeight;
+    this.availableWidth = this.width() - this.margin().left - this.margin().right;
+    this.availableHeight = this.height() - this.margin().top - this.margin().bottom - detailsHeight;
+
+
+    // Update plot
+    this.plotWrap
+        .attr('transform', trans(0, detailsHeight));
+
+
+    // Update scales
+    this.x
+        .domain(this.components().map(function(d, i) { return i; }))
+        .rangeRoundBands([0, this.availableWidth], 0);
+
+    var yDomain = d3.extent(this.components(), function(d) {
+      return widget.getMainMetric(d);
+    });
+    this.y
+        .domain(yDomain)
+        .range([this.availableHeight, this.availableHeight / 3]);
+
+    if (this.components().length < 11) {
+      this.color = d3.scale.category10();
+    } else if (this.components().length < 21) {
+      this.color = d3.scale.category20();
+    }
+
+
+    // Configure bars
+    this.bars = this.plotWrap.selectAll('.bar')
+        .data(this.components());
+
+    this.bars
+        .enter()
+        .append('rect')
+        .classed('bar', true)
+        .style('fill', function(d, i) { return widget.color(i); })
+        .style('stroke', '#fff');
+
+    this.bars
+        .transition()
+        .attr('x', function(d, i) { return widget.x(i); })
+        .attr('y', function(d) { return widget.availableHeight - widget.y(widget.getMainMetric(d)); })
+        .attr('width', this.x.rangeBand())
+        .attr('height', function(d) { return  widget.y(widget.getMainMetric(d)); });
+
+    this.bars
+        .exit().remove();
+
+
+    // Configure events
+    var enterHandler = function(bar, d, i) {
+          var metrics = widget.metricsPriority().map(function(m) {
+            return {
+              name: widget.metrics()[m].name,
+              value: widget.fm(d.measures[m].val, m)
+            };
+          });
+          metrics.unshift({ name: d.name });
+          updateMetrics(metrics);
+
+          widget.detailsColorIndicator
+              .style('opacity', 1)
+              .style('fill', widget.color(i));
+        },
+
+        leaveHandler = function() {
+          widget.detailsColorIndicator
+              .style('opacity', 0);
+          widget.detailsMetrics
+              .style('opacity', 0);
+        },
+
+        updateMetrics = function(metrics) {
+          widget.detailsMetrics = widget.detailsWrap.selectAll('.details-metric')
+              .data(metrics);
+
+          widget.detailsMetrics.enter().append('text')
+              .classed('details-metric', true)
+              .classed('details-metric-main', function(d, i) { return i === 0; })
+              .attr('transform', function(d, i) { return trans(10, i * widget._lineHeight); })
+              .attr('dy', '1.2em');
+
+          widget.detailsMetrics
+              .text(function(d) { return d.name + (d.value ? ': ' + d.value : ''); })
+              .style('opacity', 1);
+
+          widget.detailsMetrics.exit().remove();
+        };
+
+    this.bars
+        .on('mouseenter', function(d, i) {
+          return enterHandler(this, d, i);
+        })
+        .on('mouseleave', leaveHandler)
+        .on('click', function(d) {
+          window.open(widget.options().baseUrl + encodeURIComponent(d.key));
+        });
+  };
+
+
+
+  window.SonarWidgets.Histogram.defaults = {
+    width: 350,
+    height: 300,
+    margin: { top: 10, right: 10, bottom: 10, left: 10 },
+    legendWidth: 160,
+    legendMargin: 30
+  };
+
+
+
+  // Some helper functions
+
+  // Gets or sets parameter
+  function param(name, value) {
+    if (value == null) {
+      return this[name];
+    } else {
+      this[name] = value;
+      return this;
+    }
+  }
+
+  // Helper for create the translate(x, y) string
+  function trans(left, top) {
+    return 'translate(' + left + ', ' + top + ')';
+  }
+
+})();
index 51db09c8fedbd4684817d7ce90691a225b5bd80b..8a7a1f83d21031c70ad591f9b14891d1bb285d4d 100644 (file)
@@ -2444,11 +2444,13 @@ textarea.width100 {
   transition: all 0.2s ease;
 }
 
-.sonar-d3 .plot:hover .arc {
+.sonar-d3 .plot:hover .arc,
+.sonar-d3 .plot:hover .bar {
   opacity: 0.4;
 }
 
-.sonar-d3 .plot .arc:hover {
+.sonar-d3 .plot .arc:hover,
+.sonar-d3 .plot .bar:hover {
   opacity: 1;
 }
 
@@ -2470,7 +2472,8 @@ textarea.width100 {
   opacity: 0.25;
 }
 
-.sonar-d3 .arc {
+.sonar-d3 .arc,
+.sonar-d3 .bar {
   cursor: pointer;
   stroke: #fff;
   stroke-width: 1px;
index 46f78721d2567a4feac1e9dd2fb89a3422e54831..3b110261c92e49d06999334e0fb35a147e878730 100644 (file)
@@ -32,6 +32,7 @@
     <js>/javascripts/widgets/timeline.js</js>
     <js>/javascripts/widgets/stack-area.js</js>
     <js>/javascripts/widgets/pie-chart.js</js>
+    <js>/javascripts/widgets/histogram.js</js>
 
     <js>/javascripts/select-list.js</js>