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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +88 -0
- data/LICENSE.txt +1 -1
- data/README.md +213 -50
- data/lib/plotly/assets/plotly.min.js +3620 -0
- data/lib/plotly/attributes.rb +221 -0
- data/lib/plotly/figure.rb +292 -0
- data/lib/plotly/frames.rb +38 -0
- data/lib/plotly/html.rb +161 -0
- data/lib/plotly/plot.rb +11 -28
- data/lib/plotly/schema/plot-schema.json +57258 -0
- data/lib/plotly/schema.rb +99 -0
- data/lib/plotly/serializer.rb +49 -0
- data/lib/plotly/subplots.rb +186 -0
- data/lib/plotly/version.rb +7 -1
- data/lib/plotly.rb +22 -0
- data/lib/rbplotly.rb +3 -4
- metadata +35 -179
- data/.gitignore +0 -11
- data/.rspec +0 -2
- data/.rubocop.yml +0 -22
- data/.travis.yml +0 -9
- data/CODE_OF_CONDUCT.md +0 -49
- data/Gemfile +0 -4
- data/Guardfile +0 -12
- data/Rakefile +0 -9
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/docs/images/line_chart.png +0 -0
- data/examples/Bar Charts.ipynb +0 -254
- data/examples/Basic Usage.ipynb +0 -227
- data/examples/Histograms.ipynb +0 -79
- data/examples/Line charts.ipynb +0 -88
- data/examples/Pie Charts.ipynb +0 -151
- data/examples/Scatter Plots.ipynb +0 -224
- data/examples/heatmaps.ipynb +0 -150
- data/lib/plotly/axis.rb +0 -18
- data/lib/plotly/castable.rb +0 -20
- data/lib/plotly/client.rb +0 -45
- data/lib/plotly/data.rb +0 -33
- data/lib/plotly/exportable.rb +0 -23
- data/lib/plotly/layout.rb +0 -33
- data/lib/plotly/line.rb +0 -17
- data/lib/plotly/marker.rb +0 -23
- data/lib/plotly/offline/exportable.rb +0 -36
- data/lib/plotly/offline/html.rb +0 -46
- data/lib/plotly/offline/plotly.min.js +0 -59
- data/lib/plotly/offline/templates/body.erb +0 -26
- data/lib/plotly/offline/templates/plot.erb +0 -12
- data/lib/plotly/util.rb +0 -11
- data/rbplotly.gemspec +0 -35
data/lib/plotly/html.rb
ADDED
|
@@ -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
|
-
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
# @
|
|
31
|
-
def
|
|
32
|
-
|
|
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
|