From d3608358d7838429a4c0e068d82b37bec74e5d87 Mon Sep 17 00:00:00 2001 From: Stas Vilchik Date: Fri, 29 May 2015 14:28:20 +0200 Subject: [PATCH] move widgets from coffee to js --- .../src/main/coffee/libs/widgets/base.coffee | 88 ----- .../main/coffee/libs/widgets/histogram.coffee | 159 --------- .../main/coffee/libs/widgets/tag-cloud.coffee | 83 ----- .../main/coffee/libs/widgets/treemap.coffee | 254 -------------- .../coffee/libs/widgets/word-cloud.coffee | 81 ----- .../src/main/js/libs/widgets/base.js | 93 +++++ .../src/main/js/libs/widgets/histogram.js | 164 +++++++++ .../src/main/js/libs/widgets/tag-cloud.js | 77 ++++ .../src/main/js/libs/widgets/treemap.js | 329 ++++++++++++++++++ .../src/main/js/libs/widgets/word-cloud.js | 76 ++++ 10 files changed, 739 insertions(+), 665 deletions(-) delete mode 100644 server/sonar-web/src/main/coffee/libs/widgets/base.coffee delete mode 100644 server/sonar-web/src/main/coffee/libs/widgets/histogram.coffee delete mode 100644 server/sonar-web/src/main/coffee/libs/widgets/tag-cloud.coffee delete mode 100644 server/sonar-web/src/main/coffee/libs/widgets/treemap.coffee delete mode 100644 server/sonar-web/src/main/coffee/libs/widgets/word-cloud.coffee create mode 100644 server/sonar-web/src/main/js/libs/widgets/base.js create mode 100644 server/sonar-web/src/main/js/libs/widgets/histogram.js create mode 100644 server/sonar-web/src/main/js/libs/widgets/tag-cloud.js create mode 100644 server/sonar-web/src/main/js/libs/widgets/treemap.js create mode 100644 server/sonar-web/src/main/js/libs/widgets/word-cloud.js diff --git a/server/sonar-web/src/main/coffee/libs/widgets/base.coffee b/server/sonar-web/src/main/coffee/libs/widgets/base.coffee deleted file mode 100644 index d2f914b8427..00000000000 --- a/server/sonar-web/src/main/coffee/libs/widgets/base.coffee +++ /dev/null @@ -1,88 +0,0 @@ -# -# SonarQube, open source software quality management tool. -# Copyright (C) 2008-2014 SonarSource -# mailto:contact AT sonarsource DOT com -# -# SonarQube is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 3 of the License, or (at your option) any later version. -# -# SonarQube is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# - -window.SonarWidgets ?= {} - -class BaseWidget - lineHeight: 20 - - colors4: ['#ee0000', '#f77700', '#80cc00', '#00aa00'] - colors4r: ['#00aa00', '#80cc00', '#f77700', '#ee0000'] - colors5: ['#ee0000', '#f77700', '#ffee00', '#80cc00', '#00aa00'] - colors5r: ['#00aa00', '#80cc00', '#ffee00', '#f77700', '#ee0000'] - colorsLevel: ['#d4333f', '#ff9900', '#85bb43', '##b4b4b4'] - colorUnknown: '#777' - - - 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) -> - if d.measures[key]? - if d.measures[key].text? then d.measures[key].text else d.measures[key].val - formattedValue: (d) -> - if d.measures[key]? - if d.measures[key].text? then d.measures[key].text else d.measures[key].fval - @ - - - trans: (left, top) -> - "translate(#{left},#{top})" - - - render: (container) -> - @update container - @ - - - update: -> - @ - - - tooltip: (d) -> - title = d.longName - title += "\n#{@colorMetric.name}: #{@colorMetric.formattedValue d}" if @colorMetric and @colorMetric.value(d)? - title += "\n#{@sizeMetric.name}: #{@sizeMetric.formattedValue d}" if @sizeMetric and @sizeMetric.value(d)? - title - - -window.SonarWidgets.BaseWidget = BaseWidget diff --git a/server/sonar-web/src/main/coffee/libs/widgets/histogram.coffee b/server/sonar-web/src/main/coffee/libs/widgets/histogram.coffee deleted file mode 100644 index c62628a56eb..00000000000 --- a/server/sonar-web/src/main/coffee/libs/widgets/histogram.coffee +++ /dev/null @@ -1,159 +0,0 @@ -# -# SonarQube, open source software quality management tool. -# Copyright (C) 2008-2014 SonarSource -# mailto:contact AT sonarsource DOT com -# -# SonarQube is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 3 of the License, or (at your option) any later version. -# -# SonarQube is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# - -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) => - window.location = @options().baseUrl + '?id=' + encodeURIComponent(d.key) - - @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 diff --git a/server/sonar-web/src/main/coffee/libs/widgets/tag-cloud.coffee b/server/sonar-web/src/main/coffee/libs/widgets/tag-cloud.coffee deleted file mode 100644 index f9202d80728..00000000000 --- a/server/sonar-web/src/main/coffee/libs/widgets/tag-cloud.coffee +++ /dev/null @@ -1,83 +0,0 @@ -# -# SonarQube, open source software quality management tool. -# Copyright (C) 2008-2014 SonarSource -# mailto:contact AT sonarsource DOT com -# -# SonarQube is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 3 of the License, or (at your option) any later version. -# -# SonarQube is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# - -class TagCloud extends window.SonarWidgets.BaseWidget - sizeLow: 10 - sizeHigh: 24 - - - constructor: -> - @addField 'width', [] - @addField 'height', [] - @addField 'tags', [] - @addField 'maxResultsReached', false - super - - - renderWords: -> - window.requestMessages().done => - words = @wordContainer.selectAll('.cloud-word').data @tags() - - wordsEnter = words.enter().append('a').classed 'cloud-word', true - wordsEnter.text (d) -> d.key - wordsEnter.attr 'href', (d) => - url = @options().baseUrl + '|tags=' + d.key - if @options().createdAfter - url += '|createdAfter=' + @options().createdAfter - url - wordsEnter.attr 'title', (d) => @tooltip d - - words.style 'font-size', (d) => - "#{@size d.value}px" - - words.sort (a, b) => - if a.key.toLowerCase() > b.key.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' - - sizeDomain = d3.extent @tags(), (d) => d.value - @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 - - - tooltip: (d) -> - suffixKey = if d.value == 1 then 'issue' else 'issues' - suffix = t(suffixKey) - "#{d.value}\u00a0" + suffix - - - parseSource: (response) -> - @tags(response.tags) - - -window.SonarWidgets.TagCloud = TagCloud diff --git a/server/sonar-web/src/main/coffee/libs/widgets/treemap.coffee b/server/sonar-web/src/main/coffee/libs/widgets/treemap.coffee deleted file mode 100644 index 9a78011c8be..00000000000 --- a/server/sonar-web/src/main/coffee/libs/widgets/treemap.coffee +++ /dev/null @@ -1,254 +0,0 @@ -# -# SonarQube, open source software quality management tool. -# Copyright (C) 2008-2014 SonarSource -# mailto:contact AT sonarsource DOT com -# -# SonarQube is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 3 of the License, or (at your option) any later version. -# -# SonarQube is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# - -class Treemap extends window.SonarWidgets.BaseWidget - sizeLow: 11 - sizeHigh: 18 - - - constructor: -> - @addField 'width', null - @addField 'height', null - @addField 'maxResultsReached', false - super - - - getNodes: -> - @treemap.nodes(children: @components()).filter (d) -> !d.children - - - renderTreemap: -> - nodes = @getNodes() - @cells = @box.selectAll('.treemap-cell').data nodes - @cells.exit().remove() - - cellsEnter = @cells.enter().append 'div' - cellsEnter.classed 'treemap-cell', true - cellsEnter.append('div').classed 'treemap-inner', true - cellsEnter.append('a').classed 'treemap-link', true - - @cells.attr 'title', (d) => @tooltip d - @cells.style 'background-color', (d) => - if @colorMetric.value(d)? then @color @colorMetric.value(d) else @colorUnknown - @cells.classed 'treemap-cell-drilldown', (d) -> - d.qualifier? && d.qualifier != 'FIL' && d.qualifier != 'CLA' - - prefix = @mostCommonPrefix _.pluck @components(), 'longName' - prefixLength = prefix.length - @cellsInner = @box.selectAll('.treemap-inner').data nodes - @cellsInner.html (d) -> - if prefixLength > 0 - "#{prefix}
#{d.longName.substr prefixLength}" - else d.longName - - @cellsLink = @box.selectAll('.treemap-link').data nodes - @cellsLink.html '' - @cellsLink.attr 'href', (d) => - @options().baseUrl + '?id=' + encodeURIComponent(d.key) - - @attachEvents cellsEnter - - @maxResultsReachedLabel.style 'display', if @maxResultsReached() then 'block' else 'none' - - - updateTreemap: (components, maxResultsReached) -> - @components components - @maxResultsReached maxResultsReached - - @renderTreemap() - @positionCells() - - - attachEvents: (cells) -> - cells.on 'click', (d) => @requestChildren d - - - positionCells: -> - @cells.style 'left', (d) -> "#{d.x}px" - @cells.style 'top', (d) -> "#{d.y}px" - @cells.style 'width', (d) -> "#{d.dx}px" - @cellsInner.style 'max-width', (d) -> "#{d.dx}px" - @cells.style 'height', (d) -> "#{d.dy}px" - @cells.style 'line-height', (d) -> "#{d.dy}px" - @cells.style 'font-size', (d) => "#{@size (d.dx / d.longName.length)}px" - @cellsLink.style 'font-size', (d) => "#{@sizeLink Math.min(d.dx, d.dy)}px" - @cells.classed 'treemap-cell-small', (d) -> (d.dx / d.longName.length) < 1 || d.dy < 40 - @cells.classed 'treemap-cell-very-small', (d) -> d.dx < 24 || d.dy < 24 - - - renderLegend: (box) -> - @legend = box.insert 'div', ':first-child' - @legend.classed 'legend', true - @legend.classed 'legend-html', true - @legend.append('span').classed('legend-text', true).html "Size: #{@sizeMetric.name}" - @legend.append('span').classed('legend-text', true).html "Color: #{@colorMetric.name}" - - - renderBreadcrumbs: (box) -> - @breadcrumbsBox = box.append('div').classed 'treemap-breadcrumbs', true - @breadcrumbs = [] - d = name: '', components: @components(), maxResultsReached: @maxResultsReached() - @addToBreadcrumbs d - - - updateBreadcrumbs: -> - breadcrumbs = @breadcrumbsBox.selectAll('.treemap-breadcrumbs-item').data @breadcrumbs - breadcrumbs.exit().remove() - breadcrumbsEnter = breadcrumbs.enter().append('span').classed 'treemap-breadcrumbs-item', true - breadcrumbsEnter.attr 'title', (d) => - if d.longName? then d.longName else @options().resource - breadcrumbsEnter.append('i').classed('icon-chevron-right', true).style 'display', (d, i) -> - if i > 0 then 'inline' else 'none' - breadcrumbsEnter.append('i').attr 'class', (d) -> - if d.qualifier? then "icon-qualifier-#{d.qualifier.toLowerCase()}" else '' - breadcrumbsEnterLinks = breadcrumbsEnter.append 'a' - breadcrumbsEnterLinks.html (d) -> d.name - breadcrumbsEnterLinks.on 'click', (d) => - @updateTreemap d.components, d.maxResultsReached - @cutBreadcrumbs d - @breadcrumbsBox.style 'display', if @breadcrumbs.length < 2 then 'none' else 'block' - - - addToBreadcrumbs: (d) -> - @breadcrumbs.push d - @updateBreadcrumbs() - - - cutBreadcrumbs: (lastElement) -> - index = null - @breadcrumbs.forEach (d, i) -> - index = i if d.key == lastElement.key - if index? - @breadcrumbs = _.initial @breadcrumbs, @breadcrumbs.length - index - 1 - @updateBreadcrumbs() - - - getColorScale: -> - return @getLevelColorScale() if @colorMetric.type == 'LEVEL' - return @getRatingColorScale() if @colorMetric.type == 'RATING' - @getPercentColorScale() - - - getPercentColorScale: -> - color = d3.scale.linear().domain([0, 25, 50, 75, 100]) - color.range if @colorMetric.direction == 1 then @colors5 else @colors5r - color - - - getRatingColorScale: -> - domain = [1, 2, 3, 4, 5] - if @components().length > 0 - colorMetricSample = @colorMetric.value _.first @components() - if typeof colorMetricSample == 'string' - domain = ['A', 'B', 'C', 'D', 'E'] - color = d3.scale.ordinal().domain(domain).range @colors5r - color - - - getLevelColorScale: -> - color = d3.scale.ordinal().domain(['ERROR', 'WARN', 'OK', 'NONE']).range @colorsLevel - color - - - render: (container) -> - box = d3.select(container).append('div') - box.classed 'sonar-d3', true - @box = box.append('div').classed 'treemap-container', true - - # Configure metrics - @addMetric 'colorMetric', 0 - @addMetric 'sizeMetric', 1 - - # Configure scales - @color = @getColorScale() - - @size = d3.scale.linear().domain([3, 15]).range([@sizeLow, @sizeHigh]).clamp true - @sizeLink = d3.scale.linear().domain([60, 100]).range([12, 14]).clamp true - - @treemap = d3.layout.treemap() - @treemap.sort (a, b) -> a.value - b.value - @treemap.round true - - @treemap.value (d) => @sizeMetric.value d - - @maxResultsReachedLabel = box.append('div').text @options().maxItemsReachedMessage - @maxResultsReachedLabel.classed 'max-results-reached-message', true - - @renderLegend box - @renderBreadcrumbs box - @renderTreemap() - super - - - update: -> - @width @box.property 'offsetWidth' - @height (@width() / 100.0 * @options().heightInPercents) - @box.style 'height', "#{@height()}px" - @treemap.size [@width(), @height()] - @cells.data @getNodes() - @positionCells() - - - formatComponents: (data) -> - components = _.filter data, (component) => - hasSizeMetric = => _.findWhere component.msr, key: @sizeMetric.key - _.isArray(component.msr) && component.msr.length > 0 && hasSizeMetric() - - if _.isArray(components) && components.length > 0 - components.map (component) => - measures = {} - component.msr.forEach (measure) -> - measures[measure.key] = val: measure.val, fval: measure.frmt_val - - key: if component.copy? then component.copy else component.key - name: component.name - longName: component.lname - qualifier: component.qualifier - measures: measures - - - requestChildren: (d) -> - metrics = @metricsPriority().join ',' - RESOURCES_URL = "#{baseUrl}/api/resources/index" - jQuery.get(RESOURCES_URL, resource: d.key, depth: 1, metrics: metrics).done (r) => - components = @formatComponents r - if components? - components = _.sortBy components, (d) => -@sizeMetric.value d - components = _.initial components, components.length - @options().maxItems - 1 - @updateTreemap components, components.length > @options().maxItems - @addToBreadcrumbs _.extend d, components: components, maxResultsReached: @maxResultsReached() - - - mostCommonPrefix: (strings) -> - sortedStrings = strings.slice(0).sort() - firstString = sortedStrings[0] - firstStringLength = firstString.length - lastString = sortedStrings[sortedStrings.length - 1] - i = 0 - while i < firstStringLength && firstString.charAt(i) == lastString.charAt(i) - i++ - prefix = firstString.substr 0, i - lastPrefixPart = _.last prefix.split /[\s\\\/]/ - prefix.substr 0, prefix.length - lastPrefixPart.length - - - - -window.SonarWidgets.Treemap = Treemap diff --git a/server/sonar-web/src/main/coffee/libs/widgets/word-cloud.coffee b/server/sonar-web/src/main/coffee/libs/widgets/word-cloud.coffee deleted file mode 100644 index 417fb6f032c..00000000000 --- a/server/sonar-web/src/main/coffee/libs/widgets/word-cloud.coffee +++ /dev/null @@ -1,81 +0,0 @@ -# -# SonarQube, open source software quality management tool. -# Copyright (C) 2008-2014 SonarSource -# mailto:contact AT sonarsource DOT com -# -# SonarQube is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 3 of the License, or (at your option) any later version. -# -# SonarQube is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# - -class WordCloud extends window.SonarWidgets.BaseWidget - 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) => - @options().baseUrl + '?id=' + encodeURIComponent(d.key) - wordsEnter.attr 'title', (d) => @tooltip d - - 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, 33, 67, 100]) - if @colorMetric.direction == 1 - @color.range @colors4 - else - @color.range @colors4r - - 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 diff --git a/server/sonar-web/src/main/js/libs/widgets/base.js b/server/sonar-web/src/main/js/libs/widgets/base.js new file mode 100644 index 00000000000..3a788a0d615 --- /dev/null +++ b/server/sonar-web/src/main/js/libs/widgets/base.js @@ -0,0 +1,93 @@ +(function () { + + if (window.SonarWidgets == null) { + window.SonarWidgets = {}; + } + + function BaseWidget () { + this.addField('components', []); + this.addField('metrics', []); + this.addField('metricsPriority', []); + this.addField('options', []); + } + + BaseWidget.prototype.lineHeight = 20; + BaseWidget.prototype.colors4 = ['#ee0000', '#f77700', '#80cc00', '#00aa00']; + BaseWidget.prototype.colors4r = ['#00aa00', '#80cc00', '#f77700', '#ee0000']; + BaseWidget.prototype.colors5 = ['#ee0000', '#f77700', '#ffee00', '#80cc00', '#00aa00']; + BaseWidget.prototype.colors5r = ['#00aa00', '#80cc00', '#ffee00', '#f77700', '#ee0000']; + BaseWidget.prototype.colorsLevel = ['#d4333f', '#ff9900', '#85bb43', '##b4b4b4']; + BaseWidget.prototype.colorUnknown = '#777'; + + + BaseWidget.prototype.addField = function (name, defaultValue) { + var privateName = '_' + name; + this[privateName] = defaultValue; + this[name] = function (d) { + return this.param.call(this, privateName, d); + }; + return this; + }; + + BaseWidget.prototype.param = function (name, value) { + if (value == null) { + return this[name]; + } + this[name] = value; + return this; + }; + + BaseWidget.prototype.addMetric = function (property, index) { + var key = this.metricsPriority()[index]; + this[property] = _.extend(this.metrics()[key], { + key: key, + value: function (d) { + if (d.measures[key] != null) { + if (d.measures[key].text != null) { + return d.measures[key].text; + } else { + return d.measures[key].val; + } + } + }, + formattedValue: function (d) { + if (d.measures[key] != null) { + if (d.measures[key].text != null) { + return d.measures[key].text; + } else { + return d.measures[key].fval; + } + } + } + }); + return this; + }; + + BaseWidget.prototype.trans = function (left, top) { + return 'translate(' + left + ',' + top + ')'; + }; + + BaseWidget.prototype.render = function (container) { + this.update(container); + return this; + }; + + BaseWidget.prototype.update = function () { + return this; + }; + + BaseWidget.prototype.tooltip = function (d) { + /* jshint nonbsp: false */ + var title = d.longName; + if (this.colorMetric && (this.colorMetric.value(d) != null)) { + title += '\n' + this.colorMetric.name + ': ' + (this.colorMetric.formattedValue(d)); + } + if (this.sizeMetric && (this.sizeMetric.value(d) != null)) { + title += '\n' + this.sizeMetric.name + ': ' + (this.sizeMetric.formattedValue(d)); + } + return title; + }; + + window.SonarWidgets.BaseWidget = BaseWidget; + +})(); diff --git a/server/sonar-web/src/main/js/libs/widgets/histogram.js b/server/sonar-web/src/main/js/libs/widgets/histogram.js new file mode 100644 index 00000000000..df902e893f6 --- /dev/null +++ b/server/sonar-web/src/main/js/libs/widgets/histogram.js @@ -0,0 +1,164 @@ +(function () { + + function Histogram () { + window.SonarWidgets.BaseWidget.apply(this, arguments); + this.addField('width', 0); + this.addField('height', window.SonarWidgets.Histogram.defaults.height); + this.addField('margin', window.SonarWidgets.Histogram.defaults.margin); + this.addField('legendWidth', window.SonarWidgets.Histogram.defaults.legendWidth); + this.addField('maxResultsReached', false); + } + + Histogram.prototype = new window.SonarWidgets.BaseWidget(); + + Histogram.prototype.barHeight = 16; + + Histogram.prototype.barFill = '#1f77b4'; + + Histogram.prototype.isDataValid = function () { + var that = this; + return this.components().reduce(function (p, c) { + return p && !!c.measures[that.mainMetric.key]; + }, true); + }; + + Histogram.prototype.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: + if (text.length > maxLength) { + return text.substr(0, maxLength - 3) + '...'; + } else { + return text; + } + } + }; + + Histogram.prototype.render = function (container) { + var box = d3.select(container); + this.addMetric('mainMetric', 0); + if (!this.isDataValid()) { + box.text(this.options().noMainMetric); + return; + } + this.width(box.property('offsetWidth')); + this.svg = box.append('svg').classed('sonar-d3', true); + this.gWrap = this.svg.append('g'); + this.gWrap.attr('transform', this.trans(this.margin().left, this.margin().top)); + this.plotWrap = this.gWrap.append('g').classed('plot', true); + this.x = d3.scale.linear(); + this.y = d3.scale.ordinal(); + this.metricLabel = this.gWrap.append('text').text(this.mainMetric.name); + this.metricLabel.attr('dy', '9px').style('font-size', '12px'); + if (this.maxResultsReached()) { + this.maxResultsReachedLabel = this.gWrap.append('text').classed('max-results-reached-message', true); + this.maxResultsReachedLabel.text(this.options().maxItemsReachedMessage); + } + return window.SonarWidgets.BaseWidget.prototype.render.apply(this, arguments); + }; + + Histogram.prototype.update = function (container) { + var that = this; + var box = d3.select(container); + this.width(box.property('offsetWidth')); + var availableWidth = this.width() - this.margin().left - this.margin().right - this.legendWidth(), + availableHeight = this.barHeight * this.components().length + this.lineHeight, + totalHeight = availableHeight + this.margin().top + this.margin().bottom; + if (this.maxResultsReached()) { + totalHeight += this.lineHeight; + } + this.height(totalHeight); + this.svg.attr('width', this.width()).attr('height', this.height()); + this.plotWrap.attr('transform', this.trans(0, this.lineHeight)); + var xDomain = d3.extent(this.components(), function (d) { + return that.mainMetric.value(d); + }); + if (!this.options().relativeScale) { + if (this.mainMetric.type === 'PERCENT') { + xDomain = [0, 100]; + } else { + xDomain[0] = 0; + } + } + this.x.domain(xDomain).range([0, availableWidth]); + this.y.domain(this.components().map(function (d, i) { + return i; + })).rangeRoundBands([0, availableHeight], 0); + 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 that.trans(0, i * that.barHeight); + }); + this.barsEnter.append('rect').style('fill', this.barFill); + this.barsEnter.append('text') + .classed('legend-text component', true) + .style('text-anchor', 'end') + .attr('dy', '-0.35em') + .text(function (d) { + return that.truncate(d.longName, d.qualifier); + }) + .attr('transform', function () { + return that.trans(that.legendWidth() - 10, that.barHeight); + }); + this.barsEnter.append('text') + .classed('legend-text value', true) + .attr('dy', '-0.35em') + .text(function (d) { + return that.mainMetric.formattedValue(d); + }) + .attr('transform', function (d) { + return that.trans(that.legendWidth() + that.x(that.mainMetric.value(d)) + 5, that.barHeight); + }); + this.bars.selectAll('rect') + .transition() + .attr('x', this.legendWidth()) + .attr('y', 0) + .attr('width', function (d) { + return Math.max(2, that.x(that.mainMetric.value(d))); + }) + .attr('height', this.barHeight); + this.bars.selectAll('.component') + .transition() + .attr('transform', function () { + return that.trans(that.legendWidth() - 10, that.barHeight); + }); + this.bars.selectAll('.value') + .transition() + .attr('transform', function (d) { + return that.trans(that.legendWidth() + that.x(that.mainMetric.value(d)) + 5, that.barHeight); + }); + this.bars.exit().remove(); + this.bars.on('click', function (d) { + window.location = that.options().baseUrl + '?id=' + encodeURIComponent(d.key); + }); + this.metricLabel.attr('transform', this.trans(this.legendWidth(), 0)); + if (this.maxResultsReached()) { + this.maxResultsReachedLabel.attr('transform', + this.trans(this.legendWidth(), this.height() - this.margin().bottom - 3)); + } + return window.SonarWidgets.BaseWidget.prototype.update.apply(this, arguments); + }; + + window.SonarWidgets.Histogram = Histogram; + window.SonarWidgets.Histogram.defaults = { + height: 300, + margin: { + top: 4, + right: 50, + bottom: 4, + left: 10 + }, + legendWidth: 220 + }; + +})(); diff --git a/server/sonar-web/src/main/js/libs/widgets/tag-cloud.js b/server/sonar-web/src/main/js/libs/widgets/tag-cloud.js new file mode 100644 index 00000000000..d09f38b55bb --- /dev/null +++ b/server/sonar-web/src/main/js/libs/widgets/tag-cloud.js @@ -0,0 +1,77 @@ +(function () { + + function TagCloud () { + window.SonarWidgets.BaseWidget.apply(this, arguments); + this.addField('width', []); + this.addField('height', []); + this.addField('tags', []); + this.addField('maxResultsReached', false); + } + + TagCloud.prototype = new window.SonarWidgets.BaseWidget(); + + TagCloud.prototype.sizeHigh = 24; + + TagCloud.prototype.sizeLow = 10; + + TagCloud.prototype.renderWords = function () { + var that = this; + return window.requestMessages().done(function () { + var words = that.wordContainer.selectAll('.cloud-word').data(that.tags()), + wordsEnter = words.enter().append('a').classed('cloud-word', true); + wordsEnter.text(function (d) { + return d.key; + }); + wordsEnter.attr('href', function (d) { + var url = that.options().baseUrl + '|tags=' + d.key; + if (that.options().createdAfter) { + url += '|createdAfter=' + that.options().createdAfter; + } + return url; + }); + wordsEnter.attr('title', function (d) { + return that.tooltip(d); + }); + words.style('font-size', function (d) { + return that.size(d.value) + 'px'; + }); + return words.sort(function (a, b) { + if (a.key.toLowerCase() > b.key.toLowerCase()) { + return 1; + } else { + return -1; + } + }); + }); + }; + + TagCloud.prototype.render = function (container) { + var box = d3.select(container).append('div'); + box.classed('sonar-d3', true); + box.classed('cloud-widget', true); + this.wordContainer = box.append('div'); + var sizeDomain = d3.extent(this.tags(), function (d) { + return d.value; + }); + this.size = d3.scale.linear().domain(sizeDomain).range([this.sizeLow, this.sizeHigh]); + if (this.maxResultsReached()) { + var maxResultsReachedLabel = box.append('div').text(this.options().maxItemsReachedMessage); + maxResultsReachedLabel.classed('max-results-reached-message', true); + } + this.renderWords(); + return window.SonarWidgets.BaseWidget.prototype.render.apply(this, arguments); + }; + + TagCloud.prototype.tooltip = function (d) { + var suffixKey = d.value === 1 ? 'issue' : 'issues', + suffix = t(suffixKey); + return (d.value + '\u00a0') + suffix; + }; + + TagCloud.prototype.parseSource = function (response) { + return this.tags(response.tags); + }; + + window.SonarWidgets.TagCloud = TagCloud; + +})(); diff --git a/server/sonar-web/src/main/js/libs/widgets/treemap.js b/server/sonar-web/src/main/js/libs/widgets/treemap.js new file mode 100644 index 00000000000..f9cc1992833 --- /dev/null +++ b/server/sonar-web/src/main/js/libs/widgets/treemap.js @@ -0,0 +1,329 @@ +(function () { + + function Treemap () { + this.addField('width', null); + this.addField('height', null); + this.addField('maxResultsReached', false); + window.SonarWidgets.BaseWidget.apply(this, arguments); + } + + Treemap.prototype = new window.SonarWidgets.BaseWidget(); + + Treemap.prototype.sizeLow = 11; + + Treemap.prototype.sizeHigh = 18; + + Treemap.prototype.getNodes = function () { + return this.treemap + .nodes({ children: this.components() }) + .filter(function (d) { + return !d.children; + }); + }; + + Treemap.prototype.renderTreemap = function () { + var that = this; + var nodes = this.getNodes(); + this.cells = this.box.selectAll('.treemap-cell').data(nodes); + this.cells.exit().remove(); + var cellsEnter = this.cells.enter().append('div'); + cellsEnter.classed('treemap-cell', true); + cellsEnter.append('div').classed('treemap-inner', true); + cellsEnter.append('a').classed('treemap-link', true); + this.cells.attr('title', function (d) { + return that.tooltip(d); + }); + this.cells.style('background-color', function (d) { + if (that.colorMetric.value(d) != null) { + return that.color(that.colorMetric.value(d)); + } else { + return that.colorUnknown; + } + }); + this.cells.classed('treemap-cell-drilldown', function (d) { + return (d.qualifier != null) && d.qualifier !== 'FIL' && d.qualifier !== 'CLA'; + }); + var prefix = this.mostCommonPrefix(_.pluck(this.components(), 'longName')), + prefixLength = prefix.length; + this.cellsInner = this.box.selectAll('.treemap-inner').data(nodes); + this.cellsInner.html(function (d) { + if (prefixLength > 0) { + return prefix + '
' + (d.longName.substr(prefixLength)); + } else { + return d.longName; + } + }); + this.cellsLink = this.box.selectAll('.treemap-link').data(nodes); + this.cellsLink.html(''); + this.cellsLink.attr('href', function (d) { + return that.options().baseUrl + '?id=' + encodeURIComponent(d.key); + }); + this.attachEvents(cellsEnter); + return this.maxResultsReachedLabel.style('display', this.maxResultsReached() ? 'block' : 'none'); + }; + + Treemap.prototype.updateTreemap = function (components, maxResultsReached) { + this.components(components); + this.maxResultsReached(maxResultsReached); + this.renderTreemap(); + return this.positionCells(); + }; + + Treemap.prototype.attachEvents = function (cells) { + var that = this; + return cells.on('click', function (d) { + return that.requestChildren(d); + }); + }; + + Treemap.prototype.positionCells = function () { + var that = this; + this.cells.style('left', function (d) { + return d.x + 'px'; + }); + this.cells.style('top', function (d) { + return d.y + 'px'; + }); + this.cells.style('width', function (d) { + return d.dx + 'px'; + }); + this.cellsInner.style('max-width', function (d) { + return d.dx + 'px'; + }); + this.cells.style('height', function (d) { + return d.dy + 'px'; + }); + this.cells.style('line-height', function (d) { + return d.dy + 'px'; + }); + this.cells.style('font-size', function (d) { + return (that.size(d.dx / d.longName.length)) + 'px'; + }); + this.cellsLink.style('font-size', function (d) { + return (that.sizeLink(Math.min(d.dx, d.dy))) + 'px'; + }); + this.cells.classed('treemap-cell-small', function (d) { + return (d.dx / d.longName.length) < 1 || d.dy < 40; + }); + return this.cells.classed('treemap-cell-very-small', function (d) { + return d.dx < 24 || d.dy < 24; + }); + }; + + Treemap.prototype.renderLegend = function (box) { + this.legend = box.insert('div', ':first-child'); + this.legend.classed('legend', true); + this.legend.classed('legend-html', true); + this.legend.append('span') + .classed('legend-text', true) + .html('Size: ' + this.sizeMetric.name + ''); + return this.legend.append('span') + .classed('legend-text', true) + .html('Color: ' + this.colorMetric.name + ''); + }; + + Treemap.prototype.renderBreadcrumbs = function (box) { + this.breadcrumbsBox = box.append('div').classed('treemap-breadcrumbs', true); + this.breadcrumbs = []; + var d = { + name: '', + components: this.components(), + maxResultsReached: this.maxResultsReached() + }; + return this.addToBreadcrumbs(d); + }; + + Treemap.prototype.updateBreadcrumbs = function () { + var that = this; + var breadcrumbs = this.breadcrumbsBox.selectAll('.treemap-breadcrumbs-item').data(this.breadcrumbs); + breadcrumbs.exit().remove(); + var breadcrumbsEnter = breadcrumbs.enter().append('span').classed('treemap-breadcrumbs-item', true); + breadcrumbsEnter.attr('title', function (d) { + if (d.longName != null) { + return d.longName; + } else { + return that.options().resource; + } + }); + breadcrumbsEnter.append('i') + .classed('icon-chevron-right', true) + .style('display', function (d, i) { + if (i > 0) { + return 'inline'; + } else { + return 'none'; + } + }); + breadcrumbsEnter.append('i').attr('class', function (d) { + if (d.qualifier != null) { + return 'icon-qualifier-' + (d.qualifier.toLowerCase()); + } else { + return ''; + } + }); + var breadcrumbsEnterLinks = breadcrumbsEnter.append('a'); + breadcrumbsEnterLinks.html(function (d) { + return d.name; + }); + breadcrumbsEnterLinks.on('click', function (d) { + that.updateTreemap(d.components, d.maxResultsReached); + return that.cutBreadcrumbs(d); + }); + this.breadcrumbsBox.style('display', this.breadcrumbs.length < 2 ? 'none' : 'block'); + }; + + Treemap.prototype.addToBreadcrumbs = function (d) { + this.breadcrumbs.push(d); + this.updateBreadcrumbs(); + }; + + Treemap.prototype.cutBreadcrumbs = function (lastElement) { + var index = null; + this.breadcrumbs.forEach(function (d, i) { + if (d.key === lastElement.key) { + index = i; + } + }); + if (index != null) { + this.breadcrumbs = _.initial(this.breadcrumbs, this.breadcrumbs.length - index - 1); + this.updateBreadcrumbs(); + } + }; + + Treemap.prototype.getColorScale = function () { + if (this.colorMetric.type === 'LEVEL') { + return this.getLevelColorScale(); + } + if (this.colorMetric.type === 'RATING') { + return this.getRatingColorScale(); + } + return this.getPercentColorScale(); + }; + + Treemap.prototype.getPercentColorScale = function () { + var color = d3.scale.linear().domain([0, 25, 50, 75, 100]); + color.range(this.colorMetric.direction === 1 ? this.colors5 : this.colors5r); + return color; + }; + + Treemap.prototype.getRatingColorScale = function () { + var domain = [1, 2, 3, 4, 5]; + if (this.components().length > 0) { + var colorMetricSample = this.colorMetric.value(_.first(this.components())); + if (typeof colorMetricSample === 'string') { + domain = ['A', 'B', 'C', 'D', 'E']; + } + } + return d3.scale.ordinal().domain(domain).range(this.colors5r); + }; + + Treemap.prototype.getLevelColorScale = function () { + return d3.scale.ordinal().domain(['ERROR', 'WARN', 'OK', 'NONE']).range(this.colorsLevel); + }; + + Treemap.prototype.render = function (container) { + var that = this; + var box = d3.select(container).append('div'); + box.classed('sonar-d3', true); + this.box = box.append('div').classed('treemap-container', true); + this.addMetric('colorMetric', 0); + this.addMetric('sizeMetric', 1); + this.color = this.getColorScale(); + this.size = d3.scale.linear().domain([3, 15]).range([this.sizeLow, this.sizeHigh]).clamp(true); + this.sizeLink = d3.scale.linear().domain([60, 100]).range([12, 14]).clamp(true); + this.treemap = d3.layout.treemap(); + this.treemap.sort(function (a, b) { + return a.value - b.value; + }); + this.treemap.round(true); + this.treemap.value(function (d) { + return that.sizeMetric.value(d); + }); + this.maxResultsReachedLabel = box.append('div').text(this.options().maxItemsReachedMessage); + this.maxResultsReachedLabel.classed('max-results-reached-message', true); + this.renderLegend(box); + this.renderBreadcrumbs(box); + this.renderTreemap(); + return window.SonarWidgets.BaseWidget.prototype.render.apply(this, arguments); + }; + + Treemap.prototype.update = function () { + this.width(this.box.property('offsetWidth')); + this.height(this.width() / 100.0 * this.options().heightInPercents); + this.box.style('height', (this.height()) + 'px'); + this.treemap.size([this.width(), this.height()]); + this.cells.data(this.getNodes()); + return this.positionCells(); + }; + + Treemap.prototype.formatComponents = function (data) { + var that = this; + var components = _.filter(data, function (component) { + var hasSizeMetric = function () { + return _.findWhere(component.msr, { + key: that.sizeMetric.key + }); + }; + return _.isArray(component.msr) && component.msr.length > 0 && hasSizeMetric(); + }); + if (_.isArray(components) && components.length > 0) { + return components.map(function (component) { + var measures = {}; + component.msr.forEach(function (measure) { + measures[measure.key] = { + val: measure.val, + fval: measure.frmt_val + }; + }); + return { + key: component.copy != null ? component.copy : component.key, + name: component.name, + longName: component.lname, + qualifier: component.qualifier, + measures: measures + }; + }); + } + }; + + Treemap.prototype.requestChildren = function (d) { + var that = this; + var metrics = this.metricsPriority().join(','), + RESOURCES_URL = baseUrl + '/api/resources/index'; + return jQuery.get(RESOURCES_URL, { + resource: d.key, + depth: 1, + metrics: metrics + }).done(function (r) { + var components = that.formatComponents(r); + if (components != null) { + components = _.sortBy(components, function (d) { + return -that.sizeMetric.value(d); + }); + components = _.initial(components, components.length - that.options().maxItems - 1); + that.updateTreemap(components, components.length > that.options().maxItems); + return that.addToBreadcrumbs(_.extend(d, { + components: components, + maxResultsReached: that.maxResultsReached() + })); + } + }); + }; + + Treemap.prototype.mostCommonPrefix = function (strings) { + var sortedStrings = strings.slice(0).sort(), + firstString = sortedStrings[0], + firstStringLength = firstString.length, + lastString = sortedStrings[sortedStrings.length - 1], + i = 0; + while (i < firstStringLength && firstString.charAt(i) === lastString.charAt(i)) { + i++; + } + var prefix = firstString.substr(0, i), + lastPrefixPart = _.last(prefix.split(/[\s\\\/]/)); + return prefix.substr(0, prefix.length - lastPrefixPart.length); + }; + + window.SonarWidgets.Treemap = Treemap; + +})(); diff --git a/server/sonar-web/src/main/js/libs/widgets/word-cloud.js b/server/sonar-web/src/main/js/libs/widgets/word-cloud.js new file mode 100644 index 00000000000..c12e02390e4 --- /dev/null +++ b/server/sonar-web/src/main/js/libs/widgets/word-cloud.js @@ -0,0 +1,76 @@ +(function () { + + function WordCloud () { + this.addField('width', []); + this.addField('height', []); + this.addField('maxResultsReached', false); + window.SonarWidgets.BaseWidget.apply(this, arguments); + } + + WordCloud.prototype = new window.SonarWidgets.BaseWidget(); + + WordCloud.prototype.sizeHigh = 24; + + WordCloud.prototype.sizeLow = 10; + + WordCloud.prototype.renderWords = function () { + var that = this; + var words = this.wordContainer.selectAll('.cloud-word').data(this.components()), + wordsEnter = words.enter().append('a').classed('cloud-word', true); + wordsEnter.text(function (d) { + return d.name; + }); + wordsEnter.attr('href', function (d) { + return that.options().baseUrl + '?id=' + encodeURIComponent(d.key); + }); + wordsEnter.attr('title', function (d) { + return that.tooltip(d); + }); + words.style('color', function (d) { + if (that.colorMetric.value(d) != null) { + return that.color(that.colorMetric.value(d)); + } else { + return that.colorUnknown; + } + }); + words.style('font-size', function (d) { + return (that.size(that.sizeMetric.value(d))) + 'px'; + }); + return words.sort(function (a, b) { + if (a.name.toLowerCase() > b.name.toLowerCase()) { + return 1; + } else { + return -1; + } + }); + }; + + WordCloud.prototype.render = function (container) { + var that = this; + var box = d3.select(container).append('div'); + box.classed('sonar-d3', true); + box.classed('cloud-widget', true); + this.wordContainer = box.append('div'); + this.addMetric('colorMetric', 0); + this.addMetric('sizeMetric', 1); + this.color = d3.scale.linear().domain([0, 33, 67, 100]); + if (this.colorMetric.direction === 1) { + this.color.range(this.colors4); + } else { + this.color.range(this.colors4r); + } + var sizeDomain = d3.extent(this.components(), function (d) { + return that.sizeMetric.value(d); + }); + this.size = d3.scale.linear().domain(sizeDomain).range([this.sizeLow, this.sizeHigh]); + if (this.maxResultsReached()) { + var maxResultsReachedLabel = box.append('div').text(this.options().maxItemsReachedMessage); + maxResultsReachedLabel.classed('max-results-reached-message', true); + } + this.renderWords(); + return window.SonarWidgets.BaseWidget.prototype.render.apply(this, arguments); + }; + + window.SonarWidgets.WordCloud = WordCloud; + +})(); -- 2.39.5