lib/sparklines.rb in sparklines-0.4.6 vs lib/sparklines.rb in sparklines-0.4.7

- old
+ new

@@ -72,19 +72,19 @@ Licensed under the MIT license. =end class Sparklines - VERSION = '0.4.6' + VERSION = '0.4.7' @@label_margin = 5.0 @@pointsize = 10.0 class << self - # Does the actual plotting of the graph. - # Calls the appropriate subclass based on the :type argument. + # Does the actual plotting of the graph. + # Calls the appropriate subclass based on the :type argument. # Defaults to 'smooth' def plot(data=[], options={}) defaults = { :type => 'smooth', :height => 14, @@ -97,30 +97,30 @@ :below_color => 'grey', :background_color => 'white', :share_color => 'red', :remain_color => 'lightgrey', :min_color => 'blue', - :max_color => 'green', - :last_color => 'red', + :max_color => 'green', + :last_color => 'red', :std_dev_color => '#efefef', - + :has_min => false, :has_max => false, :has_last => false, :has_std_dev => false, - + :label => nil } # HACK for HashWithIndifferentAccess options_sym = Hash.new options.keys.each do |key| options_sym[key.to_sym] = options[key] end options_sym = defaults.merge(options_sym) - + # Call the appropriate method for actual plotting. sparkline = self.new(data, options_sym) if %w(area bar pie smooth discrete whisker).include? options_sym[:type] sparkline.send options_sym[:type] else @@ -145,37 +145,37 @@ ## # Creates a continuous area sparkline. Relevant options. # # :step - An integer that determines the distance between each point on the sparkline. Defaults to 2. - # + # # :height - An integer that determines what the height of the sparkline will be. Defaults to 14 - # + # # :upper - An integer that determines the threshold for colorization purposes. Any value less than upper will be colored using below_color, anything above and equal to upper will use above_color. Defaults to 50. - # + # # :has_min - Determines whether a dot will be drawn at the lowest value or not. Defaults to false. - # + # # :has_max - Determines whether a dot will be drawn at the highest value or not. Defaults to false. - # + # # :has_last - Determines whether a dot will be drawn at the last value or not. Defaults to false. - # + # # :min_color - A string or color code representing the color that the dot drawn at the smallest value will be displayed as. Defaults to blue. - # + # # :max_color - A string or color code representing the color that the dot drawn at the largest value will be displayed as. Defaults to green. - # + # # :last_color - A string or color code representing the color that the dot drawn at the last value will be displayed as. Defaults to red. - # + # # :above_color - A string or color code representing the color to draw values above or equal the upper value. Defaults to red. - # + # # :below_color - A string or color code representing the color to draw values below the upper value. Defaults to gray. def area step = @options[:step].to_f height = @options[:height].to_f background_color = @options[:background_color] - + create_canvas((@norm_data.size - 1) * step + 4, height, background_color) upper = @options[:upper].to_f has_min = @options[:has_min] @@ -190,12 +190,12 @@ coords = [[0,(height - 3 - upper/(101.0/(height-4)))]] i=0 @norm_data.each do |r| - coords.push [(2 + i), (height - 3 - r/(101.0/(height-4)))] - i += step + coords.push [(2 + i), (height - 3 - r/(101.0/(height-4)))] + i += step end coords.push [(@norm_data.size - 1) * step + 4, (height - 3 - upper/(101.0/(height-4)))] # TODO Refactor! Should take a block and do both. # @@ -223,14 +223,14 @@ # so a max dot can be displayed @draw.define_clip_path('all') do @draw.rectangle(0,0,@canvas.columns,@canvas.rows) end @draw.clip_path('all') - + drawbox(coords[@norm_data.index(@norm_data.min)+1], 1, min_color) if has_min == true drawbox(coords[@norm_data.index(@norm_data.max)+1], 1, max_color) if has_max == true - + drawbox(coords[-2], 1, last_color) if has_last == true @draw.draw(@canvas) @canvas.to_blob end @@ -242,22 +242,22 @@ step = @options[:step].to_f height = @options[:height].to_f background_color = @options[:background_color] create_canvas(@norm_data.length * step + 2, height, background_color) - + upper = @options[:upper].to_f below_color = @options[:below_color] above_color = @options[:above_color] i = 1 @norm_data.each_with_index do |r, index| color = (r >= upper) ? above_color : below_color @draw.stroke('transparent') @draw.fill(color) - @draw.rectangle( i, @canvas.rows, - i + step - 2, @canvas.rows - ( (r / @maximum_value) * @canvas.rows) ) + @draw.rectangle( i, @canvas.rows, + i + step - 2, @canvas.rows - ( (r / @maximum_value) * @canvas.rows) ) i += step end @draw.draw(@canvas) @canvas.to_blob @@ -281,26 +281,26 @@ upper = @options[:upper].to_f background_color = @options[:background_color] step = @options[:step].to_f width = @norm_data.size * step - 1 - + create_canvas(@norm_data.size * step - 1, height, background_color) - + below_color = @options[:below_color] above_color = @options[:above_color] std_dev_color = @options[:std_dev_color] drawstddevbox(width,height,std_dev_color) if @options[:has_std_dev] == true i = 0 @norm_data.each do |r| - color = (r >= upper) ? above_color : below_color - @draw.stroke(color) - @draw.line(i, (@canvas.rows - r/(101.0/(height-4))-4).to_f, - i, (@canvas.rows - r/(101.0/(height-4))).to_f) - i += step + color = (r >= upper) ? above_color : below_color + @draw.stroke(color) + @draw.line(i, (@canvas.rows - r/(101.0/(height-4))-4).to_f, + i, (@canvas.rows - r/(101.0/(height-4))).to_f) + i += step end @draw.draw(@canvas) @canvas.to_blob end @@ -318,31 +318,31 @@ def pie diameter = @options[:diameter].to_f background_color = @options[:background_color] create_canvas(diameter, diameter, background_color) - + share_color = @options[:share_color] remain_color = @options[:remain_color] percent = @norm_data[0] - + # Adjust the radius so there's some edge left in the pie r = diameter/2.0 - 2 @draw.fill(remain_color) @draw.ellipse(r + 2, r + 2, r , r , 0, 360) @draw.fill(share_color) # Special exceptions if percent == 0 - # For 0% return blank - @draw.draw(@canvas) - return @canvas.to_blob + # For 0% return blank + @draw.draw(@canvas) + return @canvas.to_blob elsif percent == 100 - # For 100% just draw a full circle - @draw.ellipse(r + 2, r + 2, r , r , 0, 360) - @draw.draw(@canvas) - return @canvas.to_blob + # For 100% just draw a full circle + @draw.ellipse(r + 2, r + 2, r , r , 0, 360) + @draw.draw(@canvas) + return @canvas.to_blob end # Okay, this part is as confusing as hell, so pay attention: # This line determines the horizontal portion of the point on the circle where the X-Axis # should end. It's caculated by taking the center of the on-image circle and adding that @@ -400,34 +400,34 @@ def smooth step = @options[:step].to_f height = @options[:height].to_f width = ((@norm_data.size - 1) * step).to_f - + background_color = @options[:background_color] create_canvas(width, height, background_color) - + min_color = @options[:min_color] max_color = @options[:max_color] last_color = @options[:last_color] has_min = @options[:has_min] has_max = @options[:has_max] has_last = @options[:has_last] line_color = @options[:line_color] has_std_dev = @options[:has_std_dev] std_dev_color = @options[:std_dev_color] - + drawstddevbox(width,height,std_dev_color) if has_std_dev == true @draw.stroke(line_color) coords = [] i=0 @norm_data.each do |r| coords.push [ i, (height - 3 - r/(101.0/(height-4))) ] i += step end - + if @options[:underneath_color] closed_polygon(height, width, coords) else open_ended_polyline(coords) end @@ -437,36 +437,36 @@ drawbox(coords[-1], 2, last_color) if has_last == true @draw.draw(@canvas) @canvas.to_blob end - + ## - # Creates a whisker sparkline to track on/off type data. There are five states: - # on, off, no value, exceptional on, exceptional off. On values create an up - # whisker and off values create a down whisker. Exceptional values may be + # Creates a whisker sparkline to track on/off type data. There are five states: + # on, off, no value, exceptional on, exceptional off. On values create an up + # whisker and off values create a down whisker. Exceptional values may be # colored differently than regular values to indicate, for example, a shut out. # No value produces an empty row to indicate a tie. - # - # * results - an array of integer values between -2 and 2. -2 is exceptional + # + # * results - an array of integer values between -2 and 2. -2 is exceptional # down, 1 is regular down, 0 is no value, 1 is up, and 2 is exceptional up. # * options - a hash that takes parameters # # :height - height of the sparkline # # :whisker_color - the color of regular whiskers; defaults to black # # :exception_color - the color of exceptional whiskers; defaults to red - + def whisker # step = @options[:step].to_f height = @options[:height].to_f background_color = @options[:background_color] create_canvas(@data.size * 2 - 1, height, background_color) - + whisker_color = @options[:whisker_color] || 'black' exception_color = @options[:exception_color] || 'red' on_row = (@canvas.rows/2.0 - 1).ceil off_row = (@canvas.rows/2.0).floor @@ -479,11 +479,11 @@ end y_mid_point = (r >= 1) ? on_row : off_row y_end_point = y_mid_point - if ( r > 0) + if ( r > 0) y_end_point = 0 end if ( r < 0 ) y_end_point = @canvas.rows @@ -493,11 +493,11 @@ @draw.line( i, y_mid_point, i, y_end_point ) i += 2 end @draw.draw(@canvas) - @canvas.to_blob + @canvas.to_blob end ## # Draw the error Sparkline. @@ -510,43 +510,49 @@ @draw.draw(@canvas) @canvas.to_blob end -private + private def normalize_data - @minumum_value = @data.min - @maximum_value = @data.max - abs_min = @minumum_value.abs + @minimum_value = @data.min + @maximum_value = @data.max if @options[:type].to_s == 'pie' @norm_data = @data else - @norm_data = @data.map { |value| value = (value+abs_min).to_f / (@maximum_value+abs_min) * 100.0 } + @norm_data = @data.map do |value| + value = ((value.to_f - @minimum_value)/(@maximum_value - @minimum_value)) * 100.0 + end end end ## # :arr - an array of points (represented as two element arrays) - + def open_ended_polyline(arr) 0.upto(arr.length - 2) { |i| @draw.line(arr[i][0], arr[i][1], arr[i+1][0], arr[i+1][1]) } end - + # Fills in the area under the line (used for a smooth graph) def closed_polygon(height, width, coords) return if @options[:underneath_color].nil? list = [] - list << [0, height] + # Start off screen so completed polygon doesn't show + list << [-1, height + 1] + list << [coords.first.first - 1, coords.first.last] + # Now the normal coords list << coords - list << [width, height] + # Close offscreen + list << [coords.last.first + 1, coords.last.last] + list << [width + 1, height + 1] @draw.fill( @options[:underneath_color] ) @draw.polygon( *list.flatten ) end - + ## # Create an image to draw on and a drawable to do the drawing with. # # TODO Refactor into smaller methods @@ -580,19 +586,19 @@ @font = @options[:font] if @options.has_key?(:font) @draw.fill = 'black' @draw.font = @font if @font @draw.gravity = Magick::WestGravity - @draw.annotate( @canvas, - @label_width, 1.0, - w - @label_and_data_last_width + @@label_margin, h - calculate_caps_height/2.0, - @options[:label]) + @draw.annotate( @canvas, + @label_width, 1.0, + w - @label_and_data_last_width + @@label_margin, h - calculate_caps_height/2.0, + @options[:label]) @draw.fill = 'red' - @draw.annotate( @canvas, - @data_last_width, 1.0, - w - @data_last_width - @@label_margin * 2.0, h - calculate_caps_height/2.0, - @data.last.to_s) + @draw.annotate( @canvas, + @data_last_width, 1.0, + w - @data_last_width - @@label_margin * 2.0, h - calculate_caps_height/2.0, + @data.last.to_s) end end ## # Utility to draw a coloured box