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
