]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-3825 add treemap rendering
authorSimon Brandhof <simon.brandhof@gmail.com>
Mon, 26 Nov 2012 14:40:01 +0000 (15:40 +0100)
committerSimon Brandhof <simon.brandhof@gmail.com>
Mon, 26 Nov 2012 14:40:19 +0000 (15:40 +0100)
sonar-server/src/main/webapp/WEB-INF/app/models/measure_filter.rb
sonar-server/src/main/webapp/WEB-INF/app/models/measure_filter_treemap.rb [new file with mode: 0644]
sonar-server/src/main/webapp/WEB-INF/app/views/measures/_display_treemap.html.erb
sonar-server/src/main/webapp/stylesheets/style.css

index 1312994d8e2e8bc84cfae2e55f6fc0c1eab1f40e..984ecc84b78ad435f40c34f0137b848475c49940 100644 (file)
@@ -64,10 +64,10 @@ class MeasureFilter < ActiveRecord::Base
 
     def align
       @align ||=
-          begin
-            # by default is table cells are left-aligned
-            (@key=='name' || @key=='short_name' || @key=='description') ? '' : 'right'
-          end
+        begin
+          # by default is table cells are left-aligned
+          (@key=='name' || @key=='short_name' || @key=='description') ? '' : 'right'
+        end
     end
 
     def sort?
@@ -110,6 +110,7 @@ class MeasureFilter < ActiveRecord::Base
     def load_links?
       @columns.index { |column| column.links? }
     end
+
   end
 
   class TreemapDisplay < Display
@@ -118,10 +119,18 @@ class MeasureFilter < ActiveRecord::Base
     KEY = :treemap
 
     def initialize(filter)
-      filter.set_criteria_default_value('columns', ['metric:ncloc', 'metric:violations'])
+      filter.set_criteria_default_value('columns', ['metric:ncloc', 'metric:violations_density'])
       @columns = filter.criteria['columns'].map { |column_key| Column.new(column_key) }
       @metric_ids = @columns.map { |column| column.metric.id if column.metric }.compact.uniq
     end
+
+    def size_metric
+      @size_metric ||= Metric.by_key('ncloc')
+    end
+
+    def color_metric
+      @color_metric ||= Metric.by_key('violations_density')
+    end
   end
 
   class CloudDisplay < Display
@@ -223,15 +232,15 @@ class MeasureFilter < ActiveRecord::Base
 
   def display
     @display ||=
-        begin
-          display_class = nil
-          key = criteria['display']
-          if key.present?
-            display_class = DISPLAYS.find { |d| d::KEY==key.to_sym }
-          end
-          display_class ||= DISPLAYS.first
-          display_class.new(self)
+      begin
+        display_class = nil
+        key = criteria['display']
+        if key.present?
+          display_class = DISPLAYS.find { |d| d::KEY==key.to_sym }
         end
+        display_class ||= DISPLAYS.first
+        display_class.new(self)
+      end
   end
 
 
@@ -301,7 +310,7 @@ class MeasureFilter < ActiveRecord::Base
 
       if display.metric_ids && !display.metric_ids.empty?
         measures = ProjectMeasure.find(:all, :conditions =>
-            ['rule_priority is null and rule_id is null and characteristic_id is null and person_id is null and snapshot_id in (?) and metric_id in (?)', snapshot_ids, display.metric_ids]
+          ['rule_priority is null and rule_id is null and characteristic_id is null and person_id is null and snapshot_id in (?) and metric_id in (?)', snapshot_ids, display.metric_ids]
         )
         measures.each do |measure|
           result = results_by_snapshot_id[measure.snapshot_id]
