class DYI::Chart::PieChart

+PieChart+ creates the image of pie chart.

Basic Usage

Using +PieChart+ and ArrayReader (or sub class of ArrayReader), you can
create the pie chart as the following:
  require 'rubygems'
  require 'dyi'

  # Nominal GDP of Asian Countries (2010)
  chart_data = [['China', 5878],
                ['Japan', 5459],
                ['India', 1538],
                ['South Koria', 1007],
                ['Other Countries', 2863]]
  reader = DYI::Chart::ArrayReader.read(chart_data, :schema => [:name, :value])

  # Creates the Pie Chart
  chart = DYI::Chart::PieChart.new(450,250)
  chart.load_data(reader)
  chart.save('asian_gdp.svg')
See {ArrayReader} about how to set the chart data.

The chart options of +PieChart+ are specified at the constractor. See
<em>Instance Attribute</em> of this class, {Base} and {Legend} for the
attributes that can be specified. The specified attributes can be refered
and set.
  # Creates the Pie Chart
  chart = DYI::Chart::PieChart.new(500,250,
            :center_point => [130, 100],
            :legend_point => [250, 50],
            :represent_3d => true,
            :_3d_settings => {:dy => 20},
            :legend_format => "{?name}\t{!e}{?value:#,0}\t{!e}({?percent:0.0%})",
            :chart_stroke_color => 'white')
  puts chart.represent_3d?    # => true
  chart.show_baloon = false
  puts chart.show_baloon?     # => false

  chart.load_data(reader)
  chart.save('asian_gdp.svg')

Adds Custom Elements to Chart

