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 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