diff options
Diffstat (limited to 'lib/SVG/Graph/Pie.rb')
-rw-r--r-- | lib/SVG/Graph/Pie.rb | 790 |
1 files changed, 395 insertions, 395 deletions
diff --git a/lib/SVG/Graph/Pie.rb b/lib/SVG/Graph/Pie.rb index 6513e23ea..ebe7eda2d 100644 --- a/lib/SVG/Graph/Pie.rb +++ b/lib/SVG/Graph/Pie.rb @@ -1,395 +1,395 @@ -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
+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 |