Using {#canvas canvas} attribute, you can add arbitrary graphical elements.
  # Creates the Pie Chart
  chart = DYI::Chart::PieChart.new(500,250,
            :center_point => [130, 100],
            :legend_point => [250, 50],
            :represent_3d => true,
            :_3d_settings => {:dy => 20},
            :legend_format => "{?name}\t{!e}{?value:#,0}\t{!e}({?percent:0.0%})",
            :chart_stroke_color => 'white')

  DYI::Drawing::Pen.black_pen.draw_text(chart.canvas,
            [250, 20],
            'Nominal GDP of Asian Countries (2010)',
            :text_anchor => 'middle')
  chart.load_data(reader)
  chart.save('asian_gdp.svg')
@see LineChart
@see ArrayReader
@since 0.0.0

Attributes

chart_canvas[R]

Returns the container element which body of chart is drawn on. @return [Shape::ShapeGroup] the container element which chart parts is

drawn on
data_label_canvas[R]

Returns the container element which data labels is drawn on. @return [Shape::ShapeGroup] the container element which chart parts is

drawn on
legend_canvas[R]

Returns the container element which legend is drawn on. @return [Shape::ShapeGroup] the container element which chart parts is

drawn on

Public Instance Methods

back_translate_value() click to toggle source
# File lib/dyi/chart/pie_chart.rb, line 222
def back_translate_value
  {:dy => (Length.new_or_nil(_3d_settings[:dy]) || chart_radius_y.quo(2))}
end
get_baloon_background_color(index) click to toggle source

@since 1.0.0

# File lib/dyi/chart/pie_chart.rb, line 227
def get_baloon_background_color(index)
  (baloon_background_colors && baloon_background_colors[index]) ||
      baloon_background_color ||
      chart_color(index).merge('white', 0.7)
end
get_baloon_border_color(index) click to toggle source

@since 1.0.0

# File lib/dyi/chart/pie_chart.rb, line 234
def get_baloon_border_color(index)
  (baloon_border_colors && baloon_border_colors[index]) ||
      baloon_border_color ||
      chart_color(index).merge('black', 0.3)
end

Private Instance Methods

create_vector_image() click to toggle source
Calls superclass method DYI::Chart::Base#create_vector_image
# File lib/dyi/chart/pie_chart.rb, line 267
def create_vector_image
  super
  if represent_3d?
    brush = Drawing::ColumnBrush.new(back_translate_value.merge(chart_stroke_color ? {:stroke_width => chart_stroke_width, :stroke => chart_stroke_color} : {}))
  else
    brush = Drawing::Brush.new(chart_stroke_color ? {:stroke_width => chart_stroke_width, :stroke => chart_stroke_color, :stroke_miterlimit => chart_stroke_width} : {})
  end
  attrs = if pie_css_class && !pie_css_class.empty?
            {:css_class => pie_css_class}
          else
            {}
          end
  @chart_canvas = Shape::ShapeGroup.draw_on(@canvas)
  attrs = if data_label_css_class && !data_label_css_class.empty?
            {:css_class => data_label_css_class}
          else
            {}
          end
  @data_label_canvas = Shape::ShapeGroup.draw_on(@canvas, attrs)
  attrs = if legend_css_class && !legend_css_class.empty?
            {:css_class => legend_css_class}
          else
            {}
          end
  @legend_canvas = Shape::ShapeGroup.draw_on(@canvas, attrs)
  @legends = []
  @sectors = []
  draw_legend(data.records, nil)
  total_value = data.inject(0.0) {|sum, record| sum + (record.value || 0)}
  accumulation = 0.0
  accumulations = []
  stop_index = -1
  data.each_with_index do |record, i|
    accumulations.push(accumulation)
    value = record.value
    if value && total_value > (accumulation + value) * 2 && value != 0.0
      brush.color = chart_color(i)
      draw_chart(brush, record, accumulation, total_value, i)
      stop_index = i
    end
    accumulation += value if value
  end
  (data.records_size - 1).downto(stop_index + 1) do |i|
    value = (record = data.records[i]).value
    if value && value != 0.0
      brush.color = chart_color(i)
      draw_chart(brush, record, accumulations[i], total_value, i)
    end
  end
  @sectors.each_with_index do |sector, i|
    @legends.each_with_index do |legend, j|
      if i != j
        sector.parent.add_painting_animation(:to => {:opacity => 0.35},
                                    :duration => animation_duration,
                                    :fill => 'freeze',
                                    :begin_event => Event.mouseover(legend),
                                    :end_event => Event.mouseout(legend))
        sector.parent.add_painting_animation(:to => {:opacity => 1},
                                    :fill => 'freeze',
                                    :begin_event => Event.mouseout(legend))
      end
    end
  end
end
default_center_point() click to toggle source
# File lib/dyi/chart/pie_chart.rb, line 242
def default_center_point
  margin = [width - chart_radius_x * 2, height - chart_radius_y * 2].min.quo(2)
  Coordinate.new(margin + chart_radius_x, margin + chart_radius_y)
end
default_chart_radius_x() click to toggle source
# File lib/dyi/chart/pie_chart.rb, line 247
def default_chart_radius_x
  [width, height].min * 0.4
end
default_chart_radius_y() click to toggle source
# File lib/dyi/chart/pie_chart.rb, line 251
def default_chart_radius_y
  represent_3d? ? chart_radius_x.quo(2) : chart_radius_x
end
default_legend_format() click to toggle source
# File lib/dyi/chart/pie_chart.rb, line 263
def default_legend_format
  "{?name}\t{?percent}"
end
default_legend_point() click to toggle source
# File lib/dyi/chart/pie_chart.rb, line 255
def default_legend_point
  if width - chart_radius_x * 2 < height - chart_radius_y * 2
    Coordinate.new(width * 0.1, chart_radius_y * 2 + (width - chart_radius_x * 2) * 0.8)
  else
    Coordinate.new(chart_radius_x * 2 + (height - chart_radius_y * 2) * 0.8, height * 0.1 + Length.new(10))
  end
end
draw_baloon(brush, record, accumulation, total_value, index, ratio, pie_sector) click to toggle source

@since 1.0.0

# File lib/dyi/chart/pie_chart.rb, line 370
def draw_baloon(brush, record, accumulation, total_value, index, ratio, pie_sector)
  value = record.value
  if show_baloon?
    dr = moved_elements && moved_elements[index]
    baloon_point = Coordinate.new(
        chart_radius_x * (baloon_position + (dr || 0)) *
            Math.cos(((accumulation * 2.0 + value) / total_value - 0.5) * Math::PI),
        chart_radius_y * (baloon_position + (dr || 0)) *
            Math.sin(((accumulation * 2.0 + value) / total_value - 0.5) * Math::PI))
    baloon_options = {:text_anchor => 'middle', :show_border => true}
    if data.has_field?(:css_class) && (css_class = record.css_class)
      baloon_options[:css_class] = record.css_class
    end
    baloon_options[:vertical_padding] = baloon_padding[:vertical_padding] || 2
    baloon_options[:horizontal_padding] = baloon_padding[:horizontal_padding] || 5
    baloon_options[:background_color] = get_baloon_background_color(index)
    baloon_options[:border_color] = get_baloon_border_color(index)
    baloon_options[:border_width] = baloon_border_width
    baloon_options[:border_rx] = baloon_round
    if baloon_css_class && baloon_css_class.empty?
      @data_label_canvas.add_css_class(baloon_css_class)
    end
    text = Drawing::Pen.black_pen(:font => baloon_font, :opacity => 0.0).draw_text(
        @data_label_canvas,
        center_point + baloon_point,
        format_string(baloon_format, record, total_value),
        baloon_options)
    text.add_painting_animation(:to => {:opacity => 1},
                                :duration => animation_duration,
                                :fill => 'freeze',
                                :begin_event => Event.mouseover(pie_sector))
    text.add_painting_animation(:to => {:opacity => 0},
                                :duration => animation_duration,
                                :fill => 'freeze',
                                :begin_event => Event.mouseout(pie_sector))
    if @legends
      text.add_painting_animation(:to => {:opacity => 1},
                                  :duration => animation_duration,
                                  :fill => 'freeze',
                                  :begin_event => Event.mouseover(@legends[index]))
      text.add_painting_animation(:to => {:opacity => 0},
                                  :duration => animation_duration,
                                  :fill => 'freeze',
                                  :begin_event => Event.mouseout(@legends[index]))
    end
  end
end
draw_chart(brush, record, accumulation, total_value, index) click to toggle source
# File lib/dyi/chart/pie_chart.rb, line 332
def draw_chart(brush, record, accumulation, total_value, index)
  canvas = Shape::ShapeGroup.draw_on(@chart_canvas)
  attrs = {}
  if data.has_field?(:css_class) && (css_class = record.css_class)
    attrs[:css_class] = record.css_class
  end
  value = record.value
  pie_sector = brush.draw_sector(
    canvas,
    center_point,
    chart_radius_x,
    chart_radius_y,
    accumulation * 360.0 / total_value - 90,
    value * 360.0 / total_value,
    attrs.merge(:inner_radius => inner_radius))
  @sectors[index] = pie_sector

  if moved_elements && (dr = moved_elements[index])
    canvas.translate(
      chart_radius_x * dr * Math.cos(((accumulation * 2.0 + value) / total_value - 0.5) * Math::PI),
      chart_radius_y * dr * Math.sin(((accumulation * 2.0 + value) / total_value - 0.5) * Math::PI))
  end

  ratio = value.to_f.quo(total_value)
  if show_data_label?
    label_point = Coordinate.new(
      chart_radius_x * (data_label_position + (dr || 0)) * Math.cos(((accumulation * 2.0 + value) / total_value - 0.5) * Math::PI),
      chart_radius_y * (data_label_position + (dr || 0)) * Math.sin(((accumulation * 2.0 + value) / total_value - 0.5) * Math::PI))
    Drawing::Pen.black_pen(:font => data_label_font).draw_text(
      @data_label_canvas,
      center_point + label_point,
      format_string(data_label_format, record, total_value),
      attrs.merge(:text_anchor => 'middle'))
  end if hide_data_label_ratio < ratio
  draw_baloon(brush, record, accumulation, total_value, index, ratio, pie_sector)
end
draw_legend(records, shapes=nil) click to toggle source

@since 1.0.0

# File lib/dyi/chart/pie_chart.rb, line 419
def draw_legend(records, shapes=nil)
  legend_canvas.translate(legend_point.x, legend_point.y)
  if show_legend?
    pen = Drawing::Pen.black_pen(:font => legend_font)
    brush = Drawing::Brush.new
    toatal = records.inject(0.0){|sum, record| sum + record.value}
    formats = legend_format.split("\t")
    legend_labels = []
    records.each_with_index do |record, i|
      legend_labels << formats.map do |format|
                         format_string(format, record, toatal)
                       end
    end
    max_lengths = legend_labels.inject(Array.new(formats.size, 0)) do |maxs, labels|
                    (0...formats.size).each do |i|
                      maxs[i] = labels[i].bytesize if maxs[i] < labels[i].bytesize
                    end
                    maxs
                  end
    canvas.add_initialize_script(Script::EcmaScript::DomLevel2.form_legend_labels(legend_canvas))
    records.each_with_index do |record, i|
      y = legend_font_size * (1.2 * (i + 1))
      attrs = {}
      if data.has_field?(:css_class) && (css_class = record.css_class)
        attrs[:css_class] = record.css_class
      end
      group = Shape::ShapeGroup.draw_on(legend_canvas, attrs)
      @legends << group
      case shapes && shapes[i]
      when Shape::Base
        shapes[i].draw_on(group)
      when NilClass
        brush.color = chart_color(i)
        brush.draw_rectangle(
          group,
          Coordinate.new(legend_font_size * 0.2, y - legend_font_size * 0.8),
          legend_font_size * 0.8,
          legend_font_size * 0.8)
      end
      x = legend_font_size * 0.2 + legend_font_size
      legend_labels[i].each_with_index do |label, j|
        formats[j] =~ /\A\{!(\w)\}/
        case $1
        when 's'
          attrs = {:text_anchor => 'start'}
          pen.draw_text(group, Coordinate.new(x, y), label, attrs)
          x += legend_font_size * max_lengths[j] * 0.5
        when 'm'
          attrs = {:text_anchor => 'middle'}
          x += legend_font_size * max_lengths[j] * 0.25
          pen.draw_text(group, Coordinate.new(x, y), label, attrs)
          x += legend_font_size * max_lengths[j] * 0.25
        when 'e'
          attrs = {:text_anchor => 'end'}
          x += legend_font_size * max_lengths[j] * 0.5
          pen.draw_text(group, Coordinate.new(x, y), label, attrs)
        else
          attrs = {}
          pen.draw_text(group, Coordinate.new(x, y), label, attrs)
          x += legend_font_size * max_lengths[j] * 0.5
        end
      end
    end
  end
end
format_string(format, record, total_value) click to toggle source

@since 1.0.0

# File lib/dyi/chart/pie_chart.rb, line 486
def format_string(format, record, total_value)
  format = format.gsub(/\A\{!(\w)\}/, '')
  format.gsub(/\{\?((?![0-9])\w+)(:[^}]*)?\}/){|m|
    fmt = $2 ? $2[1..-1] : nil
    if $1 == 'percent'
      value = record.value.quo(total_value)
      fmt ||= '0.0%'
    else
      value = record.__send__($1)
    end
    if fmt
      case value
        when Numeric then value.strfnum(fmt)
        when DateTime, Time then value.strftime(fmt)
        else fmt % value
      end
    else
      value
    end
  }
end