rbplotly 0.1.2 → 1.0.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.
Files changed (51) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +88 -0
  3. data/LICENSE.txt +1 -1
  4. data/README.md +213 -50
  5. data/lib/plotly/assets/plotly.min.js +3620 -0
  6. data/lib/plotly/attributes.rb +221 -0
  7. data/lib/plotly/figure.rb +292 -0
  8. data/lib/plotly/frames.rb +38 -0
  9. data/lib/plotly/html.rb +161 -0
  10. data/lib/plotly/plot.rb +11 -28
  11. data/lib/plotly/schema/plot-schema.json +57258 -0
  12. data/lib/plotly/schema.rb +99 -0
  13. data/lib/plotly/serializer.rb +49 -0
  14. data/lib/plotly/subplots.rb +186 -0
  15. data/lib/plotly/version.rb +7 -1
  16. data/lib/plotly.rb +22 -0
  17. data/lib/rbplotly.rb +3 -4
  18. metadata +35 -179
  19. data/.gitignore +0 -11
  20. data/.rspec +0 -2
  21. data/.rubocop.yml +0 -22
  22. data/.travis.yml +0 -9
  23. data/CODE_OF_CONDUCT.md +0 -49
  24. data/Gemfile +0 -4
  25. data/Guardfile +0 -12
  26. data/Rakefile +0 -9
  27. data/bin/console +0 -14
  28. data/bin/setup +0 -8
  29. data/docs/images/line_chart.png +0 -0
  30. data/examples/Bar Charts.ipynb +0 -254
  31. data/examples/Basic Usage.ipynb +0 -227
  32. data/examples/Histograms.ipynb +0 -79
  33. data/examples/Line charts.ipynb +0 -88
  34. data/examples/Pie Charts.ipynb +0 -151
  35. data/examples/Scatter Plots.ipynb +0 -224
  36. data/examples/heatmaps.ipynb +0 -150
  37. data/lib/plotly/axis.rb +0 -18
  38. data/lib/plotly/castable.rb +0 -20
  39. data/lib/plotly/client.rb +0 -45
  40. data/lib/plotly/data.rb +0 -33
  41. data/lib/plotly/exportable.rb +0 -23
  42. data/lib/plotly/layout.rb +0 -33
  43. data/lib/plotly/line.rb +0 -17
  44. data/lib/plotly/marker.rb +0 -23
  45. data/lib/plotly/offline/exportable.rb +0 -36
  46. data/lib/plotly/offline/html.rb +0 -46
  47. data/lib/plotly/offline/plotly.min.js +0 -59
  48. data/lib/plotly/offline/templates/body.erb +0 -26
  49. data/lib/plotly/offline/templates/plot.erb +0 -12
  50. data/lib/plotly/util.rb +0 -11
  51. data/rbplotly.gemspec +0 -35
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cgi"
4
+ require "securerandom"
5
+
6
+ module Plotly
7
+ # Renders figures as HTML.
8
+ module HTML
9
+ # The plotly.js build that `include_plotlyjs: :cdn` and notebook output load.
10
+ CDN_URL = "https://cdn.plot.ly/plotly-#{PLOTLY_JS_VERSION}.min.js"
11
+ # The same build, bundled in the gem for `include_plotlyjs: :inline`.
12
+ BUNDLE_PATH = File.expand_path("assets/plotly.min.js", __dir__)
13
+ # Config applied under the figure's own config.
14
+ DEFAULT_CONFIG = {"responsive" => true}.freeze
15
+
16
+ module_function
17
+
18
+ # @param figure [Figure]
19
+ # @param include_plotlyjs [:cdn, :inline, false, String] how the page gets plotly.js:
20
+ # the pinned CDN build, the bundled copy embedded in the page (works offline), not at
21
+ # all (the page already loads it), or from the given URL
22
+ # @param full_html [Boolean] a whole document instead of a fragment
23
+ # @param div_id [String, nil] id of the chart element; random by default
24
+ # @param width [Integer, String, nil] element width (Integer means pixels); 100% by default
25
+ # @param height [Integer, String, nil] element height; by default the layout height, else
26
+ # 450px for a fragment (a percentage would collapse in a container of automatic height)
27
+ # and the whole window for a full document
28
+ # @param auto_play [Boolean] start animation after registering frames
29
+ # @param animation_opts [Hash] options passed to Plotly.animate (frame and transition durations, etc.)
30
+ # @return [String]
31
+ def render(figure, include_plotlyjs: :cdn, full_html: false, div_id: nil, width: nil, height: nil,
32
+ auto_play: true, animation_opts: {})
33
+ id = div_id || "rbplotly-#{SecureRandom.uuid}"
34
+ body = [
35
+ plotlyjs_tag(include_plotlyjs),
36
+ %(<div id="#{CGI.escapeHTML(id)}" class="plotly-graph-div" style="#{style(width, height || default_height(figure, full_html))}"></div>),
37
+ "<script>\n#{draw_call(figure, id, auto_play: auto_play, animation_opts: animation_opts)}\n</script>"
38
+ ].compact.join("\n")
39
+ full_html ? document(figure, body) : body
40
+ end
41
+
42
+ # HTML for Jupyter frontends (classic Notebook, JupyterLab, VS Code): loads plotly.js once
43
+ # per page and draws. plotly.js 3+ always registers itself as `window.Plotly`, never as an
44
+ # AMD module, so a plain script element works even where the page uses RequireJS.
45
+ def notebook(figure)
46
+ id = "rbplotly-#{SecureRandom.uuid}"
47
+ <<~HTML
48
+ <div id="#{id}" class="plotly-graph-div" style="#{style(nil, default_height(figure, false))}"></div>
49
+ <script>
50
+ (function () {
51
+ var src = "#{CDN_URL}";
52
+ function draw(Plotly) {
53
+ #{draw_call(figure, id).gsub("\n", "\n ")}
54
+ }
55
+ if (window.Plotly) { draw(window.Plotly); return; }
56
+ var script = document.querySelector('script[data-rbplotly="' + src + '"]');
57
+ if (!script) {
58
+ script = document.createElement("script");
59
+ script.src = src;
60
+ script.setAttribute("data-rbplotly", src);
61
+ document.head.appendChild(script);
62
+ }
63
+ script.addEventListener("load", function () { draw(window.Plotly); });
64
+ script.addEventListener("error", function () {
65
+ script.remove(); // so the next output tries again
66
+ var el = document.getElementById("#{id}");
67
+ if (el) el.textContent = "rbplotly: could not load plotly.js from " + src;
68
+ });
69
+ })();
70
+ </script>
71
+ HTML
72
+ end
73
+
74
+ # @api private
75
+ def draw_call(figure, id, auto_play: true, animation_opts: {})
76
+ config = DEFAULT_CONFIG.merge(figure.config)
77
+ args = [id, figure.data, figure.layout, config].map { |arg| Serializer.dump(arg) }
78
+ script = "Plotly.newPlot(#{args.join(", ")})"
79
+ unless figure.frames.empty?
80
+ plot_id = Serializer.dump(id)
81
+ script += ".then(function () { return Plotly.addFrames(#{plot_id}, #{Serializer.dump(figure.frames)}); })"
82
+ if auto_play
83
+ script += ".then(function () { return Plotly.animate(#{plot_id}, null, #{Serializer.dump(animation_opts)}); })"
84
+ end
85
+ end
86
+ script + ";"
87
+ end
88
+
89
+ def plotlyjs_tag(mode)
90
+ case mode
91
+ when :cdn then script_src(CDN_URL)
92
+ when :inline then "<script>#{bundle.gsub("</script", "<\\/script")}</script>"
93
+ when false, nil then nil
94
+ when String then script_src(mode)
95
+ else
96
+ raise ArgumentError, "include_plotlyjs must be :cdn, :inline, false or a URL, got #{mode.inspect}"
97
+ end
98
+ end
99
+
100
+ def script_src(url) = %(<script src="#{CGI.escapeHTML(url)}" charset="utf-8"></script>)
101
+
102
+ def bundle
103
+ @bundle ||= File.read(BUNDLE_PATH, encoding: "UTF-8")
104
+ rescue Errno::ENOENT
105
+ raise Error, "the bundled plotly.js is missing (#{BUNDLE_PATH}). In a git checkout, run " \
106
+ "`bundle exec rake plotlyjs:fetch`, or use include_plotlyjs: :cdn"
107
+ end
108
+
109
+ def style(width, height)
110
+ "height:#{length(height)};width:#{length(width || "100%")};"
111
+ end
112
+
113
+ def default_height(figure, full_html)
114
+ layout_height = figure.layout["height"]
115
+ return layout_height.round if layout_height.is_a?(Numeric)
116
+
117
+ full_html ? "100%" : 450
118
+ end
119
+
120
+ def length(value) = value.is_a?(Integer) ? "#{value}px" : CGI.escapeHTML(value.to_s)
121
+
122
+ def document(figure, body)
123
+ title = figure.layout.dig("title", "text")
124
+ title = (title.is_a?(String) || title.is_a?(Symbol)) ? title.to_s : "Plotly figure"
125
+ <<~HTML
126
+ <!DOCTYPE html>
127
+ <html>
128
+ <head>
129
+ <meta charset="utf-8">
130
+ <meta name="viewport" content="width=device-width, initial-scale=1">
131
+ <title>#{CGI.escapeHTML(title)}</title>
132
+ <style>html, body { height: 100%; margin: 0; }</style>
133
+ </head>
134
+ <body>
135
+ #{body}
136
+ </body>
137
+ </html>
138
+ HTML
139
+ end
140
+
141
+ private_class_method :plotlyjs_tag, :script_src, :bundle, :style, :default_height, :length, :document
142
+ end
143
+
144
+ # Opens files in the desktop's default browser.
145
+ module Browser
146
+ module_function
147
+
148
+ # @param path [String] absolute path of an HTML file
149
+ def open(path)
150
+ command = case RbConfig::CONFIG["host_os"]
151
+ when /darwin/ then ["open", path]
152
+ # Not `cmd /c start`: cmd.exe would interpret &, | and ^ in the file name.
153
+ when /mswin|mingw|cygwin/ then ["explorer.exe", path]
154
+ else ["xdg-open", path]
155
+ end
156
+ Process.detach(Process.spawn(*command, out: File::NULL, err: File::NULL))
157
+ rescue SystemCallError
158
+ warn "rbplotly: could not open a browser; the chart is at #{path}"
159
+ end
160
+ end
161
+ end
data/lib/plotly/plot.rb CHANGED
@@ -1,35 +1,18 @@
1
- require 'plotly/data'
2
- require 'plotly/layout'
3
- require 'plotly/exportable'
4
- require 'plotly/offline/exportable'
1
+ # frozen_string_literal: true
5
2
 
