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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: a522ee6da8f411c886cf522dca6e8ab1a6e6dc4b
4
- data.tar.gz: 917c7446f2d06edc9ba644948d17f3cb63f3e446
2
+ SHA256:
3
+ metadata.gz: c6eaa56f94856a998ecd2f0befef3a38c5529da886266cd15150cf4e9758168f
4
+ data.tar.gz: f9eb26d8c758153505d95d450e57c3fa0dfbfcdd9018d89bb6bcee3b656cebff
5
5
  SHA512:
6
- metadata.gz: 149a97802da1b358eb5fa43d77066c3c1950b644dcc5ae817f360c64b3045ea7d56505590fa0bd4144c22e342829c0e1bda59a3960b4557d2e52172a18f5a608
7
- data.tar.gz: bfe772c7f4b72975960645206f3283250878c9d83da249c237b9baf4eca0d8b79ba5107acaa189c9f455c7e05709343666372bdd583c950e6fa74cd6e0713925
6
+ metadata.gz: 5c77c61810a9d855bc469ac4672d9917f0bbb345b5464235c0a06e9d901aa796c8cba67c12e181200ca3865ea2ce9629d8ae39065aec831b77c707fdc46720d4
7
+ data.tar.gz: d270039fc1ea0340a515f554c4ff1f6437d0138c62ff09daafb5c1599df819d096ab04ffa23fe8862cac16b4bd6fcc71efea973e001fa97cfd1e5058e7444a82
@@ -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 an effort to revive the [SVG::Graph library](http://www.germane-software.com/software/SVG/SVG::Graph/) by Sean Russell. I'd also 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)
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
- 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.
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
- * gem build svg-graph.gemspec
122
+
123
+ `gem build svg-graph.gemspec`
124
+
110
125
  * Install:
111
- * gem install svg-graph-\<version>.gem
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: %w[test]
24
+ # by default run all unit tests with 'rake test'
25
+ task default: [:test]
26
26
 
27
27
  task :test do
28
- ruby "test/test_data_point.rb"
29
- ruby "test/test_plot.rb"
30
- ruby "test/test_svg_graph.rb"
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
@@ -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
- make_datapoint_text(left + bar_width/2.0, top - font_size/2, dataset[:data][i])
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 , dataset[:data][i].to_s)
146
+ add_popup(left + bar_width/2.0, top , value_string)
140
147
  dataset_count += 1
141
148
  end
142
149
  field_count += 1
@@ -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
- make_datapoint_text(left+length+5, top+y_mod, dataset[:data][i], "text-anchor: start; ")
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 , dataset[:data][i].to_s)
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