From c64ea8dbb039d6bc7e40385ce05289287c23301b Mon Sep 17 00:00:00 2001 From: Stas Vilchik Date: Mon, 16 Dec 2013 16:49:07 +0600 Subject: [PATCH] SONAR-3762 Provide a new Histogram widget to display a measure filter First preview --- .../plugins/core/widgets/histogram.html.erb | 53 +++- .../WEB-INF/app/views/layouts/_head.html.erb | 1 + .../webapp/javascripts/widgets/histogram.js | 290 ++++++++++++++++++ .../src/main/webapp/stylesheets/style.css | 9 +- sonar-server/wro.xml | 1 + 5 files changed, 347 insertions(+), 7 deletions(-) create mode 100644 sonar-server/src/main/webapp/javascripts/widgets/histogram.js diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/histogram.html.erb b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/histogram.html.erb index 7a2951cd6c4..7838b44ebfa 100644 --- a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/histogram.html.erb +++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/histogram.html.erb @@ -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 +%> + +
+ + + + <% if chartTitle %> +

<%= h(chartTitle) -%>

+ <% end %> + +
+ + + + diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_head.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_head.html.erb index cd405b7318f..77d4b16e06d 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_head.html.erb +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_head.html.erb @@ -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 index 00000000000..147d606c95f --- /dev/null +++ b/sonar-server/src/main/webapp/javascripts/widgets/histogram.js @@ -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 + ')'; + } + +})(); diff --git a/sonar-server/src/main/webapp/stylesheets/style.css b/sonar-server/src/main/webapp/stylesheets/style.css index 51db09c8fed..8a7a1f83d21 100644 --- a/sonar-server/src/main/webapp/stylesheets/style.css +++ b/sonar-server/src/main/webapp/stylesheets/style.css @@ -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; diff --git a/sonar-server/wro.xml b/sonar-server/wro.xml index 46f78721d25..3b110261c92 100644 --- a/sonar-server/wro.xml +++ b/sonar-server/wro.xml @@ -32,6 +32,7 @@ /javascripts/widgets/timeline.js /javascripts/widgets/stack-area.js /javascripts/widgets/pie-chart.js + /javascripts/widgets/histogram.js /javascripts/select-list.js -- 2.39.5