Class | SVG::Graph::Graph |
In: |
lib/SVG/Graph/Graph.rb
|
Parent: | Object |
This class is only used as a superclass of specialized charts. Do not attempt to use this class directly, unless creating a new chart type.
For examples of how to subclass this class, see the existing specific subclasses, such as SVG::Graph::Pie.
For examples of how to use this package, see either the test files, or the documentation for the specific class you want to use.
This package should be used as a base for creating SVG graphs.
Leo Lapworth for creating the SVG::TT::Graph package which this Ruby port is based on.
Stephen Morgan for creating the TT template and SVG.
Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom>
Copyright 2004 Sean E. Russell This software is available under the Ruby license
KEY_BOX_SIZE | = | 12 |
add_popups | [RW] | Add popups for the data points on some graphs |
font_size | [RW] | Set the font size (in points) of the data point labels |
graph_subtitle | [RW] | What the subtitle on the graph should be. |
graph_title | [RW] | What the title on the graph should be. |
height | [RW] | Set the height of the graph box, this is the total height of the SVG box created - not the graph it self which auto scales to fix the space. |
key | [RW] | Whether to show a key, defaults to false, set to true if you want to show it. |
key_font_size | [RW] | Set the key font size |
key_position | [RW] | Where the key should be positioned, defaults to :right, set to :bottom if you want to move it. |
min_scale_value | [RW] | The point at which the Y axis starts, defaults to ‘0’, if set to nil it will default to the minimum data value. |
no_css | [RW] | Do not use CSS if set to true. Many SVG viewers do not support CSS, but not using CSS can result in larger SVGs as well as making it impossible to change colors after the chart is generated. Defaults to false. |
popup_radius | [RW] | Customize popup radius |
right_align | [RW] | |
right_font | [RW] | |
rotate_x_labels | [RW] | This turns the X axis labels by 90 degrees. Default it false, to turn on set to true. |
rotate_y_labels | [RW] | This turns the Y axis labels by 90 degrees. Default it false, to turn on set to true. |
scale_divisions | [RW] | This defines the gap between markers on the Y axis, default is a 10th of the max_value, e.g. you will have 10 markers on the Y axis. NOTE: do not set this too low - you are limited to 999 markers, after that the graph won‘t generate. |
scale_integers | [RW] | Ensures only whole numbers are used as the scale divisions. Default it false, to turn on set to true. This has no effect if scale divisions are less than 1. |
show_data_values | [RW] | (Bool) Show the value of each element of data on the graph |
show_graph_subtitle | [RW] | Whether to show a subtitle on the graph, defaults to false, set to true to show. |
show_graph_title | [RW] | Whether to show a title on the graph, defaults to false, set to true to show. |
show_x_guidelines | [RW] | Show guidelines for the X axis |
show_x_labels | [RW] | Whether to show labels on the X axis or not, defaults to true, set to false if you want to turn them off. |
show_x_title | [RW] | Whether to show the title under the X axis labels, default is false, set to true to show. |
show_y_guidelines | [RW] | Show guidelines for the Y axis |
show_y_labels | [RW] | Whether to show labels on the Y axis or not, defaults to true, set to false if you want to turn them off. |
show_y_title | [RW] | Whether to show the title under the Y axis labels, default is false, set to true to show. |
stagger_x_labels | [RW] | This puts the X labels at alternative levels so if they are long field names they will not overlap so easily. Default it false, to turn on set to true. |
stagger_y_labels | [RW] | This puts the Y labels at alternative levels so if they are long field names they will not overlap so easily. Default it false, to turn on set to true. |
step_include_first_x_label | [RW] | Whether to (when taking "steps" between X axis labels) step from the first label (i.e. always include the first label) or step from the X axis origin (i.e. start with a gap if step_x_labels is greater than one). |
step_x_labels | [RW] | How many "steps" to use between displayed X axis labels, a step of one means display every label, a step of two results in every other label being displayed (label <gap> label <gap> label), a step of three results in every third label being displayed (label <gap> <gap> label <gap> <gap> label) and so on. |
style_sheet | [RW] |
Set the path to an external stylesheet, set to ’’ if you want
to revert back to using the defaut internal version.
To create an external stylesheet create a graph using the default internal version and copy the stylesheet section to an external file and edit from there. |
subtitle_font_size | [RW] | Set the subtitle font size |
title_font_size | [RW] | Set the title font size |
top_align | [RW] | |
top_font | [RW] | |
width | [RW] | Set the width of the graph box, this is the total width of the SVG box created - not the graph it self which auto scales to fix the space. |
x_label_font_size | [RW] | Set the font size of the X axis labels |
x_title | [RW] | What the title under X axis should be, e.g. ‘Months’. |
x_title_font_size | [RW] | Set the font size of the X axis title |
y_label_font_size | [RW] | Set the font size of the Y axis labels |
y_title | [RW] | What the title under Y axis should be, e.g. ‘Sales in thousands’. |
y_title_font_size | [RW] | Set the font size of the Y axis title |
y_title_text_direction | [RW] | Aligns writing mode for Y axis label. Defaults to :bt (Bottom to Top). Change to :tb (Top to Bottom) to reverse. |
Initialize the graph object with the graph settings. You won‘t instantiate this class directly; see the subclass for options.
# File lib/SVG/Graph/Graph.rb, line 100 100: def initialize( config ) 101: @config = config 102: @data = nil 103: self.top_align = self.top_font = self.right_align = self.right_font = 0 104: 105: init_with({ 106: :width => 500, 107: :height => 300, 108: :show_x_guidelines => false, 109: :show_y_guidelines => true, 110: :show_data_values => true, 111: 112: # :min_scale_value => 0, 113: 114: :show_x_labels => true, 115: :stagger_x_labels => false, 116: :rotate_x_labels => false, 117: :step_x_labels => 1, 118: :step_include_first_x_label => true, 119: 120: :show_y_labels => true, 121: :rotate_y_labels => false, 122: :stagger_y_labels => false, 123: :scale_integers => false, 124: 125: :show_x_title => false, 126: :x_title => 'X Field names', 127: 128: :show_y_title => false, 129: :y_title_text_direction => :bt, 130: :y_title => 'Y Scale', 131: 132: :show_graph_title => false, 133: :graph_title => 'Graph Title', 134: :show_graph_subtitle => false, 135: :graph_subtitle => 'Graph Sub Title', 136: :key => true, 137: :key_position => :right, # bottom or right 138: 139: :font_size =>12, 140: :title_font_size =>16, 141: :subtitle_font_size =>14, 142: :x_label_font_size =>12, 143: :y_label_font_size =>12, 144: :x_title_font_size =>14, 145: :y_label_font_size =>12, 146: :y_title_font_size =>14, 147: :key_font_size =>10, 148: 149: :no_css =>false, 150: :add_popups =>false, 151: }) 152: set_defaults if self.respond_to? :set_defaults 153: init_with config 154: end
This method allows you do add data to the graph object. It can be called several times to add more data sets in.
data_sales_02 = [12, 45, 21]; graph.add_data({ :data => data_sales_02, :title => 'Sales 2002' })
# File lib/SVG/Graph/Graph.rb, line 166 166: def add_data conf 167: @data = [] unless (defined? @data and !@data.nil?) 168: 169: if conf[:data] and conf[:data].kind_of? Array 170: @data << conf 171: else 172: raise "No data provided by #{conf.inspect}" 173: end 174: end
This method processes the template with the data and config which has been set and returns the resulting SVG.
This method will croak unless at least one data set has been added to the graph object.
print graph.burn
# File lib/SVG/Graph/Graph.rb, line 193 193: def burn 194: raise "No data available" unless @data.size > 0 195: 196: calculations if methods.include? 'calculations' 197: 198: start_svg 199: calculate_graph_dimensions 200: @foreground = Element.new( "g" ) 201: draw_graph 202: draw_titles 203: draw_legend 204: draw_data 205: @graph.add_element( @foreground ) 206: style 207: 208: data = "" 209: @doc.write( data, 0 ) 210: 211: if @config[:compress] 212: if @@__have_zlib 213: inp, out = IO.pipe 214: gz = Zlib::GzipWriter.new( out ) 215: gz.write data 216: gz.close 217: data = inp.read 218: else 219: data << "<!-- Ruby Zlib not available for SVGZ -->"; 220: end 221: end 222: 223: return data 224: end
Adds pop-up point information to a graph.
# File lib/SVG/Graph/Graph.rb, line 414 414: def add_popup( x, y, label ) 415: txt_width = label.length * font_size * 0.6 + 10 416: tx = (x+txt_width > width ? x-5 : x+5) 417: t = @foreground.add_element( "text", { 418: "x" => tx.to_s, 419: "y" => (y - font_size).to_s, 420: "visibility" => "hidden", 421: }) 422: t.attributes["style"] = "fill: #000; "+ 423: (x+txt_width > width ? "text-anchor: end;" : "text-anchor: start;") 424: t.text = label.to_s 425: t.attributes["id"] = t.object_id.to_s 426: 427: @foreground.add_element( "circle", { 428: "cx" => x.to_s, 429: "cy" => y.to_s, 430: "r" => "#{@popup_radius}", 431: "style" => "opacity: 0", 432: "onmouseover" => 433: "document.getElementById(#{t.object_id}).setAttribute('visibility', 'visible' )", 434: "onmouseout" => 435: "document.getElementById(#{t.object_id}).setAttribute('visibility', 'hidden' )", 436: }) 437: 438: end
Override this (and call super) to change the margin to the bottom of the plot area. Results in @border_bottom being set.
# File lib/SVG/Graph/Graph.rb, line 443 443: def calculate_bottom_margin 444: @border_bottom = 7 445: if key and key_position == :bottom 446: @border_bottom += @data.size * (font_size + 5) 447: @border_bottom += 10 448: end 449: if show_x_labels 450: max_x_label_height_px = (not rotate_x_labels) ? 451: x_label_font_size : 452: get_x_labels.max{|a,b| 453: a.to_s.length<=>b.to_s.length 454: }.to_s.length * x_label_font_size * 0.6 455: @border_bottom += max_x_label_height_px 456: @border_bottom += max_x_label_height_px + 10 if stagger_x_labels 457: end 458: @border_bottom += x_title_font_size + 5 if show_x_title 459: end
Override this (and call super) to change the margin to the left of the plot area. Results in @border_left being set.
# File lib/SVG/Graph/Graph.rb, line 369 369: def calculate_left_margin 370: @border_left = 7 371: # Check for Y labels 372: max_y_label_height_px = @rotate_y_labels ? 373: @y_label_font_size : 374: get_y_labels.max{|a,b| 375: a.to_s.length<=>b.to_s.length 376: }.to_s.length * @y_label_font_size * 0.6 377: @border_left += max_y_label_height_px if @show_y_labels 378: @border_left += max_y_label_height_px + 10 if @stagger_y_labels 379: @border_left += y_title_font_size + 5 if @show_y_title 380: end
Override this (and call super) to change the margin to the right of the plot area. Results in @border_right being set.
# File lib/SVG/Graph/Graph.rb, line 392 392: def calculate_right_margin 393: @border_right = 7 394: if key and key_position == :right 395: val = keys.max { |a,b| a.length <=> b.length } 396: @border_right += val.length * key_font_size * 0.6 397: @border_right += KEY_BOX_SIZE 398: @border_right += 10 # Some padding around the box 399: end 400: end
Override this (and call super) to change the margin to the top of the plot area. Results in @border_top being set.
# File lib/SVG/Graph/Graph.rb, line 405 405: def calculate_top_margin 406: @border_top = 5 407: @border_top += title_font_size if show_graph_title 408: @border_top += 5 409: @border_top += subtitle_font_size if show_graph_subtitle 410: end
Draws the background, axis, and labels.
# File lib/SVG/Graph/Graph.rb, line 463 463: def draw_graph 464: @graph = @root.add_element( "g", { 465: "transform" => "translate( #@border_left #@border_top )" 466: }) 467: 468: # Background 469: @graph.add_element( "rect", { 470: "x" => "0", 471: "y" => "0", 472: "width" => @graph_width.to_s, 473: "height" => @graph_height.to_s, 474: "class" => "graphBackground" 475: }) 476: 477: # Axis 478: @graph.add_element( "path", { 479: "d" => "M 0 0 v#@graph_height", 480: "class" => "axis", 481: "id" => "xAxis" 482: }) 483: @graph.add_element( "path", { 484: "d" => "M 0 #@graph_height h#@graph_width", 485: "class" => "axis", 486: "id" => "yAxis" 487: }) 488: 489: draw_x_labels 490: draw_y_labels 491: end
Draws the legend on the graph
# File lib/SVG/Graph/Graph.rb, line 712 712: def draw_legend 713: if key 714: group = @root.add_element( "g" ) 715: 716: key_count = 0 717: for key_name in keys 718: y_offset = (KEY_BOX_SIZE * key_count) + (key_count * 5) 719: group.add_element( "rect", { 720: "x" => 0.to_s, 721: "y" => y_offset.to_s, 722: "width" => KEY_BOX_SIZE.to_s, 723: "height" => KEY_BOX_SIZE.to_s, 724: "class" => "key#{key_count+1}" 725: }) 726: group.add_element( "text", { 727: "x" => (KEY_BOX_SIZE + 5).to_s, 728: "y" => (y_offset + KEY_BOX_SIZE).to_s, 729: "class" => "keyText" 730: }).text = key_name.to_s 731: key_count += 1 732: end 733: 734: case key_position 735: when :right 736: x_offset = @graph_width + @border_left + 10 737: y_offset = @border_top + 20 738: when :bottom 739: x_offset = @border_left + 20 740: y_offset = @border_top + @graph_height + 5 741: if show_x_labels 742: max_x_label_height_px = (not rotate_x_labels) ? 743: x_label_font_size : 744: get_x_labels.max{|a,b| 745: a.to_s.length<=>b.to_s.length 746: }.to_s.length * x_label_font_size * 0.6 747: x_label_font_size 748: y_offset += max_x_label_height_px 749: y_offset += max_x_label_height_px + 5 if stagger_x_labels 750: end 751: y_offset += x_title_font_size + 5 if show_x_title 752: end 753: group.attributes["transform"] = "translate(#{x_offset} #{y_offset})" 754: end 755: end
Draws the graph title and subtitle
# File lib/SVG/Graph/Graph.rb, line 653 653: def draw_titles 654: if show_graph_title 655: @root.add_element( "text", { 656: "x" => (width / 2).to_s, 657: "y" => (title_font_size).to_s, 658: "class" => "mainTitle" 659: }).text = graph_title.to_s 660: end 661: 662: if show_graph_subtitle 663: y_subtitle = show_graph_title ? 664: title_font_size + 10 : 665: subtitle_font_size 666: @root.add_element("text", { 667: "x" => (width / 2).to_s, 668: "y" => (y_subtitle).to_s, 669: "class" => "subTitle" 670: }).text = graph_subtitle.to_s 671: end 672: 673: if show_x_title 674: y = @graph_height + @border_top + x_title_font_size 675: if show_x_labels 676: y += x_label_font_size + 5 if stagger_x_labels 677: y += x_label_font_size + 5 678: end 679: x = width / 2 680: 681: @root.add_element("text", { 682: "x" => x.to_s, 683: "y" => y.to_s, 684: "class" => "xAxisTitle", 685: }).text = x_title.to_s 686: end 687: 688: if show_y_title 689: x = y_title_font_size + (y_title_text_direction==:bt ? 3 : -3) 690: y = height / 2 691: 692: text = @root.add_element("text", { 693: "x" => x.to_s, 694: "y" => y.to_s, 695: "class" => "yAxisTitle", 696: }) 697: text.text = y_title.to_s 698: if y_title_text_direction == :bt 699: text.attributes["transform"] = "rotate( -90, #{x}, #{y} )" 700: else 701: text.attributes["transform"] = "rotate( 90, #{x}, #{y} )" 702: end 703: end 704: end
Draws the X axis guidelines
# File lib/SVG/Graph/Graph.rb, line 631 631: def draw_x_guidelines( label_height, count ) 632: if count != 0 633: @graph.add_element( "path", { 634: "d" => "M#{label_height*count} 0 v#@graph_height", 635: "class" => "guideLines" 636: }) 637: end 638: end
Draws the X axis labels
# File lib/SVG/Graph/Graph.rb, line 520 520: def draw_x_labels 521: stagger = x_label_font_size + 5 522: if show_x_labels 523: label_width = field_width 524: 525: count = 0 526: for label in get_x_labels 527: if step_include_first_x_label == true then 528: step = count % step_x_labels 529: else 530: step = (count + 1) % step_x_labels 531: end 532: 533: if step == 0 then 534: text = @graph.add_element( "text" ) 535: text.attributes["class"] = "xAxisLabels" 536: text.text = label.to_s 537: 538: x = count * label_width + x_label_offset( label_width ) 539: y = @graph_height + x_label_font_size + 3 540: #t = 0 - (font_size / 2) 541: 542: if stagger_x_labels and count % 2 == 1 543: y += stagger 544: @graph.add_element( "path", { 545: "d" => "M#{x} #@graph_height v#{stagger}", 546: "class" => "staggerGuideLine" 547: }) 548: end 549: 550: text.attributes["x"] = x.to_s 551: text.attributes["y"] = y.to_s 552: if rotate_x_labels 553: text.attributes["transform"] = 554: "rotate( 90 #{x} #{y-x_label_font_size} )"+ 555: " translate( 0 -#{x_label_font_size/4} )" 556: text.attributes["style"] = "text-anchor: start" 557: else 558: text.attributes["style"] = "text-anchor: middle" 559: end 560: end 561: 562: draw_x_guidelines( label_width, count ) if show_x_guidelines 563: count += 1 564: end 565: end 566: end
Draws the Y axis guidelines
# File lib/SVG/Graph/Graph.rb, line 642 642: def draw_y_guidelines( label_height, count ) 643: if count != 0 644: @graph.add_element( "path", { 645: "d" => "M0 #{@graph_height-(label_height*count)} h#@graph_width", 646: "class" => "guideLines" 647: }) 648: end 649: end
Draws the Y axis labels
# File lib/SVG/Graph/Graph.rb, line 589 589: def draw_y_labels 590: stagger = y_label_font_size + 5 591: if show_y_labels 592: label_height = field_height 593: 594: count = 0 595: y_offset = @graph_height + y_label_offset( label_height ) 596: y_offset += font_size/1.2 unless rotate_y_labels 597: for label in get_y_labels 598: y = y_offset - (label_height * count) 599: x = rotate_y_labels ? 0 : -3 600: 601: if stagger_y_labels and count % 2 == 1 602: x -= stagger 603: @graph.add_element( "path", { 604: "d" => "M#{x} #{y} h#{stagger}", 605: "class" => "staggerGuideLine" 606: }) 607: end 608: 609: text = @graph.add_element( "text", { 610: "x" => x.to_s, 611: "y" => y.to_s, 612: "class" => "yAxisLabels" 613: }) 614: text.text = label.to_s 615: if rotate_y_labels 616: text.attributes["transform"] = "translate( -#{font_size} 0 ) "+ 617: "rotate( 90 #{x} #{y} ) " 618: text.attributes["style"] = "text-anchor: middle" 619: else 620: text.attributes["y"] = (y - (y_label_font_size/2)).to_s 621: text.attributes["style"] = "text-anchor: end" 622: end 623: draw_y_guidelines( label_height, count ) if show_y_guidelines 624: count += 1 625: end 626: end 627: end
# File lib/SVG/Graph/Graph.rb, line 582 582: def field_height 583: (@graph_height.to_f - font_size*2*top_font) / 584: (get_y_labels.length - top_align) 585: end
# File lib/SVG/Graph/Graph.rb, line 576 576: def field_width 577: (@graph_width.to_f - font_size*2*right_font) / 578: (get_x_labels.length - right_align) 579: end
Overwrite configuration options with supplied options. Used by subclasses.
# File lib/SVG/Graph/Graph.rb, line 356 356: def init_with config 357: config.each { |key, value| 358: self.send( key.to_s+"=", value ) if self.respond_to? key 359: } 360: @popup_radius ||= 10 361: end
# File lib/SVG/Graph/Graph.rb, line 706 706: def keys 707: i = 0 708: return @data.collect{ |d| i+=1; d[:title] || "Serie #{i}" } 709: end
# File lib/SVG/Graph/Graph.rb, line 500 500: def make_datapoint_text( x, y, value, style="" ) 501: if show_data_values 502: @foreground.add_element( "text", { 503: "x" => x.to_s, 504: "y" => y.to_s, 505: "class" => "dataPointLabel", 506: "style" => "#{style} stroke: #fff; stroke-width: 2;" 507: }).text = value.to_s 508: text = @foreground.add_element( "text", { 509: "x" => x.to_s, 510: "y" => y.to_s, 511: "class" => "dataPointLabel" 512: }) 513: text.text = value.to_s 514: text.attributes["style"] = style if style.length > 0 515: end 516: end
Calculates the width of the widest Y label. This will be the character height if the Y labels are rotated
# File lib/SVG/Graph/Graph.rb, line 385 385: def max_y_label_width_px 386: return font_size if rotate_y_labels 387: end
# File lib/SVG/Graph/Graph.rb, line 350 350: def sort( *arrys ) 351: sort_multiple( arrys ) 352: end
Where in the X area the label is drawn Centered in the field, should be width/2. Start, 0.
# File lib/SVG/Graph/Graph.rb, line 496 496: def x_label_offset( width ) 497: 0 498: end