svg-graph-test 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/GPL.txt +340 -0
- data/History.txt +97 -0
- data/LICENSE.txt +57 -0
- data/README.md +132 -0
- data/README.txt +89 -0
- data/Rakefile +42 -0
- data/lib/SVG/Graph/Bar.rb +154 -0
- data/lib/SVG/Graph/BarBase.rb +149 -0
- data/lib/SVG/Graph/BarHorizontal.rb +155 -0
- data/lib/SVG/Graph/C3js.rb +274 -0
- data/lib/SVG/Graph/DataPoint.rb +86 -0
- data/lib/SVG/Graph/ErrBar.rb +198 -0
- data/lib/SVG/Graph/Graph.rb +1338 -0
- data/lib/SVG/Graph/Line.rb +467 -0
- data/lib/SVG/Graph/Pie.rb +442 -0
- data/lib/SVG/Graph/Plot.rb +636 -0
- data/lib/SVG/Graph/Schedule.rb +386 -0
- data/lib/SVG/Graph/TimeSeries.rb +263 -0
- data/lib/svggraph.rb +18 -0
- data/test/test_svg_graph.rb +68 -0
- metadata +79 -0
@@ -0,0 +1,274 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module SVG
|
5
|
+
module Graph
|
6
|
+
|
7
|
+
# This class provides a lightweight generator for html code indluding c3js based
|
8
|
+
# graphs specified as javascript.
|
9
|
+
class C3js
|
10
|
+
|
11
|
+
# By default, the generated html code links the javascript and css dependencies
|
12
|
+
# to the d3 and c3 libraries in the <head> element. The latest versions of d3 and c3 available
|
13
|
+
# at the time of gem release are used through cdnjs.
|
14
|
+
# Custom versions of d3 and c3 can easily be used by specifying the corresponding keys
|
15
|
+
# in the optional Hash argument.
|
16
|
+
#
|
17
|
+
# If the dependencies are http(s) urls a simple href / src link is inserted in the
|
18
|
+
# html header.
|
19
|
+
# If you want to create a fully offline capable html file, you can do this by
|
20
|
+
# downloading the (minified) versions of d3.js, c3.css, c3.js to disk and then
|
21
|
+
# point to the files instead of http links. This will then inline the complete
|
22
|
+
# script and css payload directly into the generated html page.
|
23
|
+
#
|
24
|
+
#
|
25
|
+
# @option opts [String] "inline_dependencies" if true will inline the script and css
|
26
|
+
# parts of d3 and c3 directly into html, otherwise they are referred
|
27
|
+
# as external dependencies. default: false
|
28
|
+
# @option opts [String] "d3_js" url or path to local files. default: d3.js url via cdnjs
|
29
|
+
# @option opts [String] "c3_css" url or path to local files. default: c3.css url via cdnjs
|
30
|
+
# @option opts [String] "c3_js" url or path to local files. default: c3.js url via cdnjs
|
31
|
+
# @example create a simple graph
|
32
|
+
# my_graph = SVG::Graph::C3js.new("my_funny_chart_var")
|
33
|
+
# @example create a graph with custom version of C3 and D3
|
34
|
+
# # use external dependencies
|
35
|
+
# opts = {
|
36
|
+
# "d3_js" => "https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js",
|
37
|
+
# "c3_css" => "https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.8/c3.min.css",
|
38
|
+
# "c3_js" => "https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.8/c3.min.js"
|
39
|
+
# }
|
40
|
+
# # or inline dependencies into generated html
|
41
|
+
# opts = {
|
42
|
+
# "inline_dependencies" => true,
|
43
|
+
# "d3_js" => "/path/to/local/copy/of/d3.min.js",
|
44
|
+
# "c3_css" => "/path/to/local/copy/of/c3.min.css",
|
45
|
+
# "c3_js" => "/path/to/local/copy/of/c3.min.js"
|
46
|
+
# }
|
47
|
+
# my_graph = SVG::Graph::C3js.new("my_funny_chart_var", opts)
|
48
|
+
def initialize(opts = {})
|
49
|
+
default_opts = {
|
50
|
+
"inline_dependencies" => false,
|
51
|
+
"d3_js" => "https://cdnjs.cloudflare.com/ajax/libs/d3/5.12.0/d3.min.js",
|
52
|
+
"c3_css" => "https://cdnjs.cloudflare.com/ajax/libs/c3/0.7.11/c3.min.css",
|
53
|
+
"c3_js" => "https://cdnjs.cloudflare.com/ajax/libs/c3/0.7.11/c3.min.js"
|
54
|
+
}
|
55
|
+
@opts = default_opts.merge(opts)
|
56
|
+
if @opts["inline_dependencies"]
|
57
|
+
# we replace the values in the opts Hash by the referred file contents
|
58
|
+
["d3_js", "c3_css", "c3_js"].each do |key|
|
59
|
+
if !File.file?(@opts[key])
|
60
|
+
raise "opts[\"#{key}\"]: No such file - #{File.expand_path(@opts[key])}"
|
61
|
+
end
|
62
|
+
@opts[key] = File.read(@opts[key])
|
63
|
+
end # ["d3_js", "c3_css", "c3_js"].each
|
64
|
+
end # if @opts["inline_dependencies"]
|
65
|
+
start_document()
|
66
|
+
end # def initialize
|
67
|
+
|
68
|
+
# Adds a javascript/json C3js chart definition into the div tag
|
69
|
+
# @param javascript [String, Hash] see example
|
70
|
+
# @param js_chart_variable_name [String] only needed if the `javascript` parameter is a Hash.
|
71
|
+
# unique variable name representing the chart in javascript scope.
|
72
|
+
# Note this is a global javascript "var" so make sure to avoid name clashes
|
73
|
+
# with other javascript us might use on the same page.
|
74
|
+
#
|
75
|
+
# @raise
|
76
|
+
# @example
|
77
|
+
# # see http://c3js.org/examples.html
|
78
|
+
# # since ruby 2.3 you can use string symbol keys:
|
79
|
+
# chart_spec = {
|
80
|
+
# # bindto is mandatory
|
81
|
+
# "bindto": "#this_is_my_awesom_graph",
|
82
|
+
# "data": {
|
83
|
+
# "columns": [
|
84
|
+
# ['data1', 30, 200, 100, 400, 150, 250],
|
85
|
+
# ['data2', 50, 20, 10, 40, 15, 25]
|
86
|
+
# ]
|
87
|
+
# }
|
88
|
+
# # otherwise simply write plain javascript into a heredoc string:
|
89
|
+
# # make sure to include the var <chartname> = c3.generate() if using heredoc
|
90
|
+
# chart_spec_string =<<-HEREDOC
|
91
|
+
# var mychart1 = c3.generate({
|
92
|
+
# // bindto is mandatory
|
93
|
+
# "bindto": "#this_is_my_awesom_graph",
|
94
|
+
# "data": {
|
95
|
+
# "columns": [
|
96
|
+
# ['data1', 30, 200, 100, 400, 150, 250],
|
97
|
+
# ['data2', 50, 20, 10, 40, 15, 25]
|
98
|
+
# ]
|
99
|
+
# });
|
100
|
+
# HEREDOC
|
101
|
+
# graph.add_chart_spec(chart_spec, "my_chart1")
|
102
|
+
# # or
|
103
|
+
# graph.add_chart_spec(chart_spec_string)
|
104
|
+
def add_chart_spec(javascript, js_chart_variable_name = "")
|
105
|
+
if javascript.kind_of?(Hash)
|
106
|
+
if js_chart_variable_name.to_s.empty? || js_chart_variable_name.to_s.match(/\s/)
|
107
|
+
raise "js_chart_variable_name ('#{js_chart_variable_name.to_s}') cannot be empty or contain spaces, " +
|
108
|
+
"a valid javascript variable name is needed."
|
109
|
+
end
|
110
|
+
chart_spec = JSON(javascript)
|
111
|
+
inline_script = "var #{js_chart_variable_name} = c3.generate(#{chart_spec});"
|
112
|
+
elsif javascript.kind_of?(String)
|
113
|
+
inline_script = javascript
|
114
|
+
if !inline_script.match(/c3\.generate/)
|
115
|
+
raise "var <chartname> = c3.generate({...}) statement is missing in javascript string"
|
116
|
+
end
|
117
|
+
else
|
118
|
+
raise "Unsupported argument type: #{javascript.class}"
|
119
|
+
end
|
120
|
+
# (.+?)" means non-greedy match up to next double quote
|
121
|
+
if m = inline_script.match(/"bindto":\s*"#(.+?)"/)
|
122
|
+
@bindto = m[1]
|
123
|
+
else
|
124
|
+
raise "Missing chart specification is missing the mandatory \"bindto\" key/value pair."
|
125
|
+
end
|
126
|
+
add_div_element_for_graph()
|
127
|
+
add_javascript() {inline_script}
|
128
|
+
end # def add_chart_spec
|
129
|
+
|
130
|
+
# Appends a <script> element to the <div> element, this can be used to add additional animations
|
131
|
+
# but any script can also directly be part of the js_chart_specification in the #add_chart_spec
|
132
|
+
# method when you use a HEREDOC string as input.
|
133
|
+
# @param attrs [Hash] attributes for the <script> element. The following attribute
|
134
|
+
# is added by default: type="text/javascript"
|
135
|
+
# @yieldreturn [String] the actual javascript code to be added to the <script> element
|
136
|
+
# @return [REXML::Element] the Element which was just added
|
137
|
+
def add_javascript(attrs={}, &block)
|
138
|
+
default_attrs = {"type" => "text/javascript"}
|
139
|
+
attrs = default_attrs.merge(attrs)
|
140
|
+
temp = REXML::Element.new("script")
|
141
|
+
temp.add_attributes(attrs)
|
142
|
+
@svg.add_element(temp)
|
143
|
+
raise "Block argument is mandatory" unless block_given?
|
144
|
+
script_content = block.call()
|
145
|
+
cdata(script_content, temp)
|
146
|
+
end # def add_javascript
|
147
|
+
|
148
|
+
|
149
|
+
# @return [String] the complete html file
|
150
|
+
def burn
|
151
|
+
f = REXML::Formatters::Pretty.new(0)
|
152
|
+
out = ''
|
153
|
+
f.write(@doc, out)
|
154
|
+
out
|
155
|
+
end # def burn
|
156
|
+
|
157
|
+
# Burns the graph but returns only the <div> node as String without the
|
158
|
+
# Doctype and XML / HTML Declaration. This allows easy integration into
|
159
|
+
# existing xml/html documents. The Javascript to create the C3js graph
|
160
|
+
# is inlined into the div tag.
|
161
|
+
#
|
162
|
+
# You have to take care to refer the proper C3 and D3 dependencies in your
|
163
|
+
# html page.
|
164
|
+
#
|
165
|
+
# @return [String] the div element into which the graph will be rendered
|
166
|
+
# by C3.js
|
167
|
+
def burn_svg_only
|
168
|
+
# initialize all instance variables by burning the graph
|
169
|
+
burn
|
170
|
+
f = REXML::Formatters::Pretty.new(0)
|
171
|
+
f.compact = true
|
172
|
+
out = ''
|
173
|
+
f.write(@svg, out)
|
174
|
+
return out
|
175
|
+
end # def burn_svg_only
|
176
|
+
|
177
|
+
private
|
178
|
+
|
179
|
+
# Appends a <style> element to the <div> element, this can be used to add additional animations
|
180
|
+
# but any script can also directly be part of the js_chart_specification in the #add_chart_spec
|
181
|
+
# method when you use a HEREDOC string as input.
|
182
|
+
# @yieldreturn [String] the actual javascript code to be added to the <script> element
|
183
|
+
# @return [REXML::Element] the Element which was just added
|
184
|
+
def add_css_to_head(&block)
|
185
|
+
raise "Block argument is mandatory" unless block_given?
|
186
|
+
css_content_or_url = block.call()
|
187
|
+
if @opts["inline_dependencies"]
|
188
|
+
# for inline css use "style"
|
189
|
+
temp = REXML::Element.new("style")
|
190
|
+
attrs = {
|
191
|
+
"type" => "text/css"
|
192
|
+
}
|
193
|
+
cdata(css_content_or_url, temp)
|
194
|
+
else
|
195
|
+
# for external css use "link"
|
196
|
+
temp = REXML::Element.new("link")
|
197
|
+
attrs = {
|
198
|
+
"href" => @opts["c3_css"],
|
199
|
+
"rel" => "stylesheet"
|
200
|
+
}
|
201
|
+
end
|
202
|
+
temp.add_attributes(attrs)
|
203
|
+
@head.add_element(temp)
|
204
|
+
end # def add_css_to_head
|
205
|
+
|
206
|
+
# Appends a <script> element to the <head> element, this can be used to add
|
207
|
+
# the dependencies/libraries.
|
208
|
+
# @yieldreturn [String] the actual javascript code to be added to the <script> element
|
209
|
+
# @return [REXML::Element] the Element which was just added
|
210
|
+
def add_js_to_head(&block)
|
211
|
+
raise "Block argument is mandatory" unless block_given?
|
212
|
+
script_content_or_url = block.call()
|
213
|
+
attrs = {"type" => "text/javascript"}
|
214
|
+
temp = REXML::Element.new("script")
|
215
|
+
if @opts["inline_dependencies"]
|
216
|
+
cdata(script_content_or_url, temp)
|
217
|
+
else
|
218
|
+
attrs["src"] = script_content_or_url
|
219
|
+
# note: self-closing xml script tags are not allowed in html. Only for xhtml this is ok.
|
220
|
+
# Thus add a space textnode to enforce closing tags.
|
221
|
+
temp.add_text(" ")
|
222
|
+
end
|
223
|
+
temp.add_attributes(attrs)
|
224
|
+
@head.add_element(temp)
|
225
|
+
end # def add_js_to_head
|
226
|
+
|
227
|
+
def start_document
|
228
|
+
# Base document
|
229
|
+
@doc = REXML::Document.new
|
230
|
+
@doc << REXML::XMLDecl.new("1.0", "UTF-8")
|
231
|
+
@doc << REXML::DocType.new("html")
|
232
|
+
# attribute xmlns is needed, otherwise the browser will only display raw xml
|
233
|
+
# instead of rendering the page
|
234
|
+
@html = @doc.add_element("html", {"xmlns" => 'http://www.w3.org/1999/xhtml'})
|
235
|
+
@html << REXML::Comment.new( " "+"\\"*66 )
|
236
|
+
@html << REXML::Comment.new( " Created with SVG::Graph - https://github.com/lumean/svg-graph2" )
|
237
|
+
@head = @html.add_element("head")
|
238
|
+
@body = @html.add_element("body")
|
239
|
+
@head.add_element("meta", {"charset" => "utf-8"})
|
240
|
+
add_js_to_head() {@opts["d3_js"]}
|
241
|
+
add_css_to_head() {@opts["c3_css"]}
|
242
|
+
add_js_to_head() {@opts["c3_js"]}
|
243
|
+
end # def start_svg
|
244
|
+
|
245
|
+
# @param attrs [Hash] html attributes for the <div> tag to which svg graph
|
246
|
+
# is bound to by C3js. The "id" attribute
|
247
|
+
# is filled automatically by this method. default: an empty hash {}
|
248
|
+
def add_div_element_for_graph(attrs={})
|
249
|
+
if @bindto.to_s.empty?
|
250
|
+
raise "#add_chart_spec needs to be called before the svg can be added"
|
251
|
+
end
|
252
|
+
attrs["id"] = @bindto
|
253
|
+
@svg = @body.add_element("div", attrs)
|
254
|
+
end
|
255
|
+
|
256
|
+
# Surrounds CData tag with c-style comments to remain compatible with normal html.
|
257
|
+
# This can be used to inline arbitrary javascript code and is compatible with many browsers.
|
258
|
+
# Example /*<![CDATA[*/\n ...content ... \n/*]]>*/
|
259
|
+
# @param str [String] the string to be enclosed in cdata
|
260
|
+
# @param parent_element [REXML::Element] the element to which cdata should be added
|
261
|
+
# @return [REXML::Element] parent_element
|
262
|
+
def cdata(str, parent_element)
|
263
|
+
# somehow there is a problem with CDATA, any text added after will automatically go into the CDATA
|
264
|
+
# so we have do add a dummy node after the CDATA and then add the text.
|
265
|
+
parent_element.add_text("/*")
|
266
|
+
parent_element.add(REXML::CData.new("*/\n"+str+"\n/*"))
|
267
|
+
parent_element.add(REXML::Comment.new("dummy comment to make c-style comments for cdata work"))
|
268
|
+
parent_element.add_text("*/")
|
269
|
+
end # def cdata
|
270
|
+
|
271
|
+
end # class C3js
|
272
|
+
|
273
|
+
end # module Graph
|
274
|
+
end # module SVG
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# Allows to customize datapoint shapes
|
2
|
+
class DataPoint
|
3
|
+
# magic string that defines if a shape is intented to be overlayed to a default.
|
4
|
+
# this allowes to have strike through of a circle etc.
|
5
|
+
OVERLAY = "OVERLAY"
|
6
|
+
DEFAULT_SHAPE = lambda{|x,y,line| ["circle", {
|
7
|
+
"cx" => x,
|
8
|
+
"cy" => y,
|
9
|
+
"r" => "2.5",
|
10
|
+
"class" => "dataPoint#{line}"
|
11
|
+
}]
|
12
|
+
} unless defined? DEFAULT_SHAPE
|
13
|
+
CRITERIA = [] unless defined? CRITERIA
|
14
|
+
|
15
|
+
# matchers are class scope. Once configured, each DataPoint instance will have
|
16
|
+
# access to the same matchers
|
17
|
+
# @param matchers [Array] multiple arrays of the following form 2 or 3 elements:
|
18
|
+
# [ regex ,
|
19
|
+
# lambda taking three arguments (x,y, line_number for css)
|
20
|
+
# -> return value of the lambda must be an array: [svg tag name,
|
21
|
+
# Hash with attributes for the svg tag, e.g. "points" and "class",
|
22
|
+
# make sure to check source code of you graph type for valid css class.],
|
23
|
+
# "OVERLAY" (magic string, if specified, puts the shape on top of existing datapoint)
|
24
|
+
# ]
|
25
|
+
# @example
|
26
|
+
# DataPoint.configure_shape_criteria(
|
27
|
+
# [/.*/, lambda{|x,y,line|
|
28
|
+
# [ 'polygon',
|
29
|
+
# {
|
30
|
+
# "points" => "#{x-1.5},#{y+2.5} #{x+1.5},#{y+2.5} #{x+1.5},#{y-2.5} #{x-1.5},#{y-2.5}",
|
31
|
+
# "class" => "dataPoint#{line}"
|
32
|
+
# }
|
33
|
+
# ]
|
34
|
+
# }]
|
35
|
+
# )
|
36
|
+
def DataPoint.configure_shape_criteria(*matchers)
|
37
|
+
CRITERIA.push(*matchers)
|
38
|
+
end
|
39
|
+
|
40
|
+
#
|
41
|
+
def DataPoint.reset_shape_criteria
|
42
|
+
CRITERIA.clear
|
43
|
+
end
|
44
|
+
|
45
|
+
# creates a new DataPoint
|
46
|
+
# @param x [Numeric] x coordinates of the point
|
47
|
+
# @param y [Numeric] y coordinates of the point
|
48
|
+
# @param line [Fixnum] line index of the current dataset (e.g. when multiple times Graph.add_data()), can be used to reference to the correct css class
|
49
|
+
def initialize(x, y, line)
|
50
|
+
@x = x
|
51
|
+
@y = y
|
52
|
+
@line = line
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns different shapes depending on datapoint descriptions, if shape criteria have been configured.
|
56
|
+
# The definded criteria are evaluated in two stages, first the ones, which are note defined as overlay.
|
57
|
+
# then the "OVERLAY"
|
58
|
+
# @param datapoint_description [String] description or label of the current datapoint
|
59
|
+
# @return [Array<Array>] see example
|
60
|
+
# @example Return value
|
61
|
+
# # two dimensional array, the splatted (*) inner array can be used as argument to REXML::add_element
|
62
|
+
# [["svgtag", {"points" => "", "class"=> "dataPoint#{line}" } ], ["svgtag", {"points"=>"", "class"=> ""}], ...]
|
63
|
+
# @exmple Usage
|
64
|
+
# dp = DataPoint.new(x, y, line).shape(data[:description])
|
65
|
+
# # for each svg we insert it to the graph
|
66
|
+
# dp.each {|s| @graph.add_element( *s )}
|
67
|
+
#
|
68
|
+
def shape(datapoint_description=nil)
|
69
|
+
# select all criteria with size 2, and collect rendered lambdas in an array
|
70
|
+
shapes = CRITERIA.select {|criteria|
|
71
|
+
criteria.size == 2
|
72
|
+
}.collect {|regexp, proc|
|
73
|
+
proc.call(@x, @y, @line) if datapoint_description =~ regexp
|
74
|
+
}.compact
|
75
|
+
# if above did not render anything use the defalt shape
|
76
|
+
shapes = [DEFAULT_SHAPE.call(@x, @y, @line)] if shapes.empty?
|
77
|
+
|
78
|
+
overlays = CRITERIA.select { |criteria|
|
79
|
+
criteria.last == OVERLAY
|
80
|
+
}.collect { |regexp, proc|
|
81
|
+
proc.call(@x, @y, @line) if datapoint_description =~ regexp
|
82
|
+
}.compact
|
83
|
+
|
84
|
+
return shapes + overlays
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,198 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
require_relative 'Graph'
|
3
|
+
require_relative 'BarBase'
|
4
|
+
|
5
|
+
module SVG
|
6
|
+
module Graph
|
7
|
+
# === Create presentation quality SVG bar graphs easily
|
8
|
+
#
|
9
|
+
# = Synopsis
|
10
|
+
#
|
11
|
+
# require 'SVG/Graph/ErrBar'
|
12
|
+
#
|
13
|
+
# fields = %w(Jan Feb);
|
14
|
+
# myarr1_mean = 10
|
15
|
+
# myarr1_confidence = 1
|
16
|
+
#
|
17
|
+
# myarr2_mean = 20
|
18
|
+
# myarr2_confidence = 2
|
19
|
+
#
|
20
|
+
# data= [myarr1_mean, myarr2_mean]
|
21
|
+
#
|
22
|
+
# err_mesure = [myarr1_confidence, myarr2_confidence]
|
23
|
+
#
|
24
|
+
# graph = SVG::Graph::ErrBar.new(
|
25
|
+
# :height => 500,
|
26
|
+
# :width => 600,
|
27
|
+
# :fields => fields,
|
28
|
+
# :errorBars => err_mesure,
|
29
|
+
# :scale_integers => true,
|
30
|
+
# )
|
31
|
+
#
|
32
|
+
# graph.add_data(
|
33
|
+
# :data => data,
|
34
|
+
# :title => 'Sales 2002'
|
35
|
+
# )
|
36
|
+
#
|
37
|
+
# print "Content-type: image/svg+xml\r\n\r\n"
|
38
|
+
# print graph.burn
|
39
|
+
#
|
40
|
+
# = Description
|
41
|
+
#
|
42
|
+
# This object aims to allow you to easily create high quality
|
43
|
+
# SVG[http://www.w3c.org/tr/svg bar graphs. You can either use the default
|
44
|
+
# style sheet or supply your own. Either way there are many options which
|
45
|
+
# can be configured to give you control over how the graph is generated -
|
46
|
+
# with or without a key, data elements at each point, title, subtitle etc.
|
47
|
+
#
|
48
|
+
# = Notes
|
49
|
+
#
|
50
|
+
# The default stylesheet handles upto 12 data sets, if you
|
51
|
+
# use more you must create your own stylesheet and add the
|
52
|
+
# additional settings for the extra data sets. You will know
|
53
|
+
# if you go over 12 data sets as they will have no style and
|
54
|
+
# be in black.
|
55
|
+
#
|
56
|
+
# = Examples
|
57
|
+
#
|
58
|
+
# * http://germane-software.com/repositories/public/SVG/test/test.rb
|
59
|
+
#
|
60
|
+
# = See also
|
61
|
+
#
|
62
|
+
# * SVG::Graph::Graph
|
63
|
+
# * SVG::Graph::BarHorizontal
|
64
|
+
# * SVG::Graph::Line
|
65
|
+
# * SVG::Graph::Pie
|
66
|
+
# * SVG::Graph::Plot
|
67
|
+
# * SVG::Graph::TimeSeries
|
68
|
+
class ErrBar < BarBase
|
69
|
+
include REXML
|
70
|
+
|
71
|
+
def initialize config
|
72
|
+
raise "fields was not supplied or is empty" unless config[:errorBars] &&
|
73
|
+
config[:errorBars].kind_of?(Array) &&
|
74
|
+
config[:errorBars].length > 0
|
75
|
+
super
|
76
|
+
end
|
77
|
+
# Array of confidence values for each item in :fields. A range from
|
78
|
+
# value[i]-errorBars[i] to value[i]+errorBars[i] is drawn into the graph.
|
79
|
+
attr_accessor :errorBars
|
80
|
+
|
81
|
+
protected
|
82
|
+
|
83
|
+
def get_x_labels
|
84
|
+
@config[:fields]
|
85
|
+
end
|
86
|
+
|
87
|
+
def get_y_labels
|
88
|
+
maxvalue = max_value
|
89
|
+
minvalue = min_value
|
90
|
+
range = maxvalue - minvalue
|
91
|
+
# add some padding on top of the graph
|
92
|
+
if range == 0
|
93
|
+
maxvalue += 10
|
94
|
+
else
|
95
|
+
maxvalue += range / 20.0
|
96
|
+
end
|
97
|
+
scale_range = maxvalue - minvalue
|
98
|
+
|
99
|
+
@y_scale_division = scale_divisions || (scale_range / 10.0)
|
100
|
+
|
101
|
+
if scale_integers
|
102
|
+
@y_scale_division = @y_scale_division < 1 ? 1 : @y_scale_division.round
|
103
|
+
end
|
104
|
+
|
105
|
+
rv = []
|
106
|
+
maxvalue = maxvalue%@y_scale_division == 0 ?
|
107
|
+
maxvalue : maxvalue + @y_scale_division
|
108
|
+
minvalue.step( maxvalue, @y_scale_division ) {|v| rv << v}
|
109
|
+
return rv
|
110
|
+
end
|
111
|
+
|
112
|
+
def x_label_offset( width )
|
113
|
+
width / 2.0
|
114
|
+
end
|
115
|
+
|
116
|
+
def draw_data
|
117
|
+
minvalue = min_value
|
118
|
+
fieldwidth = field_width
|
119
|
+
|
120
|
+
unit_size = field_height
|
121
|
+
bargap = bar_gap ? (fieldwidth < 10 ? fieldwidth / 2 : 10) : 0
|
122
|
+
|
123
|
+
bar_width = fieldwidth - (bargap *2)
|
124
|
+
bar_width /= @data.length if stack == :side
|
125
|
+
|
126
|
+
bottom = @graph_height
|
127
|
+
|
128
|
+
field_count = 0
|
129
|
+
@config[:fields].each_index { |i|
|
130
|
+
dataset_count = 0
|
131
|
+
for dataset in @data
|
132
|
+
|
133
|
+
# cases (assume 0 = +ve):
|
134
|
+
# value min length
|
135
|
+
# +ve +ve value - min
|
136
|
+
# +ve -ve value - 0
|
137
|
+
# -ve -ve value.abs - 0
|
138
|
+
|
139
|
+
value = dataset[:data][i].to_f/@y_scale_division
|
140
|
+
|
141
|
+
left = (fieldwidth * field_count)
|
142
|
+
left += bargap
|
143
|
+
|
144
|
+
|
145
|
+
length = (value.abs - (minvalue > 0 ? minvalue : 0)) * unit_size
|
146
|
+
# top is 0 if value is negative
|
147
|
+
top = bottom - (((value < 0 ? 0 : value) - minvalue) * unit_size)
|
148
|
+
left += bar_width * dataset_count if stack == :side
|
149
|
+
|
150
|
+
@graph.add_element( "rect", {
|
151
|
+
"x" => left.to_s,
|
152
|
+
"y" => top.to_s,
|
153
|
+
"width" => bar_width.to_s,
|
154
|
+
"height" => length.to_s,
|
155
|
+
"class" => "fill#{dataset_count+1}"
|
156
|
+
})
|
157
|
+
|
158
|
+
threshold = @config[:errorBars][i].to_f/@y_scale_division * unit_size
|
159
|
+
middlePointErr = left+bar_width/2
|
160
|
+
upperErr = top+threshold
|
161
|
+
bottomErr = top-threshold
|
162
|
+
withthErr = bar_width/4
|
163
|
+
|
164
|
+
@graph.add_element( "line", {
|
165
|
+
"x1" => middlePointErr.to_s,
|
166
|
+
"y1" => upperErr.to_s,
|
167
|
+
"x2" => middlePointErr.to_s,
|
168
|
+
"y2" => bottomErr.to_s,
|
169
|
+
"style" => "stroke:rgb(0,0,0);stroke-width:1"
|
170
|
+
})
|
171
|
+
|
172
|
+
@graph.add_element( "line", {
|
173
|
+
"x1" => (middlePointErr-withthErr).to_s,
|
174
|
+
"y1" => upperErr.to_s,
|
175
|
+
"x2" => (middlePointErr+withthErr).to_s,
|
176
|
+
"y2" => upperErr.to_s,
|
177
|
+
"style" => "stroke:rgb(0,0,0);stroke-width:1"
|
178
|
+
})
|
179
|
+
@graph.add_element( "line", {
|
180
|
+
"x1" => (middlePointErr-withthErr).to_s,
|
181
|
+
"y1" => bottomErr.to_s,
|
182
|
+
"x2" => (middlePointErr+withthErr).to_s,
|
183
|
+
"y2" => bottomErr.to_s,
|
184
|
+
"style" => "stroke:rgb(0,0,0);stroke-width:1"
|
185
|
+
})
|
186
|
+
|
187
|
+
make_datapoint_text(left + bar_width/2.0, top - 6, dataset[:data][i])
|
188
|
+
# number format shall not apply to popup (use .to_s conversion)
|
189
|
+
add_popup(left + bar_width/2.0, top , dataset[:data][i].to_s)
|
190
|
+
dataset_count += 1
|
191
|
+
end
|
192
|
+
field_count += 1
|
193
|
+
} # config[:fields].each_index
|
194
|
+
end # draw_data
|
195
|
+
|
196
|
+
end # ErrBar
|
197
|
+
end
|
198
|
+
end
|