svg-graph 2.1.1 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/History.txt +35 -1
- data/{README.markdown → README.md} +27 -6
- data/Rakefile +16 -5
- 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/DataPoint.rb +28 -16
- data/lib/SVG/Graph/Graph.rb +211 -104
- data/lib/SVG/Graph/Line.rb +12 -7
- data/lib/SVG/Graph/Pie.rb +52 -41
- data/lib/SVG/Graph/Plot.rb +105 -94
- data/lib/SVG/Graph/TimeSeries.rb +27 -27
- data/lib/svggraph.rb +4 -2
- data/test/test_svg_graph.rb +7 -7
- metadata +14 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: c6eaa56f94856a998ecd2f0befef3a38c5529da886266cd15150cf4e9758168f
|
4
|
+
data.tar.gz: f9eb26d8c758153505d95d450e57c3fa0dfbfcdd9018d89bb6bcee3b656cebff
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5c77c61810a9d855bc469ac4672d9917f0bbb345b5464235c0a06e9d901aa796c8cba67c12e181200ca3865ea2ce9629d8ae39065aec831b77c707fdc46720d4
|
7
|
+
data.tar.gz: d270039fc1ea0340a515f554c4ff1f6437d0138c62ff09daafb5c1599df819d096ab04ffa23fe8862cac16b4bd6fcc71efea973e001fa97cfd1e5058e7444a82
|
data/History.txt
CHANGED
@@ -1,6 +1,40 @@
|
|
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
|
3
|
+
* Fix bug in Plot where min/max_x/y_value are not respected, TODO
|
4
|
+
|
5
|
+
=== 2.2.1 / 2020-12-25
|
6
|
+
* Remove inline styling for data point labels and popups [thanks marnen, PR #23]
|
7
|
+
* fix #29 text background not aligned close to axis due to missing anchors
|
8
|
+
* add & document :shape and :url hash keys for `add_data` [thanks t12nslookup, PR #32]
|
9
|
+
* add custom datapoint support to Line graphs
|
10
|
+
* Add white text behind popup text so that the black text is more readable [thanks t12nslookup, PR #30]
|
11
|
+
* fix #19 rotated lables should no longer be cropped or overlapping
|
12
|
+
|
13
|
+
=== 2.2.0 / 2019-11-26
|
14
|
+
* on top of 2.2.0.beta adds the following
|
15
|
+
* Fixed Divizion by zero when data is 0,0 in Pie [thanks chrismedrdz, PR #16]
|
16
|
+
* Automatically render graphs in iruby [thanks dansbits, PR #14]
|
17
|
+
|
18
|
+
=== 2.2.0.beta / 2019-05-07
|
19
|
+
* Fix typo in Plot's `scale_y_integers` [thanks ashleydavies, PR #9]
|
20
|
+
* Fix bug in Plot which enabled too-short axis [thanks ashleydavies, PR #10]
|
21
|
+
* Fix issue where horizontal bar graphs were not lined up properly [thanks erullmann]
|
22
|
+
* removed some outdated documentation and uneeded files
|
23
|
+
* add support for c3js / d3js based graphs `SVG::Graph::C3js`, see http://c3js.org/
|
24
|
+
* add *very* basic unit tests for all types of graphs
|
25
|
+
* add :show_percent for Bar graphs [thanks Cameron2920]
|
26
|
+
* add :inline_style_sheet parameter for all graphs to allow inline css instead of specifying external stylesheets via url [thanks erullmann]
|
27
|
+
* add parameters to control the key :key_box_size, :key_spacing, :key_width [thanks erullmann]
|
28
|
+
* add support for X and Y label rotation to be different than 90 degree [thanks frenkel, PR #12]
|
29
|
+
|
30
|
+
=== 2.1.3 / 2017-12-27
|
31
|
+
* fixes float comparison and color for pie chart [thanks tiwi, pull request #7]
|
32
|
+
|
33
|
+
=== 2.1.2 / 2017-02-26
|
34
|
+
* show_key_actual_values in pie chart lets user chose to show values in key [thanks olegsfinest, pull request #5]
|
35
|
+
|
36
|
+
=== 2.1.1 / 2017-02-26
|
37
|
+
* Bar graphs are rounding bars to nearest Y axis label number [thanks adamalbrecht, pull request #4]
|
4
38
|
|
5
39
|
=== 2.1.0 / 2017-02-26 [lumean]
|
6
40
|
* [#2] change behavior for number format, Strings axis labels will never be formatted
|
@@ -1,9 +1,14 @@
|
|
1
1
|
SVG::Graph
|
2
2
|
============
|
3
3
|
|
4
|
+
[](https://travis-ci.com/lumean/svg-graph2)
|
5
|
+
[](https://codeclimate.com/github/lumean/svg-graph2/maintainability)
|
6
|
+
[](https://codeclimate.com/github/lumean/svg-graph2/test_coverage)
|
7
|
+
[](https://percy.io/a5e00e98/svg-graph2)
|
8
|
+
|
4
9
|
Description
|
5
10
|
-----------
|
6
|
-
This is
|
11
|
+
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
12
|
|
8
13
|
[Changelog](../master/History.txt)
|
9
14
|
|
@@ -37,7 +42,7 @@ require 'SVG/Graph/Bar'
|
|
37
42
|
|
38
43
|
x_axis = ['1-10', '10-30', '30-50', '50-70', 'older']
|
39
44
|
|
40
|
-
options = {
|
45
|
+
options = {
|
41
46
|
:width => 640,
|
42
47
|
:height => 300,
|
43
48
|
:stack => :side, # the stack option is valid for Bar graphs only
|
@@ -66,7 +71,7 @@ g.add_data( {
|
|
66
71
|
:title => "Male"
|
67
72
|
})
|
68
73
|
|
69
|
-
# graph.burn # this returns a full valid xml document containing the graph
|
74
|
+
# graph.burn # this returns a full valid xml document containing the graph
|
70
75
|
# graph.burn_svg_only # this only returns the <svg>...</svg> node
|
71
76
|
File.open('bar.svg', 'w') {|f| f.write(g.burn_svg_only)}
|
72
77
|
```
|
@@ -100,12 +105,28 @@ File.open('bar.svg', 'w') {|f| f.write(g.burn_svg_only)}
|
|
100
105
|
|
101
106
|

|
102
107
|
|
103
|
-
|
108
|
+
### C3js
|
109
|
+
|
110
|
+
Source: [C3js.rb](../master/examples/c3js.rb)
|
111
|
+
|
112
|
+
[Link to Preview](https://cdn.rawgit.com/lumean/svg-graph2/master/examples/c3js.html)
|
113
|
+
|
104
114
|
|
105
115
|
Build
|
106
116
|
-----
|
117
|
+
* Test
|
118
|
+
|
119
|
+
`bundle exec rake`
|
107
120
|
|
108
121
|
* Build gem:
|
109
|
-
|
122
|
+
|
123
|
+
`gem build svg-graph.gemspec`
|
124
|
+
|
110
125
|
* Install:
|
111
|
-
|
126
|
+
|
127
|
+
`gem install svg-graph-\<version>.gem`
|
128
|
+
|
129
|
+
Percy.io integration
|
130
|
+
---
|
131
|
+
https://docs.percy.io/docs/travis-ci
|
132
|
+
https://docs.percy.io/docs/snapshot-cli-command
|
data/Rakefile
CHANGED
@@ -21,11 +21,22 @@
|
|
21
21
|
# self.remote_rdoc_dir = 'svg-graph'
|
22
22
|
#end
|
23
23
|
|
24
|
-
# run all unit tests with 'rake test'
|
25
|
-
task default:
|
24
|
+
# by default run all unit tests with 'rake test'
|
25
|
+
task default: [:test]
|
26
26
|
|
27
27
|
task :test do
|
28
|
-
|
29
|
-
|
30
|
-
|
28
|
+
[
|
29
|
+
"test/test_data_point.rb",
|
30
|
+
"test/test_plot.rb",
|
31
|
+
"test/test_svg_graph.rb",
|
32
|
+
"test/test_graph.rb",
|
33
|
+
"test/run_examples_and_percy.io.rb"
|
34
|
+
].each do |file|
|
35
|
+
# exec all above scripts (with simplecov if env is set)
|
36
|
+
args = file
|
37
|
+
if ENV['COVERAGE']
|
38
|
+
args = '-r ./test/simplecov ' + file
|
39
|
+
end
|
40
|
+
ruby args
|
41
|
+
end
|
31
42
|
end
|
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.12.0/d3.min.js",
|
52
|
+
"c3_css" => "https://cdnjs.cloudflare.com/ajax/libs/c3/0.7.11/c3.min.css",
|
53
|
+
"c3_js" => "https://cdnjs.cloudflare.com/ajax/libs/c3/0.7.11/c3.min.js"
|
54
|
+
}
|
55
|
+
@opts = default_opts.merge(opts)
|
56
|
+
if @opts["inline_dependencies"]
|
57
|
+
# we replace the values in the opts Hash by the referred file contents
|
58
|
+
["d3_js", "c3_css", "c3_js"].each do |key|
|
59
|
+
if !File.file?(@opts[key])
|
60
|
+
raise "opts[\"#{key}\"]: No such file - #{File.expand_path(@opts[key])}"
|
61
|
+
end
|
62
|
+
@opts[key] = File.read(@opts[key])
|
63
|
+
end # ["d3_js", "c3_css", "c3_js"].each
|
64
|
+
end # if @opts["inline_dependencies"]
|
65
|
+
start_document()
|
66
|
+
end # def initialize
|
67
|
+
|
68
|
+
# Adds a javascript/json C3js chart definition into the div tag
|
69
|
+
# @param javascript [String, Hash] see example
|
70
|
+
# @param js_chart_variable_name [String] only needed if the `javascript` parameter is a Hash.
|
71
|
+
# unique variable name representing the chart in javascript scope.
|
72
|
+
# Note this is a global javascript "var" so make sure to avoid name clashes
|
73
|
+
# with other javascript us might use on the same page.
|
74
|
+
#
|
75
|
+
# @raise
|
76
|
+
# @example
|
77
|
+
# # see http://c3js.org/examples.html
|
78
|
+
# # since ruby 2.3 you can use string symbol keys:
|
79
|
+
# chart_spec = {
|
80
|
+
# # bindto is mandatory
|
81
|
+
# "bindto": "#this_is_my_awesom_graph",
|
82
|
+
# "data": {
|
83
|
+
# "columns": [
|
84
|
+
# ['data1', 30, 200, 100, 400, 150, 250],
|
85
|
+
# ['data2', 50, 20, 10, 40, 15, 25]
|
86
|
+
# ]
|
87
|
+
# }
|
88
|
+
# # otherwise simply write plain javascript into a heredoc string:
|
89
|
+
# # make sure to include the var <chartname> = c3.generate() if using heredoc
|
90
|
+
# chart_spec_string =<<-HEREDOC
|
91
|
+
# var mychart1 = c3.generate({
|
92
|
+
# // bindto is mandatory
|
93
|
+
# "bindto": "#this_is_my_awesom_graph",
|
94
|
+
# "data": {
|
95
|
+
# "columns": [
|
96
|
+
# ['data1', 30, 200, 100, 400, 150, 250],
|
97
|
+
# ['data2', 50, 20, 10, 40, 15, 25]
|
98
|
+
# ]
|
99
|
+
# });
|
100
|
+
# HEREDOC
|
101
|
+
# graph.add_chart_spec(chart_spec, "my_chart1")
|
102
|
+
# # or
|
103
|
+
# graph.add_chart_spec(chart_spec_string)
|
104
|
+
def add_chart_spec(javascript, js_chart_variable_name = "")
|
105
|
+
if javascript.kind_of?(Hash)
|
106
|
+
if js_chart_variable_name.to_s.empty? || js_chart_variable_name.to_s.match(/\s/)
|
107
|
+
raise "js_chart_variable_name ('#{js_chart_variable_name.to_s}') cannot be empty or contain spaces, " +
|
108
|
+
"a valid javascript variable name is needed."
|
109
|
+
end
|
110
|
+
chart_spec = JSON(javascript)
|
111
|
+
inline_script = "var #{js_chart_variable_name} = c3.generate(#{chart_spec});"
|
112
|
+
elsif javascript.kind_of?(String)
|
113
|
+
inline_script = javascript
|
114
|
+
if !inline_script.match(/c3\.generate/)
|
115
|
+
raise "var <chartname> = c3.generate({...}) statement is missing in javascript string"
|
116
|
+
end
|
117
|
+
else
|
118
|
+
raise "Unsupported argument type: #{javascript.class}"
|
119
|
+
end
|
120
|
+
# (.+?)" means non-greedy match up to next double quote
|
121
|
+
if m = inline_script.match(/"bindto":\s*"#(.+?)"/)
|
122
|
+
@bindto = m[1]
|
123
|
+
else
|
124
|
+
raise "Missing chart specification is missing the mandatory \"bindto\" key/value pair."
|
125
|
+
end
|
126
|
+
add_div_element_for_graph()
|
127
|
+
add_javascript() {inline_script}
|
128
|
+
end # def add_chart_spec
|
129
|
+
|
130
|
+
# Appends a <script> element to the <div> element, this can be used to add additional animations
|
131
|
+
# but any script can also directly be part of the js_chart_specification in the #add_chart_spec
|
132
|
+
# method when you use a HEREDOC string as input.
|
133
|
+
# @param attrs [Hash] attributes for the <script> element. The following attribute
|
134
|
+
# is added by default: type="text/javascript"
|
135
|
+
# @yieldreturn [String] the actual javascript code to be added to the <script> element
|
136
|
+
# @return [REXML::Element] the Element which was just added
|
137
|
+
def add_javascript(attrs={}, &block)
|
138
|
+
default_attrs = {"type" => "text/javascript"}
|
139
|
+
attrs = default_attrs.merge(attrs)
|
140
|
+
temp = REXML::Element.new("script")
|
141
|
+
temp.add_attributes(attrs)
|
142
|
+
@svg.add_element(temp)
|
143
|
+
raise "Block argument is mandatory" unless block_given?
|
144
|
+
script_content = block.call()
|
145
|
+
cdata(script_content, temp)
|
146
|
+
end # def add_javascript
|
147
|
+
|
148
|
+
|
149
|
+
# @return [String] the complete html file
|
150
|
+
def burn
|
151
|
+
f = REXML::Formatters::Pretty.new(0)
|
152
|
+
out = ''
|
153
|
+
f.write(@doc, out)
|
154
|
+
out
|
155
|
+
end # def burn
|
156
|
+
|
157
|
+
# Burns the graph but returns only the <div> node as String without the
|
158
|
+
# Doctype and XML / HTML Declaration. This allows easy integration into
|
159
|
+
# existing xml/html documents. The Javascript to create the C3js graph
|
160
|
+
# is inlined into the div tag.
|
161
|
+
#
|
162
|
+
# You have to take care to refer the proper C3 and D3 dependencies in your
|
163
|
+
# html page.
|
164
|
+
#
|
165
|
+
# @return [String] the div element into which the graph will be rendered
|
166
|
+
# by C3.js
|
167
|
+
def burn_svg_only
|
168
|
+
# initialize all instance variables by burning the graph
|
169
|
+
burn
|
170
|
+
f = REXML::Formatters::Pretty.new(0)
|
171
|
+
f.compact = true
|
172
|
+
out = ''
|
173
|
+
f.write(@svg, out)
|
174
|
+
return out
|
175
|
+
end # def burn_svg_only
|
176
|
+
|
177
|
+
private
|
178
|
+
|
179
|
+
# Appends a <style> element to the <div> element, this can be used to add additional animations
|
180
|
+
# but any script can also directly be part of the js_chart_specification in the #add_chart_spec
|
181
|
+
# method when you use a HEREDOC string as input.
|
182
|
+
# @yieldreturn [String] the actual javascript code to be added to the <script> element
|
183
|
+
# @return [REXML::Element] the Element which was just added
|
184
|
+
def add_css_to_head(&block)
|
185
|
+
raise "Block argument is mandatory" unless block_given?
|
186
|
+
css_content_or_url = block.call()
|
187
|
+
if @opts["inline_dependencies"]
|
188
|
+
# for inline css use "style"
|
189
|
+
temp = REXML::Element.new("style")
|
190
|
+
attrs = {
|
191
|
+
"type" => "text/css"
|
192
|
+
}
|
193
|
+
cdata(css_content_or_url, temp)
|
194
|
+
else
|
195
|
+
# for external css use "link"
|
196
|
+
temp = REXML::Element.new("link")
|
197
|
+
attrs = {
|
198
|
+
"href" => @opts["c3_css"],
|
199
|
+
"rel" => "stylesheet"
|
200
|
+
}
|
201
|
+
end
|
202
|
+
temp.add_attributes(attrs)
|
203
|
+
@head.add_element(temp)
|
204
|
+
end # def add_css_to_head
|
205
|
+
|
206
|
+
# Appends a <script> element to the <head> element, this can be used to add
|
207
|
+
# the dependencies/libraries.
|
208
|
+
# @yieldreturn [String] the actual javascript code to be added to the <script> element
|
209
|
+
# @return [REXML::Element] the Element which was just added
|
210
|
+
def add_js_to_head(&block)
|
211
|
+
raise "Block argument is mandatory" unless block_given?
|
212
|
+
script_content_or_url = block.call()
|
213
|
+
attrs = {"type" => "text/javascript"}
|
214
|
+
temp = REXML::Element.new("script")
|
215
|
+
if @opts["inline_dependencies"]
|
216
|
+
cdata(script_content_or_url, temp)
|
217
|
+
else
|
218
|
+
attrs["src"] = script_content_or_url
|
219
|
+
# note: self-closing xml script tags are not allowed in html. Only for xhtml this is ok.
|
220
|
+
# Thus add a space textnode to enforce closing tags.
|
221
|
+
temp.add_text(" ")
|
222
|
+
end
|
223
|
+
temp.add_attributes(attrs)
|
224
|
+
@head.add_element(temp)
|
225
|
+
end # def add_js_to_head
|
226
|
+
|
227
|
+
def start_document
|
228
|
+
# Base document
|
229
|
+
@doc = REXML::Document.new
|
230
|
+
@doc << REXML::XMLDecl.new("1.0", "UTF-8")
|
231
|
+
@doc << REXML::DocType.new("html")
|
232
|
+
# attribute xmlns is needed, otherwise the browser will only display raw xml
|
233
|
+
# instead of rendering the page
|
234
|
+
@html = @doc.add_element("html", {"xmlns" => 'http://www.w3.org/1999/xhtml'})
|
235
|
+
@html << REXML::Comment.new( " "+"\\"*66 )
|
236
|
+
@html << REXML::Comment.new( " Created with SVG::Graph - https://github.com/lumean/svg-graph2" )
|
237
|
+
@head = @html.add_element("head")
|
238
|
+
@body = @html.add_element("body")
|
239
|
+
@head.add_element("meta", {"charset" => "utf-8"})
|
240
|
+
add_js_to_head() {@opts["d3_js"]}
|
241
|
+
add_css_to_head() {@opts["c3_css"]}
|
242
|
+
add_js_to_head() {@opts["c3_js"]}
|
243
|
+
end # def start_svg
|
244
|
+
|
245
|
+
# @param attrs [Hash] html attributes for the <div> tag to which svg graph
|
246
|
+
# is bound to by C3js. The "id" attribute
|
247
|
+
# is filled automatically by this method. default: an empty hash {}
|
248
|
+
def add_div_element_for_graph(attrs={})
|
249
|
+
if @bindto.to_s.empty?
|
250
|
+
raise "#add_chart_spec needs to be called before the svg can be added"
|
251
|
+
end
|
252
|
+
attrs["id"] = @bindto
|
253
|
+
@svg = @body.add_element("div", attrs)
|
254
|
+
end
|
255
|
+
|
256
|
+
# Surrounds CData tag with c-style comments to remain compatible with normal html.
|
257
|
+
# This can be used to inline arbitrary javascript code and is compatible with many browsers.
|
258
|
+
# Example /*<![CDATA[*/\n ...content ... \n/*]]>*/
|
259
|
+
# @param str [String] the string to be enclosed in cdata
|
260
|
+
# @param parent_element [REXML::Element] the element to which cdata should be added
|
261
|
+
# @return [REXML::Element] parent_element
|
262
|
+
def cdata(str, parent_element)
|
263
|
+
# somehow there is a problem with CDATA, any text added after will automatically go into the CDATA
|
264
|
+
# so we have do add a dummy node after the CDATA and then add the text.
|
265
|
+
parent_element.add_text("/*")
|
266
|
+
parent_element.add(REXML::CData.new("*/\n"+str+"\n/*"))
|
267
|
+
parent_element.add(REXML::Comment.new("dummy comment to make c-style comments for cdata work"))
|
268
|
+
parent_element.add_text("*/")
|
269
|
+
end # def cdata
|
270
|
+
|
271
|
+
end # class C3js
|
272
|
+
|
273
|
+
end # module Graph
|
274
|
+
end # module SVG
|