view lib/SVG/Graph/Bar.rb @ 922:ad295b270cd4 live

FIx #446: "non-utf8 paths in repositories blow up repo viewer and reposman" by ensuring the iconv conversion always happens even if source and dest are intended to be the same encoding
author Chris Cannam
date Tue, 13 Mar 2012 16:33:49 +0000
parents 513646585e45
children 3e4c3460b6ca
line wrap: on
line source
require 'rexml/document'
require 'SVG/Graph/Graph'
require 'SVG/Graph/BarBase'

module SVG
  module Graph
    # === Create presentation quality SVG bar graphs easily
    #
    # = Synopsis
    #
    #   require 'SVG/Graph/Bar'
    #
    #   fields = %w(Jan Feb Mar);
    #   data_sales_02 = [12, 45, 21]
    #
    #   graph = SVG::Graph::Bar.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[http://www.w3c.org/tr/svg bar 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, data elements at each point, title, subtitle etc.
    #
    # = Notes
    #
    # The default stylesheet handles upto 12 data sets, if you
    # use more you must create your own stylesheet and add the
    # additional settings for the extra data sets. You will know
    # if you go over 12 data sets as they will have no style and
    # be in black.
    #
    # = Examples
    #
    # * http://germane-software.com/repositories/public/SVG/test/test.rb
    #
    # = See also
    #
    # * SVG::Graph::Graph
    # * SVG::Graph::BarHorizontal
    # * SVG::Graph::Line
    # * SVG::Graph::Pie
    # * SVG::Graph::Plot
    # * SVG::Graph::TimeSeries
    class Bar < BarBase
      include REXML

      # See Graph::initialize and BarBase::set_defaults
      def set_defaults 
        super
        self.top_align = self.top_font = 1
      end

      protected

      def get_x_labels
        @config[:fields]
      end

      def get_y_labels
        maxvalue = max_value
        minvalue = min_value
        range = maxvalue - minvalue

        top_pad = range == 0 ? 10 : range / 20.0
        scale_range = (maxvalue + top_pad) - minvalue

        scale_division = scale_divisions || (scale_range / 10.0)

        if scale_integers
          scale_division = scale_division < 1 ? 1 : scale_division.round
        end

        rv = []
        maxvalue = maxvalue%scale_division == 0 ? 
          maxvalue : maxvalue + scale_division
        minvalue.step( maxvalue, scale_division ) {|v| rv << v}
        return rv
      end

      def x_label_offset( width )
        width / 2.0
      end

      def draw_data
        minvalue = min_value
        fieldwidth = field_width

        unit_size =  (@graph_height.to_f - font_size*2*top_font) / 
                          (get_y_labels.max - get_y_labels.min)
        bargap = bar_gap ? (fieldwidth < 10 ? fieldwidth / 2 : 10) : 0

        bar_width = fieldwidth - bargap
        bar_width /= @data.length if stack == :side
        x_mod = (@graph_width-bargap)/2 - (stack==:side ? bar_width/2 : 0)
 
        bottom = @graph_height

        field_count = 0
        @config[:fields].each_index { |i|
          dataset_count = 0
          for dataset in @data
          
            # cases (assume 0 = +ve):
            #   value  min  length
            #    +ve   +ve  value - min
            #    +ve   -ve  value - 0
            #    -ve   -ve  value.abs - 0
          
            value = dataset[:data][i]
            
            left = (fieldwidth * field_count)
            
            length = (value.abs - (minvalue > 0 ? minvalue : 0)) * unit_size
            # top is 0 if value is negative
            top = bottom - (((value < 0 ? 0 : value) - minvalue) * unit_size)
            left += bar_width * dataset_count if stack == :side
 
            @graph.add_element( "rect", {
              "x" => left.to_s,
              "y" => top.to_s,
              "width" => bar_width.to_s,
              "height" => length.to_s,
              "class" => "fill#{dataset_count+1}"
            })

            make_datapoint_text(left + bar_width/2.0, top - 6, value.to_s)
            dataset_count += 1
          end
          field_count += 1
        }
      end
    end
  end
end