Class | SVG::Graph::Graph |
In: |
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. |
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 SVG/Graph/Graph.rb, line 101 101: def initialize( config ) 102: @config = config 103: 104: self.top_align = self.top_font = self.right_align = self.right_font = 0 105: 106: init_with({ 107: :width => 500, 108: :height => 300, 109: :show_x_guidelines => false, 110: :show_y_guidelines => true, 111: :show_data_values => true, 112: 113: # :min_scale_value => 0, 114: 115: :show_x_labels => true, 116: :stagger_x_labels => false, 117: :rotate_x_labels => false, 118: :step_x_labels => 1, 119: :step_include_first_x_label => true, 120: 121: :show_y_labels => true, 122: :rotate_y_labels => false, 123: :stagger_y_labels => false, 124: :scale_integers => false, 125: 126: :show_x_title => false, 127: :x_title => 'X Field names', 128: 129: :show_y_title => false, 130: :y_title_text_direction => :bt, 131: :y_title => 'Y Scale', 132: 133: :show_graph_title => false, 134: :graph_title => 'Graph Title', 135: :show_graph_subtitle => false, 136: :graph_subtitle => 'Graph Sub Title', 137: :key => true, 138: :key_position => :right, # bottom or right 139: 140: :font_size =>12, 141: :title_font_size =>16, 142: :subtitle_font_size =>14, 143: :x_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: 153: set_defaults if methods.include? "set_defaults" 154: 155: init_with config 156: 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 SVG/Graph/Graph.rb, line 168 168: def add_data conf 169: @data = [] unless defined? @data 170: 171: if conf[:data] and conf[:data].kind_of? Array 172: @data << conf 173: else 174: raise "No data provided by #{conf.inspect}" 175: end 176: 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 SVG/Graph/Graph.rb, line 195 195: def burn 196: raise "No data available" unless @data.size > 0 197: 198: calculations if methods.include? 'calculations' 199: 200: start_svg 201: calculate_graph_dimensions 202: @foreground = Element.new( "g" ) 203: draw_graph 204: draw_titles 205: draw_legend 206: draw_data 207: @graph.add_element( @foreground ) 208: style 209: 210: data = "" 211: @doc.write( data, 0 ) 212: 213: if @config[:compress] 214: if @@__have_zlib 215: inp, out = IO.pipe 216: gz = Zlib::GzipWriter.new( out ) 217: gz.write data 218: gz.close 219: data = inp.read 220: else 221: data << "<!-- Ruby Zlib not available for SVGZ -->"; 222: end 223: end 224: 225: return data 226: end
Adds pop-up point information to a graph.
# File SVG/Graph/Graph.rb, line 413 413: def add_popup( x, y, label ) 414: txt_width = label.length * font_size * 0.6 + 10 415: tx = (x+txt_width > width ? x-5 : x+5) 416: t = @foreground.add_element( "text", { 417: "x" => tx.to_s, 418: "y" => (y - font_size).to_s, 419: "visibility" => "hidden", 420: }) 421: t.attributes["style"] = "fill: #000; "+ 422: (x+txt_width > width ? "text-anchor: end;" : "text-anchor: start;") 423: t.text = label.to_s 424: t.attributes["id"] = t.object_id.to_s 425: 426: @foreground.add_element( "circle", { 427: "cx" => x.to_s, 428: "cy" => y.to_s, 429: "r" => "10", 430: "style" => "opacity: 0", 431: "onmouseover" => 432: "document.getElementById(#{t.object_id}).setAttribute('visibility', 'visible' )", 433: "onmouseout" => 434: "document.getElementById(#{t.object_id}).setAttribute('visibility', 'hidden' )", 435: }) 436: 437: end
Override this (and call super) to change the margin to the bottom of the plot area. Results in @border_bottom being set.
# File SVG/Graph/Graph.rb, line 442 442: def calculate_bottom_margin 443: @border_bottom = 7 444: if key and key_position == :bottom 445: @border_bottom += @data.size * (font_size + 5) 446: @border_bottom += 10 447: end 448: if show_x_labels 449: max_x_label_height_px = (not rotate_x_labels) ? 450: x_label_font_size : 451: get_x_labels.max{|a,b| 452: a.to_s.length<=>b.to_s.length 453: }.to_s.length * x_label_font_size * 0.6 454: @border_bottom += max_x_label_height_px 455: @border_bottom += max_x_label_height_px + 10 if stagger_x_labels 456: end 457: @border_bottom += x_title_font_size + 5 if show_x_title 458: end
Override this (and call super) to change the margin to the left of the plot area. Results in @border_left being set.
# File SVG/Graph/Graph.rb, line 368 368: def calculate_left_margin 369: @border_left = 7 370: # Check for Y labels 371: max_y_label_height_px = rotate_y_labels ? 372: y_label_font_size : 373: get_y_labels.max{|a,b| 374: a.to_s.length<=>b.to_s.length 375: }.to_s.length * y_label_font_size * 0.6 376: @border_left += max_y_label_height_px if show_y_labels 377: @border_left += max_y_label_height_px + 10 if stagger_y_labels 378: @border_left += y_title_font_size + 5 if show_y_title 379: end
Override this (and call super) to change the margin to the right of the plot area. Results in @border_right being set.
# File SVG/Graph/Graph.rb, line 391 391: def calculate_right_margin 392: @border_right = 7 393: if key and key_position == :right 394: val = keys.max { |a,b| a.length <=> b.length } 395: @border_right += val.length * key_font_size * 0.6 396: @border_right += KEY_BOX_SIZE 397: @border_right += 10 # Some padding around the box 398: end 399: end
Override this (and call super) to change the margin to the top of the plot area. Results in @border_top being set.
# File SVG/Graph/Graph.rb, line 404 404: def calculate_top_margin 405: @border_top = 5 406: @border_top += title_font_size if show_graph_title 407: @border_top += 5 408: @border_top += subtitle_font_size if show_graph_subtitle 409: end
Draws the background, axis, and labels.
# File SVG/Graph/Graph.rb, line 462 462: def draw_graph 463: @graph = @root.add_element( "g", { 464: "transform" => "translate( #@border_left #@border_top )" 465: }) 466: 467: # Background 468: @graph.add_element( "rect", { 469: "x" => "0", 470: "y" => "0", 471: "width" => @graph_width.to_s, 472: "height" => @graph_height.to_s, 473: "class" => "graphBackground" 474: }) 475: 476: # Axis 477: @graph.add_element( "path", { 478: "d" => "M 0 0 v#@graph_height", 479: "class" => "axis", 480: "id" => "xAxis" 481: }) 482: @graph.add_element( "path", { 483: "d" => "M 0 #@graph_height h#@graph_width", 484: "class" => "axis", 485: "id" => "yAxis" 486: }) 487: 488: draw_x_labels 489: draw_y_labels 490: end
Draws the legend on the graph
# File SVG/Graph/Graph.rb, line 710 710: def draw_legend 711: if key 712: group = @root.add_element( "g" ) 713: 714: key_count = 0 715: for key_name in keys 716: y_offset = (KEY_BOX_SIZE * key_count) + (key_count * 5) 717: group.add_element( "rect", { 718: "x" => 0.to_s, 719: "y" => y_offset.to_s, 720: "width" => KEY_BOX_SIZE.to_s, 721: "height" => KEY_BOX_SIZE.to_s, 722: "class" => "key#{key_count+1}" 723: }) 724: group.add_element( "text", { 725: "x" => (KEY_BOX_SIZE + 5).to_s, 726: "y" => (y_offset + KEY_BOX_SIZE).to_s, 727: "class" => "keyText" 728: }).text = key_name.to_s 729: key_count += 1 730: end 731: 732: case key_position 733: when :right 734: x_offset = @graph_width + @border_left + 10 735: y_offset = @border_top + 20 736: when :bottom 737: x_offset = @border_left + 20 738: y_offset = @border_top + @graph_height + 5 739: if show_x_labels 740: max_x_label_height_px = (not rotate_x_labels) ? 741: x_label_font_size : 742: get_x_labels.max{|a,b| 743: a.to_s.length<=>b.to_s.length 744: }.to_s.length * x_label_font_size * 0.6 745: x_label_font_size 746: y_offset += max_x_label_height_px 747: y_offset += max_x_label_height_px + 5 if stagger_x_labels 748: end 749: y_offset += x_title_font_size + 5 if show_x_title 750: end 751: group.attributes["transform"] = "translate(#{x_offset} #{y_offset})" 752: end 753: end
Draws the graph title and subtitle
# File SVG/Graph/Graph.rb, line 652 652: def draw_titles 653: if show_graph_title 654: @root.add_element( "text", { 655: "x" => (width / 2).to_s, 656: "y" => (title_font_size).to_s, 657: "class" => "mainTitle" 658: }).text = graph_title.to_s 659: end 660: 661: if show_graph_subtitle 662: y_subtitle = show_graph_title ? 663: title_font_size + 10 : 664: subtitle_font_size 665: @root.add_element("text", { 666: "x" => (width / 2).to_s, 667: "y" => (y_subtitle).to_s, 668: "class" => "subTitle" 669: }).text = graph_subtitle.to_s 670: end 671: 672: if show_x_title 673: y = @graph_height + @border_top + x_title_font_size 674: if show_x_labels 675: y += x_label_font_size + 5 if stagger_x_labels 676: y += x_label_font_size + 5 677: end 678: x = width / 2 679: 680: @root.add_element("text", { 681: "x" => x.to_s, 682: "y" => y.to_s, 683: "class" => "xAxisTitle", 684: }).text = x_title.to_s 685: end 686: 687: if show_y_title 688: x = y_title_font_size + (y_title_text_direction==:bt ? 3 : -3) 689: y = height / 2 690: 691: text = @root.add_element("text", { 692: "x" => x.to_s, 693: "y" => y.to_s, 694: "class" => "yAxisTitle", 695: }) 696: text.text = y_title.to_s 697: if y_title_text_direction == :bt 698: text.attributes["transform"] = "rotate( -90, #{x}, #{y} )" 699: else 700: text.attributes["transform"] = "rotate( 90, #{x}, #{y} )" 701: end 702: end 703: end
Draws the X axis guidelines
# File SVG/Graph/Graph.rb, line 630 630: def draw_x_guidelines( label_height, count ) 631: if count != 0 632: @graph.add_element( "path", { 633: "d" => "M#{label_height*count} 0 v#@graph_height", 634: "class" => "guideLines" 635: }) 636: end 637: end
Draws the X axis labels
# File SVG/Graph/Graph.rb, line 519 519: def draw_x_labels 520: stagger = x_label_font_size + 5 521: if show_x_labels 522: label_width = field_width 523: 524: count = 0 525: for label in get_x_labels 526: if step_include_first_x_label == true then 527: step = count % step_x_labels 528: else 529: step = (count + 1) % step_x_labels 530: end 531: 532: if step == 0 then 533: text = @graph.add_element( "text" ) 534: text.attributes["class"] = "xAxisLabels" 535: text.text = label.to_s 536: 537: x = count * label_width + x_label_offset( label_width ) 538: y = @graph_height + x_label_font_size + 3 539: t = 0 - (font_size / 2) 540: 541: if stagger_x_labels and count % 2 == 1 542: y += stagger 543: @graph.add_element( "path", { 544: "d" => "M#{x} #@graph_height v#{stagger}", 545: "class" => "staggerGuideLine" 546: }) 547: end 548: 549: text.attributes["x"] = x.to_s 550: text.attributes["y"] = y.to_s 551: if rotate_x_labels 552: text.attributes["transform"] = 553: "rotate( 90 #{x} #{y-x_label_font_size} )"+ 554: " translate( 0 -#{x_label_font_size/4} )" 555: text.attributes["style"] = "text-anchor: start" 556: else 557: text.attributes["style"] = "text-anchor: middle" 558: end 559: end 560: 561: draw_x_guidelines( label_width, count ) if show_x_guidelines 562: count += 1 563: end 564: end 565: end
Draws the Y axis guidelines
# File SVG/Graph/Graph.rb, line 641 641: def draw_y_guidelines( label_height, count ) 642: if count != 0 643: @graph.add_element( "path", { 644: "d" => "M0 #{@graph_height-(label_height*count)} h#@graph_width", 645: "class" => "guideLines" 646: }) 647: end 648: end
Draws the Y axis labels
# File SVG/Graph/Graph.rb, line 588 588: def draw_y_labels 589: stagger = y_label_font_size + 5 590: if show_y_labels 591: label_height = field_height 592: 593: count = 0 594: y_offset = @graph_height + y_label_offset( label_height ) 595: y_offset += font_size/1.2 unless rotate_y_labels 596: for label in get_y_labels 597: y = y_offset - (label_height * count) 598: x = rotate_y_labels ? 0 : -3 599: 600: if stagger_y_labels and count % 2 == 1 601: x -= stagger 602: @graph.add_element( "path", { 603: "d" => "M#{x} #{y} h#{stagger}", 604: "class" => "staggerGuideLine" 605: }) 606: end 607: 608: text = @graph.add_element( "text", { 609: "x" => x.to_s, 610: "y" => y.to_s, 611: "class" => "yAxisLabels" 612: }) 613: text.text = label.to_s 614: if rotate_y_labels 615: text.attributes["transform"] = "translate( -#{font_size} 0 ) "+ 616: "rotate( 90 #{x} #{y} ) " 617: text.attributes["style"] = "text-anchor: middle" 618: else 619: text.attributes["y"] = (y - (y_label_font_size/2)).to_s 620: text.attributes["style"] = "text-anchor: end" 621: end 622: draw_y_guidelines( label_height, count ) if show_y_guidelines 623: count += 1 624: end 625: end 626: end
# File SVG/Graph/Graph.rb, line 581 581: def field_height 582: (@graph_height.to_f - font_size*2*top_font) / 583: (get_y_labels.length - top_align) 584: end
# File SVG/Graph/Graph.rb, line 575 575: def field_width 576: (@graph_width.to_f - font_size*2*right_font) / 577: (get_x_labels.length - right_align) 578: end
Overwrite configuration options with supplied options. Used by subclasses.
# File SVG/Graph/Graph.rb, line 356 356: def init_with config 357: config.each { |key, value| 358: self.send( key.to_s+"=", value ) if methods.include? key.to_s 359: } 360: end
# File SVG/Graph/Graph.rb, line 705 705: def keys 706: return @data.collect{ |d| d[:title] } 707: end
# File SVG/Graph/Graph.rb, line 499 499: def make_datapoint_text( x, y, value, style="" ) 500: if show_data_values 501: @foreground.add_element( "text", { 502: "x" => x.to_s, 503: "y" => y.to_s, 504: "class" => "dataPointLabel", 505: "style" => "#{style} stroke: #fff; stroke-width: 2;" 506: }).text = value.to_s 507: text = @foreground.add_element( "text", { 508: "x" => x.to_s, 509: "y" => y.to_s, 510: "class" => "dataPointLabel" 511: }) 512: text.text = value.to_s 513: text.attributes["style"] = style if style.length > 0 514: end 515: end
Calculates the width of the widest Y label. This will be the character height if the Y labels are rotated
# File SVG/Graph/Graph.rb, line 384 384: def max_y_label_width_px 385: return font_size if rotate_y_labels 386: end
Where in the X area the label is drawn Centered in the field, should be width/2. Start, 0.
# File SVG/Graph/Graph.rb, line 495 495: def x_label_offset( width ) 496: 0 497: end