require File.dirname(__FILE__) + '/base' class Gruff::Pie < Gruff::Base def draw @hide_line_markers = true super return unless @has_data diameter = @graph_height radius = @graph_height / 2.0 top_x = @graph_left + (@graph_width - diameter) / 2.0 center_x = @graph_left + (@graph_width / 2.0) center_y = @graph_top + (@graph_height / 2.0) - 10 # Move graph up a bit total_sum = sums_for_pie() prev_degrees = 0.0 # Use full data since we can easily calculate percentages @data.each do |data_row| if data_row[1][0] > 0 @d = @d.stroke data_row[DATA_COLOR_INDEX] @d = @d.fill 'transparent' @d.stroke_width(200.0) current_degrees = (data_row[1][0] / total_sum) * 360.0 radius = 100.0 @d = @d.ellipse(center_x, center_y, radius, radius, prev_degrees, prev_degrees + current_degrees + 0.5) # <= +0.5 'fudge factor' gets rid of the ugly gaps half_angle = prev_degrees + ((prev_degrees + current_degrees) - prev_degrees) / 2 @d = draw_label(center_x,center_y, half_angle, radius, ((data_row[1][0] / total_sum) * 100).round.to_s + '% ') prev_degrees += current_degrees end end @d.draw(@base_image) end private def draw_label(center_x, center_y, angle, radius, amount) r_offset = 130 # The distance out from the center of the pie to get point x_offset = center_x + 15 # The label points need to be tweaked slightly y_offset = center_y + 0 # This one doesn't though x = x_offset + ((radius + r_offset) * Math.cos(angle.deg2rad)) y = y_offset + ((radius + r_offset) * Math.sin(angle.deg2rad)) # Draw label @d.fill = @marker_color @d.font = @font if @font @d.pointsize = scale_fontsize(20) @d.stroke = 'transparent' @d.font_weight = BoldWeight @d.gravity = CenterGravity @d.annotate_scaled( @base_image, 0, 0, x, y, amount, @scale) end def sums_for_pie total_sum = 0.0 @data.collect {|data_row| total_sum += data_row[1][0] } total_sum end end class Float # Used for degree => radian conversions def deg2rad self * (Math::PI/180.0) end end # From sparklines...not currently used class Magick::Draw def pie_slice(center_x=0.0, center_y=0.0, radius=100.0, percent=0.0, rot_x=0.0) # 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 # to the radius multiplied by the formula for determinig the point on a unit circle that a # angle corresponds to. 3.6 * percent gives us that angle, but it's in degrees, so we need to # convert, hence the muliplication by Pi over 180 arc_end_x = radius + (radius * Math.cos((3.6 * percent)*(Math::PI/180.0))) # The same goes for here, except it's the vertical point instead of the horizontal one arc_end_y = radius + (radius * Math.sin((3.6 * percent)*(Math::PI/180.0))) # Because the SVG path format is seriously screwy, we need to set the large-arc-flag to 1 # if the angle of an arc is greater than 180 degrees. I have no idea why this is, but it is. percent > 50 ? large_arc_flag = 1 : large_arc_flag = 0 # This is also confusing # M tells us to move to an absolute point on the image. # We're moving to the center of the pie # h tells us to move to a relative point. # We're moving to the right edge of the circle. # A tells us to start an absolute elliptical arc. # The first two values are the radii of the ellipse # The third value is the x-axis-rotation (how to rotate the ellipse) # The fourth value is our large-arc-flag # The fifth is the sweep-flag # The sixth and seventh values are the end point of the arc which we calculated previously # More info on the SVG path string format at: http://www.w3.org/TR/SVG/paths.html # #path = "M#{radius + 2},#{radius + 2} h#{radius} A#{radius},#{radius} #{rot_x} #{large_arc_flag},1 #{arc_end_x},#{arc_end_y} z" path = "M#{radius},#{radius} h#{radius} A#{radius},#{radius} #{rot_x} #{large_arc_flag},1 #{arc_end_x},#{arc_end_y} z" puts "PATH: #{path}" self.path(path) end end