From 9c811fbeea8de168d5927f610cc41af5bfffdc07 Mon Sep 17 00:00:00 2001 From: Stas Vilchik Date: Tue, 29 Apr 2014 22:38:08 +0600 Subject: [PATCH] Refactor widgets --- sonar-server/Gruntfile.coffee | 4 +- sonar-server/karma.conf.js | 1 + .../main/coffee/tests/widgets/BaseSpec.coffee | 47 +++ .../src/main/coffee/widgets/base.coffee | 50 +++ .../src/main/coffee/widgets/histogram.coffee | 142 ++++++++ .../src/main/coffee/widgets/word-cloud.coffee | 184 ++++------- sonar-server/src/main/js/tests/main.js | 1 - sonar-server/src/main/js/widgets/histogram.js | 307 ------------------ sonar-server/src/main/less/style.less | 19 ++ 9 files changed, 332 insertions(+), 423 deletions(-) create mode 100644 sonar-server/src/main/coffee/tests/widgets/BaseSpec.coffee create mode 100644 sonar-server/src/main/coffee/widgets/base.coffee create mode 100644 sonar-server/src/main/coffee/widgets/histogram.coffee delete mode 100644 sonar-server/src/main/js/widgets/histogram.js diff --git a/sonar-server/Gruntfile.coffee b/sonar-server/Gruntfile.coffee index 498263d1a77..3dc59ffafc9 100644 --- a/sonar-server/Gruntfile.coffee +++ b/sonar-server/Gruntfile.coffee @@ -65,6 +65,7 @@ module.exports = (grunt) -> '<%= pkg.assets %>js/third-party/select2.js' '<%= pkg.assets %>js/third-party/keymaster.js' '<%= pkg.assets %>js/select2-jquery-ui-fix.js' + '<%= pkg.assets %>js/widgets/base.js' '<%= pkg.assets %>js/widgets/widget.js' '<%= pkg.assets %>js/widgets/bubble-chart.js' '<%= pkg.assets %>js/widgets/timeline.js' @@ -99,6 +100,7 @@ module.exports = (grunt) -> '<%= pkg.assets %>js/third-party/select2.js' '<%= pkg.assets %>js/third-party/keymaster.js' '<%= pkg.assets %>js/select2-jquery-ui-fix.js' + '<%= pkg.assets %>js/widgets/base.js' '<%= pkg.assets %>js/widgets/widget.js' '<%= pkg.assets %>js/widgets/bubble-chart.js' '<%= pkg.assets %>js/widgets/timeline.js' @@ -262,4 +264,4 @@ module.exports = (grunt) -> 'uglify:build', 'requirejs', 'clean:js', 'copy:build', 'copy:requirejs', 'clean:build'] - grunt.registerTask 'test', ['coffee:build', 'handlebars:build', 'copy:js', 'karma:unit'] + grunt.registerTask 'test', ['coffee:build', 'handlebars:build', 'copy:js', 'concat:dev', 'karma:unit'] diff --git a/sonar-server/karma.conf.js b/sonar-server/karma.conf.js index 72936a7621d..037950ad1c1 100644 --- a/sonar-server/karma.conf.js +++ b/sonar-server/karma.conf.js @@ -29,6 +29,7 @@ module.exports = function(config) { 'require.js', 'translate.js', 'common/inputs.js', + 'widgets/base.js', // libs { pattern: 'third-party/**/*.js', included: false }, diff --git a/sonar-server/src/main/coffee/tests/widgets/BaseSpec.coffee b/sonar-server/src/main/coffee/tests/widgets/BaseSpec.coffee new file mode 100644 index 00000000000..797acf75588 --- /dev/null +++ b/sonar-server/src/main/coffee/tests/widgets/BaseSpec.coffee @@ -0,0 +1,47 @@ +$ = jQuery + +describe 'base widget suite', -> + + it 'exists', -> + expect(window.SonarWidgets).toBeDefined() + expect(window.SonarWidgets.BaseWidget).toBeDefined() + + + it 'adds fields', -> + widget = new window.SonarWidgets.BaseWidget() + widget.addField 'fieldName', 1 + + expect(typeof widget.fieldName).toBe 'function' + expect(widget.fieldName()).toBe 1 + + expect(widget.fieldName(2)).toBe widget + expect(widget.fieldName()).toBe 2 + + + it 'adds metrics', -> + widget = new window.SonarWidgets.BaseWidget() + widget.addField 'metrics', 'metricA': { name: 'Metric A', someField: 2 } + widget.addField 'metricsPriority', ['metricA'] + widget.addMetric 'myMetric', 0 + + expect(widget.myMetric).toBeDefined() + expect(widget.myMetric.key).toBe 'metricA' + expect(widget.myMetric.name).toBe 'Metric A' + expect(widget.myMetric.someField).toBe 2 + expect(typeof widget.myMetric.value).toBe 'function' + expect(typeof widget.myMetric.formattedValue).toBe 'function' + + + it 'has default properties', -> + widget = new window.SonarWidgets.BaseWidget() + + expect(widget.components).toBeDefined() + expect(widget.metrics).toBeDefined() + expect(widget.metricsPriority).toBeDefined() + expect(widget.options).toBeDefined() + + + it 'created "translate" string', -> + widget = new window.SonarWidgets.BaseWidget() + + expect(widget.trans(1, 2)).toBe 'translate(1,2)' \ No newline at end of file diff --git a/sonar-server/src/main/coffee/widgets/base.coffee b/sonar-server/src/main/coffee/widgets/base.coffee new file mode 100644 index 00000000000..133c15e464d --- /dev/null +++ b/sonar-server/src/main/coffee/widgets/base.coffee @@ -0,0 +1,50 @@ +window.SonarWidgets ?= {} + +class BaseWidget + lineHeight: 20 + + + constructor: -> + @addField 'components', [] + @addField 'metrics', [] + @addField 'metricsPriority', [] + @addField 'options', [] + @ + + + addField: (name, defaultValue) -> + privateName = "_#{name}" + @[privateName] = defaultValue + @[name] = (d) -> @param.call @, privateName, d + @ + + + param: (name, value) -> + return @[name] unless value? + @[name] = value + @ + + + addMetric: (property, index) -> + key = @metricsPriority()[index] + @[property] = _.extend @metrics()[key], + key: key + value: (d) -> d.measures[key]?.val + formattedValue: (d) -> d.measures[key]?.fval + @ + + + trans: (left, top) -> + "translate(#{left},#{top})" + + + render: (container) -> + @update container + @ + + + update: -> + @ + + +window.SonarWidgets.BaseWidget = BaseWidget \ No newline at end of file diff --git a/sonar-server/src/main/coffee/widgets/histogram.coffee b/sonar-server/src/main/coffee/widgets/histogram.coffee new file mode 100644 index 00000000000..2dacc0b73bc --- /dev/null +++ b/sonar-server/src/main/coffee/widgets/histogram.coffee @@ -0,0 +1,142 @@ +class Histogram extends window.SonarWidgets.BaseWidget + barHeight: 16 + barFill: '#1f77b4' + + constructor: -> + @addField 'width', 0 + @addField 'height', window.SonarWidgets.Histogram.defaults.height + @addField 'margin', window.SonarWidgets.Histogram.defaults.margin + @addField 'legendWidth', window.SonarWidgets.Histogram.defaults.legendWidth + @addField 'maxResultsReached', false + super + + + isDataValid: -> + @components().reduce ((p, c) => p && !! c.measures[@mainMetric.key]), true + + + truncate: (text, type) -> + maxLength = 40 + switch type + when "FIL", "CLA" + n = text.length + if n > maxLength + shortText = text.substr(n - maxLength + 2, n - 1) + dotIndex = shortText.indexOf(".") + return "..." + shortText.substr(dotIndex + 1) + else + return text + else + (if text.length > maxLength then text.substr(0, maxLength - 3) + "..." else text) + + + render: (container) -> + box = d3.select container + + # Configure the main metric + @addMetric 'mainMetric', 0 + + unless @isDataValid() + box.text @options().noMainMetric + return + + @width box.property 'offsetWidth' + + # Create skeleton + @svg = box.append('svg').classed 'sonar-d3', true + @gWrap = @svg.append 'g' + @gWrap.attr 'transform', @trans(@margin().left, @margin().top) + @plotWrap = @gWrap.append('g').classed 'plot', true + + # Configure scales + @x = d3.scale.linear() + @y = d3.scale.ordinal() + + @metricLabel = @gWrap.append('text').text @mainMetric.name + @metricLabel.attr('dy', '9px').style 'font-size', '12px' + + if @maxResultsReached() + @maxResultsReachedLabel = @gWrap.append('text').classed 'max-results-reached-message', true + @maxResultsReachedLabel.text @options().maxItemsReachedMessage + + super + + + update: (container) -> + box = d3.select(container) + + @width box.property 'offsetWidth' + availableWidth = @width() - @margin().left - @margin().right - @legendWidth() + availableHeight = @barHeight * @components().length + @lineHeight + totalHeight = availableHeight + @margin().top + @margin().bottom + totalHeight += @lineHeight if @maxResultsReached() + @height totalHeight + + @svg.attr('width', @width()).attr 'height', @height() + @plotWrap.attr 'transform', @trans 0, @lineHeight + + # Configure scales + xDomain = d3.extent @components(), (d) => @mainMetric.value d + unless @options().relativeScale + if @mainMetric.type == 'PERCENT' + xDomain = [0, 100] + else + xDomain[0] = 0 + + @x.domain xDomain + .range [0, availableWidth] + + @y.domain @components().map (d, i) -> i + .rangeRoundBands [0, availableHeight], 0 + + # Configure bars + @bars = @plotWrap.selectAll('.bar').data @components() + @barsEnter = @bars.enter().append 'g' + .classed 'bar', true + .attr 'transform', (d, i) => @trans 0, i * @barHeight + @barsEnter.append 'rect' + .style 'fill', @barFill + @barsEnter.append 'text' + .classed 'legend-text component', true + .style 'text-anchor', 'end' + .attr 'dy', '-0.35em' + .text (d) => @truncate d.longName, d.qualifier + .attr 'transform', => @trans @legendWidth() - 10, @barHeight + @barsEnter.append 'text' + .classed 'legend-text value', true + .attr 'dy', '-0.35em' + .text (d) => @mainMetric.formattedValue d + .attr 'transform', (d) => @trans @legendWidth() + @x(@mainMetric.value d) + 5, @barHeight + @bars.selectAll 'rect' + .transition() + .attr 'x', @legendWidth() + .attr 'y', 0 + .attr 'width', (d) => Math.max 2, @x(@mainMetric.value d) + .attr 'height', @barHeight + @bars.selectAll '.component' + .transition() + .attr 'transform', => @trans @legendWidth() - 10, @barHeight + @bars.selectAll '.value' + .transition() + .attr 'transform', (d) => @trans @legendWidth() + @x(@mainMetric.value d) + 5, @barHeight + @bars.exit().remove() + @bars.on 'click', (d) => + url = @options().baseUrl + encodeURIComponent d.key + if d.qualifier == 'CLA' || d.qualifier == 'FIL' + url += '?metric=' + encodeURIComponent @mainMetric.key + window.location = url + + @metricLabel.attr 'transform', @trans @legendWidth(), 0 + + if @maxResultsReached() + @maxResultsReachedLabel.attr 'transform', (@trans @legendWidth(), @height() - @margin().bottom - 3) + + super + + + +window.SonarWidgets.Histogram = Histogram +window.SonarWidgets.Histogram.defaults = + height: 300 + margin: { top: 4, right: 50, bottom: 4, left: 10 } + legendWidth: 220 \ No newline at end of file diff --git a/sonar-server/src/main/coffee/widgets/word-cloud.coffee b/sonar-server/src/main/coffee/widgets/word-cloud.coffee index 8b718ef50b7..1e09b55a6d5 100644 --- a/sonar-server/src/main/coffee/widgets/word-cloud.coffee +++ b/sonar-server/src/main/coffee/widgets/word-cloud.coffee @@ -1,114 +1,70 @@ -# Some helper functions - -# Gets or sets parameter -param = (name, value) -> - unless value? - return this[name] - else - this[name] = value - return @ - - -window.SonarWidgets ?= {} - -window.SonarWidgets.WordCloud = -> - @_components = [] - @_metrics = [] - @_metricsPriority = [] - @_width = window.SonarWidgets.WordCloud.defaults.width - @_height = window.SonarWidgets.WordCloud.defaults.height - @_margin = window.SonarWidgets.WordCloud.defaults.margin - @_legendWidth = window.SonarWidgets.WordCloud.defaults.legendWidth - @_legendMargin = window.SonarWidgets.WordCloud.defaults.legendMargin - @_detailsWidth = window.SonarWidgets.WordCloud.defaults.detailsWidth - @_maxResultsReached = false - @_options = {} - - @_lineHeight = 20 - - # Export global variables - @metrics = (_) -> param.call(this, '_metrics', _) - @metricsPriority = (_) -> param.call(this, '_metricsPriority', _) - @components = (_) -> param.call(this, '_components', _) - @width = (_) -> param.call(this, '_width', _) - @height = (_) -> param.call(this, '_height', _) - @margin = (_) -> param.call(this, '_margin', _) - @legendWidth = (_) -> param.call(this, '_legendWidth', _) - @legendMargin = (_) -> param.call(this, '_legendMargin', _) - @detailsWidth = (_) -> param.call(this, '_detailsWidth', _) - @maxResultsReached = (_) -> param.call(this, '_maxResultsReached', _) - @options = (_) -> param.call(this, '_options', _) - @ - - -window.SonarWidgets.WordCloud.prototype.render = (container) -> - @box = d3.select(container).append('div').classed 'sonar-d3', true - @box.style 'text-align', 'center' - - # Configure metrics - @colorMetric = @metricsPriority()[0] - @getColorMetric = (d) => d.measures[@colorMetric]?.val - @getFColorMetric = (d) => d.measures[@colorMetric]?.fval - @sizeMetric = @metricsPriority()[1] - @getSizeMetric = (d) => d.measures[@sizeMetric]?.val - @getFSizeMetric = (d) => d.measures[@sizeMetric]?.fval - - # Configure scales - @color = d3.scale.linear().domain([0, 100]) - if @metrics()[@colorMetric].direction == 1 - @color.range ['#d62728', '#1f77b4'] - else - @color.range ['#1f77b4', '#d62728'] - - sizeDomain = d3.extent @components(), (d) => @getSizeMetric d - @size = d3.scale.linear().domain(sizeDomain).range([10, 24]) - - @update container - @ - - -window.SonarWidgets.WordCloud.prototype.update = -> - # Configure words - @words = @box.selectAll('a').data @components() - - wordsEnter = @words.enter().append('a') - wordsEnter.classed 'cloud-word', true - wordsEnter.text (d) -> d.name - wordsEnter.attr 'href', (d) => - url = @options().baseUrl + encodeURIComponent(d.key) - url += '?metric=' + encodeURIComponent(@colorMetric) if d.qualifier == 'CLA' || d.qualifier == 'FIL' - url - wordsEnter.attr 'title', (d) => - title = d.longName - title += " | #{@metrics()[@colorMetric].name}: #{@getFColorMetric d}" if @getColorMetric(d)? - title += " | #{@metrics()[@sizeMetric].name}: #{@getFSizeMetric d}" if @getSizeMetric(d)? - title - - @words.style 'color', (d) => - if @getColorMetric(d)? then @color @getColorMetric d else '#999' - @words.style 'font-size', (d) => "#{@size @getSizeMetric d}px" - - @words.sort (a, b) => - if a.name.toLowerCase() > b.name.toLowerCase() then 1 else -1 - - # Show maxResultsReached message - if @maxResultsReached() - @maxResultsReachedLabel.remove() if @maxResultsReachedLabel? - @maxResultsReachedLabel = @box.append('div').text @options().maxItemsReachedMessage - @maxResultsReachedLabel.style 'color', '#777' - @maxResultsReachedLabel.style 'font-size', '12px' - @maxResultsReachedLabel.style 'text-align', 'center' - @maxResultsReachedLabel.style 'margin-top', '10px' - - @words.exit().remove() - - - - - -window.SonarWidgets.WordCloud.defaults = - width: 350 - height: 300 - margin: { top: 10, right: 10, bottom: 10, left: 10 } - legendWidth: 160 - legendMargin: 30 \ No newline at end of file +class WordCloud extends window.SonarWidgets.BaseWidget + colorLow: '#d62728' + colorHigh: '#1f77b4' + colorUnknown: '#777' + sizeLow: 10 + sizeHigh: 24 + + + constructor: -> + @addField 'width', [] + @addField 'height', [] + @addField 'maxResultsReached', false + super + + + renderWords: -> + words = @wordContainer.selectAll('.cloud-word').data @components() + + wordsEnter = words.enter().append('a').classed 'cloud-word', true + wordsEnter.text (d) -> d.name + wordsEnter.attr 'href', (d) => + url = @options().baseUrl + encodeURIComponent(d.key) + url += '?metric=' + encodeURIComponent(@colorMetric.key) if d.qualifier == 'CLA' || d.qualifier == 'FIL' + url + wordsEnter.attr 'title', (d) => + title = d.longName + title += " | #{@colorMetric.name}: #{@colorMetric.formattedValue d}" if @colorMetric.value(d)? + title += " | #{@sizeMetric.name}: #{@sizeMetric.formattedValue d}" if @sizeMetric.value(d)? + title + + words.style 'color', (d) => + if @colorMetric.value(d)? then @color @colorMetric.value(d) else @colorUnknown + words.style 'font-size', (d) => "#{@size @sizeMetric.value d}px" + + words.sort (a, b) => + if a.name.toLowerCase() > b.name.toLowerCase() then 1 else -1 + + + render: (container) -> + box = d3.select(container).append('div') + box.classed 'sonar-d3', true + box.classed 'cloud-widget', true + @wordContainer = box.append 'div' + + # Configure metrics + @addMetric 'colorMetric', 0 + @addMetric 'sizeMetric', 1 + + # Configure scales + @color = d3.scale.linear().domain([0, 100]) + if @colorMetric.direction == 1 + @color.range [@colorLow, @colorHigh] + else + @color.range [@colorHigh, @colorLow] + + sizeDomain = d3.extent @components(), (d) => @sizeMetric.value d + @size = d3.scale.linear().domain(sizeDomain).range [@sizeLow, @sizeHigh] + + # Show maxResultsReached message + if @maxResultsReached() + maxResultsReachedLabel = box.append('div').text @options().maxItemsReachedMessage + maxResultsReachedLabel.classed 'max-results-reached-message', true + + @renderWords() + + super + + + +window.SonarWidgets.WordCloud = WordCloud \ No newline at end of file diff --git a/sonar-server/src/main/js/tests/main.js b/sonar-server/src/main/js/tests/main.js index c012e4777e2..eb95545cb5b 100644 --- a/sonar-server/src/main/js/tests/main.js +++ b/sonar-server/src/main/js/tests/main.js @@ -9,7 +9,6 @@ for (var file in window.__karma__.files) { } } - requirejs.config({ baseUrl: '/base', diff --git a/sonar-server/src/main/js/widgets/histogram.js b/sonar-server/src/main/js/widgets/histogram.js deleted file mode 100644 index 3cce1220605..00000000000 --- a/sonar-server/src/main/js/widgets/histogram.js +++ /dev/null @@ -1,307 +0,0 @@ -/*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._maxResultsReached = false; - 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.maxResultsReached = function (_) { - return param.call(this, '_maxResultsReached', _); - }; - - this.options = function (_) { - return param.call(this, '_options', _); - }; - }; - - window.SonarWidgets.Histogram.prototype.render = function (container) { - var widget = this, - containerS = container; - - container = d3.select(container); - - var validData = this.components().reduce(function(p, c) { - return p && !!c.measures[widget.metricsPriority()[0]]; - }, true); - - if (!validData) { - container.text(this.options().noMainMetric); - return; - } - - - 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; - }; - - - // Configure scales - this.x = d3.scale.linear(); - this.y = d3.scale.ordinal(); - - - // Configure truncate function - this.truncate = function(text, type) { - var maxLength = 40; - - switch (type) { - case 'FIL': - case 'CLA': - var n = text.length; - if (n > maxLength) { - var shortText = text.substr(n - maxLength + 2, n - 1), - dotIndex = shortText.indexOf('.'); - return '...' + shortText.substr(dotIndex + 1); - } else { - return text; - } - break; - default: - return text.length > maxLength ? - text.substr(0, maxLength - 3) + '...' : - text; - } - }; - - - // Configure metric label - this.metricLabel = this.gWrap.append('text') - .text(this.metrics()[this.mainMetric].name) - .attr('dy', '9px') - .style('font-size', '12px'); - - - // Show maxResultsReached message - if (this.maxResultsReached()) { - this.maxResultsReachedLabel = this.gWrap.append('text') - .classed('max-results-reached', true) - .style('font-size', '12px') - .style('fill', '#777') - .text(this.options().maxItemsReachedMessage); - } - - - // Update widget - this.update(containerS); - - return this; - }; - - - - window.SonarWidgets.Histogram.prototype.update = function(container) { - container = d3.select(container); - - var widget = this, - barHeight = 16, - width = container.property('offsetWidth'); - this.width(width > 100 ? width : 100); - - - // Update available size - this.availableWidth = this.width() - this.margin().left - this.margin().right - this.legendWidth(); - this.availableHeight = barHeight * this.components().length + this._lineHeight; - var totalHeight = this.availableHeight + this.margin().top + this.margin().bottom; - if (this.maxResultsReached()) { - totalHeight += this._lineHeight; - } - this.height(totalHeight); - - - // Update svg canvas - this.svg - .attr('width', this.width()) - .attr('height', this.height()); - - - // Update plot - this.plotWrap - .attr('transform', trans(0, this._lineHeight)); - - - // Update scales - var xDomain = d3.extent(this.components(), function(d) { - return widget.getMainMetric(d); - }); - - if (!this.options().relativeScale) { - if (this.metrics()[this.mainMetric].type === 'PERCENT') { - xDomain = [0, 100]; - } else { - xDomain[0] = 0; - } - } - - this.x - .domain(xDomain) - .range([0, this.availableWidth]); - - this.y - .domain(this.components().map(function(d, i) { return i; })) - .rangeRoundBands([0, this.availableHeight], 0); - - - // Configure bars - this.bars = this.plotWrap.selectAll('.bar') - .data(this.components()); - - this.barsEnter = this.bars.enter() - .append('g') - .classed('bar', true) - .attr('transform', function(d, i) { return trans(0, i * barHeight); }); - - this.barsEnter - .append('rect') - .style('fill', '#1f77b4'); - - this.barsEnter - .append('text') - .classed('legend-text component', true) - .style('text-anchor', 'end') - .attr('dy', '-0.35em') - .text(function(d) { return widget.truncate(d.longName, d.qualifier); }) - .attr('transform', function() { return trans(widget.legendWidth() - 10, barHeight); }); - - this.barsEnter - .append('text') - .classed('legend-text value', true) - .attr('dy', '-0.35em') - .text(function(d) { return d.measures[widget.mainMetric].fval; }) - .attr('transform', function(d) { - return trans(widget.legendWidth() + widget.x(widget.getMainMetric(d)) + 5, barHeight); - }); - - this.bars.selectAll('rect') - .transition() - .attr('x', this.legendWidth()) - .attr('y', 0) - .attr('width', function(d) { return Math.max(2, widget.x(widget.getMainMetric(d))); }) - .attr('height', barHeight); - - this.bars.selectAll('.component') - .transition() - .attr('transform', function() { return trans(widget.legendWidth() - 10, barHeight); }); - - this.bars.selectAll('.value') - .transition() - .attr('transform', function(d) { - return trans(widget.legendWidth() + widget.x(widget.getMainMetric(d)) + 5, barHeight); - }); - - this.bars - .exit().remove(); - - this.bars - .on('click', function(d) { - switch (d.qualifier) { - case 'CLA': - case 'FIL': - window.location = widget.options().baseUrl + encodeURIComponent(d.key) + - '?metric=' + encodeURIComponent(widget.mainMetric); - break; - default: - window.location = widget.options().baseUrl + encodeURIComponent(d.key); - } - }); - - - // Configure metric label - this.metricLabel - .attr('transform', trans(this.legendWidth(), 0)); - - - // Show maxResultsReached message - if (this.maxResultsReached()) { - this.maxResultsReachedLabel - .attr('transform', trans(this.legendWidth(), this.height() - this.margin().bottom - 3)); - } - }; - - - - window.SonarWidgets.Histogram.defaults = { - width: 350, - height: 300, - margin: { top: 4, right: 50, bottom: 4, left: 10 }, - legendWidth: 220 - }; - - - - // 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/less/style.less b/sonar-server/src/main/less/style.less index 5cdb6e32113..e6309af27db 100644 --- a/sonar-server/src/main/less/style.less +++ b/sonar-server/src/main/less/style.less @@ -2743,6 +2743,10 @@ div.rule-title { transition: all 0.3s ease; } +.sonar-d3.cloud-widget { + text-align: center; +} + .sonar-d3 .cloud-word { display: inline-block; vertical-align: baseline; @@ -2753,6 +2757,21 @@ div.rule-title { &:hover { text-decoration: underline; } } +.sonar-d3 .max-results-reached-message { + font-size: 12px; +} + +.sonar-d3 div.max-results-reached-message { + margin-top: 10px; + color: #777; + text-align: center; +} + +.sonar-d3 text.max-results-reached-message { + fill: #777; +} + + /* ------------------- Admin pages ------------------- */ .admin-page-title { -- 2.39.5