@@ -328,7 +337,7 @@ class MeasureFilter < ActiveRecord::Base
         @base_result = Result.new(base_snapshot)
         if display.metric_ids && !display.metric_ids.empty?
           base_measures = ProjectMeasure.find(:all, :conditions =>
-              ['rule_priority is null and rule_id is null and characteristic_id is null and person_id is null and snapshot_id=? and metric_id in (?)', base_snapshot.id, display.metric_ids]
+            ['rule_priority is null and rule_id is null and characteristic_id is null and person_id is null and snapshot_id=? and metric_id in (?)', base_snapshot.id, display.metric_ids]
           )
           base_measures.each do |base_measure|
             @base_result.add_measure(base_measure)
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/models/measure_filter_treemap.rb b/sonar-server/src/main/webapp/WEB-INF/app/models/measure_filter_treemap.rb
new file mode 100644 (file)
index 0000000..f2ea09a
--- /dev/null
@@ -0,0 +1,134 @@
+#
+# Sonar, entreprise quality control tool.
+# Copyright (C) 2008-2012 SonarSource
+# mailto:contact AT sonarsource DOT com
+#
+# Sonar 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.
+#
+# Sonar 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 Sonar; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+#
+class MeasureFilterTreemap
+  include ActionView::Helpers::UrlHelper
+
+  attr_reader :filter, :height, :id, :size, :size_metric, :color_metric
+
+  def initialize(filter, height, id)
+    @filter = filter
+    @height = height
+    @id = id
+    @size = 0
+    @size_metric = filter.display.size_metric
+    @color_metric = filter.display.color_metric
+  end
+
+  def html
+    root = Treemap::Node.new(:id => -1, :label => '')
+    build_tree(root)
+
+    output = Sonar::HtmlOutput.new do |o|
+      # width in percents
+      o.width = 100
+      o.height = @height
+      o.full_html = false
+      o.details_at_depth = 1
+    end
+    html = output.to_html(root)
+    html + "<script>treemapById(#{@id}).onLoaded(#{@size});</script>"
+  end
+
+  def empty?
+    @size==0
+  end
+
+  private
+
+  def build_tree(node)
+    if @filter.results
+      @filter.results.each do |result|
+        size_measure=result.measure(@size_metric)
+        if size_measure
+          color_measure=(@color_metric ? result.measure(@color_metric) : nil)
+          resource = result.snapshot.resource
+          child = Treemap::Node.new(:id => "#{@id}-#{@size += 1}",
+                                    :size => size_value(size_measure),
+                                    :label => resource.name(false),
+                                    :title => escape_javascript(resource.name(true)),
+                                    :tooltip => tooltip(resource, size_measure, color_measure),
+                                    :color => html_color(color_measure),
+                                    :rid => resource.id,
+                                    :leaf => resource.source_code?)
+          node.add_child(child)
+        end
+      end
+    end
+  end
+
+  def tooltip(resource, size_measure, color_measure)
+    html=CGI::escapeHTML(resource.name(true))
+    html += " - #{CGI::escapeHTML(@size_metric.short_name)}: #{CGI::escapeHTML(size_measure.formatted_value)}"
+    if color_measure
+      html += " - #{CGI::escapeHTML(@color_metric.short_name)}: #{CGI::escapeHTML(color_measure.formatted_value)}"
+    end
+    html
+  end
+
+  def size_value(measure)
+    if measure.value
+      measure.value.to_f.abs||0.0
+    else
+      0.0
+    end
+  end
+
+  def html_color(measure)
+    MeasureColor.color(measure).html
+  end
+
+end
+
+class Sonar::HtmlOutput < Treemap::HtmlOutput
+
+  def draw_node(node)
+    return "" if node.bounds.nil?
+
+    html = ''
+    html += "<div style=\""
+    html += "overflow:hidden;position:absolute;"
+    html += "left:#{node.bounds.x1}%; top:#{node.bounds.y1}px;"
+    html += "width:#{node.bounds.width}%;height: #{node.bounds.height}px;"
+    html += "background-color:#FFF;\">"
+    html += "<div rid='#{node.rid}' id=\"tm-node-#{node.id}\" style='margin: 1px;background-color: #{node.color}; height: #{node.bounds.height-4}px;
+border: 1px solid #{node.color};' alt=\"#{node.tooltip}\" title=\"#{node.tooltip}\""
+    if node.leaf
+      html += "l=1 "
+    end
+    html += ' >'
+    html += draw_node_body(node)
+
+    if (!node.children.nil? && node.children.size > 0)
+      node.children.each do |c|
+        html += draw_node(c)
+      end
+    end
+    html + '</div></div>'
+  end
+
+  def draw_label(node)
+    if node.leaf
+      "<a onclick=\"window.open(this.href,'resource','height=800,width=900,scrollbars=1,resizable=1');return false;\" " +
+        "href=\"#{ApplicationController.root_context}/resource/index/#{node.rid}\">#{node_label(node)}</a>"
+    else
+      "<a href='#{ApplicationController.root_context}/dashboard/index/#{node.rid}'>#{node_label(node)}</a>"
+    end
+  end
+end
\ No newline at end of file
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0f4f89108a4c9cc07b2f8a8049f0ac51ff2377b6 100644 (file)
@@ -0,0 +1,4 @@
+<% treemap = MeasureFilterTreemap.new(@filter, 600, @filter.id.to_s) %>
+<div class="treemap" style="width: 100%; height:<%= treemap.height %>px;">
+  <%= treemap.html() -%>
+</div>
\ No newline at end of file
index 1260a2805153c57f1a93096e83493c3f9db1ed09..b6c02a430f7d48a97f18c6a7ef67db54c82e7dbb 100644 (file)
@@ -274,9 +274,6 @@ h4, .h4 {
   text-decoration: none;
   font-size: 12px;
   padding: 1px;
-}
-
-.treemap a:hover {
   text-decoration: underline;
 }