6
3
  module Plotly
7
- class Plot
8
- include Exportable
9
- include Offline::Exportable
10
-
11
- # @!attribute [r] data
12
- # @return [Array] list of Plotly::Data objects
13
- # @!attribute [r] layout
14
- # @return [Plotly::Layout]
15
- attr_reader :data, :layout
16
-
17
- # @option data [Array] list of Hash or Plotly::Data objects
18
- # @option layout [Hash or Plotly::Layout]
19
- def initialize(data: [], layout: {})
20
- @data = data.map { |d| d.is_a?(Hash) ? Data.new(d) : d }
21
- @layout = layout.convert_to(Plotly::Layout)
22
- end
23
-
24
- # @param data [Array] list of Hash or Plotly::Data objects
25
- def data=(data)
26
- raise unless data.is_a?(Array)
27
- @data = data.map { |d| d.convert_to(Plotly::Data) }
4
+ # The 0.x entry point, kept so existing code runs while it moves to {Figure}.
5
+ # @deprecated Use {Figure}; `generate_html(path:, open:)` becomes `write_html(path, open:)`.
6
+ class Plot < Figure
7
+ def initialize(data: [], layout: {}, **options)
8
+ warn "Plotly::Plot is deprecated and will be removed in rbplotly 2.0; use Plotly::Figure " \
9
+ "(generate_html(path:) is now write_html(path))", uplevel: 1
10
+ super
28
11
  end
29
12
 
30
- # @param layout [Hash or Plotly::Layout]
31
- def layout=(layout)
32
- @layout = layout.convert_to(Plotly::Layout)
13
+ # @deprecated Use {Figure#write_html}.
14
+ def generate_html(path: "plot.html", open: true)
15
+ write_html(path, open: open)
33
16
  end
34
17
  end
35
18
  end