lib/SVG/Graph/Graph.rb in svg-graph-2.0.1 vs lib/SVG/Graph/Graph.rb in svg-graph-2.0.2.beta
- old
+ new
@@ -100,11 +100,12 @@
# [add_popups] false
# [number_format] '%.2f'
def initialize( config )
@config = config
@data = []
- self.top_align = self.top_font = self.right_align = self.right_font = 0
+ #self.top_align = self.top_font = 0
+ #self.right_align = self.right_font = 0
init_with({
:width => 500,
:height => 300,
:show_x_guidelines => false,
@@ -190,24 +191,27 @@
# 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
+ #
def burn
raise "No data available" unless @data.size > 0
- calculations if methods.include? 'calculations'
+ # undocumented and not used in any sublass
+ # to be removed
+ #calculations if methods.include? 'calculations'
start_svg
calculate_graph_dimensions
@foreground = Element.new( "g" )
draw_graph
draw_titles
draw_legend
- draw_data
+ draw_data # this method needs to be implemented by child classes
@graph.add_element( @foreground )
style
data = ""
@doc.write( data, 0 )
@@ -276,11 +280,11 @@
attr_accessor :stagger_y_labels
# This turns the X axis labels by 90 degrees.
# Default it false, to turn on set to true.
attr_accessor :rotate_x_labels
# This turns the Y axis labels by 90 degrees.
- # Default it false, to turn on set to true.
+ # Default it true, to turn on set to false.
attr_accessor :rotate_y_labels
# 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
@@ -380,11 +384,13 @@
# defaults to '%.2f'
attr_accessor :number_format
protected
-
+
+ # implementation of quicksort
+ # used for Schedule and Plot
def sort( *arrys )
sort_multiple( arrys )
end
# Overwrite configuration options with supplied options. Used
@@ -394,39 +400,49 @@
self.send( key.to_s+"=", value ) if self.respond_to? key
}
@popup_radius ||= 10
end
- attr_accessor :top_align, :top_font, :right_align, :right_font
+ # unknown why needed
+ # attr_accessor :top_align, :top_font, :right_align, :right_font
+ # size of the square box in the legend which indicates the colors
KEY_BOX_SIZE = 12
# Override this (and call super) to change the margin to the left
# of the plot area. Results in @border_left being set.
+ #
+ # By default it is 7 + max label height(font size or string length, depending on rotate) + title height
def calculate_left_margin
@border_left = 7
# Check size of Y labels
- max_y_label_height_px = y_label_font_size
- if !rotate_y_labels
- max_y_label_height_px = get_longest_label(get_y_labels).to_s.length * y_label_font_size * 0.6
+ @border_left += max_y_label_width_px
+ if (show_y_title && (y_title_location ==:middle))
+ @border_left += y_title_font_size + 5
end
-
- @border_left += max_y_label_height_px if show_y_labels
- @border_left += max_y_label_height_px + 10 if stagger_y_labels
- @border_left += y_title_font_size + 5 if (show_y_title && (y_title_location ==:middle))
end
-
# Calculates the width of the widest Y label. This will be the
- # character height if the Y labels are rotated
+ # character height if the Y labels are rotated. Returns 0 if labels
+ # are not shown
def max_y_label_width_px
- return font_size if rotate_y_labels
+ return 0 if !show_y_labels
+ if !rotate_y_labels
+ max_width = get_longest_label(get_y_labels).to_s.length * y_label_font_size * 0.6
+ else
+ max_width = y_label_font_size + 3
+ end
+ max_width += 10 if stagger_y_labels
+ return max_width
end
# Override this (and call super) to change the margin to the right
# of the plot area. Results in @border_right being set.
+ #
+ # By default it is 7 + width of the key if it is placed on the right
+ # or the maximum of this value or the tilte length (if title is placed at :end)
def calculate_right_margin
@border_right = 7
if key and key_position == :right
val = keys.max { |a,b| a.length <=> b.length }
@border_right += val.length * key_font_size * 0.6
@@ -439,43 +455,55 @@
end
# Override this (and call super) to change the margin to the top
# of the plot area. Results in @border_top being set.
+ #
+ # This is 5 + the Title size + 5 + subTitle size
def calculate_top_margin
@border_top = 5
@border_top += [title_font_size, y_title_font_size].max if (show_graph_title || (y_title_location ==:end))
@border_top += 5
@border_top += subtitle_font_size if show_graph_subtitle
end
+ def add_datapoint_text_and_popup( x, y, label )
+ add_popup( x, y, label )
+ make_datapoint_text( x, y, label )
+ end
- # Adds pop-up point information to a graph.
- def add_popup( x, y, label )
- txt_width = label.length * font_size * 0.6 + 10
- tx = (x+txt_width > width ? x-5 : x+5)
- t = @foreground.add_element( "text", {
- "x" => tx.to_s,
- "y" => (y - font_size).to_s,
- "visibility" => "hidden",
- })
- t.attributes["style"] = "fill: #000; "+
- (x+txt_width > width ? "text-anchor: end;" : "text-anchor: start;")
- t.text = label.to_s
- t.attributes["id"] = t.object_id.to_s
-
- @foreground.add_element( "circle", {
- "cx" => x.to_s,
- "cy" => y.to_s,
- "r" => "#{@popup_radius}",
- "style" => "opacity: 0",
- "onmouseover" =>
- "document.getElementById(#{t.object_id}).setAttribute('visibility', 'visible' )",
- "onmouseout" =>
- "document.getElementById(#{t.object_id}).setAttribute('visibility', 'hidden' )",
- })
-
+ # Adds pop-up point information to a graph only if the config option is set.
+ def add_popup( x, y, label, style="" )
+ if add_popups
+ if( numeric?(label) )
+ label = @number_format % label
+ end
+ txt_width = label.length * font_size * 0.6 + 10
+ tx = (x+txt_width > width ? x-5 : x+5)
+ t = @foreground.add_element( "text", {
+ "x" => tx.to_s,
+ "y" => (y - font_size).to_s,
+ "class" => "dataPointLabel",
+ "visibility" => "hidden",
+ })
+ t.attributes["style"] = "stroke-width: 2; fill: #000; #{style}"+
+ (x+txt_width > width ? "text-anchor: end;" : "text-anchor: start;")
+ t.text = label.to_s
+ t.attributes["id"] = t.object_id.to_s
+
+ # add a circle to catch the mouseover
+ @foreground.add_element( "circle", {
+ "cx" => x.to_s,
+ "cy" => y.to_s,
+ "r" => "#{popup_radius}",
+ "style" => "opacity: 0",
+ "onmouseover" =>
+ "document.getElementById(#{t.object_id}).setAttribute('visibility', 'visible' )",
+ "onmouseout" =>
+ "document.getElementById(#{t.object_id}).setAttribute('visibility', 'hidden' )",
+ })
+ end # add_popups
end
# returns the longest label from an array of labels as string
# each object in the array must support .to_s
def get_longest_label(arry)
@@ -489,27 +517,37 @@
return longest_label
end
# Override this (and call super) to change the margin to the bottom
# of the plot area. Results in @border_bottom being set.
+ #
+ # 7 + max label height(font size or string length, depending on rotate) + title height
def calculate_bottom_margin
@border_bottom = 7
if key and key_position == :bottom
@border_bottom += @data.size * (font_size + 5)
@border_bottom += 10
end
- if show_x_labels
- max_x_label_height_px = x_label_font_size
- if rotate_x_labels
- max_x_label_height_px = get_longest_label(get_x_labels).to_s.length * x_label_font_size * 0.6
- end
-
- @border_bottom += max_x_label_height_px
- @border_bottom += max_x_label_height_px + 10 if stagger_x_labels
+ @border_bottom += max_x_label_height_px
+ if (show_x_title && (x_title_location ==:middle))
+ @border_bottom += x_title_font_size + 5
end
- @border_bottom += x_title_font_size + 5 if (show_x_title && (x_title_location ==:middle))
end
+
+ # returns the maximum height of the labels respect the rotation or 0 if
+ # the labels are not shown
+ def max_x_label_height_px
+ return 0 if !show_x_labels
+
+ if rotate_x_labels
+ max_height = get_longest_label(get_x_labels).to_s.length * x_label_font_size * 0.6
+ else
+ max_height = x_label_font_size + 3
+ end
+ max_height += 10 if stagger_x_labels
+ return max_height
+ end
# Draws the background, axis, and labels.
def draw_graph
@graph = @root.add_element( "g", {
@@ -551,33 +589,40 @@
# check if an object can be converted to float
def numeric?(object)
true if Float(object) rescue false
end
+ # adds the datapoint text to the graph only if the config option is set
def make_datapoint_text( x, y, value, style="" )
if show_data_values
textStr = value
if( numeric?(value) )
textStr = @number_format % value
end
+ # change anchor is label overlaps axis
+ if x < textStr.length/2 * font_size * 0.6
+ style << "text-anchor: start;"
+ end
+ # white background for better readability
@foreground.add_element( "text", {
"x" => x.to_s,
"y" => y.to_s,
"class" => "dataPointLabel",
"style" => "#{style} stroke: #fff; stroke-width: 2;"
}).text = textStr
+ # actual label
text = @foreground.add_element( "text", {
"x" => x.to_s,
"y" => y.to_s,
"class" => "dataPointLabel"
})
text.text = textStr
text.attributes["style"] = style if style.length > 0
end
end
+
-
# Draws the X axis labels
def draw_x_labels
stagger = x_label_font_size + 5
if show_x_labels
label_width = field_width
@@ -589,10 +634,14 @@
else
step = (count + 1) % step_x_labels
end
if step == 0 then
+ label = label.to_s
+ if( numeric?(label) )
+ label = @number_format % label
+ end
text = @graph.add_element( "text" )
text.attributes["class"] = "xAxisLabels"
text.text = label.to_s
x = count * label_width + x_label_offset( label_width )
@@ -630,24 +679,43 @@
# Centered in the field, should be width/2. Start, 0.
def y_label_offset( height )
0
end
-
+ # override this method in child class
+ # must return the array of labels for the x-axis
+ def get_x_labels
+ end
+
+ # override this method in child class
+ # must return the array of labels for the y-axis
+ # this method defines @y_scale_division
+ def get_y_labels
+ end
+
+ # space in px between x-labels
def field_width
- (@graph_width.to_f - font_size*2*right_font) /
- (get_x_labels.length - right_align)
+ #(@graph_width.to_f - font_size*2*right_font) /
+ # (get_x_labels.length - right_align)
+ @graph_width.to_f / get_x_labels.length
end
-
+ # space in px between the y-labels
def field_height
- (@graph_height.to_f - font_size*2*top_font) /
- (get_y_labels.length - top_align)
+ #(@graph_height.to_f - font_size*2*top_font) /
+ # (get_y_labels.length - top_align)
+ @graph_height.to_f / get_y_labels.length
end
- # Draws the Y axis labels
+ # Draws the Y axis labels, the Y-Axis (@graph_height) is divided equally into #get_y_labels.lenght sections
+ # So the y coordinate for an arbitrary value is calculated as follows:
+ # y = @graph_height equals the min_value
+ # #normalize value of a single scale_division:
+ # count = value /(@y_scale_division)
+ # y = @graph_height - count * field_height
+ #
def draw_y_labels
stagger = y_label_font_size + 5
if show_y_labels
label_height = field_height
@@ -733,36 +801,32 @@
"class" => "subTitle"
}).text = graph_subtitle.to_s
end
if show_x_title
- y = @graph_height + @border_top + x_title_font_size
if (x_title_location == :end)
- y = y - x_title_font_size/2.0
- x = width - x_title.length * x_title_font_size * 0.6/2.0
+ y = @graph_height + @border_top + x_title_font_size/2.0
+ x = @border_left + @graph_width + x_title.length * x_title_font_size * 0.6/2.0
else
- x = width / 2
- if show_x_labels
- y += x_label_font_size + 5 if stagger_x_labels
- y += x_label_font_size + 5
- end
+ y = @graph_height + @border_top + x_title_font_size + max_x_label_height_px
+ x = @border_left + @graph_width / 2
end
@root.add_element("text", {
"x" => x.to_s,
"y" => y.to_s,
"class" => "xAxisTitle",
}).text = x_title.to_s
end
if show_y_title
- x = y_title_font_size + (y_title_text_direction==:bt ? 3 : -3)
if (y_title_location == :end)
x = y_title.length * y_title_font_size * 0.6/2.0 # positioning is not optimal but ok for now
y = @border_top - y_title_font_size/2.0
else
- y = height / 2
+ x = y_title_font_size + (y_title_text_direction==:bt ? 3 : -3)
+ y = @border_top + @graph_height / 2
end
text = @root.add_element("text", {
"x" => x.to_s,
"y" => y.to_s,
"class" => "yAxisTitle",
@@ -814,16 +878,16 @@
y_offset = @border_top + 20
when :bottom
x_offset = @border_left + 20
y_offset = @border_top + @graph_height + 5
if show_x_labels
- max_x_label_height_px = (not rotate_x_labels) ?
- x_label_font_size :
- get_x_labels.max{|a,b|
- a.to_s.length<=>b.to_s.length
- }.to_s.length * x_label_font_size * 0.6
- x_label_font_size
+ # max_x_label_height_px = (not rotate_x_labels) ?
+ # x_label_font_size :
+ # get_x_labels.max{|a,b|
+ # a.to_s.length<=>b.to_s.length
+ # }.to_s.length * x_label_font_size * 0.6
+ # x_label_font_size
y_offset += max_x_label_height_px
y_offset += max_x_label_height_px + 5 if stagger_x_labels
end
y_offset += x_title_font_size + 5 if show_x_title
end
@@ -896,14 +960,21 @@
return rv
end
# Override and place code to add defs here
+ # @param defs [REXML::Element]
def add_defs defs
end
-
+ # Creates the XML document and adds the root svg element with
+ # the width, height and viewBox attributes already set.
+ # The element is stored as @root.
+ #
+ # In addition a rectangle background of the same size as the
+ # svg is added.
+ #
def start_svg
# Base document
@doc = Document.new
@doc << XMLDecl.new
@doc << DocType.new( %q{svg PUBLIC "-//W3C//DTD SVG 1.0//EN" } +
@@ -944,10 +1015,10 @@
"y" => "0",
"class" => "svgBackground"
})
end
-
+ #
def calculate_graph_dimensions
calculate_left_margin
calculate_right_margin
calculate_bottom_margin
calculate_top_margin