diff lib/SVG/Graph/Pie.rb @ 1294:3e4c3460b6ca redmine-2.2

Update to Redmine SVN revision 11972 on 2.2-stable branch
author Chris Cannam
date Fri, 14 Jun 2013 09:01:12 +0100
parents 513646585e45
children
line wrap: on
line diff
--- a/lib/SVG/Graph/Pie.rb	Mon Jan 07 12:01:42 2013 +0000
+++ b/lib/SVG/Graph/Pie.rb	Fri Jun 14 09:01:12 2013 +0100
@@ -1,395 +1,395 @@
-require 'SVG/Graph/Graph'
-
-module SVG
-  module Graph
-    # === Create presentation quality SVG pie graphs easily
-    # 
-    # == Synopsis
-    # 
-    #   require 'SVG/Graph/Pie'
-    # 
-    #   fields = %w(Jan Feb Mar)
-    #   data_sales_02 = [12, 45, 21]
-    #   
-    #   graph = SVG::Graph::Pie.new({
-    #   	:height => 500,
-    # 	  :width  => 300,
-    # 	  :fields => fields,
-    #   })
-    #   
-    #   graph.add_data({
-    #   	:data => data_sales_02,
-    # 	  :title => 'Sales 2002',
-    #   })
-    #   
-    #   print "Content-type: image/svg+xml\r\n\r\n"
-    #   print graph.burn();
-    # 
-    # == Description
-    # 
-    # This object aims to allow you to easily create high quality
-    # SVG pie graphs. You can either use the default style sheet
-    # or supply your own. Either way there are many options which can
-    # be configured to give you control over how the graph is
-    # generated - with or without a key, display percent on pie chart,
-    # title, subtitle etc.
-    #
-    # = Examples
-    # 
-    # http://www.germane-software/repositories/public/SVG/test/single.rb
-    # 
-    # == See also
-    #
-    # * SVG::Graph::Graph
-    # * SVG::Graph::BarHorizontal
-    # * SVG::Graph::Bar
-    # * SVG::Graph::Line
-    # * SVG::Graph::Plot
-    # * SVG::Graph::TimeSeries
-    #
-    # == Author
-    #
-    # Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom>
-    #
-    # Copyright 2004 Sean E. Russell
-    # This software is available under the Ruby license[LICENSE.txt]
-    #
-    class Pie < Graph
-      # Defaults are those set by Graph::initialize, and
-      # [show_shadow] true
-      # [shadow_offset] 10
-      # [show_data_labels] false
-      # [show_actual_values] false
-      # [show_percent] true
-      # [show_key_data_labels] true
-      # [show_key_actual_values] true
-      # [show_key_percent] false
-      # [expanded] false
-      # [expand_greatest] false
-      # [expand_gap] 10
-      # [show_x_labels] false
-      # [show_y_labels] false
-      # [datapoint_font_size] 12
-      def set_defaults
-        init_with(
-          :show_shadow		        => true,
-          :shadow_offset	        => 10, 
-          
-          :show_data_labels	      => false,
-          :show_actual_values     => false,
-          :show_percent		        => true,
-
-          :show_key_data_labels	  => true,
-          :show_key_actual_values => true,
-          :show_key_percent		    => false,
-          
-          :expanded				        => false,
-          :expand_greatest		    => false,
-          :expand_gap             => 10,
-          
-          :show_x_labels          => false,
-          :show_y_labels          => false,
-          :datapoint_font_size    => 12
-        )
-        @data = []
-      end
-
-      # Adds a data set to the graph.
-      #
-      #   graph.add_data( { :data => [1,2,3,4] } )
-      #
-      # Note that the :title is not necessary.  If multiple
-      # data sets are added to the graph, the pie chart will
-      # display the +sums+ of the data.  EG:
-      #
-      #   graph.add_data( { :data => [1,2,3,4] } )
-      #   graph.add_data( { :data => [2,3,5,9] } )
-      #
-      # is the same as:
-      #
-      #   graph.add_data( { :data => [3,5,8,13] } )
-      def add_data arg
-        arg[:data].each_index {|idx|
-          @data[idx] = 0 unless @data[idx]
-          @data[idx] += arg[:data][idx]
-        }
-      end
-
-      # If true, displays a drop shadow for the chart
-      attr_accessor :show_shadow 
-      # Sets the offset of the shadow from the pie chart
-      attr_accessor :shadow_offset
-      # If true, display the data labels on the chart
-      attr_accessor :show_data_labels 
-      # If true, display the actual field values in the data labels
-      attr_accessor :show_actual_values 
-      # If true, display the percentage value of each pie wedge in the data
-      # labels
-      attr_accessor :show_percent
-      # If true, display the labels in the key
-      attr_accessor :show_key_data_labels 
-      # If true, display the actual value of the field in the key
-      attr_accessor :show_key_actual_values 
-      # If true, display the percentage value of the wedges in the key
-      attr_accessor :show_key_percent
-      # If true, "explode" the pie (put space between the wedges)
-      attr_accessor :expanded 
-      # If true, expand the largest pie wedge
-      attr_accessor :expand_greatest 
-      # The amount of space between expanded wedges
-      attr_accessor :expand_gap 
-      # The font size of the data point labels
-      attr_accessor :datapoint_font_size
-
-
-      protected
-
-      def add_defs defs
-        gradient = defs.add_element( "filter", {
-          "id"=>"dropshadow",
-          "width" => "1.2",
-          "height" => "1.2",
-        } )
-        gradient.add_element( "feGaussianBlur", {
-          "stdDeviation" => "4",
-          "result" => "blur"
-        })
-      end
-
-      # We don't need the graph
-      def draw_graph
-      end
-
-      def get_y_labels
-        [""]
-      end
-
-      def get_x_labels
-        [""]
-      end
-
-      def keys
-        total = 0
-        max_value = 0
-        @data.each {|x| total += x }
-        percent_scale = 100.0 / total
-        count = -1
-        a = @config[:fields].collect{ |x|
-          count += 1
-          v = @data[count]
-          perc = show_key_percent ? " "+(v * percent_scale).round.to_s+"%" : ""
-          x + " [" + v.to_s + "]" + perc
-        }
-      end
-
-      RADIANS = Math::PI/180
-
-      def draw_data
-        @graph = @root.add_element( "g" )
-        background = @graph.add_element("g")
-        midground = @graph.add_element("g")
-
-        diameter = @graph_height > @graph_width ? @graph_width : @graph_height
-        diameter -= expand_gap if expanded or expand_greatest
-        diameter -= datapoint_font_size if show_data_labels
-        diameter -= 10 if show_shadow
-        radius = diameter / 2.0
-
-        xoff = (width - diameter) / 2
-        yoff = (height - @border_bottom - diameter)
-        yoff -= 10 if show_shadow
-        @graph.attributes['transform'] = "translate( #{xoff} #{yoff} )"
-
-        wedge_text_pad = 5
-        wedge_text_pad = 20 if show_percent and show_data_labels
-
-        total = 0
-        max_value = 0
-        @data.each {|x| 
-          max_value = max_value < x ? x : max_value
-          total += x 
-        }
-        percent_scale = 100.0 / total
-
-        prev_percent = 0
-        rad_mult = 3.6 * RADIANS
-        @config[:fields].each_index { |count|
-          value = @data[count]
-          percent = percent_scale * value
-
-          radians = prev_percent * rad_mult
-          x_start = radius+(Math.sin(radians) * radius)
-          y_start = radius-(Math.cos(radians) * radius)
-          radians = (prev_percent+percent) * rad_mult
-          x_end = radius+(Math.sin(radians) * radius)
-          x_end -= 0.00001 if @data.length == 1
-          y_end = radius-(Math.cos(radians) * radius)
-          path = "M#{radius},#{radius} L#{x_start},#{y_start} "+
-            "A#{radius},#{radius} "+
-            "0, #{percent >= 50 ? '1' : '0'},1, "+
-            "#{x_end} #{y_end} Z"
-
-
-          wedge = @foreground.add_element( "path", {
-            "d" => path,
-            "class" => "fill#{count+1}"
-          })
-
-          translate = nil
-          tx = 0
-          ty = 0
-          half_percent = prev_percent + percent / 2
-          radians = half_percent * rad_mult
-
-          if show_shadow
-            shadow = background.add_element( "path", {
-              "d" => path,
-              "filter" => "url(#dropshadow)",
-              "style" => "fill: #ccc; stroke: none;"
-            })
-            clear = midground.add_element( "path", {
-              "d" => path,
-              "style" => "fill: #fff; stroke: none;"
-            })
-          end
-
-          if expanded or (expand_greatest && value == max_value)
-            tx = (Math.sin(radians) * expand_gap)
-            ty = -(Math.cos(radians) * expand_gap)
-            translate = "translate( #{tx} #{ty} )"
-            wedge.attributes["transform"] = translate
-            clear.attributes["transform"] = translate if clear
-          end
-
-          if show_shadow
-            shadow.attributes["transform"] = 
-              "translate( #{tx+shadow_offset} #{ty+shadow_offset} )"
-          end
-          
-          if show_data_labels and value != 0
-            label = ""
-            label += @config[:fields][count] if show_key_data_labels
-            label += " ["+value.to_s+"]" if show_actual_values
-            label += " "+percent.round.to_s+"%" if show_percent
-
-            msr = Math.sin(radians)
-            mcr = Math.cos(radians)
-            tx = radius + (msr * radius)
-            ty = radius -(mcr * radius)
-
-            if expanded or (expand_greatest && value == max_value)
-              tx += (msr * expand_gap)
-              ty -= (mcr * expand_gap)
-            end
-            @foreground.add_element( "text", {
-              "x" => tx.to_s,
-              "y" => ty.to_s,
-              "class" => "dataPointLabel",
-              "style" => "stroke: #fff; stroke-width: 2;"
-            }).text = label.to_s
-            @foreground.add_element( "text", {
-              "x" => tx.to_s,
-              "y" => ty.to_s,
-              "class" => "dataPointLabel",
-            }).text = label.to_s
-          end
-
-          prev_percent += percent
-        }
-      end
-      
-
-      def round val, to
-        up = 10**to.to_f
-        (val * up).to_i / up
-      end
-
-
-      def get_css
-        return <<EOL
-.dataPointLabel{
-	fill: #000000;
-	text-anchor:middle;
-	font-size: #{datapoint_font_size}px;
-	font-family: "Arial", sans-serif;
-	font-weight: normal;
-}
-
-/* key - MUST match fill styles */
-.key1,.fill1{
-	fill: #ff0000;
-	fill-opacity: 0.7;
-	stroke: none;
-	stroke-width: 1px;	
-}
-.key2,.fill2{
-	fill: #0000ff;
-	fill-opacity: 0.7;
-	stroke: none;
-	stroke-width: 1px;	
-}
-.key3,.fill3{
-	fill-opacity: 0.7;
-	fill: #00ff00;
-	stroke: none;
-	stroke-width: 1px;	
-}
-.key4,.fill4{
-	fill-opacity: 0.7;
-	fill: #ffcc00;
-	stroke: none;
-	stroke-width: 1px;	
-}
-.key5,.fill5{
-	fill-opacity: 0.7;
-	fill: #00ccff;
-	stroke: none;
-	stroke-width: 1px;	
-}
-.key6,.fill6{
-	fill-opacity: 0.7;
-	fill: #ff00ff;
-	stroke: none;
-	stroke-width: 1px;	
-}
-.key7,.fill7{
-	fill-opacity: 0.7;
-	fill: #00ff99;
-	stroke: none;
-	stroke-width: 1px;	
-}
-.key8,.fill8{
-	fill-opacity: 0.7;
-	fill: #ffff00;
-	stroke: none;
-	stroke-width: 1px;	
-}
-.key9,.fill9{
-	fill-opacity: 0.7;
-	fill: #cc6666;
-	stroke: none;
-	stroke-width: 1px;	
-}
-.key10,.fill10{
-	fill-opacity: 0.7;
-	fill: #663399;
-	stroke: none;
-	stroke-width: 1px;	
-}
-.key11,.fill11{
-	fill-opacity: 0.7;
-	fill: #339900;
-	stroke: none;
-	stroke-width: 1px;	
-}
-.key12,.fill12{
-	fill-opacity: 0.7;
-	fill: #9966FF;
-	stroke: none;
-	stroke-width: 1px;	
-}
-EOL
-      end
-    end
-  end
-end
+require 'SVG/Graph/Graph'
+
+module SVG
+  module Graph
+    # === Create presentation quality SVG pie graphs easily
+    # 
+    # == Synopsis
+    # 
+    #   require 'SVG/Graph/Pie'
+    # 
+    #   fields = %w(Jan Feb Mar)
+    #   data_sales_02 = [12, 45, 21]
+    #   
+    #   graph = SVG::Graph::Pie.new({
+    #   	:height => 500,
+    # 	  :width  => 300,
+    # 	  :fields => fields,
+    #   })
+    #   
+    #   graph.add_data({
+    #   	:data => data_sales_02,
+    # 	  :title => 'Sales 2002',
+    #   })
+    #   
+    #   print "Content-type: image/svg+xml\r\n\r\n"
+    #   print graph.burn();
+    # 
+    # == Description
+    # 
+    # This object aims to allow you to easily create high quality
+    # SVG pie graphs. You can either use the default style sheet
+    # or supply your own. Either way there are many options which can
+    # be configured to give you control over how the graph is
+    # generated - with or without a key, display percent on pie chart,
+    # title, subtitle etc.
+    #
+    # = Examples
+    # 
+    # http://www.germane-software/repositories/public/SVG/test/single.rb
+    # 
+    # == See also
+    #
+    # * SVG::Graph::Graph
+    # * SVG::Graph::BarHorizontal
+    # * SVG::Graph::Bar
+    # * SVG::Graph::Line
+    # * SVG::Graph::Plot
+    # * SVG::Graph::TimeSeries
+    #
+    # == Author
+    #
+    # Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom>
+    #
+    # Copyright 2004 Sean E. Russell
+    # This software is available under the Ruby license[LICENSE.txt]
+    #
+    class Pie < Graph
+      # Defaults are those set by Graph::initialize, and
+      # [show_shadow] true
+      # [shadow_offset] 10
+      # [show_data_labels] false
+      # [show_actual_values] false
+      # [show_percent] true
+      # [show_key_data_labels] true
+      # [show_key_actual_values] true
+      # [show_key_percent] false
+      # [expanded] false
+      # [expand_greatest] false
+      # [expand_gap] 10
+      # [show_x_labels] false
+      # [show_y_labels] false
+      # [datapoint_font_size] 12
+      def set_defaults
+        init_with(
+          :show_shadow		        => true,
+          :shadow_offset	        => 10, 
+          
+          :show_data_labels	      => false,
+          :show_actual_values     => false,
+          :show_percent		        => true,
+
+          :show_key_data_labels	  => true,
+          :show_key_actual_values => true,
+          :show_key_percent		    => false,
+          
+          :expanded				        => false,
+          :expand_greatest		    => false,
+          :expand_gap             => 10,
+          
+          :show_x_labels          => false,
+          :show_y_labels          => false,
+          :datapoint_font_size    => 12
+        )
+        @data = []
+      end
+
+      # Adds a data set to the graph.
+      #
+      #   graph.add_data( { :data => [1,2,3,4] } )
+      #
+      # Note that the :title is not necessary.  If multiple
+      # data sets are added to the graph, the pie chart will
+      # display the +sums+ of the data.  EG:
+      #
+      #   graph.add_data( { :data => [1,2,3,4] } )
+      #   graph.add_data( { :data => [2,3,5,9] } )
+      #
+      # is the same as:
+      #
+      #   graph.add_data( { :data => [3,5,8,13] } )
+      def add_data arg
+        arg[:data].each_index {|idx|
+          @data[idx] = 0 unless @data[idx]
+          @data[idx] += arg[:data][idx]
+        }
+      end
+
+      # If true, displays a drop shadow for the chart
+      attr_accessor :show_shadow 
+      # Sets the offset of the shadow from the pie chart
+      attr_accessor :shadow_offset
+      # If true, display the data labels on the chart
+      attr_accessor :show_data_labels 
+      # If true, display the actual field values in the data labels
+      attr_accessor :show_actual_values 
+      # If true, display the percentage value of each pie wedge in the data
+      # labels
+      attr_accessor :show_percent
+      # If true, display the labels in the key
+      attr_accessor :show_key_data_labels 
+      # If true, display the actual value of the field in the key
+      attr_accessor :show_key_actual_values 
+      # If true, display the percentage value of the wedges in the key
+      attr_accessor :show_key_percent
+      # If true, "explode" the pie (put space between the wedges)
+      attr_accessor :expanded 
+      # If true, expand the largest pie wedge
+      attr_accessor :expand_greatest 
+      # The amount of space between expanded wedges
+      attr_accessor :expand_gap 
+      # The font size of the data point labels
+      attr_accessor :datapoint_font_size
+
+
+      protected
+
+      def add_defs defs
+        gradient = defs.add_element( "filter", {
+          "id"=>"dropshadow",
+          "width" => "1.2",
+          "height" => "1.2",
+        } )
+        gradient.add_element( "feGaussianBlur", {
+          "stdDeviation" => "4",
+          "result" => "blur"
+        })
+      end
+
+      # We don't need the graph
+      def draw_graph
+      end
+
+      def get_y_labels
+        [""]
+      end
+
+      def get_x_labels
+        [""]
+      end
+
+      def keys
+        total = 0
+        max_value = 0
+        @data.each {|x| total += x }
+        percent_scale = 100.0 / total
+        count = -1
+        a = @config[:fields].collect{ |x|
+          count += 1
+          v = @data[count]
+          perc = show_key_percent ? " "+(v * percent_scale).round.to_s+"%" : ""
+          x + " [" + v.to_s + "]" + perc
+        }
+      end
+
+      RADIANS = Math::PI/180
+
+      def draw_data
+        @graph = @root.add_element( "g" )
+        background = @graph.add_element("g")
+        midground = @graph.add_element("g")
+
+        diameter = @graph_height > @graph_width ? @graph_width : @graph_height
+        diameter -= expand_gap if expanded or expand_greatest
+        diameter -= datapoint_font_size if show_data_labels
+        diameter -= 10 if show_shadow
+        radius = diameter / 2.0
+
+        xoff = (width - diameter) / 2
+        yoff = (height - @border_bottom - diameter)
+        yoff -= 10 if show_shadow
+        @graph.attributes['transform'] = "translate( #{xoff} #{yoff} )"
+
+        wedge_text_pad = 5
+        wedge_text_pad = 20 if show_percent and show_data_labels
+
+        total = 0
+        max_value = 0
+        @data.each {|x| 
+          max_value = max_value < x ? x : max_value
+          total += x 
+        }
+        percent_scale = 100.0 / total
+
+        prev_percent = 0
+        rad_mult = 3.6 * RADIANS
+        @config[:fields].each_index { |count|
+          value = @data[count]
+          percent = percent_scale * value
+
+          radians = prev_percent * rad_mult
+          x_start = radius+(Math.sin(radians) * radius)
+          y_start = radius-(Math.cos(radians) * radius)
+          radians = (prev_percent+percent) * rad_mult
+          x_end = radius+(Math.sin(radians) * radius)
+          x_end -= 0.00001 if @data.length == 1
+          y_end = radius-(Math.cos(radians) * radius)
+          path = "M#{radius},#{radius} L#{x_start},#{y_start} "+
+            "A#{radius},#{radius} "+
+            "0, #{percent >= 50 ? '1' : '0'},1, "+
+            "#{x_end} #{y_end} Z"
+
+
+          wedge = @foreground.add_element( "path", {
+            "d" => path,
+            "class" => "fill#{count+1}"
+          })
+
+          translate = nil
+          tx = 0
+          ty = 0
+          half_percent = prev_percent + percent / 2
+          radians = half_percent * rad_mult
+
+          if show_shadow
+            shadow = background.add_element( "path", {
+              "d" => path,
+              "filter" => "url(#dropshadow)",
+              "style" => "fill: #ccc; stroke: none;"
+            })
+            clear = midground.add_element( "path", {
+              "d" => path,
+              "style" => "fill: #fff; stroke: none;"
+            })
+          end
+
+          if expanded or (expand_greatest && value == max_value)
+            tx = (Math.sin(radians) * expand_gap)
+            ty = -(Math.cos(radians) * expand_gap)
+            translate = "translate( #{tx} #{ty} )"
+            wedge.attributes["transform"] = translate
+            clear.attributes["transform"] = translate if clear
+          end
+
+          if show_shadow
+            shadow.attributes["transform"] = 
+              "translate( #{tx+shadow_offset} #{ty+shadow_offset} )"
+          end
+          
+          if show_data_labels and value != 0
+            label = ""
+            label += @config[:fields][count] if show_key_data_labels
+            label += " ["+value.to_s+"]" if show_actual_values
+            label += " "+percent.round.to_s+"%" if show_percent
+
+            msr = Math.sin(radians)
+            mcr = Math.cos(radians)
+            tx = radius + (msr * radius)
+            ty = radius -(mcr * radius)
+
+            if expanded or (expand_greatest && value == max_value)
+              tx += (msr * expand_gap)
+              ty -= (mcr * expand_gap)
+            end
+            @foreground.add_element( "text", {
+              "x" => tx.to_s,
+              "y" => ty.to_s,
+              "class" => "dataPointLabel",
+              "style" => "stroke: #fff; stroke-width: 2;"
+            }).text = label.to_s
+            @foreground.add_element( "text", {
+              "x" => tx.to_s,
+              "y" => ty.to_s,
+              "class" => "dataPointLabel",
+            }).text = label.to_s
+          end
+
+          prev_percent += percent
+        }
+      end
+      
+
+      def round val, to
+        up = 10**to.to_f
+        (val * up).to_i / up
+      end
+
+
+      def get_css
+        return <<EOL
+.dataPointLabel{
+	fill: #000000;
+	text-anchor:middle;
+	font-size: #{datapoint_font_size}px;
+	font-family: "Arial", sans-serif;
+	font-weight: normal;
+}
+
+/* key - MUST match fill styles */
+.key1,.fill1{
+	fill: #ff0000;
+	fill-opacity: 0.7;
+	stroke: none;
+	stroke-width: 1px;	
+}
+.key2,.fill2{
+	fill: #0000ff;
+	fill-opacity: 0.7;
+	stroke: none;
+	stroke-width: 1px;	
+}
+.key3,.fill3{
+	fill-opacity: 0.7;
+	fill: #00ff00;
+	stroke: none;
+	stroke-width: 1px;	
+}
+.key4,.fill4{
+	fill-opacity: 0.7;
+	fill: #ffcc00;
+	stroke: none;
+	stroke-width: 1px;	
+}
+.key5,.fill5{
+	fill-opacity: 0.7;
+	fill: #00ccff;
+	stroke: none;
+	stroke-width: 1px;	
+}
+.key6,.fill6{
+	fill-opacity: 0.7;
+	fill: #ff00ff;
+	stroke: none;
+	stroke-width: 1px;	
+}
+.key7,.fill7{
+	fill-opacity: 0.7;
+	fill: #00ff99;
+	stroke: none;
+	stroke-width: 1px;	
+}
+.key8,.fill8{
+	fill-opacity: 0.7;
+	fill: #ffff00;
+	stroke: none;
+	stroke-width: 1px;	
+}
+.key9,.fill9{
+	fill-opacity: 0.7;
+	fill: #cc6666;
+	stroke: none;
+	stroke-width: 1px;	
+}
+.key10,.fill10{
+	fill-opacity: 0.7;
+	fill: #663399;
+	stroke: none;
+	stroke-width: 1px;	
+}
+.key11,.fill11{
+	fill-opacity: 0.7;
+	fill: #339900;
+	stroke: none;
+	stroke-width: 1px;	
+}
+.key12,.fill12{
+	fill-opacity: 0.7;
+	fill: #9966FF;
+	stroke: none;
+	stroke-width: 1px;	
+}
+EOL
+      end
+    end
+  end
+end