svg-graph 2.1.1 → 2.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.com/lumean/svg-graph2.svg?branch=master)](https://travis-ci.com/lumean/svg-graph2)
|
5
|
+
[![Maintainability](https://api.codeclimate.com/v1/badges/0a2b2d977bb9a43f488a/maintainability)](https://codeclimate.com/github/lumean/svg-graph2/maintainability)
|
6
|
+
[![Test Coverage](https://api.codeclimate.com/v1/badges/0a2b2d977bb9a43f488a/test_coverage)](https://codeclimate.com/github/lumean/svg-graph2/test_coverage)
|
7
|
+
[![This project is using Percy.io for visual regression testing.](https://percy.io/static/images/percy-badge.svg)](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
|
![example timeseries graph](https://cdn.rawgit.com/lumean/svg-graph2/master/examples/timeseries.svg)
|
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
|