svg-graph 2.1.3 → 2.2.0.beta
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
![example timeseries graph](https://cdn.rawgit.com/lumean/svg-graph2/master/examples/timeseries.svg)
|
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
|