svg-graph-test 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|