svg-graph 2.1.3 → 2.2.0.beta
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 +5 -5
- data/History.txt +12 -3
- data/{README.markdown → README.md} +11 -3
- data/lib/SVG/Graph/Bar.rb +10 -3
- data/lib/SVG/Graph/BarBase.rb +21 -17
- data/lib/SVG/Graph/BarHorizontal.rb +12 -5
- data/lib/SVG/Graph/C3js.rb +274 -0
- data/lib/SVG/Graph/Graph.rb +79 -37
- data/lib/SVG/Graph/Plot.rb +65 -65
- data/lib/svggraph.rb +4 -2
- data/test/test_svg_graph.rb +7 -7
- metadata +13 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ffe9d9ef41e6f8e53cc9acc30eea176b5c08df460fecd4809e5a974930b07316
|
4
|
+
data.tar.gz: c7a4de59354a36eb79da7c3b337859425b348a17ba11254a155a8cc4f761d41f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 742d05482c8cdecf6359ca8aac09e0c1e307c9e063934b3b029908b4ceac87477ea5f3089b44ddebec41c58c5d56b22734246d741a78fb697eee8bc54ffba46d
|
7
|
+
data.tar.gz: '078f9a897f0fca0e88b3483e84aba191fb8bfa3eb46bd6505565f0de037d08a12ecb86770270e53ba7e607c0018291b22f4b668af9ec0549d562775e430d6b96'
|
data/History.txt
CHANGED
@@ -1,10 +1,19 @@
|
|
1
1
|
TODO / Backlog
|
2
|
-
* add unit tests for all types of graphs
|
3
2
|
* refactor various hardcoded constant pixel offsets in Graph class to use named constants or variables
|
4
|
-
*
|
3
|
+
* Fix bug in Plot where min/max_x/y_value are not respected, TODO
|
5
4
|
|
6
|
-
=== 2.2.0 / todo
|
7
5
|
|
6
|
+
=== 2.2.0.beta / 2019-05-07
|
7
|
+
* Fix typo in Plot's `scale_y_integers` [thanks ashleydavies, PR #9]
|
8
|
+
* Fix bug in Plot which enabled too-short axis [thanks ashleydavies, PR #10]
|
9
|
+
* Fix issue where horizontal bar graphs were not lined up properly [thanks erullmann]
|
10
|
+
* removed some outdated documentation and uneeded files
|
11
|
+
* add support for c3js / d3js based graphs `SVG::Graph::C3js`, see http://c3js.org/
|
12
|
+
* add *very* basic unit tests for all types of graphs
|
13
|
+
* add :show_percent for Bar graphs [thanks Cameron2920]
|
14
|
+
* add :inline_style_sheet parameter for all graphs to allow inline css instead of specifying external stylesheets via url [thanks erullmann]
|
15
|
+
* add parameters to control the key :key_box_size, :key_spacing, :key_width [thanks erullmann]
|
16
|
+
* add support for X and Y label rotation to be different than 90 degree [thanks frenkel, PR #12]
|
8
17
|
|
9
18
|
=== 2.1.3 / 2017-12-27
|
10
19
|
* fixes float comparison and color for pie chart [thanks tiwi, pull request #7]
|
@@ -3,7 +3,7 @@ SVG::Graph
|
|
3
3
|
|
4
4
|
Description
|
5
5
|
-----------
|
6
|
-
This is
|
6
|
+
This repo is the continuation of the original [SVG::Graph library](http://www.germane-software.com/software/SVG/SVG::Graph/) by Sean Russell. I'd like to thank Claudio Bustos for giving me permissions to continue publishing the gem under it's original name: [svg-graph](https://rubygems.org/gems/svg-graph)
|
7
7
|
|
8
8
|
[Changelog](../master/History.txt)
|
9
9
|
|
@@ -37,7 +37,7 @@ require 'SVG/Graph/Bar'
|
|
37
37
|
|
38
38
|
x_axis = ['1-10', '10-30', '30-50', '50-70', 'older']
|
39
39
|
|
40
|
-
options = {
|
40
|
+
options = {
|
41
41
|
:width => 640,
|
42
42
|
:height => 300,
|
43
43
|
:stack => :side, # the stack option is valid for Bar graphs only
|
@@ -66,7 +66,7 @@ g.add_data( {
|
|
66
66
|
:title => "Male"
|
67
67
|
})
|
68
68
|
|
69
|
-
# graph.burn # this returns a full valid xml document containing the graph
|
69
|
+
# graph.burn # this returns a full valid xml document containing the graph
|
70
70
|
# graph.burn_svg_only # this only returns the <svg>...</svg> node
|
71
71
|
File.open('bar.svg', 'w') {|f| f.write(g.burn_svg_only)}
|
72
72
|
```
|
@@ -100,6 +100,14 @@ File.open('bar.svg', 'w') {|f| f.write(g.burn_svg_only)}
|
|
100
100
|
|
101
101
|

|
102
102
|
|
103
|
+
### C3js
|
104
|
+
|
105
|
+
Source: [C3js.rb](../master/examples/c3js.rb)
|
106
|
+
|
107
|
+
[Link to Preview](https://cdn.rawgit.com/lumean/svg-graph2/master/examples/c3js.html)
|
108
|
+
|
109
|
+
<iframe src="https://cdn.rawgit.com/lumean/svg-graph2/master/examples/c3js.html" width="600px"> </iframe>
|
110
|
+
|
103
111
|
Also have a look at the original [SVG::Graph web page](http://www.germane-software.com/software/SVG/SVG::Graph/), but note that this repository has already added some additional functionality, not available with the original.
|
104
112
|
|
105
113
|
Build
|
data/lib/SVG/Graph/Bar.rb
CHANGED
@@ -110,6 +110,10 @@ module SVG
|
|
110
110
|
@config[:fields].each_index { |i|
|
111
111
|
dataset_count = 0
|
112
112
|
for dataset in @data
|
113
|
+
total = 0
|
114
|
+
dataset[:data].each {|x|
|
115
|
+
total += x
|
116
|
+
}
|
113
117
|
|
114
118
|
# cases (assume 0 = +ve):
|
115
119
|
# value min length
|
@@ -133,10 +137,13 @@ module SVG
|
|
133
137
|
"height" => length.to_s,
|
134
138
|
"class" => "fill#{dataset_count+1}"
|
135
139
|
})
|
136
|
-
|
137
|
-
|
140
|
+
value_string = ""
|
141
|
+
value_string += (@number_format % dataset[:data][i]) if show_actual_values
|
142
|
+
percent = 100.0 * dataset[:data][i] / total
|
143
|
+
value_string += " (" + percent.round.to_s + "%)" if show_percent
|
144
|
+
make_datapoint_text(left + bar_width/2.0, top - font_size/2, value_string)
|
138
145
|
# number format shall not apply to popup (use .to_s conversion)
|
139
|
-
add_popup(left + bar_width/2.0, top ,
|
146
|
+
add_popup(left + bar_width/2.0, top , value_string)
|
140
147
|
dataset_count += 1
|
141
148
|
end
|
142
149
|
field_count += 1
|
data/lib/SVG/Graph/BarBase.rb
CHANGED
@@ -28,7 +28,7 @@ module SVG
|
|
28
28
|
# [bar_gap] true
|
29
29
|
# [stack] :overlap
|
30
30
|
def set_defaults
|
31
|
-
init_with( :bar_gap => true, :stack => :overlap )
|
31
|
+
init_with( :bar_gap => true, :stack => :overlap, :show_percent => false, :show_actual_values => true)
|
32
32
|
end
|
33
33
|
|
34
34
|
# Whether to have a gap between the bars or not, default
|
@@ -38,22 +38,26 @@ module SVG
|
|
38
38
|
# transparent colors, :top stacks bars on top of one another,
|
39
39
|
# :side stacks the bars side-by-side. Defaults to :overlap.
|
40
40
|
attr_accessor :stack
|
41
|
+
# If true, display the percentage value of each bar. Default: false
|
42
|
+
attr_accessor :show_percent
|
43
|
+
# If true, display the actual field values in the data labels. Default: true
|
44
|
+
attr_accessor :show_actual_values
|
41
45
|
protected
|
42
|
-
|
46
|
+
|
43
47
|
# space in px between x-labels, we override the Graph version because
|
44
48
|
# we need the extra space (i.e. don't subtract 1 from get_x_labels.length)
|
45
49
|
def field_width
|
46
|
-
# don't use -1 otherwise bar is out of bounds
|
50
|
+
# don't use -1 otherwise bar is out of bounds
|
47
51
|
@graph_width.to_f / ( get_x_labels.length )
|
48
52
|
end
|
49
|
-
|
53
|
+
|
50
54
|
def max_value
|
51
55
|
@data.collect{|x| x[:data].max}.max
|
52
56
|
end
|
53
57
|
|
54
58
|
def min_value
|
55
59
|
min = 0
|
56
|
-
if min_scale_value.nil?
|
60
|
+
if min_scale_value.nil?
|
57
61
|
min = @data.collect{|x| x[:data].min}.min
|
58
62
|
# by default bar should always start from zero unless there are negative values
|
59
63
|
min = min > 0 ? 0 : min
|
@@ -70,73 +74,73 @@ module SVG
|
|
70
74
|
fill: #ff0000;
|
71
75
|
fill-opacity: 0.5;
|
72
76
|
stroke: none;
|
73
|
-
stroke-width: 0.5px;
|
77
|
+
stroke-width: 0.5px;
|
74
78
|
}
|
75
79
|
.key2,.fill2{
|
76
80
|
fill: #0000ff;
|
77
81
|
fill-opacity: 0.5;
|
78
82
|
stroke: none;
|
79
|
-
stroke-width: 1px;
|
83
|
+
stroke-width: 1px;
|
80
84
|
}
|
81
85
|
.key3,.fill3{
|
82
86
|
fill: #00ff00;
|
83
87
|
fill-opacity: 0.5;
|
84
88
|
stroke: none;
|
85
|
-
stroke-width: 1px;
|
89
|
+
stroke-width: 1px;
|
86
90
|
}
|
87
91
|
.key4,.fill4{
|
88
92
|
fill: #ffcc00;
|
89
93
|
fill-opacity: 0.5;
|
90
94
|
stroke: none;
|
91
|
-
stroke-width: 1px;
|
95
|
+
stroke-width: 1px;
|
92
96
|
}
|
93
97
|
.key5,.fill5{
|
94
98
|
fill: #00ccff;
|
95
99
|
fill-opacity: 0.5;
|
96
100
|
stroke: none;
|
97
|
-
stroke-width: 1px;
|
101
|
+
stroke-width: 1px;
|
98
102
|
}
|
99
103
|
.key6,.fill6{
|
100
104
|
fill: #ff00ff;
|
101
105
|
fill-opacity: 0.5;
|
102
106
|
stroke: none;
|
103
|
-
stroke-width: 1px;
|
107
|
+
stroke-width: 1px;
|
104
108
|
}
|
105
109
|
.key7,.fill7{
|
106
110
|
fill: #00ffff;
|
107
111
|
fill-opacity: 0.5;
|
108
112
|
stroke: none;
|
109
|
-
stroke-width: 1px;
|
113
|
+
stroke-width: 1px;
|
110
114
|
}
|
111
115
|
.key8,.fill8{
|
112
116
|
fill: #ffff00;
|
113
117
|
fill-opacity: 0.5;
|
114
118
|
stroke: none;
|
115
|
-
stroke-width: 1px;
|
119
|
+
stroke-width: 1px;
|
116
120
|
}
|
117
121
|
.key9,.fill9{
|
118
122
|
fill: #cc6666;
|
119
123
|
fill-opacity: 0.5;
|
120
124
|
stroke: none;
|
121
|
-
stroke-width: 1px;
|
125
|
+
stroke-width: 1px;
|
122
126
|
}
|
123
127
|
.key10,.fill10{
|
124
128
|
fill: #663399;
|
125
129
|
fill-opacity: 0.5;
|
126
130
|
stroke: none;
|
127
|
-
stroke-width: 1px;
|
131
|
+
stroke-width: 1px;
|
128
132
|
}
|
129
133
|
.key11,.fill11{
|
130
134
|
fill: #339900;
|
131
135
|
fill-opacity: 0.5;
|
132
136
|
stroke: none;
|
133
|
-
stroke-width: 1px;
|
137
|
+
stroke-width: 1px;
|
134
138
|
}
|
135
139
|
.key12,.fill12{
|
136
140
|
fill: #9966FF;
|
137
141
|
fill-opacity: 0.5;
|
138
142
|
stroke: none;
|
139
|
-
stroke-width: 1px;
|
143
|
+
stroke-width: 1px;
|
140
144
|
}
|
141
145
|
EOL
|
142
146
|
end
|
@@ -110,14 +110,18 @@ module SVG
|
|
110
110
|
bar_height = fieldheight - bargap
|
111
111
|
bar_height /= @data.length if stack == :side
|
112
112
|
y_mod = (bar_height / 2) + (font_size / 2)
|
113
|
-
|
114
113
|
field_count = 1
|
114
|
+
|
115
115
|
@config[:fields].each_index { |i|
|
116
116
|
dataset_count = 0
|
117
117
|
for dataset in @data
|
118
|
+
total = 0
|
119
|
+
dataset[:data].each {|x|
|
120
|
+
total += x
|
121
|
+
}
|
118
122
|
value = dataset[:data][i]
|
119
123
|
|
120
|
-
top = @graph_height - (fieldheight * field_count) + bargap
|
124
|
+
top = @graph_height - (fieldheight * field_count) + (bargap/2)
|
121
125
|
top += (bar_height * dataset_count) if stack == :side
|
122
126
|
# cases (assume 0 = +ve):
|
123
127
|
# value min length left
|
@@ -134,10 +138,13 @@ module SVG
|
|
134
138
|
"height" => bar_height.to_s,
|
135
139
|
"class" => "fill#{dataset_count+1}"
|
136
140
|
})
|
137
|
-
|
138
|
-
|
141
|
+
value_string = ""
|
142
|
+
value_string += (@number_format % dataset[:data][i]) if show_actual_values
|
143
|
+
percent = 100.0 * dataset[:data][i] / total
|
144
|
+
value_string += " (" + percent.round.to_s + "%)" if show_percent
|
145
|
+
make_datapoint_text(left+length+5, top+y_mod, value_string, "text-anchor: start; ")
|
139
146
|
# number format shall not apply to popup (use .to_s conversion)
|
140
|
-
add_popup(left+length, top+y_mod ,
|
147
|
+
add_popup(left+length, top+y_mod , value_string)
|
141
148
|
dataset_count += 1
|
142
149
|
end
|
143
150
|
field_count += 1
|
@@ -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.9.2/d3.min.js",
|
52
|
+
"c3_css" => "https://cdnjs.cloudflare.com/ajax/libs/c3/0.7.0/c3.min.css",
|
53
|
+
"c3_js" => "https://cdnjs.cloudflare.com/ajax/libs/c3/0.7.0/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
|
data/lib/SVG/Graph/Graph.rb
CHANGED
@@ -144,22 +144,29 @@ module SVG
|
|
144
144
|
:show_graph_subtitle => false,
|
145
145
|
:graph_subtitle => 'Graph Sub Title',
|
146
146
|
:key => true,
|
147
|
+
:key_width => nil,
|
147
148
|
:key_position => :right, # bottom or right
|
148
149
|
|
149
|
-
:font_size =>12,
|
150
|
-
:title_font_size =>16,
|
151
|
-
:subtitle_font_size =>14,
|
152
|
-
:x_label_font_size =>12,
|
153
|
-
:y_label_font_size =>12,
|
154
|
-
:x_title_font_size =>14,
|
155
|
-
:y_title_font_size =>14,
|
156
|
-
:key_font_size =>10,
|
157
|
-
|
158
|
-
:
|
159
|
-
|
160
|
-
:
|
150
|
+
:font_size => 12,
|
151
|
+
:title_font_size => 16,
|
152
|
+
:subtitle_font_size => 14,
|
153
|
+
:x_label_font_size => 12,
|
154
|
+
:y_label_font_size => 12,
|
155
|
+
:x_title_font_size => 14,
|
156
|
+
:y_title_font_size => 14,
|
157
|
+
:key_font_size => 10,
|
158
|
+
:key_box_size => 12,
|
159
|
+
:key_spacing => 5,
|
160
|
+
|
161
|
+
:no_css => false,
|
162
|
+
:add_popups => false,
|
163
|
+
:popup_radius => 10,
|
164
|
+
:number_format => '%.2f',
|
165
|
+
:style_sheet => '',
|
166
|
+
:inline_style_sheet => ''
|
161
167
|
})
|
162
168
|
set_defaults if self.respond_to? :set_defaults
|
169
|
+
# override default values with user supplied values
|
163
170
|
init_with config
|
164
171
|
end
|
165
172
|
|
@@ -256,13 +263,23 @@ module SVG
|
|
256
263
|
# of the SVG box created - not the graph it self which auto
|
257
264
|
# scales to fix the space.
|
258
265
|
attr_accessor :width
|
259
|
-
# Set the path to an external stylesheet, set to '' if
|
266
|
+
# Set the path/url to an external stylesheet, set to '' if
|
260
267
|
# you want to revert back to using the defaut internal version.
|
261
268
|
#
|
262
269
|
# To create an external stylesheet create a graph using the
|
263
270
|
# default internal version and copy the stylesheet section to
|
264
271
|
# an external file and edit from there.
|
265
272
|
attr_accessor :style_sheet
|
273
|
+
# Define as String the stylesheet contents to be inlined, set to '' to disable.
|
274
|
+
# This can be used, when referring to a url via :style_sheet is not suitable.
|
275
|
+
# E.g. in situations where there will be no internet access or the graph must
|
276
|
+
# consist of only one file.
|
277
|
+
#
|
278
|
+
# If not empty, the :style_sheet parameter (url) above will be ignored and is
|
279
|
+
# not written to the file
|
280
|
+
# see also https://github.com/erullmann/svg-graph2/commit/55eb6e983f6fcc69cc5a110d0ee6e05f906f639a
|
281
|
+
# Default: ''
|
282
|
+
attr_accessor :inline_style_sheet
|
266
283
|
# (Bool) Show the value of each element of data on the graph
|
267
284
|
attr_accessor :show_data_values
|
268
285
|
# By default (nil/undefined) the x-axis is at the bottom of the graph.
|
@@ -292,10 +309,12 @@ module SVG
|
|
292
309
|
# are long field names they will not overlap so easily.
|
293
310
|
# Default is false, to turn on set to true.
|
294
311
|
attr_accessor :stagger_y_labels
|
295
|
-
# This turns the X axis labels by 90 degrees
|
312
|
+
# This turns the X axis labels by 90 degrees when true or by a custom
|
313
|
+
# amount when a numeric value is given.
|
296
314
|
# Default is false, to turn on set to true.
|
297
315
|
attr_accessor :rotate_x_labels
|
298
|
-
# This turns the Y axis labels by 90 degrees
|
316
|
+
# This turns the Y axis labels by 90 degrees when true or by a custom
|
317
|
+
# amount when a numeric value is given.
|
299
318
|
# Default is true, to turn on set to false.
|
300
319
|
attr_accessor :rotate_y_labels
|
301
320
|
# How many "steps" to use between displayed X axis labels,
|
@@ -358,6 +377,13 @@ module SVG
|
|
358
377
|
# Where the key should be positioned, defaults to
|
359
378
|
# :right, set to :bottom if you want to move it.
|
360
379
|
attr_accessor :key_position
|
380
|
+
|
381
|
+
attr_accessor :key_box_size
|
382
|
+
|
383
|
+
attr_accessor :key_spacing
|
384
|
+
|
385
|
+
attr_accessor :key_width
|
386
|
+
|
361
387
|
# Set the font size (in points) of the data point labels.
|
362
388
|
# Defaults to 12.
|
363
389
|
attr_accessor :font_size
|
@@ -414,12 +440,8 @@ module SVG
|
|
414
440
|
config.each { |key, value|
|
415
441
|
self.send( key.to_s+"=", value ) if self.respond_to? key
|
416
442
|
}
|
417
|
-
@popup_radius ||= 10
|
418
443
|
end
|
419
444
|
|
420
|
-
# size of the square box in the legend which indicates the colors
|
421
|
-
KEY_BOX_SIZE = 12
|
422
|
-
|
423
445
|
# Override this (and call super) to change the margin to the left
|
424
446
|
# of the plot area. Results in @border_left being set.
|
425
447
|
#
|
@@ -458,8 +480,14 @@ module SVG
|
|
458
480
|
if key and key_position == :right
|
459
481
|
val = keys.max { |a,b| a.length <=> b.length }
|
460
482
|
@border_right += val.length * key_font_size * 0.6
|
461
|
-
@border_right +=
|
483
|
+
@border_right += key_box_size
|
462
484
|
@border_right += 10 # Some padding around the box
|
485
|
+
|
486
|
+
if key_width.nil?
|
487
|
+
@border_right
|
488
|
+
else
|
489
|
+
@border_right = [key_width, @border_right].min
|
490
|
+
end
|
463
491
|
end
|
464
492
|
if (x_title_location == :end)
|
465
493
|
@border_right = [@border_right, x_title.length * x_title_font_size * 0.6].max
|
@@ -734,8 +762,12 @@ module SVG
|
|
734
762
|
text.attributes["x"] = x.to_s
|
735
763
|
text.attributes["y"] = y.to_s
|
736
764
|
if rotate_x_labels
|
765
|
+
degrees = 90
|
766
|
+
if numeric? rotate_x_labels
|
767
|
+
degrees = rotate_x_labels
|
768
|
+
end
|
737
769
|
text.attributes["transform"] =
|
738
|
-
"rotate(
|
770
|
+
"rotate( #{degrees} #{x} #{y-x_label_font_size} )"+
|
739
771
|
" translate( 0 -#{x_label_font_size/4} )"
|
740
772
|
text.attributes["style"] = "text-anchor: start"
|
741
773
|
else
|
@@ -818,8 +850,12 @@ module SVG
|
|
818
850
|
end
|
819
851
|
text.text = textStr
|
820
852
|
if rotate_y_labels
|
853
|
+
degrees = 90
|
854
|
+
if numeric? rotate_y_labels
|
855
|
+
degrees = rotate_y_labels
|
856
|
+
end
|
821
857
|
text.attributes["transform"] = "translate( -#{font_size} 0 ) "+
|
822
|
-
"rotate(
|
858
|
+
"rotate( #{degrees} #{x} #{y} ) "
|
823
859
|
text.attributes["style"] = "text-anchor: middle"
|
824
860
|
else
|
825
861
|
text.attributes["y"] = (y - (y_label_font_size/2)).to_s
|
@@ -929,17 +965,17 @@ module SVG
|
|
929
965
|
|
930
966
|
key_count = 0
|
931
967
|
for key_name in keys
|
932
|
-
y_offset = (
|
968
|
+
y_offset = (key_box_size * key_count) + (key_count * key_spacing)
|
933
969
|
group.add_element( "rect", {
|
934
970
|
"x" => 0.to_s,
|
935
971
|
"y" => y_offset.to_s,
|
936
|
-
"width" =>
|
937
|
-
"height" =>
|
972
|
+
"width" => key_box_size.to_s,
|
973
|
+
"height" => key_box_size.to_s,
|
938
974
|
"class" => "key#{key_count+1}"
|
939
975
|
})
|
940
976
|
group.add_element( "text", {
|
941
|
-
"x" => (
|
942
|
-
"y" => (y_offset +
|
977
|
+
"x" => (key_box_size + key_spacing).to_s,
|
978
|
+
"y" => (y_offset + key_box_size).to_s,
|
943
979
|
"class" => "keyText"
|
944
980
|
}).text = key_name.to_s
|
945
981
|
key_count += 1
|
@@ -947,15 +983,15 @@ module SVG
|
|
947
983
|
|
948
984
|
case key_position
|
949
985
|
when :right
|
950
|
-
x_offset = @graph_width + @border_left +
|
951
|
-
y_offset = @border_top +
|
986
|
+
x_offset = @graph_width + @border_left + (key_spacing * 2)
|
987
|
+
y_offset = @border_top + (key_spacing * 2)
|
952
988
|
when :bottom
|
953
|
-
x_offset = @border_left +
|
954
|
-
y_offset = @border_top + @graph_height +
|
989
|
+
x_offset = @border_left + (key_spacing * 2)
|
990
|
+
y_offset = @border_top + @graph_height + key_spacing
|
955
991
|
if show_x_labels
|
956
992
|
y_offset += max_x_label_height_px
|
957
993
|
end
|
958
|
-
y_offset += x_title_font_size +
|
994
|
+
y_offset += x_title_font_size + key_spacing if show_x_title
|
959
995
|
end
|
960
996
|
group.attributes["transform"] = "translate(#{x_offset} #{y_offset})"
|
961
997
|
end
|
@@ -1045,7 +1081,8 @@ module SVG
|
|
1045
1081
|
@doc << XMLDecl.new
|
1046
1082
|
@doc << DocType.new( %q{svg PUBLIC "-//W3C//DTD SVG 1.0//EN" } +
|
1047
1083
|
%q{"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"} )
|
1048
|
-
if style_sheet && style_sheet != ''
|
1084
|
+
if style_sheet && style_sheet != '' && inline_style_sheet.to_s.empty?
|
1085
|
+
# if inline_style_sheet is defined, url style sheet is ignored
|
1049
1086
|
@doc << Instruction.new( "xml-stylesheet",
|
1050
1087
|
%Q{href="#{style_sheet}" type="text/css"} )
|
1051
1088
|
end
|
@@ -1067,10 +1104,15 @@ module SVG
|
|
1067
1104
|
|
1068
1105
|
defs = @root.add_element( "defs" )
|
1069
1106
|
add_defs defs
|
1070
|
-
if
|
1071
|
-
|
1072
|
-
|
1073
|
-
|
1107
|
+
if !no_css
|
1108
|
+
if inline_style_sheet && inline_style_sheet != ''
|
1109
|
+
style = defs.add_element( "style", {"type"=>"text/css"} )
|
1110
|
+
style << CData.new( inline_style_sheet )
|
1111
|
+
else
|
1112
|
+
@root << Comment.new(" include default stylesheet if none specified ")
|
1113
|
+
style = defs.add_element( "style", {"type"=>"text/css"} )
|
1114
|
+
style << CData.new( get_style )
|
1115
|
+
end
|
1074
1116
|
end
|
1075
1117
|
|
1076
1118
|
@root << Comment.new( "SVG Background" )
|
data/lib/SVG/Graph/Plot.rb
CHANGED
@@ -4,48 +4,48 @@ require_relative 'DataPoint'
|
|
4
4
|
module SVG
|
5
5
|
module Graph
|
6
6
|
# === For creating SVG plots of scalar data
|
7
|
-
#
|
7
|
+
#
|
8
8
|
# = Synopsis
|
9
|
-
#
|
9
|
+
#
|
10
10
|
# require 'SVG/Graph/Plot'
|
11
|
-
#
|
11
|
+
#
|
12
12
|
# # Data sets are x,y pairs
|
13
13
|
# # Note that multiple data sets can differ in length, and that the
|
14
14
|
# # data in the datasets needn't be in order; they will be ordered
|
15
15
|
# # by the plot along the X-axis.
|
16
16
|
# projection = [
|
17
17
|
# 6, 11, 0, 5, 18, 7, 1, 11, 13, 9, 1, 2, 19, 0, 3, 13,
|
18
|
-
# 7, 9
|
18
|
+
# 7, 9
|
19
19
|
# ]
|
20
20
|
# actual = [
|
21
|
-
# 0, 18, 8, 15, 9, 4, 18, 14, 10, 2, 11, 6, 14, 12,
|
21
|
+
# 0, 18, 8, 15, 9, 4, 18, 14, 10, 2, 11, 6, 14, 12,
|
22
22
|
# 15, 6, 4, 17, 2, 12
|
23
23
|
# ]
|
24
|
-
#
|
24
|
+
#
|
25
25
|
# graph = SVG::Graph::Plot.new({
|
26
26
|
# :height => 500,
|
27
27
|
# :width => 300,
|
28
28
|
# :key => true,
|
29
29
|
# :scale_x_integers => true,
|
30
|
-
# :
|
30
|
+
# :scale_y_integers => true,
|
31
31
|
# })
|
32
|
-
#
|
32
|
+
#
|
33
33
|
# graph.add_data({
|
34
34
|
# :data => projection
|
35
35
|
# :title => 'Projected',
|
36
36
|
# })
|
37
|
-
#
|
37
|
+
#
|
38
38
|
# graph.add_data({
|
39
39
|
# :data => actual,
|
40
40
|
# :title => 'Actual',
|
41
41
|
# })
|
42
|
-
#
|
42
|
+
#
|
43
43
|
# print graph.burn()
|
44
|
-
#
|
44
|
+
#
|
45
45
|
# = Description
|
46
|
-
#
|
46
|
+
#
|
47
47
|
# Produces a graph of scalar data.
|
48
|
-
#
|
48
|
+
#
|
49
49
|
# This object aims to allow you to easily create high quality
|
50
50
|
# SVG[http://www.w3c.org/tr/svg] scalar plots. You can either use the
|
51
51
|
# default style sheet or supply your own. Either way there are many options
|
@@ -54,11 +54,11 @@ module SVG
|
|
54
54
|
# subtitle etc.
|
55
55
|
#
|
56
56
|
# = Examples
|
57
|
-
#
|
57
|
+
#
|
58
58
|
# http://www.germane-software/repositories/public/SVG/test/plot.rb
|
59
|
-
#
|
59
|
+
#
|
60
60
|
# = Notes
|
61
|
-
#
|
61
|
+
#
|
62
62
|
# The default stylesheet handles upto 10 data sets, if you
|
63
63
|
# use more you must create your own stylesheet and add the
|
64
64
|
# additional settings for the extra data sets. You will know
|
@@ -72,9 +72,9 @@ module SVG
|
|
72
72
|
# Additional possible notation
|
73
73
|
# [ [1,2], 5,6] # A data set with 2 points: (1,2) and (5,6), mixed notation
|
74
74
|
# [ [1,2], [5,6]] # A data set with 2 points: (1,2) and (5,6), nested array
|
75
|
-
#
|
75
|
+
#
|
76
76
|
# = See also
|
77
|
-
#
|
77
|
+
#
|
78
78
|
# * SVG::Graph::Graph
|
79
79
|
# * SVG::Graph::BarHorizontal
|
80
80
|
# * SVG::Graph::Bar
|
@@ -105,7 +105,7 @@ module SVG
|
|
105
105
|
:show_lines => true,
|
106
106
|
:round_popups => true,
|
107
107
|
:scale_x_integers => false,
|
108
|
-
:
|
108
|
+
:scale_y_integers => false,
|
109
109
|
)
|
110
110
|
end
|
111
111
|
|
@@ -124,20 +124,20 @@ module SVG
|
|
124
124
|
# would cause the graph to attempt to generate labels stepped by 0.5; EG:
|
125
125
|
# 0, 0.5, 1, 1.5, 2, ...
|
126
126
|
# default is automatic such that there are 10 labels
|
127
|
-
attr_accessor :scale_y_divisions
|
127
|
+
attr_accessor :scale_y_divisions
|
128
128
|
# Make the X axis labels integers, default: false
|
129
|
-
attr_accessor :scale_x_integers
|
129
|
+
attr_accessor :scale_x_integers
|
130
130
|
# Make the Y axis labels integers, default: false
|
131
|
-
attr_accessor :scale_y_integers
|
131
|
+
attr_accessor :scale_y_integers
|
132
132
|
# Fill the area under the line, default: false
|
133
|
-
attr_accessor :area_fill
|
133
|
+
attr_accessor :area_fill
|
134
134
|
# Show a small circle on the graph where the line
|
135
135
|
# goes from one point to the next. default: true
|
136
136
|
attr_accessor :show_data_points
|
137
137
|
# Set the minimum value of the X axis, if nil the minimum from data is chosen, default: nil
|
138
|
-
attr_accessor :min_x_value
|
138
|
+
attr_accessor :min_x_value
|
139
139
|
# Set the maximum value of the X axis, if nil the maximum from data is chosen, default: nil
|
140
|
-
attr_accessor :max_x_value
|
140
|
+
attr_accessor :max_x_value
|
141
141
|
# Set the minimum value of the Y axis, if nil the minimum from data is chosen, default: nil
|
142
142
|
attr_accessor :min_y_value
|
143
143
|
# Set the maximum value of the Y axis, if nil the maximum from data is chosen, default: nil
|
@@ -155,15 +155,15 @@ module SVG
|
|
155
155
|
# data_set2 = [[1,2], 5,6]
|
156
156
|
# or
|
157
157
|
# data_set2 = [[1,2], [5,6]]
|
158
|
-
#
|
158
|
+
#
|
159
159
|
# graph.add_data({
|
160
160
|
# :data => data_set1,
|
161
161
|
# :title => 'single point'
|
162
|
-
# })
|
162
|
+
# })
|
163
163
|
# graph.add_data({
|
164
164
|
# :data => data_set2,
|
165
165
|
# :title => 'two points'
|
166
|
-
# })
|
166
|
+
# })
|
167
167
|
def add_data(conf)
|
168
168
|
@data ||= []
|
169
169
|
raise "No data provided by #{conf.inspect}" unless conf[:data] and
|
@@ -174,10 +174,10 @@ module SVG
|
|
174
174
|
raise "Data supplied must be x,y pairs! "+
|
175
175
|
"The data provided contained an odd set of "+
|
176
176
|
"data points" unless conf[:data].length % 2 == 0
|
177
|
-
|
177
|
+
|
178
178
|
# remove nil values
|
179
179
|
conf[:data] = conf[:data].compact
|
180
|
-
|
180
|
+
|
181
181
|
return if conf[:data].length == 0
|
182
182
|
|
183
183
|
conf[:description] ||= Array.new(conf[:data].size/2)
|
@@ -248,7 +248,7 @@ module SVG
|
|
248
248
|
|
249
249
|
scale_division = scale_x_divisions || (scale_range / 10.0)
|
250
250
|
@x_offset = 0
|
251
|
-
|
251
|
+
|
252
252
|
if scale_x_integers
|
253
253
|
scale_division = scale_division < 1 ? 1 : scale_division.round
|
254
254
|
@x_offset = min_value.to_f - min_value.floor
|
@@ -261,7 +261,7 @@ module SVG
|
|
261
261
|
def get_x_values
|
262
262
|
min_value, max_value, @x_scale_division = x_label_range
|
263
263
|
rv = []
|
264
|
-
min_value.step( max_value, @x_scale_division ) {|v| rv << v}
|
264
|
+
min_value.step( max_value + @x_scale_division , @x_scale_division ) {|v| rv << v}
|
265
265
|
return rv
|
266
266
|
end
|
267
267
|
alias :get_x_labels :get_x_values
|
@@ -270,7 +270,7 @@ module SVG
|
|
270
270
|
# exclude values which are outside max_x_range
|
271
271
|
values = get_x_values
|
272
272
|
@graph_width.to_f / (values.length - 1 ) # -1 is to use entire x-axis
|
273
|
-
# otherwise there is always 1 division unused
|
273
|
+
# otherwise there is always 1 division unused
|
274
274
|
end
|
275
275
|
|
276
276
|
|
@@ -300,7 +300,7 @@ module SVG
|
|
300
300
|
|
301
301
|
scale_division = scale_y_divisions || (scale_range / 10.0)
|
302
302
|
@y_offset = 0
|
303
|
-
|
303
|
+
|
304
304
|
if scale_y_integers
|
305
305
|
scale_division = scale_division < 1 ? 1 : scale_division.round
|
306
306
|
@y_offset = (min_value.to_f - min_value.floor).to_f
|
@@ -318,7 +318,7 @@ module SVG
|
|
318
318
|
end
|
319
319
|
end
|
320
320
|
rv = []
|
321
|
-
min_value.step( max_value, @y_scale_division ) {|v| rv << v}
|
321
|
+
min_value.step( max_value + @y_scale_division, @y_scale_division ) {|v| rv << v}
|
322
322
|
rv << rv[0] + 1 if rv.length == 1
|
323
323
|
return rv
|
324
324
|
end
|
@@ -335,19 +335,19 @@ module SVG
|
|
335
335
|
end
|
336
336
|
@graph_height.to_f / values.length
|
337
337
|
end
|
338
|
-
|
338
|
+
|
339
339
|
def calc_coords(x, y)
|
340
340
|
coords = {:x => 0, :y => 0}
|
341
341
|
# scale the coordinates, use float division / multiplication
|
342
342
|
# otherwise the point will be place inaccurate
|
343
|
-
coords[:x] = (x + @x_offset)/@x_scale_division.to_f * field_width
|
343
|
+
coords[:x] = (x + @x_offset)/@x_scale_division.to_f * field_width
|
344
344
|
coords[:y] = @graph_height - (y + @y_offset)/@y_scale_division.to_f * field_height
|
345
345
|
return coords
|
346
346
|
end
|
347
347
|
|
348
348
|
def draw_data
|
349
349
|
line = 1
|
350
|
-
|
350
|
+
|
351
351
|
x_min = min_x_range
|
352
352
|
x_max = max_x_range
|
353
353
|
y_min = min_y_range
|
@@ -395,7 +395,7 @@ module SVG
|
|
395
395
|
line += 1
|
396
396
|
end
|
397
397
|
end
|
398
|
-
|
398
|
+
|
399
399
|
# returns the formatted string which is added as popup information
|
400
400
|
def format x, y, desc
|
401
401
|
info = []
|
@@ -404,69 +404,69 @@ module SVG
|
|
404
404
|
info << desc
|
405
405
|
"(#{info.compact.join(', ')})"
|
406
406
|
end
|
407
|
-
|
407
|
+
|
408
408
|
def get_css
|
409
409
|
return <<EOL
|
410
410
|
/* default line styles */
|
411
411
|
.line1{
|
412
412
|
fill: none;
|
413
413
|
stroke: #ff0000;
|
414
|
-
stroke-width: 1px;
|
414
|
+
stroke-width: 1px;
|
415
415
|
}
|
416
416
|
.line2{
|
417
417
|
fill: none;
|
418
418
|
stroke: #0000ff;
|
419
|
-
stroke-width: 1px;
|
419
|
+
stroke-width: 1px;
|
420
420
|
}
|
421
421
|
.line3{
|
422
422
|
fill: none;
|
423
423
|
stroke: #00ff00;
|
424
|
-
stroke-width: 1px;
|
424
|
+
stroke-width: 1px;
|
425
425
|
}
|
426
426
|
.line4{
|
427
427
|
fill: none;
|
428
428
|
stroke: #ffcc00;
|
429
|
-
stroke-width: 1px;
|
429
|
+
stroke-width: 1px;
|
430
430
|
}
|
431
431
|
.line5{
|
432
432
|
fill: none;
|
433
433
|
stroke: #00ccff;
|
434
|
-
stroke-width: 1px;
|
434
|
+
stroke-width: 1px;
|
435
435
|
}
|
436
436
|
.line6{
|
437
437
|
fill: none;
|
438
438
|
stroke: #ff00ff;
|
439
|
-
stroke-width: 1px;
|
439
|
+
stroke-width: 1px;
|
440
440
|
}
|
441
441
|
.line7{
|
442
442
|
fill: none;
|
443
443
|
stroke: #00ffff;
|
444
|
-
stroke-width: 1px;
|
444
|
+
stroke-width: 1px;
|
445
445
|
}
|
446
446
|
.line8{
|
447
447
|
fill: none;
|
448
448
|
stroke: #ffff00;
|
449
|
-
stroke-width: 1px;
|
449
|
+
stroke-width: 1px;
|
450
450
|
}
|
451
451
|
.line9{
|
452
452
|
fill: none;
|
453
|
-
stroke: #
|
454
|
-
stroke-width: 1px;
|
453
|
+
stroke: #cc6666;
|
454
|
+
stroke-width: 1px;
|
455
455
|
}
|
456
456
|
.line10{
|
457
457
|
fill: none;
|
458
458
|
stroke: #663399;
|
459
|
-
stroke-width: 1px;
|
459
|
+
stroke-width: 1px;
|
460
460
|
}
|
461
461
|
.line11{
|
462
462
|
fill: none;
|
463
463
|
stroke: #339900;
|
464
|
-
stroke-width: 1px;
|
464
|
+
stroke-width: 1px;
|
465
465
|
}
|
466
466
|
.line12{
|
467
467
|
fill: none;
|
468
468
|
stroke: #9966FF;
|
469
|
-
stroke-width: 1px;
|
469
|
+
stroke-width: 1px;
|
470
470
|
}
|
471
471
|
/* default fill styles */
|
472
472
|
.fill1{
|
@@ -533,62 +533,62 @@ module SVG
|
|
533
533
|
.key1,.dataPoint1{
|
534
534
|
fill: #ff0000;
|
535
535
|
stroke: none;
|
536
|
-
stroke-width: 1px;
|
536
|
+
stroke-width: 1px;
|
537
537
|
}
|
538
538
|
.key2,.dataPoint2{
|
539
539
|
fill: #0000ff;
|
540
540
|
stroke: none;
|
541
|
-
stroke-width: 1px;
|
541
|
+
stroke-width: 1px;
|
542
542
|
}
|
543
543
|
.key3,.dataPoint3{
|
544
544
|
fill: #00ff00;
|
545
545
|
stroke: none;
|
546
|
-
stroke-width: 1px;
|
546
|
+
stroke-width: 1px;
|
547
547
|
}
|
548
548
|
.key4,.dataPoint4{
|
549
549
|
fill: #ffcc00;
|
550
550
|
stroke: none;
|
551
|
-
stroke-width: 1px;
|
551
|
+
stroke-width: 1px;
|
552
552
|
}
|
553
553
|
.key5,.dataPoint5{
|
554
554
|
fill: #00ccff;
|
555
555
|
stroke: none;
|
556
|
-
stroke-width: 1px;
|
556
|
+
stroke-width: 1px;
|
557
557
|
}
|
558
558
|
.key6,.dataPoint6{
|
559
559
|
fill: #ff00ff;
|
560
560
|
stroke: none;
|
561
|
-
stroke-width: 1px;
|
561
|
+
stroke-width: 1px;
|
562
562
|
}
|
563
563
|
.key7,.dataPoint7{
|
564
564
|
fill: #00ffff;
|
565
565
|
stroke: none;
|
566
|
-
stroke-width: 1px;
|
566
|
+
stroke-width: 1px;
|
567
567
|
}
|
568
568
|
.key8,.dataPoint8{
|
569
569
|
fill: #ffff00;
|
570
570
|
stroke: none;
|
571
|
-
stroke-width: 1px;
|
571
|
+
stroke-width: 1px;
|
572
572
|
}
|
573
573
|
.key9,.dataPoint9{
|
574
574
|
fill: #cc6666;
|
575
575
|
stroke: none;
|
576
|
-
stroke-width: 1px;
|
576
|
+
stroke-width: 1px;
|
577
577
|
}
|
578
578
|
.key10,.dataPoint10{
|
579
579
|
fill: #663399;
|
580
580
|
stroke: none;
|
581
|
-
stroke-width: 1px;
|
581
|
+
stroke-width: 1px;
|
582
582
|
}
|
583
583
|
.key11,.dataPoint11{
|
584
584
|
fill: #339900;
|
585
585
|
stroke: none;
|
586
|
-
stroke-width: 1px;
|
586
|
+
stroke-width: 1px;
|
587
587
|
}
|
588
588
|
.key12,.dataPoint12{
|
589
589
|
fill: #9966FF;
|
590
590
|
stroke: none;
|
591
|
-
stroke-width: 1px;
|
591
|
+
stroke-width: 1px;
|
592
592
|
}
|
593
593
|
EOL
|
594
594
|
end
|
data/lib/svggraph.rb
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
module SVG
|
2
2
|
module Graph
|
3
|
-
VERSION = '2.
|
3
|
+
VERSION = '2.2.0.beta'
|
4
|
+
|
4
5
|
end
|
5
6
|
end
|
7
|
+
require_relative 'SVG/Graph/C3js'
|
8
|
+
require_relative 'SVG/Graph/Graph'
|
6
9
|
require_relative 'SVG/Graph/DataPoint'
|
7
10
|
require_relative 'SVG/Graph/BarBase'
|
8
|
-
require_relative 'SVG/Graph/Graph'
|
9
11
|
require_relative 'SVG/Graph/Bar'
|
10
12
|
require_relative 'SVG/Graph/BarHorizontal'
|
11
13
|
require_relative 'SVG/Graph/ErrBar'
|
data/test/test_svg_graph.rb
CHANGED
@@ -12,7 +12,7 @@ class TestSvgGraph < Minitest::Test
|
|
12
12
|
def test_bar_line_and_pie
|
13
13
|
fields = %w(Jan Feb Mar);
|
14
14
|
data_sales_02 = [12, 45, 21]
|
15
|
-
[SVG::Graph::Bar, SVG::Graph::BarHorizontal, SVG::Graph::Line, SVG::Graph::Pie].each do
|
15
|
+
[SVG::Graph::Bar, SVG::Graph::BarHorizontal, SVG::Graph::Line, SVG::Graph::Pie].each do
|
16
16
|
|klass|
|
17
17
|
graph = klass.new(
|
18
18
|
:height => 500,
|
@@ -27,14 +27,14 @@ class TestSvgGraph < Minitest::Test
|
|
27
27
|
assert(out=~/Created with SVG::Graph/)
|
28
28
|
end
|
29
29
|
end # test_bar_line_and_pie
|
30
|
-
|
30
|
+
|
31
31
|
def test_pie_100_percent
|
32
32
|
fields = %w(Internet TV Newspaper Magazine Radio)
|
33
33
|
#data1 = [2, 3, 1, 3, 1]
|
34
34
|
#data2 = [0, 2, 1, 5, 4]
|
35
35
|
data1 = [0, 3, 0, 0, 0]
|
36
36
|
data2 = [0, 6, 0, 0, 0]
|
37
|
-
|
37
|
+
|
38
38
|
graph = SVG::Graph::Pie.new(
|
39
39
|
:height => 500,
|
40
40
|
:width => 300,
|
@@ -50,19 +50,19 @@ class TestSvgGraph < Minitest::Test
|
|
50
50
|
:data => data1,
|
51
51
|
:title => 'data1'
|
52
52
|
)
|
53
|
-
|
53
|
+
|
54
54
|
graph.add_data(
|
55
55
|
:data => data2,
|
56
56
|
:title => 'data2'
|
57
57
|
)
|
58
58
|
out = graph.burn
|
59
|
-
File.open("pie_100.svg", "w") {|fout|
|
59
|
+
File.open(File.expand_path("pie_100.svg",__dir__), "w") {|fout|
|
60
60
|
fout.print( out )
|
61
61
|
}
|
62
|
-
assert_match(/TV 100%/, out, "100% text not found in graph")
|
62
|
+
assert_match(/TV 100%/, out, "100% text not found in graph")
|
63
63
|
assert_match(/circle/, out, "no circle was found in graph")
|
64
64
|
|
65
65
|
end # test_pie_100_percent
|
66
|
-
|
66
|
+
|
67
67
|
end
|
68
68
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: svg-graph
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.2.0.beta
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sean Russell
|
@@ -12,14 +12,14 @@ authors:
|
|
12
12
|
autorequire:
|
13
13
|
bindir: bin
|
14
14
|
cert_chain: []
|
15
|
-
date:
|
15
|
+
date: 2019-05-07 00:00:00.000000000 Z
|
16
16
|
dependencies: []
|
17
17
|
description: "Gem version of SVG:::Graph. SVG:::Graph is a pure Ruby library for generating
|
18
|
-
charts
|
19
|
-
SVG::Graph has a verry similar API to
|
20
|
-
resulting charts also look the same. This isn't surprising
|
18
|
+
charts,\nwhich are a type of graph where the values of one axis are not scalar.
|
19
|
+
SVG::Graph has a verry similar API to\nthe Perl library SVG::TT::Graph, and the
|
20
|
+
resulting charts also look the same. This isn't surprising,\nbecause SVG::Graph
|
21
21
|
started as a loose port of SVG::TT::Graph, although the internal code no longer
|
22
|
-
resembles
|
22
|
+
resembles\nthe Perl original at all.\n "
|
23
23
|
email:
|
24
24
|
- ser_AT_germane-software.com
|
25
25
|
- clbustos_AT_gmail.com
|
@@ -30,18 +30,19 @@ executables: []
|
|
30
30
|
extensions: []
|
31
31
|
extra_rdoc_files:
|
32
32
|
- LICENSE.txt
|
33
|
-
- README.
|
33
|
+
- README.md
|
34
34
|
- README.txt
|
35
35
|
files:
|
36
36
|
- GPL.txt
|
37
37
|
- History.txt
|
38
38
|
- LICENSE.txt
|
39
|
-
- README.
|
39
|
+
- README.md
|
40
40
|
- README.txt
|
41
41
|
- Rakefile
|
42
42
|
- lib/SVG/Graph/Bar.rb
|
43
43
|
- lib/SVG/Graph/BarBase.rb
|
44
44
|
- lib/SVG/Graph/BarHorizontal.rb
|
45
|
+
- lib/SVG/Graph/C3js.rb
|
45
46
|
- lib/SVG/Graph/DataPoint.rb
|
46
47
|
- lib/SVG/Graph/ErrBar.rb
|
47
48
|
- lib/SVG/Graph/Graph.rb
|
@@ -52,7 +53,7 @@ files:
|
|
52
53
|
- lib/SVG/Graph/TimeSeries.rb
|
53
54
|
- lib/svggraph.rb
|
54
55
|
- test/test_svg_graph.rb
|
55
|
-
homepage:
|
56
|
+
homepage: https://github.com/lumean/svg-graph2
|
56
57
|
licenses:
|
57
58
|
- GPL-2.0
|
58
59
|
metadata: {}
|
@@ -67,12 +68,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
67
68
|
version: '0'
|
68
69
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
70
|
requirements:
|
70
|
-
- - "
|
71
|
+
- - ">"
|
71
72
|
- !ruby/object:Gem::Version
|
72
|
-
version:
|
73
|
+
version: 1.3.1
|
73
74
|
requirements: []
|
74
|
-
|
75
|
-
rubygems_version: 2.6.14
|
75
|
+
rubygems_version: 3.0.1
|
76
76
|
signing_key:
|
77
77
|
specification_version: 4
|
78
78
|
summary: SVG:::Graph is a pure Ruby library for generating charts, which are a type
|