svg-graph19 0.6.2 → 0.6.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +11 -1
- metadata +7 -12
- data/lib/SVG/Graph/BarBase.rb +0 -139
- data/lib/SVG/Graph/BarHorizontal.rb +0 -149
- data/lib/SVG/Graph/Graph.rb +0 -978
- data/lib/SVG/Graph/Line.rb +0 -444
- data/lib/SVG/Graph/Pie.rb +0 -395
- data/lib/SVG/Graph/Plot.rb +0 -500
- data/lib/SVG/Graph/Schedule.rb +0 -384
- data/lib/SVG/Graph/TimeSeries.rb +0 -252
- data/lib/SVG/Graph/bar.rb +0 -148
data/README.markdown
CHANGED
@@ -1,4 +1,14 @@
|
|
1
1
|
SVG::Graph19
|
2
2
|
============
|
3
3
|
|
4
|
-
|
4
|
+
Description
|
5
|
+
-----------
|
6
|
+
This is a minor revision of the [SVG::Graph library](http://www.germane-software.com/software/SVG/SVG::Graph/) by Sean Russell with few minor touch-ups to make it run on Ruby 1.9.x and to have it [gem-installable](http://gemcutter.org/gems/svg-graph19).
|
7
|
+
|
8
|
+
Warning
|
9
|
+
-------
|
10
|
+
I'm not sure that all the parts of the original SVG library work as expected under 1.9.x too. Please notify me (via github messages or on the Issues section) if you find any bug.
|
11
|
+
|
12
|
+
Usage
|
13
|
+
-----
|
14
|
+
Yet to be written. Look at the original [SVG::Graph web page](http://www.germane-software.com/software/SVG/SVG::Graph/) for the moment (I'm not introducing new methods nor changing APIs, for the moment).
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: svg-graph19
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sean Russell. Paolo Bosetti moved into gem and made 1.9-compatible
|
@@ -9,11 +9,15 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-12-
|
12
|
+
date: 2009-12-02 00:00:00 +01:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
16
|
-
description:
|
16
|
+
description: |
|
17
|
+
THIS VERSION IS RUBY 1.9.x COMPATIBLE!
|
18
|
+
Gem version of SVG:::Graph. SVG:::Graph is a pure Ruby library for generating charts, which are a type of graph where the values of one axis are not scalar.
|
19
|
+
SVG::Graph has a very similar API to the Perl library SVG::TT::Graph, and the resulting charts also look the same. This isn't surprising, because SVG::Graph started as a loose port of SVG::TT::Graph, although the internal code no longer resembles the Perl original at all.
|
20
|
+
|
17
21
|
email: paolo.bosetti@me.com
|
18
22
|
executables: []
|
19
23
|
|
@@ -23,15 +27,6 @@ extra_rdoc_files: []
|
|
23
27
|
|
24
28
|
files:
|
25
29
|
- README.markdown
|
26
|
-
- lib/SVG/Graph/bar.rb
|
27
|
-
- lib/SVG/Graph/BarBase.rb
|
28
|
-
- lib/SVG/Graph/BarHorizontal.rb
|
29
|
-
- lib/SVG/Graph/Graph.rb
|
30
|
-
- lib/SVG/Graph/Line.rb
|
31
|
-
- lib/SVG/Graph/Pie.rb
|
32
|
-
- lib/SVG/Graph/Plot.rb
|
33
|
-
- lib/SVG/Graph/Schedule.rb
|
34
|
-
- lib/SVG/Graph/TimeSeries.rb
|
35
30
|
has_rdoc: true
|
36
31
|
homepage: http://github.com/pbosetti/svg-graph19
|
37
32
|
licenses: []
|
data/lib/SVG/Graph/BarBase.rb
DELETED
@@ -1,139 +0,0 @@
|
|
1
|
-
require 'rexml/document'
|
2
|
-
require 'SVG/Graph/Graph'
|
3
|
-
|
4
|
-
module SVG
|
5
|
-
module Graph
|
6
|
-
# = Synopsis
|
7
|
-
#
|
8
|
-
# A superclass for bar-style graphs. Do not attempt to instantiate
|
9
|
-
# directly; use one of the subclasses instead.
|
10
|
-
#
|
11
|
-
# = Author
|
12
|
-
#
|
13
|
-
# Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom>
|
14
|
-
#
|
15
|
-
# Copyright 2004 Sean E. Russell
|
16
|
-
# This software is available under the Ruby license[LICENSE.txt]
|
17
|
-
#
|
18
|
-
class BarBase < SVG::Graph::Graph
|
19
|
-
# Ensures that :fields are provided in the configuration.
|
20
|
-
def initialize config
|
21
|
-
raise "fields was not supplied or is empty" unless config[:fields] &&
|
22
|
-
config[:fields].kind_of?(Array) &&
|
23
|
-
config[:fields].length > 0
|
24
|
-
super
|
25
|
-
end
|
26
|
-
|
27
|
-
# In addition to the defaults set in Graph::initialize, sets
|
28
|
-
# [bar_gap] true
|
29
|
-
# [stack] :overlap
|
30
|
-
def set_defaults
|
31
|
-
init_with( :bar_gap => true, :stack => :overlap )
|
32
|
-
end
|
33
|
-
|
34
|
-
# Whether to have a gap between the bars or not, default
|
35
|
-
# is true, set to false if you don't want gaps.
|
36
|
-
attr_accessor :bar_gap
|
37
|
-
# How to stack data sets. :overlap overlaps bars with
|
38
|
-
# transparent colors, :top stacks bars on top of one another,
|
39
|
-
# :side stacks the bars side-by-side. Defaults to :overlap.
|
40
|
-
attr_accessor :stack
|
41
|
-
|
42
|
-
|
43
|
-
protected
|
44
|
-
|
45
|
-
def max_value
|
46
|
-
@data.collect{|x| x[:data].max}.max
|
47
|
-
end
|
48
|
-
|
49
|
-
def min_value
|
50
|
-
min = 0
|
51
|
-
if min_scale_value.nil?
|
52
|
-
min = @data.collect{|x| x[:data].min}.min
|
53
|
-
min = min > 0 ? 0 : min
|
54
|
-
else
|
55
|
-
min = min_scale_value
|
56
|
-
end
|
57
|
-
return min
|
58
|
-
end
|
59
|
-
|
60
|
-
def get_css
|
61
|
-
return <<EOL
|
62
|
-
/* default fill styles for multiple datasets (probably only use a single dataset on this graph though) */
|
63
|
-
.key1,.fill1{
|
64
|
-
fill: #ff0000;
|
65
|
-
fill-opacity: 0.5;
|
66
|
-
stroke: none;
|
67
|
-
stroke-width: 0.5px;
|
68
|
-
}
|
69
|
-
.key2,.fill2{
|
70
|
-
fill: #0000ff;
|
71
|
-
fill-opacity: 0.5;
|
72
|
-
stroke: none;
|
73
|
-
stroke-width: 1px;
|
74
|
-
}
|
75
|
-
.key3,.fill3{
|
76
|
-
fill: #00ff00;
|
77
|
-
fill-opacity: 0.5;
|
78
|
-
stroke: none;
|
79
|
-
stroke-width: 1px;
|
80
|
-
}
|
81
|
-
.key4,.fill4{
|
82
|
-
fill: #ffcc00;
|
83
|
-
fill-opacity: 0.5;
|
84
|
-
stroke: none;
|
85
|
-
stroke-width: 1px;
|
86
|
-
}
|
87
|
-
.key5,.fill5{
|
88
|
-
fill: #00ccff;
|
89
|
-
fill-opacity: 0.5;
|
90
|
-
stroke: none;
|
91
|
-
stroke-width: 1px;
|
92
|
-
}
|
93
|
-
.key6,.fill6{
|
94
|
-
fill: #ff00ff;
|
95
|
-
fill-opacity: 0.5;
|
96
|
-
stroke: none;
|
97
|
-
stroke-width: 1px;
|
98
|
-
}
|
99
|
-
.key7,.fill7{
|
100
|
-
fill: #00ffff;
|
101
|
-
fill-opacity: 0.5;
|
102
|
-
stroke: none;
|
103
|
-
stroke-width: 1px;
|
104
|
-
}
|
105
|
-
.key8,.fill8{
|
106
|
-
fill: #ffff00;
|
107
|
-
fill-opacity: 0.5;
|
108
|
-
stroke: none;
|
109
|
-
stroke-width: 1px;
|
110
|
-
}
|
111
|
-
.key9,.fill9{
|
112
|
-
fill: #cc6666;
|
113
|
-
fill-opacity: 0.5;
|
114
|
-
stroke: none;
|
115
|
-
stroke-width: 1px;
|
116
|
-
}
|
117
|
-
.key10,.fill10{
|
118
|
-
fill: #663399;
|
119
|
-
fill-opacity: 0.5;
|
120
|
-
stroke: none;
|
121
|
-
stroke-width: 1px;
|
122
|
-
}
|
123
|
-
.key11,.fill11{
|
124
|
-
fill: #339900;
|
125
|
-
fill-opacity: 0.5;
|
126
|
-
stroke: none;
|
127
|
-
stroke-width: 1px;
|
128
|
-
}
|
129
|
-
.key12,.fill12{
|
130
|
-
fill: #9966FF;
|
131
|
-
fill-opacity: 0.5;
|
132
|
-
stroke: none;
|
133
|
-
stroke-width: 1px;
|
134
|
-
}
|
135
|
-
EOL
|
136
|
-
end
|
137
|
-
end
|
138
|
-
end
|
139
|
-
end
|
@@ -1,149 +0,0 @@
|
|
1
|
-
require 'rexml/document'
|
2
|
-
require 'SVG/Graph/BarBase'
|
3
|
-
|
4
|
-
module SVG
|
5
|
-
module Graph
|
6
|
-
# === Create presentation quality SVG horitonzal bar graphs easily
|
7
|
-
#
|
8
|
-
# = Synopsis
|
9
|
-
#
|
10
|
-
# require 'SVG/Graph/BarHorizontal'
|
11
|
-
#
|
12
|
-
# fields = %w(Jan Feb Mar)
|
13
|
-
# data_sales_02 = [12, 45, 21]
|
14
|
-
#
|
15
|
-
# graph = SVG::Graph::BarHorizontal.new({
|
16
|
-
# :height => 500,
|
17
|
-
# :width => 300,
|
18
|
-
# :fields => fields,
|
19
|
-
# })
|
20
|
-
#
|
21
|
-
# graph.add_data({
|
22
|
-
# :data => data_sales_02,
|
23
|
-
# :title => 'Sales 2002',
|
24
|
-
# })
|
25
|
-
#
|
26
|
-
# print "Content-type: image/svg+xml\r\n\r\n"
|
27
|
-
# print graph.burn
|
28
|
-
#
|
29
|
-
# = Description
|
30
|
-
#
|
31
|
-
# This object aims to allow you to easily create high quality
|
32
|
-
# SVG horitonzal bar graphs. You can either use the default style sheet
|
33
|
-
# or supply your own. Either way there are many options which can
|
34
|
-
# be configured to give you control over how the graph is
|
35
|
-
# generated - with or without a key, data elements at each point,
|
36
|
-
# title, subtitle etc.
|
37
|
-
#
|
38
|
-
# = Examples
|
39
|
-
#
|
40
|
-
# * http://germane-software.com/repositories/public/SVG/test/test.rb
|
41
|
-
#
|
42
|
-
# = See also
|
43
|
-
#
|
44
|
-
# * SVG::Graph::Graph
|
45
|
-
# * SVG::Graph::Bar
|
46
|
-
# * SVG::Graph::Line
|
47
|
-
# * SVG::Graph::Pie
|
48
|
-
# * SVG::Graph::Plot
|
49
|
-
# * SVG::Graph::TimeSeries
|
50
|
-
#
|
51
|
-
# == Author
|
52
|
-
#
|
53
|
-
# Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom>
|
54
|
-
#
|
55
|
-
# Copyright 2004 Sean E. Russell
|
56
|
-
# This software is available under the Ruby license[LICENSE.txt]
|
57
|
-
#
|
58
|
-
class BarHorizontal < BarBase
|
59
|
-
# In addition to the defaults set in BarBase::set_defaults, sets
|
60
|
-
# [rotate_y_labels] true
|
61
|
-
# [show_x_guidelines] true
|
62
|
-
# [show_y_guidelines] false
|
63
|
-
def set_defaults
|
64
|
-
super
|
65
|
-
init_with(
|
66
|
-
:rotate_y_labels => true,
|
67
|
-
:show_x_guidelines => true,
|
68
|
-
:show_y_guidelines => false
|
69
|
-
)
|
70
|
-
self.right_align = self.right_font = 1
|
71
|
-
end
|
72
|
-
|
73
|
-
protected
|
74
|
-
|
75
|
-
def get_x_labels
|
76
|
-
maxvalue = max_value
|
77
|
-
minvalue = min_value
|
78
|
-
range = maxvalue - minvalue
|
79
|
-
top_pad = range == 0 ? 10 : range / 20.0
|
80
|
-
scale_range = (maxvalue + top_pad) - minvalue
|
81
|
-
|
82
|
-
scale_division = scale_divisions || (scale_range / 10.0)
|
83
|
-
|
84
|
-
if scale_integers
|
85
|
-
scale_division = scale_division < 1 ? 1 : scale_division.round
|
86
|
-
end
|
87
|
-
|
88
|
-
rv = []
|
89
|
-
maxvalue = maxvalue%scale_division == 0 ?
|
90
|
-
maxvalue : maxvalue + scale_division
|
91
|
-
minvalue.step( maxvalue, scale_division ) {|v| rv << v}
|
92
|
-
return rv
|
93
|
-
end
|
94
|
-
|
95
|
-
def get_y_labels
|
96
|
-
@config[:fields]
|
97
|
-
end
|
98
|
-
|
99
|
-
def y_label_offset( height )
|
100
|
-
height / -2.0
|
101
|
-
end
|
102
|
-
|
103
|
-
def draw_data
|
104
|
-
minvalue = min_value
|
105
|
-
fieldheight = field_height
|
106
|
-
|
107
|
-
unit_size = (@graph_width.to_f - font_size*2*right_font ) /
|
108
|
-
(get_x_labels.max - get_x_labels.min )
|
109
|
-
bargap = bar_gap ? (fieldheight < 10 ? fieldheight / 2 : 10) : 0
|
110
|
-
|
111
|
-
bar_height = fieldheight - bargap
|
112
|
-
bar_height /= @data.length if stack == :side
|
113
|
-
y_mod = (bar_height / 2) + (font_size / 2)
|
114
|
-
|
115
|
-
field_count = 1
|
116
|
-
@config[:fields].each_index { |i|
|
117
|
-
dataset_count = 0
|
118
|
-
for dataset in @data
|
119
|
-
value = dataset[:data][i]
|
120
|
-
|
121
|
-
top = @graph_height - (fieldheight * field_count)
|
122
|
-
top += (bar_height * dataset_count) if stack == :side
|
123
|
-
# cases (assume 0 = +ve):
|
124
|
-
# value min length left
|
125
|
-
# +ve +ve value.abs - min minvalue.abs
|
126
|
-
# +ve -ve value.abs - 0 minvalue.abs
|
127
|
-
# -ve -ve value.abs - 0 minvalue.abs + value
|
128
|
-
length = (value.abs - (minvalue > 0 ? minvalue : 0)) * unit_size
|
129
|
-
left = (minvalue.abs + (value < 0 ? value : 0)) * unit_size
|
130
|
-
|
131
|
-
@graph.add_element( "rect", {
|
132
|
-
"x" => left.to_s,
|
133
|
-
"y" => top.to_s,
|
134
|
-
"width" => length.to_s,
|
135
|
-
"height" => bar_height.to_s,
|
136
|
-
"class" => "fill#{dataset_count+1}"
|
137
|
-
})
|
138
|
-
|
139
|
-
make_datapoint_text(
|
140
|
-
left+length+5, top+y_mod, value, "text-anchor: start; "
|
141
|
-
)
|
142
|
-
dataset_count += 1
|
143
|
-
end
|
144
|
-
field_count += 1
|
145
|
-
}
|
146
|
-
end
|
147
|
-
end
|
148
|
-
end
|
149
|
-
end
|
data/lib/SVG/Graph/Graph.rb
DELETED
@@ -1,978 +0,0 @@
|
|
1
|
-
begin
|
2
|
-
require 'zlib'
|
3
|
-
@@__have_zlib = true
|
4
|
-
rescue
|
5
|
-
@@__have_zlib = false
|
6
|
-
end
|
7
|
-
|
8
|
-
require 'rexml/document'
|
9
|
-
|
10
|
-
module SVG
|
11
|
-
module Graph
|
12
|
-
VERSION = '@ANT_VERSION@'
|
13
|
-
|
14
|
-
# === Base object for generating SVG Graphs
|
15
|
-
#
|
16
|
-
# == Synopsis
|
17
|
-
#
|
18
|
-
# This class is only used as a superclass of specialized charts. Do not
|
19
|
-
# attempt to use this class directly, unless creating a new chart type.
|
20
|
-
#
|
21
|
-
# For examples of how to subclass this class, see the existing specific
|
22
|
-
# subclasses, such as SVG::Graph::Pie.
|
23
|
-
#
|
24
|
-
# == Examples
|
25
|
-
#
|
26
|
-
# For examples of how to use this package, see either the test files, or
|
27
|
-
# the documentation for the specific class you want to use.
|
28
|
-
#
|
29
|
-
# * file:test/plot.rb
|
30
|
-
# * file:test/single.rb
|
31
|
-
# * file:test/test.rb
|
32
|
-
# * file:test/timeseries.rb
|
33
|
-
#
|
34
|
-
# == Description
|
35
|
-
#
|
36
|
-
# This package should be used as a base for creating SVG graphs.
|
37
|
-
#
|
38
|
-
# == Acknowledgements
|
39
|
-
#
|
40
|
-
# Leo Lapworth for creating the SVG::TT::Graph package which this Ruby
|
41
|
-
# port is based on.
|
42
|
-
#
|
43
|
-
# Stephen Morgan for creating the TT template and SVG.
|
44
|
-
#
|
45
|
-
# == See
|
46
|
-
#
|
47
|
-
# * SVG::Graph::BarHorizontal
|
48
|
-
# * SVG::Graph::Bar
|
49
|
-
# * SVG::Graph::Line
|
50
|
-
# * SVG::Graph::Pie
|
51
|
-
# * SVG::Graph::Plot
|
52
|
-
# * SVG::Graph::TimeSeries
|
53
|
-
#
|
54
|
-
# == Author
|
55
|
-
#
|
56
|
-
# Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom>
|
57
|
-
#
|
58
|
-
# Copyright 2004 Sean E. Russell
|
59
|
-
# This software is available under the Ruby license[LICENSE.txt]
|
60
|
-
#
|
61
|
-
class Graph
|
62
|
-
include REXML
|
63
|
-
|
64
|
-
# Initialize the graph object with the graph settings. You won't
|
65
|
-
# instantiate this class directly; see the subclass for options.
|
66
|
-
# [width] 500
|
67
|
-
# [height] 300
|
68
|
-
# [show_x_guidelines] false
|
69
|
-
# [show_y_guidelines] true
|
70
|
-
# [show_data_values] true
|
71
|
-
# [min_scale_value] 0
|
72
|
-
# [show_x_labels] true
|
73
|
-
# [stagger_x_labels] false
|
74
|
-
# [rotate_x_labels] false
|
75
|
-
# [step_x_labels] 1
|
76
|
-
# [step_include_first_x_label] true
|
77
|
-
# [show_y_labels] true
|
78
|
-
# [rotate_y_labels] false
|
79
|
-
# [scale_integers] false
|
80
|
-
# [show_x_title] false
|
81
|
-
# [x_title] 'X Field names'
|
82
|
-
# [show_y_title] false
|
83
|
-
# [y_title_text_direction] :bt
|
84
|
-
# [y_title] 'Y Scale'
|
85
|
-
# [show_graph_title] false
|
86
|
-
# [graph_title] 'Graph Title'
|
87
|
-
# [show_graph_subtitle] false
|
88
|
-
# [graph_subtitle] 'Graph Sub Title'
|
89
|
-
# [key] true,
|
90
|
-
# [key_position] :right, # bottom or righ
|
91
|
-
# [font_size] 12
|
92
|
-
# [title_font_size] 16
|
93
|
-
# [subtitle_font_size] 14
|
94
|
-
# [x_label_font_size] 12
|
95
|
-
# [x_title_font_size] 14
|
96
|
-
# [y_label_font_size] 12
|
97
|
-
# [y_title_font_size] 14
|
98
|
-
# [key_font_size] 10
|
99
|
-
# [no_css] false
|
100
|
-
# [add_popups] false
|
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
|
157
|
-
|
158
|
-
|
159
|
-
# This method allows you do add data to the graph object.
|
160
|
-
# It can be called several times to add more data sets in.
|
161
|
-
#
|
162
|
-
# data_sales_02 = [12, 45, 21];
|
163
|
-
#
|
164
|
-
# graph.add_data({
|
165
|
-
# :data => data_sales_02,
|
166
|
-
# :title => 'Sales 2002'
|
167
|
-
# })
|
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
|
177
|
-
|
178
|
-
|
179
|
-
# This method removes all data from the object so that you can
|
180
|
-
# reuse it to create a new graph but with the same config options.
|
181
|
-
#
|
182
|
-
# graph.clear_data
|
183
|
-
def clear_data
|
184
|
-
@data = []
|
185
|
-
end
|
186
|
-
|
187
|
-
|
188
|
-
# This method processes the template with the data and
|
189
|
-
# config which has been set and returns the resulting SVG.
|
190
|
-
#
|
191
|
-
# This method will croak unless at least one data set has
|
192
|
-
# been added to the graph object.
|
193
|
-
#
|
194
|
-
# print graph.burn
|
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
|
227
|
-
|
228
|
-
|
229
|
-
# Set the height of the graph box, this is the total height
|
230
|
-
# of the SVG box created - not the graph it self which auto
|
231
|
-
# scales to fix the space.
|
232
|
-
attr_accessor :height
|
233
|
-
# Set the width of the graph box, this is the total width
|
234
|
-
# of the SVG box created - not the graph it self which auto
|
235
|
-
# scales to fix the space.
|
236
|
-
attr_accessor :width
|
237
|
-
# Set the path to an external stylesheet, set to '' if
|
238
|
-
# you want to revert back to using the defaut internal version.
|
239
|
-
#
|
240
|
-
# To create an external stylesheet create a graph using the
|
241
|
-
# default internal version and copy the stylesheet section to
|
242
|
-
# an external file and edit from there.
|
243
|
-
attr_accessor :style_sheet
|
244
|
-
# (Bool) Show the value of each element of data on the graph
|
245
|
-
attr_accessor :show_data_values
|
246
|
-
# The point at which the Y axis starts, defaults to '0',
|
247
|
-
# if set to nil it will default to the minimum data value.
|
248
|
-
attr_accessor :min_scale_value
|
249
|
-
# Whether to show labels on the X axis or not, defaults
|
250
|
-
# to true, set to false if you want to turn them off.
|
251
|
-
attr_accessor :show_x_labels
|
252
|
-
# This puts the X labels at alternative levels so if they
|
253
|
-
# are long field names they will not overlap so easily.
|
254
|
-
# Default it false, to turn on set to true.
|
255
|
-
attr_accessor :stagger_x_labels
|
256
|
-
# This puts the Y labels at alternative levels so if they
|
257
|
-
# are long field names they will not overlap so easily.
|
258
|
-
# Default it false, to turn on set to true.
|
259
|
-
attr_accessor :stagger_y_labels
|
260
|
-
# This turns the X axis labels by 90 degrees.
|
261
|
-
# Default it false, to turn on set to true.
|
262
|
-
attr_accessor :rotate_x_labels
|
263
|
-
# This turns the Y axis labels by 90 degrees.
|
264
|
-
# Default it false, to turn on set to true.
|
265
|
-
attr_accessor :rotate_y_labels
|
266
|
-
# How many "steps" to use between displayed X axis labels,
|
267
|
-
# a step of one means display every label, a step of two results
|
268
|
-
# in every other label being displayed (label <gap> label <gap> label),
|
269
|
-
# a step of three results in every third label being displayed
|
270
|
-
# (label <gap> <gap> label <gap> <gap> label) and so on.
|
271
|
-
attr_accessor :step_x_labels
|
272
|
-
# Whether to (when taking "steps" between X axis labels) step from
|
273
|
-
# the first label (i.e. always include the first label) or step from
|
274
|
-
# the X axis origin (i.e. start with a gap if step_x_labels is greater
|
275
|
-
# than one).
|
276
|
-
attr_accessor :step_include_first_x_label
|
277
|
-
# Whether to show labels on the Y axis or not, defaults
|
278
|
-
# to true, set to false if you want to turn them off.
|
279
|
-
attr_accessor :show_y_labels
|
280
|
-
# Ensures only whole numbers are used as the scale divisions.
|
281
|
-
# Default it false, to turn on set to true. This has no effect if
|
282
|
-
# scale divisions are less than 1.
|
283
|
-
attr_accessor :scale_integers
|
284
|
-
# This defines the gap between markers on the Y axis,
|
285
|
-
# default is a 10th of the max_value, e.g. you will have
|
286
|
-
# 10 markers on the Y axis. NOTE: do not set this too
|
287
|
-
# low - you are limited to 999 markers, after that the
|
288
|
-
# graph won't generate.
|
289
|
-
attr_accessor :scale_divisions
|
290
|
-
# Whether to show the title under the X axis labels,
|
291
|
-
# default is false, set to true to show.
|
292
|
-
attr_accessor :show_x_title
|
293
|
-
# What the title under X axis should be, e.g. 'Months'.
|
294
|
-
attr_accessor :x_title
|
295
|
-
# Whether to show the title under the Y axis labels,
|
296
|
-
# default is false, set to true to show.
|
297
|
-
attr_accessor :show_y_title
|
298
|
-
# Aligns writing mode for Y axis label.
|
299
|
-
# Defaults to :bt (Bottom to Top).
|
300
|
-
# Change to :tb (Top to Bottom) to reverse.
|
301
|
-
attr_accessor :y_title_text_direction
|
302
|
-
# What the title under Y axis should be, e.g. 'Sales in thousands'.
|
303
|
-
attr_accessor :y_title
|
304
|
-
# Whether to show a title on the graph, defaults
|
305
|
-
# to false, set to true to show.
|
306
|
-
attr_accessor :show_graph_title
|
307
|
-
# What the title on the graph should be.
|
308
|
-
attr_accessor :graph_title
|
309
|
-
# Whether to show a subtitle on the graph, defaults
|
310
|
-
# to false, set to true to show.
|
311
|
-
attr_accessor :show_graph_subtitle
|
312
|
-
# What the subtitle on the graph should be.
|
313
|
-
attr_accessor :graph_subtitle
|
314
|
-
# Whether to show a key, defaults to false, set to
|
315
|
-
# true if you want to show it.
|
316
|
-
attr_accessor :key
|
317
|
-
# Where the key should be positioned, defaults to
|
318
|
-
# :right, set to :bottom if you want to move it.
|
319
|
-
attr_accessor :key_position
|
320
|
-
# Set the font size (in points) of the data point labels
|
321
|
-
attr_accessor :font_size
|
322
|
-
# Set the font size of the X axis labels
|
323
|
-
attr_accessor :x_label_font_size
|
324
|
-
# Set the font size of the X axis title
|
325
|
-
attr_accessor :x_title_font_size
|
326
|
-
# Set the font size of the Y axis labels
|
327
|
-
attr_accessor :y_label_font_size
|
328
|
-
# Set the font size of the Y axis title
|
329
|
-
attr_accessor :y_title_font_size
|
330
|
-
# Set the title font size
|
331
|
-
attr_accessor :title_font_size
|
332
|
-
# Set the subtitle font size
|
333
|
-
attr_accessor :subtitle_font_size
|
334
|
-
# Set the key font size
|
335
|
-
attr_accessor :key_font_size
|
336
|
-
# Show guidelines for the X axis
|
337
|
-
attr_accessor :show_x_guidelines
|
338
|
-
# Show guidelines for the Y axis
|
339
|
-
attr_accessor :show_y_guidelines
|
340
|
-
# Do not use CSS if set to true. Many SVG viewers do not support CSS, but
|
341
|
-
# not using CSS can result in larger SVGs as well as making it impossible to
|
342
|
-
# change colors after the chart is generated. Defaults to false.
|
343
|
-
attr_accessor :no_css
|
344
|
-
# Add popups for the data points on some graphs
|
345
|
-
attr_accessor :add_popups
|
346
|
-
|
347
|
-
|
348
|
-
protected
|
349
|
-
|
350
|
-
def sort( *arrys )
|
351
|
-
sort_multiple( arrys )
|
352
|
-
end
|
353
|
-
|
354
|
-
# Overwrite configuration options with supplied options. Used
|
355
|
-
# by subclasses.
|
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
|
361
|
-
|
362
|
-
attr_accessor :top_align, :top_font, :right_align, :right_font
|
363
|
-
|
364
|
-
KEY_BOX_SIZE = 12
|
365
|
-
|
366
|
-
# Override this (and call super) to change the margin to the left
|
367
|
-
# of the plot area. Results in @border_left being set.
|
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
|
380
|
-
|
381
|
-
|
382
|
-
# Calculates the width of the widest Y label. This will be the
|
383
|
-
# character height if the Y labels are rotated
|
384
|
-
def max_y_label_width_px
|
385
|
-
return font_size if rotate_y_labels
|
386
|
-
end
|
387
|
-
|
388
|
-
|
389
|
-
# Override this (and call super) to change the margin to the right
|
390
|
-
# of the plot area. Results in @border_right being set.
|
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
|
400
|
-
|
401
|
-
|
402
|
-
# Override this (and call super) to change the margin to the top
|
403
|
-
# of the plot area. Results in @border_top being set.
|
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
|
410
|
-
|
411
|
-
|
412
|
-
# Adds pop-up point information to a graph.
|
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
|
438
|
-
|
439
|
-
|
440
|
-
# Override this (and call super) to change the margin to the bottom
|
441
|
-
# of the plot area. Results in @border_bottom being set.
|
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
|
459
|
-
|
460
|
-
|
461
|
-
# Draws the background, axis, and labels.
|
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
|
491
|
-
|
492
|
-
|
493
|
-
# Where in the X area the label is drawn
|
494
|
-
# Centered in the field, should be width/2. Start, 0.
|
495
|
-
def x_label_offset( width )
|
496
|
-
0
|
497
|
-
end
|
498
|
-
|
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
|
516
|
-
|
517
|
-
|
518
|
-
# Draws the X axis labels
|
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
|
566
|
-
|
567
|
-
|
568
|
-
# Where in the Y area the label is drawn
|
569
|
-
# Centered in the field, should be width/2. Start, 0.
|
570
|
-
def y_label_offset( height )
|
571
|
-
0
|
572
|
-
end
|
573
|
-
|
574
|
-
|
575
|
-
def field_width
|
576
|
-
(@graph_width.to_f - font_size*2*right_font) /
|
577
|
-
(get_x_labels.length - right_align)
|
578
|
-
end
|
579
|
-
|
580
|
-
|
581
|
-
def field_height
|
582
|
-
(@graph_height.to_f - font_size*2*top_font) /
|
583
|
-
(get_y_labels.length - top_align)
|
584
|
-
end
|
585
|
-
|
586
|
-
|
587
|
-
# Draws the Y axis labels
|
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
|
627
|
-
|
628
|
-
|
629
|
-
# Draws the X axis guidelines
|
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
|
638
|
-
|
639
|
-
|
640
|
-
# Draws the Y axis guidelines
|
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
|
649
|
-
|
650
|
-
|
651
|
-
# Draws the graph title and subtitle
|
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
|
704
|
-
|
705
|
-
def keys
|
706
|
-
return @data.collect{ |d| d[:title] }
|
707
|
-
end
|
708
|
-
|
709
|
-
# Draws the legend on the graph
|
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
|
754
|
-
|
755
|
-
|
756
|
-
private
|
757
|
-
|
758
|
-
def sort_multiple( arrys, lo=0, hi=arrys[0].length-1 )
|
759
|
-
if lo < hi
|
760
|
-
p = partition(arrys,lo,hi)
|
761
|
-
sort_multiple(arrys, lo, p-1)
|
762
|
-
sort_multiple(arrys, p+1, hi)
|
763
|
-
end
|
764
|
-
arrys
|
765
|
-
end
|
766
|
-
|
767
|
-
def partition( arrys, lo, hi )
|
768
|
-
p = arrys[0][lo]
|
769
|
-
l = lo
|
770
|
-
z = lo+1
|
771
|
-
while z <= hi
|
772
|
-
if arrys[0][z] < p
|
773
|
-
l += 1
|
774
|
-
arrys.each { |arry| arry[z], arry[l] = arry[l], arry[z] }
|
775
|
-
end
|
776
|
-
z += 1
|
777
|
-
end
|
778
|
-
arrys.each { |arry| arry[lo], arry[l] = arry[l], arry[lo] }
|
779
|
-
l
|
780
|
-
end
|
781
|
-
|
782
|
-
def style
|
783
|
-
if no_css
|
784
|
-
styles = parse_css
|
785
|
-
@root.elements.each("//*[@class]") { |el|
|
786
|
-
cl = el.attributes["class"]
|
787
|
-
style = styles[cl]
|
788
|
-
style += el.attributes["style"] if el.attributes["style"]
|
789
|
-
el.attributes["style"] = style
|
790
|
-
}
|
791
|
-
end
|
792
|
-
end
|
793
|
-
|
794
|
-
def parse_css
|
795
|
-
css = get_style
|
796
|
-
rv = {}
|
797
|
-
while css =~ /^(\.(\w+)(?:\s*,\s*\.\w+)*)\s*\{/m
|
798
|
-
names_orig = names = $1
|
799
|
-
css = $'
|
800
|
-
css =~ /([^}]+)\}/m
|
801
|
-
content = $1
|
802
|
-
css = $'
|
803
|
-
|
804
|
-
nms = []
|
805
|
-
while names =~ /^\s*,?\s*\.(\w+)/
|
806
|
-
nms << $1
|
807
|
-
names = $'
|
808
|
-
end
|
809
|
-
|
810
|
-
content = content.tr( "\n\t", " ")
|
811
|
-
for name in nms
|
812
|
-
current = rv[name]
|
813
|
-
current = current ? current+"; "+content : content
|
814
|
-
rv[name] = current.strip.squeeze(" ")
|
815
|
-
end
|
816
|
-
end
|
817
|
-
return rv
|
818
|
-
end
|
819
|
-
|
820
|
-
|
821
|
-
# Override and place code to add defs here
|
822
|
-
def add_defs defs
|
823
|
-
end
|
824
|
-
|
825
|
-
|
826
|
-
def start_svg
|
827
|
-
# Base document
|
828
|
-
@doc = Document.new
|
829
|
-
@doc << XMLDecl.new
|
830
|
-
@doc << DocType.new( %q{svg PUBLIC "-//W3C//DTD SVG 1.0//EN" } +
|
831
|
-
%q{"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"} )
|
832
|
-
if style_sheet && style_sheet != ''
|
833
|
-
@doc << Instruction.new( "xml-stylesheet",
|
834
|
-
%Q{href="#{style_sheet}" type="text/css"} )
|
835
|
-
end
|
836
|
-
@root = @doc.add_element( "svg", {
|
837
|
-
"width" => width.to_s,
|
838
|
-
"height" => height.to_s,
|
839
|
-
"viewBox" => "0 0 #{width} #{height}",
|
840
|
-
"xmlns" => "http://www.w3.org/2000/svg",
|
841
|
-
"xmlns:xlink" => "http://www.w3.org/1999/xlink",
|
842
|
-
"xmlns:a3" => "http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/",
|
843
|
-
"a3:scriptImplementation" => "Adobe"
|
844
|
-
})
|
845
|
-
@root << Comment.new( " "+"\\"*66 )
|
846
|
-
@root << Comment.new( " Created with SVG::Graph " )
|
847
|
-
@root << Comment.new( " SVG::Graph by Sean E. Russell " )
|
848
|
-
@root << Comment.new( " Losely based on SVG::TT::Graph for Perl by"+
|
849
|
-
" Leo Lapworth & Stephan Morgan " )
|
850
|
-
@root << Comment.new( " "+"/"*66 )
|
851
|
-
|
852
|
-
defs = @root.add_element( "defs" )
|
853
|
-
add_defs defs
|
854
|
-
if not(style_sheet && style_sheet != '') and !no_css
|
855
|
-
@root << Comment.new(" include default stylesheet if none specified ")
|
856
|
-
style = defs.add_element( "style", {"type"=>"text/css"} )
|
857
|
-
style << CData.new( get_style )
|
858
|
-
end
|
859
|
-
|
860
|
-
@root << Comment.new( "SVG Background" )
|
861
|
-
@root.add_element( "rect", {
|
862
|
-
"width" => width.to_s,
|
863
|
-
"height" => height.to_s,
|
864
|
-
"x" => "0",
|
865
|
-
"y" => "0",
|
866
|
-
"class" => "svgBackground"
|
867
|
-
})
|
868
|
-
end
|
869
|
-
|
870
|
-
|
871
|
-
def calculate_graph_dimensions
|
872
|
-
calculate_left_margin
|
873
|
-
calculate_right_margin
|
874
|
-
calculate_bottom_margin
|
875
|
-
calculate_top_margin
|
876
|
-
@graph_width = width - @border_left - @border_right
|
877
|
-
@graph_height = height - @border_top - @border_bottom
|
878
|
-
end
|
879
|
-
|
880
|
-
def get_style
|
881
|
-
return <<EOL
|
882
|
-
/* Copy from here for external style sheet */
|
883
|
-
.svgBackground{
|
884
|
-
fill:#ffffff;
|
885
|
-
}
|
886
|
-
.graphBackground{
|
887
|
-
fill:#f0f0f0;
|
888
|
-
}
|
889
|
-
|
890
|
-
/* graphs titles */
|
891
|
-
.mainTitle{
|
892
|
-
text-anchor: middle;
|
893
|
-
fill: #000000;
|
894
|
-
font-size: #{title_font_size}px;
|
895
|
-
font-family: "Arial", sans-serif;
|
896
|
-
font-weight: normal;
|
897
|
-
}
|
898
|
-
.subTitle{
|
899
|
-
text-anchor: middle;
|
900
|
-
fill: #999999;
|
901
|
-
font-size: #{subtitle_font_size}px;
|
902
|
-
font-family: "Arial", sans-serif;
|
903
|
-
font-weight: normal;
|
904
|
-
}
|
905
|
-
|
906
|
-
.axis{
|
907
|
-
stroke: #000000;
|
908
|
-
stroke-width: 1px;
|
909
|
-
}
|
910
|
-
|
911
|
-
.guideLines{
|
912
|
-
stroke: #666666;
|
913
|
-
stroke-width: 1px;
|
914
|
-
stroke-dasharray: 5 5;
|
915
|
-
}
|
916
|
-
|
917
|
-
.xAxisLabels{
|
918
|
-
text-anchor: middle;
|
919
|
-
fill: #000000;
|
920
|
-
font-size: #{x_label_font_size}px;
|
921
|
-
font-family: "Arial", sans-serif;
|
922
|
-
font-weight: normal;
|
923
|
-
}
|
924
|
-
|
925
|
-
.yAxisLabels{
|
926
|
-
text-anchor: end;
|
927
|
-
fill: #000000;
|
928
|
-
font-size: #{y_label_font_size}px;
|
929
|
-
font-family: "Arial", sans-serif;
|
930
|
-
font-weight: normal;
|
931
|
-
}
|
932
|
-
|
933
|
-
.xAxisTitle{
|
934
|
-
text-anchor: middle;
|
935
|
-
fill: #ff0000;
|
936
|
-
font-size: #{x_title_font_size}px;
|
937
|
-
font-family: "Arial", sans-serif;
|
938
|
-
font-weight: normal;
|
939
|
-
}
|
940
|
-
|
941
|
-
.yAxisTitle{
|
942
|
-
fill: #ff0000;
|
943
|
-
text-anchor: middle;
|
944
|
-
font-size: #{y_title_font_size}px;
|
945
|
-
font-family: "Arial", sans-serif;
|
946
|
-
font-weight: normal;
|
947
|
-
}
|
948
|
-
|
949
|
-
.dataPointLabel{
|
950
|
-
fill: #000000;
|
951
|
-
text-anchor:middle;
|
952
|
-
font-size: 10px;
|
953
|
-
font-family: "Arial", sans-serif;
|
954
|
-
font-weight: normal;
|
955
|
-
}
|
956
|
-
|
957
|
-
.staggerGuideLine{
|
958
|
-
fill: none;
|
959
|
-
stroke: #000000;
|
960
|
-
stroke-width: 0.5px;
|
961
|
-
}
|
962
|
-
|
963
|
-
#{get_css}
|
964
|
-
|
965
|
-
.keyText{
|
966
|
-
fill: #000000;
|
967
|
-
text-anchor:start;
|
968
|
-
font-size: #{key_font_size}px;
|
969
|
-
font-family: "Arial", sans-serif;
|
970
|
-
font-weight: normal;
|
971
|
-
}
|
972
|
-
/* End copy for external style sheet */
|
973
|
-
EOL
|
974
|
-
end
|
975
|
-
|
976
|
-
end
|
977
|
-
end
|
978
|
-
end
|